/**
 * 그누보드7 템플릿 엔진 진입점
 *
 * 모든 코어 엔진 모듈을 통합하고 외부에서 사용할 수 있는 공개 API를 제공합니다.
 *
 * @packageDocumentation
 */

import React, { startTransition } from 'react';
import ReactDOM from 'react-dom/client';
import { ComponentRegistry } from './template-engine/ComponentRegistry';
import { DataBindingEngine } from './template-engine/DataBindingEngine';
import { TranslationEngine, TranslationContext } from './template-engine/TranslationEngine';
import { ActionDispatcher, setActionDispatcherInstance } from './template-engine/ActionDispatcher';
import DynamicRenderer from './template-engine/DynamicRenderer';
import { LayoutLoader } from './template-engine/LayoutLoader';
import { TransitionProvider } from './template-engine/TransitionContext';
import { TranslationProvider } from './template-engine/TranslationContext';
import { transitionManager } from './template-engine/TransitionManager';
import { ResponsiveProvider } from './template-engine/ResponsiveContext';
import { responsiveManager } from './template-engine/ResponsiveManager';
import { SlotProvider } from './template-engine/SlotContext';
import { DataSourceManager } from './template-engine/DataSourceManager';
import { ModalDataSourceWrapper } from './template-engine/ModalDataSourceWrapper';
import { ParentContextProvider } from './template-engine/ParentContextProvider';
import { TemplateNotFoundError } from './template-engine/TemplateEngineError';
import { mergeLocalInitSlot } from './template-engine/localInitSlot';
import { ErrorDisplay } from './template-engine/ErrorDisplay';
import { TemplateApp, initTemplateApp } from './TemplateApp';
import { createLogger } from './utils/Logger';
import { webSocketManager } from './websocket/WebSocketManager';
import { initializeG7CoreGlobals, initDevToolsAPI } from './template-engine/G7CoreGlobals';
import { checkLayoutEditorMode } from './template-engine/layout-editor/hooks/useEditorMode';
// LayoutEditorChrome 은 정적 import 하지 않는다 — 편집기는 별도 lazy 번들
// (layout-editor.min.js)로 분리되어 `/admin/layout-editor/*` 진입 시에만 로드된다.
// @since engine-v1.51.0

const logger = createLogger('TemplateEngine');

/**
 * 템플릿 메타데이터 인터페이스
 */
interface TemplateMetadata {
  identifier: string;
  locales: string[];
  name: Record<string, string>;
  description: Record<string, string>;
  version: string;
  type: string;
}

/**
 * 템플릿 엔진 상태 인터페이스
 */
interface TemplateEngineState {
  templateId: string | null;
  locale: string;
  isInitialized: boolean;
  reactRoot: ReactDOM.Root | null;
  containerId: string | null;
  currentLayoutJson: any | null;
  currentDataContext: Record<string, any>;
  translationContext: TranslationContext;
  registry: ComponentRegistry | null;
  bindingEngine: DataBindingEngine | null;
  translationEngine: TranslationEngine | null;
  actionDispatcher: ActionDispatcher | null;
  templateMetadata: TemplateMetadata | null;
}

/**
 * 템플릿 엔진 초기화 옵션
 */
interface InitOptions {
  templateId: string;
  templateType?: string;
  locale?: string;
  debug?: boolean;
  /** 확장 기능 캐시 버전 (모듈/플러그인 활성화 시 갱신됨) */
  cacheVersion?: number;
}

/**
 * 렌더링 옵션
 */
interface RenderOptions {
  containerId: string;
  layoutJson: any;
  dataContext?: Record<string, any>;
  translationContext?: TranslationContext;
}

/**
 * 데이터 업데이트 옵션
 */
interface UpdateOptions {
  /**
   * true인 경우 startTransition 없이 즉시 동기적으로 렌더링합니다.
   * 드래그 앤 드롭 후 순서 변경 등 즉각적인 UI 반영이 필요한 경우 사용합니다.
   * 기본값: false (startTransition 사용)
   */
  sync?: boolean;
}

/**
 * 템플릿 엔진 공개 API
 */
interface TemplateEngineAPI {
  initTemplateEngine: (options: InitOptions) => Promise<void>;
  renderTemplate: (options: RenderOptions) => Promise<void>;
  updateTemplateData: (data: Record<string, any>) => void;
  destroyTemplate: () => void;
  getState: () => Readonly<TemplateEngineState>;
}

/**
 * 전역 상태
 */
const state: TemplateEngineState = {
  templateId: null,
  locale: 'ko',
  isInitialized: false,
  reactRoot: null,
  containerId: null,
  currentLayoutJson: null,
  currentDataContext: {},
  translationContext: {
    templateId: '',
    locale: 'ko',
  },
  registry: null,
  bindingEngine: null,
  translationEngine: null,
  actionDispatcher: null,
  templateMetadata: null,
};

/**
 * 디버그 모드 플래그 (ModalDataSourceWrapper 등에서 참조)
 */
let debugMode = false;

/**
 * React가 준비되기 전에 도착한 데이터 업데이트를 저장하는 큐
 * renderTemplate 완료 후 순차적으로 적용됨
 */
const pendingDataUpdates: Array<{ data: Record<string, any>; options?: UpdateOptions }> = [];

/**
 * 템플릿 메타데이터 로드
 *
 * 템플릿 컴포넌트 번들에 포함된 메타데이터를 가져옵니다.
 * API 호출 없이 전역 변수에서 직접 접근하여 성능을 개선합니다.
 *
 * @param templateId - 템플릿 ID
 * @returns 템플릿 메타데이터
 */
async function loadTemplateMetadata(templateId: string): Promise<TemplateMetadata> {
  try {
    logger.log('템플릿 메타데이터 로드 중...', templateId);

    // 템플릿 컴포넌트 번들의 전역 변수명 생성
    // 예: 'sirsoft-admin_basic' → 'SirsoftAdminBasic'
    const globalVarName = templateId
      .split('-')
      .map(part =>
        part
          .split('_')
          .map(subPart => subPart.charAt(0).toUpperCase() + subPart.slice(1))
          .join('')
      )
      .join('');

    // 전역 변수에서 메타데이터 가져오기
    const templateBundle = (window as any)[globalVarName];

    if (templateBundle?.templateMetadata) {
      logger.log('템플릿 메타데이터 로드 완료 (전역 변수)', templateBundle.templateMetadata);
      return templateBundle.templateMetadata;
    }

    // 폴백: 템플릿 번들에 메타데이터가 없는 경우 경고
    logger.warn(
      `템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${globalVarName}`
    );

    // 기본값 반환
    return {
      identifier: templateId,
      locales: ['ko', 'en'],
      name: { ko: templateId, en: templateId },
      description: { ko: '', en: '' },
      version: '1.0.0',
      type: 'admin',
    };
  } catch (error) {
    logger.error('템플릿 메타데이터 로드 실패', error);
    // 기본값 반환
    return {
      identifier: templateId,
      locales: ['ko', 'en'],
      name: { ko: templateId, en: templateId },
      description: { ko: '', en: '' },
      version: '1.0.0',
      type: 'admin',
    };
  }
}

/**
 * 전역 변수 생성
 *
 * 템플릿 엔진 기획서에 명시된 전역 변수를 생성합니다:
 * - $locale: 현재 언어 코드 (예: 'ko', 'en')
 * - $locales: 시스템 활성 언어 목록 (활성 언어팩 기반, 예: ['ko', 'en', 'ja'])
 * - $templateLocales: 현재 템플릿이 자체 번역을 제공하는 언어 목록 (template.json `locales`)
 * - $user: 현재 로그인한 사용자 정보 (추후 구현)
 * - $auth: 인증 상태 (추후 구현)
 *
 * @returns 전역 변수 객체
 */
function createGlobalVariables(): Record<string, any> {
  // 시스템 활성 언어팩(_global.appConfig.supportedLocales) 을 우선 사용한다.
  // 언어팩 설치/제거 시 즉시 사용자 선택 UI 에 반영되어야 하기 때문이다.
  // 미초기화/누락 시에만 템플릿 정적 메타데이터로 폴백한다.
  const templateApp = (typeof window !== 'undefined' ? (window as any).__templateApp : undefined);
  const systemLocales = templateApp?.globalState?.appConfig?.supportedLocales;
  const templateLocales = state.templateMetadata?.locales;

  return {
    $locale: state.locale,
    $locales: (Array.isArray(systemLocales) && systemLocales.length > 0)
      ? systemLocales
      : (templateLocales || ['ko', 'en']),
    $templateLocales: templateLocales || ['ko', 'en'],
    $templateId: state.templateId,
    // 추후 추가 예정:
    // $user: getCurrentUser(),
    // $auth: getAuthState(),
  };
}

/**
 * 템플릿 엔진 초기화
 *
 * 모든 엔진 인스턴스를 생성하고 템플릿 컴포넌트를 로드합니다.
 *
 * @param options - 초기화 옵션
 * @throws {Error} 이미 초기화된 경우 또는 초기화 실패 시
 */
async function initTemplateEngine(options: InitOptions): Promise<void> {
  try {
    logger.log('템플릿 엔진 초기화 시작', options);

    // 이미 초기화된 경우 에러
    if (state.isInitialized) {
      throw new Error('템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.');
    }

    // 옵션 검증
    if (!options.templateId) {
      throw new TemplateNotFoundError();
    }

    // 디버그 모드 설정
    debugMode = options.debug ?? false;

    // G7Config에 debug 모드 설정 (DevTools에서 참조)
    if (typeof window !== 'undefined') {
      if (!(window as any).G7Config) {
        (window as any).G7Config = {};
      }
      (window as any).G7Config.debug = debugMode;
    }

    // DevTools API 초기화 (debug 옵션 설정 후)
    initDevToolsAPI();

    // 상태 업데이트
    state.templateId = options.templateId;
    state.locale = options.locale || 'ko';

    // 엔진 인스턴스 생성
    logger.log('엔진 인스턴스 생성 중...');

    state.registry = ComponentRegistry.getInstance();
    state.bindingEngine = new DataBindingEngine();
    state.translationEngine = TranslationEngine.getInstance();

    // TranslationEngine에 캐시 버전 설정
    if (options.cacheVersion !== undefined && options.cacheVersion > 0) {
      state.translationEngine.setCacheVersion(options.cacheVersion);
    }

    // TranslationContext 초기화
    state.translationContext = {
      templateId: options.templateId,
      locale: state.locale,
    };

    // ActionDispatcher에 TranslationEngine과 TranslationContext 전달
    state.actionDispatcher = new ActionDispatcher(
      {},
      state.translationEngine,
      state.translationContext
    );

    // ActionDispatcher 싱글톤 인스턴스로 설정
    // renderItemChildren 등에서 getActionDispatcher()를 통해 동일한 인스턴스를 사용할 수 있도록 함
    setActionDispatcherInstance(state.actionDispatcher);

    // 다국어 파일 병렬 로드
    logger.log('다국어 파일 로드 중...', options.templateId, state.locale);

    const fallbackLocale = 'en';
    const translationPromises = [
        state.translationEngine
            .loadTranslations(options.templateId, state.locale)
            .then(() => {
                logger.log(`다국어 파일 로드 완료: ${state.locale}`);
            })
            .catch((error) => {
                logger.warn(
                    `다국어 파일 로드 실패 (${state.locale}):`,
                    error instanceof Error ? error.message : error
                );
            }),
    ];

    // 폴백 로케일 로드 (기본 로케일과 다른 경우에만)
    if (state.locale !== fallbackLocale) {
        translationPromises.push(
            state.translationEngine
                .loadTranslations(options.templateId, fallbackLocale)
                .then(() => {
                    logger.log(`폴백 다국어 파일 로드 완료: ${fallbackLocale}`);
                })
                .catch((error) => {
                    logger.warn(
                        `폴백 다국어 파일 로드 실패 (${fallbackLocale}):`,
                        error instanceof Error ? error.message : error
                    );
                })
        );
    }

    // 모든 다국어 파일 로드 대기 (병렬 실행)
    await Promise.allSettled(translationPromises);

    // 템플릿 메타데이터 로드
    logger.log('템플릿 메타데이터 로드 중...', options.templateId);
    state.templateMetadata = await loadTemplateMetadata(options.templateId);

    // ComponentRegistry 로딩은 TemplateApp에서 병렬로 처리됨
    // (성능 최적화를 위해 routes.json과 함께 병렬 로드)

    // 초기화 완료
    state.isInitialized = true;

    logger.log('템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)');
  } catch (error) {
    logger.error('템플릿 엔진 초기화 실패', error);

    // 초기화 실패 시 상태 초기화
    state.isInitialized = false;
    state.registry = null;
    state.bindingEngine = null;
    state.translationEngine = null;
    state.actionDispatcher = null;

    throw error;
  }
}

/**
 * 편집기 lazy 번들(layout-editor.min.js) 로드 후 LayoutEditorChrome 컴포넌트 반환.
 *
 * @since engine-v1.51.0
 *
 * 편집기는 메인 코어 번들에서 분리되어 `/admin/layout-editor/*` 진입 시에만 로드된다.
 * 이미 로드돼 있으면 즉시 반환(멱등), 아니면 `<script>` 를 1회 주입하고 로드 완료를 대기한다.
 * 동시 다중 호출은 in-flight promise 로 병합해 중복 주입을 막는다.
 *
 * @returns LayoutEditorChrome React 컴포넌트
 * @throws {Error} 스크립트 로드 실패 또는 컴포넌트 미노출 시
 */
let layoutEditorLoadPromise: Promise<any> | null = null;

function loadLayoutEditorBundle(): Promise<any> {
  const g7 = (window as any).G7Core;

  // 이미 로드됨 — 즉시 반환 (멱등)
  if (g7?.__LayoutEditorChrome) {
    return Promise.resolve(g7.__LayoutEditorChrome);
  }

  // 진행 중인 로드가 있으면 재사용 (중복 주입 방지)
  if (layoutEditorLoadPromise) {
    return layoutEditorLoadPromise;
  }

  layoutEditorLoadPromise = new Promise((resolve, reject) => {
    // 번들 URL — blade 가 주입한 버전 포함 URL 우선, 폴백은 표준 경로
    const src =
      (window as any).G7Config?.coreEditorAsset || '/build/core/layout-editor.min.js';
    const scriptId = 'g7-layout-editor-bundle';

    // 이미 주입된 <script> 재사용
    const existing = document.getElementById(scriptId) as HTMLScriptElement | null;

    const onReady = () => {
      const chrome = (window as any).G7Core?.__LayoutEditorChrome;
      if (chrome) {
        resolve(chrome);
      } else {
        reject(new Error('편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출'));
      }
    };

    // 편집기 엔트리가 컴포넌트 노출 직후 호출할 준비 완료 콜백 (onload fallback 병행)
    (window as any).G7Core = (window as any).G7Core || {};
    (window as any).G7Core.__onChromeReady = onReady;

    if (existing) {
      // 주입돼 있으나 아직 실행 전 — load 이벤트 대기
      existing.addEventListener('load', onReady, { once: true });
      existing.addEventListener(
        'error',
        () => reject(new Error(`편집기 번들 로드 실패: ${src}`)),
        { once: true }
      );
      return;
    }

    const script = document.createElement('script');
    script.id = scriptId;
    script.src = src;
    script.async = false; // 로드 순서 보존 (메인 번들 이후)
    script.addEventListener('load', onReady, { once: true });
    script.addEventListener(
      'error',
      () => {
        layoutEditorLoadPromise = null; // 재시도 가능하도록 초기화
        reject(new Error(`편집기 번들 로드 실패: ${src}`));
      },
      { once: true }
    );
    document.head.appendChild(script);
  });

  return layoutEditorLoadPromise;
}

/**
 * 템플릿 렌더링
 *
 * 레이아웃 JSON을 기반으로 React 컴포넌트를 렌더링합니다.
 *
 * @param options - 렌더링 옵션
 * @throws {Error} 초기화되지 않은 경우 또는 렌더링 실패 시
 */
async function renderTemplate(options: RenderOptions): Promise<void> {
  try {
    logger.log('템플릿 렌더링 시작', options);

    // 초기화 확인
    if (!state.isInitialized) {
      throw new Error('템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.');
    }

    // 옵션 검증
    if (!options.containerId) {
      throw new Error('containerId는 필수입니다.');
    }

    if (!options.layoutJson) {
      throw new Error('layoutJson은 필수입니다.');
    }

    // DOM 컨테이너 확인
    const container = document.getElementById(options.containerId);
    if (!container) {
      throw new Error(`컨테이너를 찾을 수 없습니다: #${options.containerId}`);
    }

    // 전역 변수 생성
    const globalVariables = createGlobalVariables();

    // 상태 업데이트 (전역 변수를 dataContext에 병합)
    state.containerId = options.containerId;
    state.currentLayoutJson = options.layoutJson;
    state.currentDataContext = {
      ...globalVariables,           // 전역 변수 (낮은 우선순위)
      ...(options.dataContext || {}), // 사용자 제공 데이터 (높은 우선순위)
    };
    state.translationContext = options.translationContext || {
      templateId: state.templateId || '',
      locale: state.locale,
    };

    // React Root 생성 또는 재사용
    if (!state.reactRoot) {
      logger.log('React Root 생성');
      state.reactRoot = ReactDOM.createRoot(container);
    }

    // 레이아웃 편집기 모드 분기
    // URL 이 `/admin/layout-editor/:identifier` 패턴이면 LayoutEditorChrome 을
    // 같은 state.reactRoot + 같은 코어 컨텍스트 래퍼 안에서 렌더한다.
    // 별도 createRoot / 별도 DOM 컨테이너 / 별도 컨텍스트 트리 일체 금지.
    if (typeof window !== 'undefined') {
      const editorMode = checkLayoutEditorMode(window.location.pathname);
      if (editorMode) {
        logger.log('레이아웃 편집기 모드 진입', editorMode);

        // 편집기 lazy 번들 로드 (layout-editor.min.js) — 진입 시에만 로드
        let LayoutEditorChrome: any;
        try {
          LayoutEditorChrome = await loadLayoutEditorBundle();
        } catch (loadError) {
          logger.error('레이아웃 편집기 번들 로드 실패', loadError);
          // 컨테이너에 직접 에러 화면 렌더 (CSS 의존성 없는 인라인 스타일)
          ErrorDisplay.render(options.containerId, {
            title: '레이아웃 편집기 로드 실패',
            message:
              loadError instanceof Error
                ? loadError.message
                : '편집기 번들을 불러오지 못했습니다.',
            icon: 'fas fa-triangle-exclamation',
            showStack: false,
            showReloadButton: true,
            debug: debugMode,
          });
          return;
        }

        const chrome = React.createElement(LayoutEditorChrome, {
          templateIdentifier: editorMode.templateIdentifier,
          initialLocale: state.locale,
        });
        state.reactRoot.render(
          React.createElement(
            TranslationProvider,
            {
              translationEngine: state.translationEngine!,
              translationContext: state.translationContext,
              children: React.createElement(
                TransitionProvider,
                {
                  children: React.createElement(
                    ResponsiveProvider,
                    {
                      children: React.createElement(SlotProvider, { children: chrome }),
                    }
                  ),
                }
              ),
            } as any
          )
        );
        return;
      }
    }

    // 레이아웃 JSON의 components 배열 가져오기
    const components = options.layoutJson.components || [];

    if (components.length === 0) {
      logger.warn('렌더링할 컴포넌트가 없습니다.');
      return;
    }

    // 레이아웃 JSON의 modals 배열 가져오기
    const modals = options.layoutJson.modals || [];
    logger.log('modals 배열:', modals);
    logger.log('modals 개수:', modals.length);

    // 루트 컴포넌트 렌더링 (TransitionProvider, ResponsiveProvider로 래핑하여 전환/반응형 상태 전파)
    logger.log('DynamicRenderer로 렌더링 시작');

    state.reactRoot.render(
      React.createElement(
        TranslationProvider,
        {
          translationEngine: state.translationEngine!,
          translationContext: state.translationContext,
        },
        React.createElement(
          TransitionProvider,
          null,
          React.createElement(
            ResponsiveProvider,
            null,
            React.createElement(
              SlotProvider,
              null,
              [
                // 일반 컴포넌트 렌더링
                ...components.map((componentDef: any, index: number) => {
                  // @since engine-v1.24.5 레이아웃 이름을 key에 포함하여
                  // SPA 네비게이션 시 동일 base layout 공유 페이지 간 React 강제 remount
                  // @since engine-v1.24.8 _fromBase 컴포넌트는 stable key → 보존(update)
                  const layoutKey = state.currentLayoutJson?.layout_name || '';
                  return React.createElement(DynamicRenderer, {
                    key: (layoutKey && !componentDef._fromBase) ? `${componentDef.id}_${layoutKey}` : componentDef.id,
                    componentDef,
                    dataContext: state.currentDataContext,
                    translationContext: state.translationContext,
                    registry: state.registry!,
                    bindingEngine: state.bindingEngine!,
                    translationEngine: state.translationEngine!,
                    actionDispatcher: state.actionDispatcher!,
                    isRootRenderer: index === 0,
                    layoutKey,
                  });
                }),
                // 모달 렌더링 (ParentContextProvider로 감싸서 $parent._local 변경 시 모달만 리렌더링)
                // modalStack 상태에 따라 isOpen 및 onClose 자동 주입
                // 멀티 모달(중첩 모달) 지원: 스택에 있는 모든 모달이 렌더링됨
                // data_sources가 정의된 모달은 ModalDataSourceWrapper로 감싸서 열릴 때 API 호출
                React.createElement(
                  ParentContextProvider,
                  { key: '__modal_parent_context_provider' },
                  modals.map((modalDef: any) => {
                  const modalStack = state.currentDataContext._global?.modalStack || [];
                const isInStack = modalStack.includes(modalDef.id);
                // 하위 호환성: modalStack이 없으면 activeModal 사용
                const isOpen = isInStack || state.currentDataContext._global?.activeModal === modalDef.id;
                // z-index는 스택 내 위치에 따라 동적으로 결정
                const stackIndex = modalStack.indexOf(modalDef.id);
                const zIndex = stackIndex >= 0 ? 50 + stackIndex : 50;

                // 부모 컨텍스트 가져오기 ($parent 바인딩 지원)
                // 모달 스택에서 현재 모달의 위치를 기반으로 부모 컨텍스트를 찾음
                const layoutContextStack: Array<{
                  state: Record<string, any>;
                  setState: (updates: any) => void;
                  dataContext?: Record<string, any>;
                }> = (window as any).__g7LayoutContextStack || [];
                // 스택의 마지막 항목이 모달을 연 시점의 부모 컨텍스트
                const parentContextEntry = layoutContextStack[layoutContextStack.length - 1];
                const parentDataContext = parentContextEntry?.dataContext;

                // 모달 렌더러 생성
                const modalRenderer = React.createElement(DynamicRenderer, {
                  key: `modal_${modalDef.id}_renderer`,
                  componentDef: {
                    ...modalDef,
                    props: {
                      ...modalDef.props,
                      isOpen,
                      // z-index를 스택 위치에 따라 설정
                      style: {
                        ...modalDef.props?.style,
                        zIndex,
                      },
                      // onClose는 closeModal 액션으로 연결
                      onClose: state.actionDispatcher?.createHandler(
                        { type: 'click', handler: 'closeModal' },
                        state.currentDataContext
                      ),
                    },
                  },
                  dataContext: state.currentDataContext,
                  translationContext: state.translationContext,
                  registry: state.registry!,
                  bindingEngine: state.bindingEngine!,
                  translationEngine: state.translationEngine!,
                  actionDispatcher: state.actionDispatcher!,
                  // $parent 바인딩을 위해 부모 데이터 컨텍스트 전달
                  parentDataContext,
                });

                // data_sources가 있으면 ModalDataSourceWrapper로 감싸기
                if (modalDef.data_sources && modalDef.data_sources.length > 0) {
                  return React.createElement(ModalDataSourceWrapper, {
                    key: `modal_${modalDef.id}`,
                    isOpen,
                    modalId: modalDef.id,
                    dataSources: modalDef.data_sources,
                    dataContext: state.currentDataContext,
                    globalStateUpdater: state.actionDispatcher?.getGlobalStateUpdater(),
                    bindingEngine: state.bindingEngine!,
                    debug: debugMode,
                    children: modalRenderer,
                  });
                }

                return modalRenderer;
              })
              ),  // ParentContextProvider 종료
              ]
            )  // SlotProvider 종료
          )  // ResponsiveProvider 종료
        )  // TransitionProvider 종료
      )  // TranslationProvider 종료
    );

    logger.log('템플릿 렌더링 완료');

    // React 준비 전에 큐에 저장된 데이터 업데이트 적용
    if (pendingDataUpdates.length > 0) {
      logger.log(`대기 중인 데이터 업데이트 ${pendingDataUpdates.length}건 적용`);
      const updates = [...pendingDataUpdates];
      pendingDataUpdates.length = 0; // 큐 비우기

      // 큐에 있는 모든 데이터를 순차적으로 적용
      for (const { data, options: updateOptions } of updates) {
        updateTemplateData(data, updateOptions);
      }
    }
  } catch (error) {
    logger.error('템플릿 렌더링 실패', error);
    throw error;
  }
}

/**
 * 템플릿 데이터 업데이트
 *
 * 현재 렌더링된 템플릿의 데이터 컨텍스트를 업데이트하고 재렌더링합니다.
 *
 * @param data - 업데이트할 데이터
 * @param options - 업데이트 옵션 (sync: true면 startTransition 없이 즉시 렌더링)
 * @throws {Error} 초기화되지 않은 경우 또는 렌더링되지 않은 경우
 */
function updateTemplateData(data: Record<string, any>, options?: UpdateOptions): void {
  try {
    logger.log('템플릿 데이터 업데이트 시작', Object.keys(data));

    // 초기화 확인
    if (!state.isInitialized) {
      throw new Error('템플릿 엔진이 초기화되지 않았습니다.');
    }

    // 렌더링 확인 - React가 준비되지 않았으면 큐에 저장
    if (!state.reactRoot || !state.currentLayoutJson) {
      logger.log('React 미준비 - 데이터 업데이트 큐잉:', Object.keys(data));
      pendingDataUpdates.push({ data, options });
      return;
    }

    // 전역 변수 재생성
    const globalVariables = createGlobalVariables();

    // 데이터 병합 (전역 변수 유지)
    // _global 객체는 명시적으로 깊은 병합 수행
    const mergedGlobalState = {
      ...(state.currentDataContext._global || {}),
      ...(data._global || {}),
    };

    // _localInit도 얕은 스프레드로 교체하면 안 됨 (engine-v1.52.2)
    // progressive 데이터소스가 둘 이상이면 각자 독립적으로 updateTemplateData를 호출하는데,
    // 소비부(DynamicRenderer의 useEffect)는 React commit 이후에 실행된다.
    // 두 호출이 같은 commit 사이에 들어오면 나중 payload가 슬롯을 통째로 교체하여
    // 먼저 도착한 소스의 initLocal이 한 번도 관측되지 않고 사라진다.
    // → 아직 관측되지 않은(unconsumed) 슬롯만 누적 병합한다. 상세: localInitSlot.ts
    const mergedLocalInit = mergeLocalInitSlot(state.currentDataContext._localInit, data._localInit);

    state.currentDataContext = {
      ...globalVariables,           // 전역 변수 (낮은 우선순위)
      ...state.currentDataContext,  // 기존 데이터
      ...data,                       // 새 데이터 (높은 우선순위)
      _global: mergedGlobalState,   // _global은 명시적으로 깊은 병합된 값 사용
      _localInit: mergedLocalInit,  // _localInit은 소비 전이면 누적 병합된 값 사용
    };

    // _local 및 _computed는 _global의 값을 canonical source로 동기화
    // SPA 네비게이션 시 이전 페이지의 stale _local/_computed가 top-level에 잔존하는 것을 방지
    // (renderTemplate → updateTemplateData 트리 구조 통일 후 React가 컴포넌트를 보존하면서 발생)
    if (mergedGlobalState._local !== undefined) {
      state.currentDataContext._local = mergedGlobalState._local;
    }
    if (mergedGlobalState._computed !== undefined) {
      state.currentDataContext._computed = mergedGlobalState._computed;
    }

    const components = state.currentLayoutJson.components || [];
    const modals = state.currentLayoutJson.modals || [];
    logger.log('updateTemplateData - modals 배열:', modals);
    logger.log('updateTemplateData - modals 개수:', modals.length);
    logger.log('updateTemplateData - activeModal:', state.currentDataContext._global?.activeModal);

    // 렌더링 함수 정의 (startTransition 유무에 따라 재사용)
    const doRender = () => {
      state.reactRoot!.render(
        React.createElement(
          TranslationProvider,
          {
            translationEngine: state.translationEngine!,
            translationContext: state.translationContext,
          },
          React.createElement(
            TransitionProvider,
            null,
            React.createElement(
              ResponsiveProvider,
              null,
              React.createElement(
                SlotProvider,
                null,
                [
                  // 일반 컴포넌트 렌더링
                  ...components.map((componentDef: any, index: number) => {
                    // @since engine-v1.24.5 레이아웃 이름을 key에 포함하여
                    // SPA 네비게이션 시 동일 base layout 공유 페이지 간 React 강제 remount
                    // @since engine-v1.24.8 _fromBase 컴포넌트는 stable key → 보존(update)
                    const layoutKey = state.currentLayoutJson?.layout_name || '';
                    return React.createElement(DynamicRenderer, {
                      key: (layoutKey && !componentDef._fromBase) ? `${componentDef.id}_${layoutKey}` : componentDef.id,
                      componentDef,
                      dataContext: state.currentDataContext,
                      translationContext: state.translationContext,
                      registry: state.registry!,
                      bindingEngine: state.bindingEngine!,
                      translationEngine: state.translationEngine!,
                      actionDispatcher: state.actionDispatcher!,
                      isRootRenderer: index === 0,
                      layoutKey,
                    });
                  }),
                  // 모달 렌더링 (ParentContextProvider로 감싸서 $parent._local 변경 시 모달만 리렌더링)
                  // modalStack 상태에 따라 isOpen 및 onClose 자동 주입
                  // 멀티 모달(중첩 모달) 지원: 스택에 있는 모든 모달이 렌더링됨
                  // data_sources가 정의된 모달은 ModalDataSourceWrapper로 감싸서 열릴 때 API 호출
                  React.createElement(
                    ParentContextProvider,
                    { key: '__modal_parent_context_provider_update' },
                    modals.map((modalDef: any) => {
                    const modalStack = state.currentDataContext._global?.modalStack || [];
                    const isInStack = modalStack.includes(modalDef.id);
                    // 하위 호환성: modalStack이 없으면 activeModal 사용
                    const isOpen = isInStack || state.currentDataContext._global?.activeModal === modalDef.id;
                    // z-index는 스택 내 위치에 따라 동적으로 결정
                    const stackIndex = modalStack.indexOf(modalDef.id);
                    const zIndex = stackIndex >= 0 ? 50 + stackIndex : 50;

                    // 부모 컨텍스트 가져오기 ($parent 바인딩 지원)
                    const layoutContextStack2: Array<{
                      state: Record<string, any>;
                      setState: (updates: any) => void;
                      dataContext?: Record<string, any>;
                    }> = (window as any).__g7LayoutContextStack || [];
                    const parentContextEntry2 = layoutContextStack2[layoutContextStack2.length - 1];
                    const parentDataContext2 = parentContextEntry2?.dataContext;

                    // 모달 렌더러 생성
                    const modalRenderer = React.createElement(DynamicRenderer, {
                      key: `modal_${modalDef.id}_renderer`,
                      componentDef: {
                        ...modalDef,
                        props: {
                          ...modalDef.props,
                          isOpen,
                          // z-index를 스택 위치에 따라 설정
                          style: {
                            ...modalDef.props?.style,
                            zIndex,
                          },
                          // onClose는 closeModal 액션으로 연결
                          onClose: state.actionDispatcher?.createHandler(
                            { type: 'click', handler: 'closeModal' },
                            state.currentDataContext
                          ),
                        },
                      },
                      dataContext: state.currentDataContext,
                      translationContext: state.translationContext,
                      registry: state.registry!,
                      bindingEngine: state.bindingEngine!,
                      translationEngine: state.translationEngine!,
                      actionDispatcher: state.actionDispatcher!,
                      // $parent 바인딩을 위해 부모 데이터 컨텍스트 전달
                      parentDataContext: parentDataContext2,
                    });

                    // data_sources가 있으면 ModalDataSourceWrapper로 감싸기
                    if (modalDef.data_sources && modalDef.data_sources.length > 0) {
                      return React.createElement(ModalDataSourceWrapper, {
                        key: `modal_${modalDef.id}`,
                        isOpen,
                        modalId: modalDef.id,
                        dataSources: modalDef.data_sources,
                        dataContext: state.currentDataContext,
                        globalStateUpdater: state.actionDispatcher?.getGlobalStateUpdater(),
                        bindingEngine: state.bindingEngine!,
                        debug: debugMode,
                        children: modalRenderer,
                      });
                    }

                    return modalRenderer;
                  })
                ),  // ParentContextProvider 종료
              ]
            )  // SlotProvider 종료
          )  // ResponsiveProvider 종료
        )  // TransitionProvider 종료
      )  // TranslationProvider 종료
      );
    };

    // sync 옵션에 따라 렌더링 방식 선택
    if (options?.sync) {
      // 동기 모드: startTransition 없이 즉시 렌더링 (드래그 앤 드롭 등 즉각적인 UI 반영 필요 시)
      logger.log('재렌더링 시작 (sync mode - 즉시 렌더링)');
      doRender();
    } else {
      // 기본 모드: startTransition으로 래핑하여 깜빡임 방지
      logger.log('재렌더링 시작 (with startTransition)');
      startTransition(() => {
        doRender();
      });
    }

    logger.log('템플릿 데이터 업데이트 완료');
  } catch (error) {
    logger.error('템플릿 데이터 업데이트 실패', error);
    throw error;
  }
}

/**
 * 템플릿 정리
 *
 * React Root를 언마운트하고 모든 상태를 초기화합니다.
 */
function destroyTemplate(): void {
  try {
    logger.log('템플릿 정리 시작');

    // React Root 언마운트
    if (state.reactRoot) {
      logger.log('React Root 언마운트');
      state.reactRoot.unmount();
      state.reactRoot = null;
    }

    // 상태 초기화
    state.templateId = null;
    state.locale = 'ko';
    state.isInitialized = false;
    state.containerId = null;
    state.currentLayoutJson = null;
    state.currentDataContext = {};
    state.translationContext = {
      templateId: '',
      locale: 'ko',
    };
    state.registry = null;
    state.bindingEngine = null;
    state.translationEngine = null;
    state.actionDispatcher = null;
    state.templateMetadata = null;

    logger.log('템플릿 정리 완료');
  } catch (error) {
    logger.error('템플릿 정리 실패', error);
    throw error;
  }
}

/**
 * 현재 상태 조회
 *
 * 읽기 전용 상태 객체를 반환합니다.
 *
 * @returns 현재 템플릿 엔진 상태
 */
function getState(): Readonly<TemplateEngineState> {
  return Object.freeze({ ...state });
}

/**
 * ActionDispatcher 인스턴스 조회
 *
 * @returns ActionDispatcher 인스턴스 또는 null
 */
function getActionDispatcher(): ActionDispatcher | null {
  return state.actionDispatcher;
}

/**
 * 템플릿 엔진 공개 API
 */
const TemplateEngine: TemplateEngineAPI = {
  initTemplateEngine,
  renderTemplate,
  updateTemplateData,
  destroyTemplate,
  getState,
};

/**
 * 전역 객체에 노출
 *
 * G7Core 전역 API는 G7CoreGlobals.ts 모듈로 분리되어 있습니다.
 */
if (typeof window !== 'undefined') {
  // G7Core 네임스페이스가 없으면 생성 (TemplateApp.ts에서 이미 생성했을 수 있음)
  if (!(window as any).G7Core) {
    (window as any).G7Core = {};
  }

  // TemplateEngine 노출
  (window as any).G7Core.TemplateEngine = TemplateEngine;

  // G7Core 전역 API 초기화
  initializeG7CoreGlobals({
    getState: () => ({
      translationEngine: state.translationEngine,
      translationContext: state.translationContext,
      bindingEngine: state.bindingEngine,
      actionDispatcher: state.actionDispatcher,
      templateMetadata: state.templateMetadata,
    }),
    transitionManager,
    responsiveManager,
    webSocketManager,
  });

  logger.log('전역 객체 window.G7Core.TemplateEngine에 노출됨');
}

/**
 * ESM export
 */
export default TemplateEngine;
export {
  TemplateEngine,
  initTemplateEngine,
  renderTemplate,
  updateTemplateData,
  destroyTemplate,
  getState,
  getActionDispatcher,
  LayoutLoader,
  DataSourceManager,
  // @since engine-v1.51.0 편집기 lazy 번들 로더 (테스트/진단용 노출)
  loadLayoutEditorBundle,
};

export type {
  TemplateEngineAPI,
  TemplateEngineState,
  InitOptions,
  RenderOptions,
  UpdateOptions,
};

export type { LayoutData, LayoutComponent } from './template-engine/LayoutLoader';
export type { DataSource, DataSourceType, ConditionContext } from './template-engine/DataSourceManager';

// TemplateApp export (이미 상단에서 import됨)
export { TemplateApp, initTemplateApp };
export type { TemplateAppConfig } from './TemplateApp';
export type { Route } from './routing/Router';

// TemplateApp.ts 모듈에서 이미 전역 노출 처리함 (Object.defineProperty getter 사용)
