/**
 * 렌더링 관련 헬퍼 함수 모듈
 *
 * DataGrid의 cellChildren, ul의 li 등 반복 컨텍스트에서
 * 자식 컴포넌트를 렌더링할 때 사용하는 헬퍼 함수들을 제공합니다.
 *
 * @packageDocumentation
 */

import React from 'react';
import { DataBindingEngine, BindingContext } from '../DataBindingEngine';
import { TranslationEngine, TranslationContext } from '../TranslationEngine';
import { ComponentDefinition } from '../DynamicRenderer';
import { responsiveManager } from '../ResponsiveManager';
import { getActionDispatcher } from '../ActionDispatcher';
import { createLogger } from '../../utils/Logger';
import { RAW_PREFIX, RAW_MARKER_START, RAW_MARKER_END, RAW_PLACEHOLDER_MARKER, isRawWrapped, unwrapRaw, containsRawMarker, wrapRawDeep } from '../rawMarkers';
import type { G7DevToolsInterface } from '../G7CoreGlobals';
import {
  evaluateConditionExpression,
  evaluateConditionBranches,
  isConditionExpression,
  isConditionBranches,
  type ConditionsProperty,
} from './ConditionEvaluator';

const logger = createLogger('RenderHelpers');

/**
 * JavaScript 리터럴 값 매핑
 *
 * resolve()가 리터럴을 컨텍스트 경로로 해석하는 것을 방지합니다.
 * undefined는 Map에서 get() 시 undefined를 반환하므로 별도 센티널 불필요 —
 * `has()`로 존재 여부를 먼저 확인하지 않고 `get() !== undefined`로 체크합니다.
 * null은 falsy이므로 undefined와 동일하게 처리됩니다.
 *
 * @since engine-v1.29.2
 */
const LITERALS = new Map<string, any>([
  ['true', true],
  ['false', false],
  ['null', null],
  // undefined는 Map.get()의 기본 반환값과 구분 불가하므로 제외
  // → resolve("undefined")는 context에 없으면 undefined 반환 → Boolean(undefined) = false (정상)
]);

/**
 * G7Core.devTools 인터페이스 가져오기
 *
 * G7DevToolsCore.getInstance() 직접 호출 대신 G7Core.devTools를 사용합니다.
 * DevTools가 비활성화되거나 초기화되지 않은 경우 안전하게 undefined 반환
 */
function getDevTools(): G7DevToolsInterface | undefined {
  try {
    const G7Core = (window as any).G7Core;
    return G7Core?.devTools;
  } catch {
    return undefined;
  }
}

/**
 * 공유 DataBindingEngine 인스턴스
 *
 * 표현식 평가에서 재사용하여 성능 최적화
 */
const sharedBindingEngine = new DataBindingEngine();

/**
 * renderItemChildren 함수 옵션
 */
export interface RenderItemChildrenOptions {
  /**
   * TranslationContext - 다국어 번역에 필요
   */
  translationContext?: TranslationContext;

  /**
   * DataBindingEngine 인스턴스 - 미제공 시 새로 생성
   */
  bindingEngine?: DataBindingEngine;

  /**
   * TranslationEngine 인스턴스 - 미제공 시 싱글톤 사용
   */
  translationEngine?: TranslationEngine;

  /**
   * ActionDispatcher 인스턴스 - 미제공 시 싱글톤 사용
   * CardGrid, DataGrid 등 composite 컴포넌트에서 actions 바인딩에 필요
   */
  actionDispatcher?: any;

  /**
   * 부모 컴포넌트 컨텍스트 (state, setState)
   *
   * expandChildren 등에서 부모 컴포넌트의 로컬 상태를 업데이트할 때 사용합니다.
   * 이 컨텍스트가 전달되면 G7Core.state.setLocal()이 부모의 dynamicState를 업데이트합니다.
   */
  componentContext?: { state?: any; setState?: (updates: any) => void };

  /**
   * remount 키 생성 함수
   *
   * DynamicRenderer의 getRemountKey 함수를 전달받아 cellChildren에서도
   * remount 핸들러가 작동하도록 지원합니다.
   *
   * @param componentId - 컴포넌트 ID
   * @param fallbackKey - ID가 없을 때 사용할 기본 키
   * @returns React key로 사용할 문자열
   */
  getRemountKey?: (componentId: string | undefined, fallbackKey: string) => string;
}

/**
 * 복잡한 표현식 여부 감지
 *
 * 삼항 연산자(?:), 논리 연산자(&&, ||), 비교 연산자, 함수 호출,
 * 배열/객체 리터럴 등이 포함된 경우 true 반환
 *
 * true 인 경우 evaluateExpression(JS 평가)로, false 인 경우 resolve(dot/bracket
 * path 탐색)로 라우팅된다.
 *
 * `[`/`]`/`{`/`}` 포함 (@since engine-v1.50.0):
 *   `{{['a','b']}}` 같은 배열 리터럴이나 `{{[{...}]}}` 같은 객체 리터럴이
 *   "단순 경로"로 오판되어 resolve() 경로탐색 → undefined 가 되던 문제를 해결한다.
 *   순수 숫자 대괄호 인덱싱(`items[0]`, `entry[1]`)은 resolve()/evaluateExpression()
 *   양 경로의 결과가 동일하므로(회귀 테스트로 입증) 경로 이동에 따른 동작 변화가 없다.
 *
 * @param expr - 검사할 표현식
 * @returns 복잡한 표현식 여부
 */
function isComplexExpression(expr: string): boolean {
  // () 추가: $localized(menu.name) 같은 함수 호출 표현식도 복잡한 표현식으로 처리
  // []{} 추가: 배열/객체 리터럴(`['a','b']`, `[{...}]`)을 evaluateExpression 으로 라우팅
  return /[?:|&!+\-*/<>=()[\]{}]/.test(expr);
}

/**
 * 단일 바인딩 표현식 추출
 *
 * `{{expression}}` 형태에서 expression 부분만 추출합니다.
 * 문자열 리터럴 내부의 `}` 문자를 올바르게 처리합니다.
 *
 * @param value - 검사할 문자열
 * @returns 추출된 표현식 또는 null (유효하지 않은 경우)
 *
 * @example
 * ```typescript
 * extractSingleBinding("{{row.name}}") // "row.name"
 * extractSingleBinding("{{row.status === 'active' ? 'yes' : 'no'}}") // "row.status === 'active' ? 'yes' : 'no'"
 * extractSingleBinding("hello {{name}}") // null (단일 바인딩이 아님)
 * ```
 */
function extractSingleBinding(value: string): string | null {
  if (!value.startsWith('{{') || !value.endsWith('}}')) {
    return null;
  }

  const inner = value.slice(2, -2);
  let inString: string | null = null;
  let braceCount = 0;

  for (let i = 0; i < inner.length; i++) {
    const char = inner[i];
    const prevChar = i > 0 ? inner[i - 1] : '';

    // 이스케이프 문자 처리
    if (prevChar === '\\') continue;

    // 문자열 시작/종료 감지 (따옴표 추적)
    if ((char === '"' || char === "'") && !inString) {
      inString = char;
    } else if (char === inString) {
      inString = null;
    }

    // 문자열 외부에서만 중괄호 카운트
    if (!inString) {
      if (char === '{') braceCount++;
      if (char === '}') braceCount--;
      if (braceCount < 0) return null;
    }
  }

  return braceCount === 0 ? inner.trim() : null;
}

/**
 * iteration source 표현식을 평가하여 배열을 반환합니다.
 *
 * 단순 경로(`users.data`)와 복잡한 표현식(`roles?.data ?? []`) 모두 지원합니다.
 * 표현식 여부에 따라 적절한 평가 방식을 선택하여 원본 타입(배열)을 반환합니다.
 *
 * @param source - iteration source 문자열 (예: "roles?.data ?? []", "users.data")
 * @param context - 데이터 컨텍스트
 * @param bindingEngine - DataBindingEngine 인스턴스
 * @returns 평가된 배열 또는 원본 값
 *
 * @example
 * ```typescript
 * // 복잡한 표현식
 * const data = resolveIterationSource("roles?.data ?? []", context, bindingEngine);
 * // 결과: [{id: 1, name: "admin"}, ...]
 *
 * // 단순 경로
 * const data = resolveIterationSource("users.data", context, bindingEngine);
 * // 결과: [{id: 1, name: "홍길동"}, ...]
 * ```
 */
export function resolveIterationSource(
  source: string,
  context: BindingContext,
  bindingEngine: DataBindingEngine
): any {
  // {{...}} 형식의 표현식에서 중괄호 제거
  let normalizedSource = source;
  if (source.startsWith('{{') && source.endsWith('}}')) {
    normalizedSource = source.slice(2, -2).trim();
  }

  // 표현식 여부 확인 (?, :, ||, &&, !, 산술/비교 연산자 등)
  if (isComplexExpression(normalizedSource)) {
    // 표현식인 경우 evaluateExpression 사용 (원본 타입 유지)
    try {
      return bindingEngine.evaluateExpression(normalizedSource, context);
    } catch (error) {
      logger.warn(`resolveIterationSource: 표현식 평가 실패: ${source}`, error);
      return undefined;
    }
  }

  // 단순 경로인 경우 resolve 사용
  return bindingEngine.resolve(normalizedSource, context, { skipCache: true });
}

/**
 * 격리된 상태 컨텍스트 인터페이스
 */
export interface IsolatedContextValue {
  state: Record<string, any>;
  setState: (path: string, value: any) => void;
  getState: (path: string) => any;
  mergeState: (updates: Record<string, any>) => void;
}

/**
 * 컴포넌트 컨텍스트 인터페이스
 */
export interface ComponentContext {
  state?: Record<string, any>;
  setState?: (updates: any) => void;
  isolatedContext?: IsolatedContextValue | null;
  /** 부모 DynamicRenderer의 전체 데이터 컨텍스트 (데이터소스 포함) @since engine-v1.19.1 */
  parentDataContext?: Record<string, any>;
}

/**
 * 효과적인 데이터 컨텍스트를 생성합니다.
 *
 * baseContext에 componentContext의 상태를 병합하여 바인딩에 사용할 수 있는
 * 완전한 컨텍스트를 생성합니다. _local, _global, _isolated 상태를 모두 포함합니다.
 *
 * @param baseContext - 기본 데이터 컨텍스트 (_local, _global 등 포함)
 * @param componentContext - 컴포넌트별 로컬 상태 및 격리 상태 컨텍스트
 * @returns 병합된 효과적인 컨텍스트
 *
 * @example
 * ```typescript
 * const effectiveContext = getEffectiveContext(
 *   { _local: { user: {...} }, _global: { settings: {...} } },
 *   { state: { filter: 'active' }, isolatedContext: { state: { step: 1 } } }
 * );
 * // 결과: {
 * //   _local: { user: {...}, filter: 'active' },
 * //   _global: { settings: {...} },
 * //   _isolated: { step: 1 }
 * // }
 * ```
 */
export function getEffectiveContext(
  baseContext: Record<string, any>,
  componentContext?: ComponentContext
): Record<string, any> {
  const result = { ...baseContext };

  // 부모 데이터 컨텍스트 병합 (engine-v1.19.1)
  // DataGrid 등 복합 컴포넌트의 cellChildren/footerCells/footerCardChildren에서
  // 데이터소스(order, active_carriers 등)에 접근할 수 있도록 최상위 컨텍스트에 병합.
  // 기존 baseContext 키(row, value, $value 등)를 덮어쓰지 않음.
  if (componentContext?.parentDataContext) {
    for (const [key, value] of Object.entries(componentContext.parentDataContext)) {
      if (!(key in result)) {
        result[key] = value;
      }
    }
  }

  // componentContext.state를 _local에 병합
  if (componentContext?.state) {
    const existingLocal = result._local || {};
    result._local = {
      ...existingLocal,
      ...componentContext.state,
    };
  }

  // isolatedContext.state를 _isolated에 포함
  if (componentContext?.isolatedContext?.state) {
    result._isolated = componentContext.isolatedContext.state;
  }

  return result;
}

/**
 * bindComponentActions 옵션
 */
export interface BindComponentActionsOptions {
  /**
   * 컴포넌트 컨텍스트 (state, setState, isolatedContext)
   * DynamicRenderer에서 컴포넌트 로컬 상태 관리에 사용
   */
  componentContext?: {
    state?: any;
    setState?: (updates: any) => void;
    /** 격리된 상태 컨텍스트 (isolatedState 속성이 있는 컴포넌트에서 제공) */
    isolatedContext?: IsolatedContextValue | null;
  };

  /**
   * ActionDispatcher 인스턴스
   * 미제공 시 getActionDispatcher() 싱글톤 사용
   * DynamicRenderer에서는 props로 전달받은 인스턴스 사용 권장
   */
  actionDispatcher?: any;
}

/**
 * 컴포넌트의 actions 배열을 props에 바인딩합니다.
 *
 * DynamicRenderer의 액션 바인딩 로직과 동일한 패턴을 사용합니다.
 * actions 배열을 이벤트 핸들러(onClick, onChange 등)로 변환합니다.
 *
 * 코드 중복을 최소화하기 위해 공유 유틸리티로 추출되었습니다.
 * DynamicRenderer와 renderItemChildren에서 동일하게 사용됩니다.
 *
 * @param props - 기존 props 객체
 * @param actions - 액션 정의 배열
 * @param context - 데이터 컨텍스트
 * @param options - 추가 옵션 (componentContext 등)
 * @returns 이벤트 핸들러가 바인딩된 props 객체 (actions prop 제거됨)
 *
 * @example
 * ```typescript
 * const actions = [{ type: 'click', handler: 'openModal', target: 'myModal' }];
 * const boundProps = bindComponentActions({ className: 'btn' }, actions, { row: {...} });
 * // 결과: { className: 'btn', onClick: [Function] }
 * ```
 */
export function bindComponentActions(
  props: Record<string, any>,
  actions: any[] | undefined,
  context: Record<string, any>,
  options?: BindComponentActionsOptions
): Record<string, any> {
  if (!actions || !Array.isArray(actions) || actions.length === 0) {
    return props;
  }

  // options에서 전달된 actionDispatcher를 우선 사용, 없으면 싱글톤 사용
  const actionDispatcher = options?.actionDispatcher ?? getActionDispatcher();
  const propsWithActions = { ...props, actions };
  const boundProps = actionDispatcher.bindActionsToProps(
    propsWithActions,
    context,
    options?.componentContext
  );
  // actions prop 제거 (HTML 속성으로 렌더링되지 않도록)
  delete boundProps.actions;

  return boundProps;
}

/**
 * 조건부 렌더링 평가 함수
 *
 * DynamicRenderer의 shouldRender 로직과 동일한 패턴을 사용합니다.
 * `if` 속성을 평가하여 컴포넌트를 렌더링할지 결정합니다.
 *
 * 코드 중복을 최소화하기 위해 공유 유틸리티로 추출되었습니다.
 * DynamicRenderer와 renderItemChildren에서 동일하게 사용됩니다.
 *
 * @param ifCondition - if 속성 값 (예: "{{row.status == 'active'}}")
 * @param context - 데이터 컨텍스트
 * @param bindingEngine - DataBindingEngine 인스턴스
 * @param componentId - 컴포넌트 ID (디버깅용, 선택)
 * @returns 렌더링 여부 (true: 렌더링, false: 숨김)
 *
 * @example
 * ```typescript
 * // 단순 조건
 * evaluateIfCondition("{{row.active}}", { row: { active: true } }, bindingEngine)
 * // 결과: true
 *
 * // 비교 조건
 * evaluateIfCondition("{{row.status == 'active'}}", { row: { status: 'active' } }, bindingEngine)
 * // 결과: true
 *
 * // falsy 값
 * evaluateIfCondition("{{row.count}}", { row: { count: 0 } }, bindingEngine)
 * // 결과: false
 * ```
 */
export function evaluateIfCondition(
  ifCondition: string | boolean | undefined,
  context: BindingContext,
  bindingEngine: DataBindingEngine,
  componentId?: string
): boolean {
  // boolean 값이 이미 해석된 경우 (엔진 prop 사전 해석으로 condition이 boolean이 될 수 있음)
  if (typeof ifCondition === 'boolean') {
    return ifCondition;
  }

  // if 조건이 없으면 항상 렌더링
  if (!ifCondition) {
    return true;
  }

  try {
    // {{variable}} 형태의 단일 바인딩인지 체크
    const singleBindingPath = extractSingleBinding(ifCondition);

    let resolved: any;
    if (singleBindingPath !== null) {
      // JavaScript 리터럴 값 직접 처리 (resolve 경로 탐색 방지) @since engine-v1.29.2
      const literal = LITERALS.get(singleBindingPath);
      if (literal !== undefined) {
        resolved = literal;
      } else if (isComplexExpression(singleBindingPath)) {
        // Optional chaining(?.)이나 복잡한 표현식은 evaluateExpression 사용
        resolved = bindingEngine.evaluateExpression(singleBindingPath, context);
      } else {
        // 단순 경로는 resolve 메서드 사용 (원본 타입 유지)
        resolved = bindingEngine.resolve(singleBindingPath, context, { skipCache: true });
      }
    } else {
      // 문자열 보간인 경우 resolveBindings 사용
      resolved = bindingEngine.resolveBindings(ifCondition, context, { skipCache: true });
    }

    // 문자열 "false", "0", "", "null", "undefined"를 false로 처리
    if (typeof resolved === 'string') {
      const lowerResolved = resolved.toLowerCase().trim();
      if (lowerResolved === 'false' || lowerResolved === '0' || lowerResolved === '' ||
          lowerResolved === 'null' || lowerResolved === 'undefined') {
        return false;
      }
    }

    // 결과를 boolean으로 변환
    return Boolean(resolved);
  } catch (error) {
    logger.warn(
      `evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${componentId || 'unknown'}):`,
      error
    );
    return false;
  }
}

/**
 * 컴포넌트 렌더링 조건 옵션
 */
export interface EvaluateRenderConditionOptions {
  /**
   * if 속성 (기존 단일 조건, 하위 호환)
   */
  if?: string;

  /**
   * condition 속성 (if의 별칭 — 컴포넌트 내부 children에서 관례적으로 사용)
   * @since engine-v1.17.1
   */
  condition?: string | boolean;

  /**
   * conditions 속성 (AND/OR 그룹 또는 if/else 체인)
   */
  conditions?: ConditionsProperty;
}

/**
 * 컴포넌트의 렌더링 조건을 평가합니다 (if 또는 conditions)
 *
 * `if` 속성과 `conditions` 속성을 모두 지원합니다.
 * - `if`: 기존 단일 조건 (하위 호환)
 * - `conditions`: AND/OR 그룹 또는 if/else 체인
 *
 * 우선순위: `if` 속성이 있으면 먼저 평가, 없으면 `conditions` 평가
 *
 * @param componentDef - 컴포넌트 정의 또는 조건 옵션 ({ if?, conditions? })
 * @param context - 데이터 컨텍스트
 * @param bindingEngine - DataBindingEngine 인스턴스
 * @param componentId - 컴포넌트 ID (디버깅용, 선택)
 * @returns 렌더링 여부 (true: 렌더링, false: 숨김)
 *
 * @example
 * ```typescript
 * // 기존 if 조건 (하위 호환)
 * evaluateRenderCondition(
 *   { if: "{{user.isAdmin}}" },
 *   context,
 *   bindingEngine
 * ); // → true/false
 *
 * // AND 그룹 조건
 * evaluateRenderCondition(
 *   { conditions: { and: ["{{route.id}}", "{{form_data?.data?.author}}"] } },
 *   context,
 *   bindingEngine
 * ); // → 모든 조건이 true일 때만 true
 *
 * // OR 그룹 조건
 * evaluateRenderCondition(
 *   { conditions: { or: ["{{user.isAdmin}}", "{{user.isManager}}"] } },
 *   context,
 *   bindingEngine
 * ); // → 하나라도 true면 true
 *
 * // if/else 체인 (컴포넌트에서 하나라도 true면 렌더링)
 * evaluateRenderCondition(
 *   {
 *     conditions: [
 *       { if: "{{user.role === 'admin'}}" },
 *       { if: "{{user.role === 'manager'}}" }
 *     ]
 *   },
 *   context,
 *   bindingEngine
 * ); // → 어느 조건이라도 true면 렌더링
 * ```
 */
export function evaluateRenderCondition(
  componentDef: EvaluateRenderConditionOptions,
  context: BindingContext,
  bindingEngine: DataBindingEngine,
  componentId?: string
): boolean {
  // 1. 기존 if 속성 우선 (하위 호환)
  if (componentDef.if !== undefined) {
    return evaluateIfCondition(componentDef.if, context, bindingEngine, componentId);
  }

  // 1-1. condition 속성 (if의 별칭 — 컴포넌트 내부 children에서 관례적 사용)
  if (componentDef.condition !== undefined) {
    return evaluateIfCondition(componentDef.condition, context, bindingEngine, componentId);
  }

  // 2. conditions 속성이 없으면 항상 렌더링
  if (componentDef.conditions === undefined) {
    return true;
  }

  const conditions = componentDef.conditions;

  try {
    // 3. 단일 ConditionExpression (문자열, AND, OR)
    if (isConditionExpression(conditions)) {
      return evaluateConditionExpression(conditions, context, bindingEngine);
    }

    // 4. ConditionBranch[] (if/else 체인)
    if (isConditionBranches(conditions)) {
      const result = evaluateConditionBranches(conditions, context, bindingEngine);
      // 컴포넌트에서는 하나라도 매칭되면 렌더링
      return result.matched;
    }

    // 알 수 없는 형식
    logger.warn(
      `evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${componentId || 'unknown'})`
    );
    return true;
  } catch (error) {
    logger.warn(
      `evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${componentId || 'unknown'}):`,
      error
    );
    return false;
  }
}

/**
 * 컴포넌트 정의에 반응형 오버라이드를 적용합니다.
 *
 * responsive 속성이 있으면 현재 화면 너비에 맞는 오버라이드를 적용합니다.
 * DynamicRenderer의 effectiveComponentDef 로직과 동일한 패턴을 사용합니다.
 *
 * @param componentDef - 원본 컴포넌트 정의
 * @returns 반응형 오버라이드가 적용된 컴포넌트 정의
 */
function applyResponsiveOverrides(componentDef: ComponentDefinition): ComponentDefinition {
  if (!componentDef.responsive) {
    return componentDef;
  }

  const currentWidth = responsiveManager.getWidth();
  const matchedKey = responsiveManager.getMatchingKey(componentDef.responsive, currentWidth);

  if (!matchedKey) {
    return componentDef;
  }

  const override = componentDef.responsive[matchedKey];
  return {
    ...componentDef,
    // props는 얕은 머지 (지정한 속성만 대체, 미지정 속성 유지)
    props: { ...componentDef.props, ...override.props },
    // children, text, if, iteration은 완전 교체
    children: override.children ?? componentDef.children,
    text: override.text ?? componentDef.text,
    if: override.if ?? componentDef.if,
    iteration: override.iteration ?? componentDef.iteration,
  };
}

/**
 * 반복 아이템의 자식 컴포넌트를 렌더링합니다.
 *
 * DataGrid의 cellChildren, ul의 li 등 반복 컨텍스트에서
 * 자식 컴포넌트를 렌더링할 때 사용합니다.
 *
 * 템플릿 엔진의 바인딩, 표현식 평가, 다국어 번역 등
 * 모든 기능을 지원합니다.
 *
 * @param children - 렌더링할 컴포넌트 정의 배열
 * @param itemContext - 반복 아이템 컨텍스트 (row, item, value, index 등)
 * @param componentMap - 컴포넌트 맵 (ComponentRegistry에서 가져온 컴포넌트들)
 * @param keyPrefix - React key 접두사 (선택)
 * @param options - 추가 옵션 (translationContext, bindingEngine 등)
 * @returns React 노드 배열
 *
 * @example
 * ```tsx
 * // DataGrid에서 cellChildren 렌더링
 * const renderedChildren = renderItemChildren(
 *   cellChildren,
 *   { row, value, index },
 *   componentMap,
 *   `cell-${index}`
 * );
 *
 * // ul에서 li 아이템 렌더링
 * const listItems = renderItemChildren(
 *   itemChildren,
 *   { item, index },
 *   componentMap,
 *   `list-item-${index}`
 * );
 * ```
 */
export function renderItemChildren(
  children: ComponentDefinition[],
  itemContext: Record<string, any>,
  componentMap: Record<string, React.ComponentType<any>>,
  keyPrefix?: string,
  options?: RenderItemChildrenOptions
): React.ReactNode[] {
  if (!children || children.length === 0) {
    return [];
  }

  // 엔진 인스턴스 확인 - 없으면 새로 생성
  const bindingEngine = options?.bindingEngine ?? new DataBindingEngine();
  const translationEngine = options?.translationEngine ?? TranslationEngine.getInstance();
  const translationContext = options?.translationContext ?? {
    templateId: '',
    locale: 'ko',
  };

  // Phase 1-1: componentContext가 있으면 자동으로 state를 _local에 병합
  // Phase 2: isolatedContext.state를 _isolated에 포함
  // 이전에는 DataGrid 등에서 수동으로 수행하던 로직
  const effectiveContext = getEffectiveContext(itemContext, options?.componentContext);

  /**
   * props 값을 바인딩/표현식 평가하여 해석
   * 반복 렌더링이므로 캐시를 사용하지 않음 (같은 경로가 다른 row에서 다른 값을 가져야 함)
   */
  const resolveValue = (value: any, context: Record<string, any>): any => {
    if (typeof value === 'string') {
      // raw 마커: 번역 면제 — 마커 제거 후 원본 반환
      // @since engine-v1.27.0
      if (isRawWrapped(value)) {
        return unwrapRaw(value);
      }

      // 혼합 보간 시 raw 마커 영역 보호
      // @since engine-v1.27.0
      if (containsRawMarker(value)) {
        const rawSegments: string[] = [];
        const protectedValue = value.replace(
          new RegExp(`${RAW_MARKER_START}([^${RAW_MARKER_END}]*)${RAW_MARKER_END}`, 'g'),
          (_m: string, content: string) => {
            rawSegments.push(content);
            return `${RAW_PLACEHOLDER_MARKER}${rawSegments.length - 1}${RAW_PLACEHOLDER_MARKER}`;
          }
        );
        let translated = protectedValue;
        if (/\$t:[a-zA-Z0-9._-]+/.test(protectedValue)) {
          translated = translationEngine.resolveTranslations(
            protectedValue,
            translationContext,
            context
          );
        }
        return translated.replace(
          new RegExp(`${RAW_PLACEHOLDER_MARKER}(\\d+)${RAW_PLACEHOLDER_MARKER}`, 'g'),
          (_m: string, idx: string) => rawSegments[parseInt(idx)]
        );
      }

      // 다국어 번역
      // $t:defer: prefix는 DynamicRenderer에서 건너뛰고 여기서 처리됨
      // defer: 부분을 제거하고 일반 $t: 번역으로 처리
      if (value.startsWith('$t:defer:')) {
        const normalizedValue = '$t:' + value.slice(9); // '$t:defer:'.length === 9
        return translationEngine.resolveTranslations(
          normalizedValue,
          translationContext,
          context
        );
      }
      if (value.startsWith('$t:')) {
        return translationEngine.resolveTranslations(
          value,
          translationContext,
          context
        );
      }

      // 단일 바인딩 표현식
      const singleBindingPath = extractSingleBinding(value);
      if (singleBindingPath !== null) {
        // raw: 접두사 감지 @since engine-v1.27.0
        let isRawBinding = false;
        let effectivePath = singleBindingPath;
        if (singleBindingPath.startsWith(RAW_PREFIX)) {
          isRawBinding = true;
          effectivePath = singleBindingPath.slice(RAW_PREFIX.length);
        }

        let result;
        if (isComplexExpression(effectivePath)) {
          try {
            result = bindingEngine.evaluateExpression(effectivePath, context);
          } catch (error) {
            logger.warn('renderItemChildren: 표현식 평가 실패:', error);
            return undefined;
          }
        } else {
          // skipCache: true - 반복 렌더링에서 같은 경로가 다른 값을 가져야 함
          result = bindingEngine.resolve(effectivePath, context, { skipCache: true });
        }

        // 표현식 평가 결과가 $t: 접두사 문자열이면 번역 처리
        // 예: {{row.published ? '$t:module.key.published' : '$t:module.key.unpublished'}}
        // → 평가 결과 "$t:module.key.published"를 번역하여 "발행" 반환
        // @since engine-v1.28.1
        if (!isRawBinding && typeof result === 'string' && /\$t:[a-zA-Z0-9._-]+/.test(result)) {
          result = translationEngine.resolveTranslations(
            result,
            translationContext,
            context
          );
        }

        return isRawBinding && result != null ? wrapRawDeep(result) : result;
      }

      // 문자열 보간
      if (value.includes('{{')) {
        // skipCache: true - 반복 렌더링에서 같은 경로가 다른 값을 가져야 함
        const interpolated = bindingEngine.resolveBindings(value, context, { skipCache: true });
        // 보간 결과에 $t: 접두사가 포함되면 번역 처리 @since engine-v1.28.1
        if (typeof interpolated === 'string' && /\$t:[a-zA-Z0-9._-]+/.test(interpolated)) {
          return translationEngine.resolveTranslations(
            interpolated,
            translationContext,
            context
          );
        }
        return interpolated;
      }

      return value;
    }

    if (Array.isArray(value)) {
      return value.map((item) => resolveValue(item, context));
    }

    if (value && typeof value === 'object') {
      const resolved: Record<string, any> = {};
      for (const [k, v] of Object.entries(value)) {
        resolved[k] = resolveValue(v, context);
      }
      return resolved;
    }

    return value;
  };

  /**
   * 컴포넌트 정의의 props를 해석
   */
  const resolveProps = (
    props: Record<string, any> | undefined,
    context: Record<string, any>
  ): Record<string, any> => {
    if (!props) return {};

    const resolved: Record<string, any> = {};
    for (const [key, value] of Object.entries(props)) {
      resolved[key] = resolveValue(value, context);
    }
    return resolved;
  };

  /**
   * iteration이 있는 컴포넌트 렌더링
   * source 배열의 각 아이템에 대해 컴포넌트를 반복 렌더링
   */
  const renderWithIteration = (
    originalChildDef: ComponentDefinition,
    baseKey: string,
    baseContext: Record<string, any>
  ): React.ReactNode[] => {
    // 반응형 오버라이드 적용
    const childDef = applyResponsiveOverrides(originalChildDef);

    const iteration = childDef.iteration as { source: string; item_var: string; index_var?: string } | undefined;
    if (!iteration) return [];

    // source 경로에서 배열 가져오기 (표현식 지원)
    const sourceArray = resolveIterationSource(iteration.source, baseContext, bindingEngine);

    if (!Array.isArray(sourceArray)) {
      logger.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${iteration.source}`);
      return [];
    }

    // DevTools: iteration 추적
    const devTools = getDevTools();
    if (devTools?.isEnabled()) {
      const iterationId = `${baseKey}-iteration`;
      devTools.trackIteration(
        iterationId,
        iteration.source,
        iteration.item_var,
        iteration.index_var,
        sourceArray.length
      );
    }

    // 컴포넌트 조회
    const Component = componentMap[childDef.name];
    if (!Component) {
      logger.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${childDef.name}`);
      return [];
    }

    // 각 아이템에 대해 컴포넌트 렌더링 (if 조건 실패 시 필터링)
    return sourceArray.flatMap((item: any, itemIndex: number) => {
      // 컨텍스트에 iteration 변수 추가
      // index_var가 지정되어 있으면 해당 이름 사용, 아니면 기본 패턴(item_var + _index) 사용
      const iterationContext = {
        ...baseContext,
        [iteration.item_var]: item,
        [`${iteration.item_var}_index`]: itemIndex,
        // index_var가 명시적으로 지정된 경우 해당 이름으로도 인덱스 추가
        ...(iteration.index_var ? { [iteration.index_var]: itemIndex } : {}),
      };

      // if/condition/conditions 조건 평가 - 조건이 false이면 렌더링하지 않음
      const shouldRender = evaluateRenderCondition(
        { if: childDef.if, condition: childDef.condition, conditions: childDef.conditions },
        iterationContext,
        bindingEngine,
        childDef.id
      );

      // DevTools: if 조건 추적 (iteration 내부)
      // TODO: conditions 속성은 DevTools Phase 5에서 별도 추적 구현 예정
      if (childDef.if && devTools?.isEnabled()) {
        const conditionId = `${baseKey}-iter-${itemIndex}-if`;
        devTools.trackIfCondition(conditionId, childDef.if, shouldRender, childDef.name);
      }

      if (!shouldRender) {
        return [];
      }

      // remount 지원: getRemountKey가 제공되면 사용, 아니면 기본 키 생성
      // childDef.id의 표현식을 해석 (예: "toggle_{{row.id}}" → "toggle_3")
      const resolvedIterationId = childDef.id ? String(resolveValue(childDef.id, iterationContext)) : undefined;
      const iterationFallbackKey = `${baseKey}-iter-${itemIndex}`;
      const iterationKey = options?.getRemountKey
        ? options.getRemountKey(resolvedIterationId, iterationFallbackKey)
        : iterationFallbackKey;

      // props 해석
      const resolvedProps = resolveProps(childDef.props, iterationContext);

      // actions 바인딩 (DynamicRenderer와 동일한 패턴)
      const finalProps = bindComponentActions(resolvedProps, childDef.actions, iterationContext, {
        actionDispatcher: options?.actionDispatcher,
        componentContext: options?.componentContext,
      });

      // text 처리
      let childrenNodes: React.ReactNode = null;
      if (childDef.text !== undefined) {
        childrenNodes = resolveValue(childDef.text, iterationContext);
      } else if (childDef.children && childDef.children.length > 0) {
        childrenNodes = renderItemChildren(
          childDef.children as ComponentDefinition[],
          iterationContext,
          componentMap,
          iterationKey,
          options
        );
      }

      // DevTools: 컴포넌트 렌더링 추적 (iteration 내부)
      if (devTools?.isEnabled()) {
        devTools.trackRender(childDef.name);
      }

      return [React.createElement(Component, { key: iterationKey, ...finalProps }, childrenNodes)];
    });
  };

  /**
   * 자식 컴포넌트 렌더링
   * Phase 1-1: effectiveContext 사용 (componentContext.state가 _local에 자동 병합됨)
   */
  return children.flatMap((originalChildDef, index) => {
    // 반응형 오버라이드 적용
    const childDef = applyResponsiveOverrides(originalChildDef);

    // remount 지원: getRemountKey가 제공되면 사용, 아니면 기본 키 생성
    // childDef.id의 표현식을 해석 (예: "toggle_{{row.id}}" → "toggle_3")
    const resolvedId = childDef.id ? String(resolveValue(childDef.id, effectiveContext)) : undefined;
    const fallbackKey = keyPrefix ? `${keyPrefix}-${resolvedId || index}` : (resolvedId || `child-${index}`);
    const key = options?.getRemountKey
      ? options.getRemountKey(resolvedId, fallbackKey)
      : fallbackKey;

    // iteration이 있는 경우 반복 렌더링
    // if 조건은 renderWithIteration 내부에서 각 아이템 컨텍스트로 평가됨
    if (childDef.iteration) {
      return renderWithIteration(childDef, key, effectiveContext);
    }

    // if/condition/conditions 조건 평가 - 조건이 false이면 렌더링하지 않음
    // iteration이 없는 경우에만 여기서 평가
    const shouldRender = evaluateRenderCondition(
      { if: childDef.if, condition: childDef.condition, conditions: childDef.conditions },
      effectiveContext,
      bindingEngine,
      childDef.id
    );

    // DevTools: if 조건 추적 (일반 컴포넌트)
    // TODO: conditions 속성은 DevTools Phase 5에서 별도 추적 구현 예정
    const devTools = getDevTools();
    if (childDef.if && devTools?.isEnabled()) {
      const conditionId = `${key}-if`;
      devTools.trackIfCondition(conditionId, childDef.if, shouldRender, childDef.name);
    }

    if (!shouldRender) {
      return [];
    }

    // 컴포넌트 조회
    const Component = componentMap[childDef.name];
    if (!Component) {
      logger.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${childDef.name}`);
      return [];
    }

    // props 해석
    const resolvedProps = resolveProps(childDef.props, effectiveContext);

    // actions 바인딩 (DynamicRenderer와 동일한 패턴)
    const finalProps = bindComponentActions(resolvedProps, childDef.actions, effectiveContext, {
      actionDispatcher: options?.actionDispatcher,
      componentContext: options?.componentContext,
    });

    // text 처리
    let childrenNodes: React.ReactNode = null;
    if (childDef.text !== undefined) {
      childrenNodes = resolveValue(childDef.text, effectiveContext);
    } else if (childDef.children && childDef.children.length > 0) {
      // 재귀적으로 자식 렌더링
      childrenNodes = renderItemChildren(
        childDef.children as ComponentDefinition[],
        effectiveContext,
        componentMap,
        key,
        options
      );
    }

    // DevTools: 컴포넌트 렌더링 추적 (일반 컴포넌트)
    if (devTools?.isEnabled()) {
      devTools.trackRender(childDef.name);
    }

    return [React.createElement(Component, { key, ...finalProps }, childrenNodes)];
  });
}

/**
 * 문자열 내의 모든 바인딩 표현식을 컨텍스트 데이터로 치환
 *
 * 단순 변수({{route.id}})뿐 아니라 복잡한 표현식({{route?.id}}, {{a ? b : c}})도 지원합니다.
 * 코드 중복을 피하기 위해 DataBindingEngine의 기능을 재사용합니다.
 *
 * @param template 바인딩 표현식이 포함된 문자열 (예: "/api/users/{{route?.id}}")
 * @param context 데이터 컨텍스트 (예: { route: { id: "123" } })
 * @param bindingEngine DataBindingEngine 인스턴스 (선택, 미제공 시 공유 인스턴스 사용)
 * @returns 치환된 문자열 (예: "/api/users/123")
 *
 * @example
 * ```typescript
 * // 단순 변수 치환
 * resolveExpressionString("/api/users/{{route.id}}", { route: { id: "123" } });
 * // 결과: "/api/users/123"
 *
 * // Optional chaining 지원
 * resolveExpressionString("/api/users/{{route?.id}}", { route: { id: "456" } });
 * // 결과: "/api/users/456"
 *
 * // 복잡한 표현식 지원
 * resolveExpressionString("{{isEdit ? 'update' : 'create'}}", { isEdit: true });
 * // 결과: "update"
 * ```
 */
/**
 * 표현식 해석 옵션
 */
export interface ResolveExpressionOptions {
  /**
   * 캐시 사용 안 함 (기본값: false)
   * true로 설정하면 매 호출마다 새로운 컨텍스트로 평가
   */
  skipCache?: boolean;
}

export function resolveExpressionString(
  template: string,
  context: BindingContext,
  bindingEngineOrOptions?: DataBindingEngine | ResolveExpressionOptions,
  options?: ResolveExpressionOptions
): string {
  // 파라미터 오버로드 처리: (template, context, options) 또는 (template, context, bindingEngine, options)
  let engine: DataBindingEngine;
  let opts: ResolveExpressionOptions | undefined;

  if (bindingEngineOrOptions instanceof DataBindingEngine) {
    engine = bindingEngineOrOptions;
    opts = options;
  } else {
    engine = sharedBindingEngine;
    opts = bindingEngineOrOptions;
  }

  return engine.resolveBindings(template, context, opts);
}

/**
 * 객체 내의 모든 문자열 값에서 바인딩 표현식을 치환
 *
 * 중첩된 객체와 배열도 재귀적으로 처리합니다.
 *
 * @param obj 바인딩 표현식이 포함된 객체
 * @param context 데이터 컨텍스트
 * @param bindingEngine DataBindingEngine 인스턴스 (선택)
 * @returns 치환된 객체
 *
 * @example
 * ```typescript
 * resolveExpressionObject(
 *   { endpoint: "/api/users/{{route?.id}}", params: { status: "{{filter}}" } },
 *   { route: { id: "123" }, filter: "active" }
 * );
 * // 결과: { endpoint: "/api/users/123", params: { status: "active" } }
 * ```
 */
export function resolveExpressionObject<T extends Record<string, any>>(
  obj: T,
  context: BindingContext,
  bindingEngine?: DataBindingEngine
): T {
  const engine = bindingEngine ?? sharedBindingEngine;
  return engine.resolveObject(obj, context) as T;
}

/**
 * classMap 정의 인터페이스
 */
export interface ClassMapDefinition {
  /** 항상 적용되는 기본 클래스 */
  base?: string;
  /** 키 값에 따른 클래스 변형 */
  variants: Record<string, string>;
  /** 변형을 결정하는 키 표현식 (예: "{{row.status}}") */
  key: string;
  /** 매칭되는 변형이 없을 때 사용할 기본 클래스 */
  default?: string;
}

/**
 * resolveClassMap 옵션
 */
export interface ResolveClassMapOptions {
  /**
   * 캐시 사용 안 함 (기본값: true)
   *
   * iteration/cellChildren 컨텍스트에서 사용될 수 있으므로 기본적으로 캐시 비활성화
   */
  skipCache?: boolean;
}

/**
 * classMap 정의를 해석하여 CSS 클래스 문자열로 변환
 *
 * 동적 값에 따라 다른 CSS 클래스를 적용합니다.
 * 중첩 삼항 연산자를 사용하는 복잡한 className 표현식을 대체합니다.
 *
 * 트러블슈팅 주의사항:
 * - iteration 컨텍스트에서 key 평가 시 skipCache: true 적용 (각 row별 다른 값)
 * - variants 객체 자체는 정적이므로 캐싱 가능
 *
 * @param classMap classMap 정의 객체
 * @param context 데이터 컨텍스트
 * @param bindingEngine DataBindingEngine 인스턴스 (선택, 미제공 시 공유 인스턴스 사용)
 * @param options 해석 옵션
 * @returns 최종 CSS 클래스 문자열
 *
 * @example
 * ```typescript
 * const classMap = {
 *   base: "px-2 py-1 rounded-full text-xs font-medium",
 *   variants: {
 *     success: "bg-green-100 text-green-800",
 *     danger: "bg-red-100 text-red-800",
 *     warning: "bg-yellow-100 text-yellow-800"
 *   },
 *   key: "{{row.status}}",
 *   default: "bg-gray-100 text-gray-600"
 * };
 *
 * const context = { row: { status: "success" } };
 * resolveClassMap(classMap, context);
 * // 결과: "px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800"
 * ```
 */
export function resolveClassMap(
  classMap: ClassMapDefinition,
  context: BindingContext,
  bindingEngine?: DataBindingEngine,
  options?: ResolveClassMapOptions
): string {
  const engine = bindingEngine ?? sharedBindingEngine;
  const opts = { skipCache: true, ...options }; // 기본적으로 캐시 비활성화

  // 1. key 표현식 평가 (항상 skipCache)
  let keyValue: string;
  try {
    const resolved = engine.resolveBindings(classMap.key, context, { skipCache: opts.skipCache });
    // 빈 문자열이거나 undefined/null인 경우 처리
    keyValue = resolved?.toString()?.trim() ?? '';
  } catch (error) {
    logger.warn('resolveClassMap: key 평가 실패:', classMap.key, error);
    keyValue = '';
  }

  // 2. variants에서 매칭되는 클래스 찾기
  let variantClass = '';
  if (keyValue && classMap.variants && keyValue in classMap.variants) {
    variantClass = classMap.variants[keyValue];
  } else if (classMap.default) {
    // 매칭되는 variant가 없으면 default 사용
    variantClass = classMap.default;
  }

  // 3. base와 variant 클래스 결합
  const classes: string[] = [];

  if (classMap.base?.trim()) {
    classes.push(classMap.base.trim());
  }

  if (variantClass?.trim()) {
    classes.push(variantClass.trim());
  }

  return classes.join(' ');
}
