import type { FunctionalComponent } from 'preact';
import { Fragment } from 'preact';
import { createPortal } from 'preact/compat';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useAsukaStore, type LayoutBlock } from '../../store/asukaStore';
import {
  ensureModuleAvailable,
  getModule,
  type AsukaModuleContext,
  useModuleRegistryVersion,
} from '../../core/modules';
import { OverlayHost, type OverlayType } from '../overlays/OverlayHost';
import { observeDomPortalTarget, resolveDomTarget, type OverlayPortalTarget } from '../../core/overlayMounts';
import { getVanillaUiHelpers } from '../../core/moduleUi';
import { getI18n } from '../../core/i18n';
import { isModuleBlockedWithoutView } from '../../core/moduleAvailability';
import { ModuleUnavailableState } from './ModuleUnavailableState';
import { shouldUseFullscreenOverlayForBreakpoint, transformOverlayTypeForBreakpoint } from '../../core/responsive';
import { createLiveModuleRenderEngine } from '../../core/engineManager';
import { logAsuka3DPerf } from '../../core/perf3dDebug';

/**
 * ModuleHost
 * ----------
 * Central entry point for all non-inline modules.
 *
 * Notes UI:
 * - Adds semantic classes to identify the overlay type (modal/drawer/floating/panels)
 *   in the DOM, in addition to the utility Tailwind classes.
 * - Styles are defined in src/styles/index.css (the .ask-overlay* classes).
 */
export interface ModuleHostProps {
  /** Default target used by contained drawer overlays when no per-module target is configured. */
  defaultDrawerMountTarget?: OverlayPortalTarget;
  /** Module bodies rendered persistently into external DOM targets. */
  pagePortalBlocks?: LayoutBlock[];
}

interface PagePortalModuleProps {
  block: LayoutBlock;
  context: AsukaModuleContext;
  hasViews: boolean;
  registryVersion: number;
}

const pagePortalHostReferences = new WeakMap<HTMLElement, number>();

const PagePortalModule: FunctionalComponent<PagePortalModuleProps> = ({
  block,
  context,
  hasViews,
  registryVersion,
}) => {
  const [target, setTarget] = useState<HTMLElement | null>(() => resolveDomTarget(block.mountTarget ?? null));

  useEffect(
    () => observeDomPortalTarget(
      block.mountTarget ?? null,
      String(block.id ?? block.kind ?? 'module'),
      setTarget,
    ),
    [block.id, block.kind, block.mountTarget],
  );

  useEffect(() => {
    if (!target) return;
    const references = (pagePortalHostReferences.get(target) ?? 0) + 1;
    pagePortalHostReferences.set(target, references);
    target.classList.add('ask-page-module-host');
    target.dataset.asukaPageModuleHost = 'true';
    return () => {
      const remaining = Math.max(0, (pagePortalHostReferences.get(target) ?? 1) - 1);
      if (remaining > 0) {
        pagePortalHostReferences.set(target, remaining);
        return;
      }
      pagePortalHostReferences.delete(target);
      target.classList.remove('ask-page-module-host');
      delete target.dataset.asukaPageModuleHost;
    };
  }, [target]);

  void registryVersion;
  const id = block.kind;
  const def = id ? getModule(id) : undefined;
  const ModuleComponent = def?.render;
  const isBlockedWithoutView = def ? isModuleBlockedWithoutView(def, hasViews) : false;
  const debugEnabled = typeof window !== 'undefined'
    && /[?&](?:asuka_portal_debug|apd_debug_template)=1(?:&|$)/.test(window.location.search);
  const portal = id && def && target && (ModuleComponent || isBlockedWithoutView)
    ? createPortal(
      <section
        class="ask-page-module"
        data-asuka-page-module={String(id)}
        data-asuka-block-id={String(block.id ?? id)}
        data-asuka-mount-target={String(block.mountTarget ?? '')}
        aria-label={String(block.label ?? def.label ?? id)}
      >
        {isBlockedWithoutView ? (
          <ModuleUnavailableState />
        ) : ModuleComponent ? (
          <ModuleComponent context={context} ui={context.ui} />
        ) : null}
      </section>,
      target,
    )
    : null;

  return (
    <Fragment>
      {debugEnabled ? (
        <span
          hidden
          data-asuka-page-portal-status={String(id ?? '')}
          data-asuka-page-portal-target={target ? 'resolved' : 'missing'}
          data-asuka-page-portal-definition={def ? 'resolved' : 'missing'}
          data-asuka-page-portal-renderer={ModuleComponent ? 'resolved' : 'missing'}
          data-asuka-page-portal-blocked={isBlockedWithoutView ? 'true' : 'false'}
        />
      ) : null}
      {portal}
    </Fragment>
  );
};

export const ModuleHost: FunctionalComponent<ModuleHostProps> = ({
  defaultDrawerMountTarget = null,
  pagePortalBlocks = [],
}) => {
  const renderStartedAtRef = useRef(typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now());
  renderStartedAtRef.current = typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now();
  const moduleRegistryVersion = useModuleRegistryVersion();


  const activeModuleId = useAsukaStore((state) => state.activeModuleId);
  const closeModule = useAsukaStore((state) => state.closeModule);
  const stack = useAsukaStore((state) => state.moduleOverlayStack);
  const overlayType = useAsukaStore((state) => state.moduleOverlayType);
  const moduleDisplayModeOverrides = useAsukaStore((state) => state.moduleDisplayModeOverrides);
  const moduleMountTargetOverrides = useAsukaStore((state) => state.moduleMountTargetOverrides);
  const backOverlay = useAsukaStore((state) => state.backModuleOverlay);
  const anchorBox = useAsukaStore((state) => state.moduleOverlayAnchorBox);
  const pendingRestoreSelectionMenu = useAsukaStore((state: any) => state.pendingRestoreSelectionMenu);
  const elementEditorState = useAsukaStore((state: any) => state.elementEditor);
  const closeElementEditor = useAsukaStore((state: any) => state.closeElementEditor);
  const closeElementVariationPicker = useAsukaStore((state: any) => state.closeElementVariationPicker);
  const setPendingRestoreSelectionMenu = useAsukaStore((state: any) => state.setPendingRestoreSelectionMenu);
  const openSelectionMenu = useAsukaStore((state: any) => state.openSelectionMenu);
  const resetSelectionMenuNav = useAsukaStore((state: any) => state.resetSelectionMenuNav);
  const mountedModules = useAsukaStore((state) => state.mountedModules);
  const hiddenModules = useAsukaStore((state) => state.hiddenModules);
  const exitingModules = useAsukaStore((state) => state.exitingModules);
  const finalizeModuleExit = useAsukaStore((state) => state.finalizeModuleExit);
  const views = useAsukaStore((state: any) => state.views ?? []);
  const documentViews = useAsukaStore((state: any) => state.documentViews ?? []);
  const hasViews = (Array.isArray(views) && views.length > 0) || (Array.isArray(documentViews) && documentViews.length > 0);
  const responsiveConfig = useAsukaStore((state: any) => state.responsiveConfig);
  const responsiveBreakpoint = useAsukaStore((state: any) => state.responsiveBreakpoint ?? 'desktop');

  const currentId = (stack && stack.length > 0 ? stack[stack.length - 1] : activeModuleId) ?? null;

  useEffect(() => {
    if (!currentId) return;
    const now = () => typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now();
    const startedAt = renderStartedAtRef.current;
    logAsuka3DPerf('ui-module', 'mounted', { id: currentId }, {
      durationMs: now() - startedAt,
      console: false,
    });
    if (typeof requestAnimationFrame !== 'function') {
      logAsuka3DPerf('ui-module', 'visible', { id: currentId }, { durationMs: now() - startedAt, console: false });
      return;
    }
    const raf = requestAnimationFrame(() => {
      const durationMs = now() - startedAt;
      logAsuka3DPerf('ui-module', 'painted', { id: currentId }, { durationMs, console: false });
      logAsuka3DPerf('ui-module', 'visible', { id: currentId }, { durationMs, console: false });
    });
    return () => cancelAnimationFrame(raf);
  }, [currentId]);

  // Modules to render:
  // - the active module (currentId)
  // - any keepMounted modules that have been mounted before (even if hidden)
  // - any modules currently exiting (crossfade A -> B)
  // IMPORTANT: render order matters for pointer events and visual stacking.
  // We want the *active* overlay to be on top, while exiting overlays stay behind
  // during their fade-out. Therefore we render:
  //   1) exiting overlays
  //   2) keepMounted overlays (hidden or visible)
  //   3) active overlay (last => on top)
  const moduleIdsToRender = useMemo(() => {
    void moduleRegistryVersion;
    const exiting: string[] = [];
    const kept: string[] = [];
    const active: string[] = [];

    Object.keys(exitingModules ?? {}).forEach((id) => {
      if ((exitingModules as any)?.[id]) exiting.push(id);
    });

    Object.keys(mountedModules ?? {}).forEach((id) => {
      const def = getModule(id as any);
      if (def?.keepMounted) kept.push(id);
    });

    if (currentId) active.push(currentId);

    // De-dup while preserving the intended order.
    const seen = new Set<string>();
    const ordered = [...exiting, ...kept, ...active].filter((id) => {
      if (seen.has(id)) return false;
      seen.add(id);
      return true;
    });

    return ordered as any[];
  }, [currentId, exitingModules, mountedModules, moduleRegistryVersion]);

  useEffect(() => {
    moduleIdsToRender.forEach((id) => { void ensureModuleAvailable(id as any); });
    pagePortalBlocks.forEach((block) => {
      if (block?.kind) void ensureModuleAvailable(block.kind as any);
    });
  }, [moduleIdsToRender, pagePortalBlocks]);

  useEffect(() => {
    // Defensive guard for state-backed overlays. Lazy module loading can otherwise
    // leave a stale active module with no payload after the user has already
    // submitted/cancelled the editor, which renders an empty modal shell.
    if (currentId === 'element-editor' && !elementEditorState) closeModule();
  }, [currentId, elementEditorState, closeModule]);


  // Keep one stable facade: each service getter resolves the active view engine
  // at call time, while mounted modules retain their component state.
  const liveEngine = useMemo(() => createLiveModuleRenderEngine(), []);
  const context = useMemo<AsukaModuleContext>(() => ({
    getEngine: () => liveEngine,
    ui: getVanillaUiHelpers(),
  }), [liveEngine]);

  if (moduleIdsToRender.length === 0 && pagePortalBlocks.length === 0) return null;

  return (
    <Fragment>
      {pagePortalBlocks.map((block) => (
        <PagePortalModule
          key={block.id}
          block={block}
          context={context}
          hasViews={hasViews}
          registryVersion={moduleRegistryVersion}
        />
      ))}
      {moduleIdsToRender.map((id) => {
        const def = getModule(id);
        if (!def) return null;
        if (id === 'element-editor' && !elementEditorState) return null;

        const isActive = id === currentId;
        const isExiting = !isActive && !!(exitingModules as any)?.[id];
        const isHidden = !isActive && !isExiting && !!(hiddenModules as any)?.[id];

        const ModuleComponent = def.render;
        const isBlockedWithoutView = isModuleBlockedWithoutView(def, hasViews);

        // IMPORTANT: the overlay type must remain stable while navigating inside the *active* overlay.
        // We keep it in the store (set on first open).
        const mode = (isActive
          ? overlayType ?? (moduleDisplayModeOverrides as any)[id] ?? def.displayMode ?? 'floating'
          : (moduleDisplayModeOverrides as any)[id] ?? def.displayMode ?? 'floating') as any;

        // Inline modules are not managed by this host.
        if (mode === 'inline') return null;

        // Helpers for DOM semantics
        const overlayAttrs = (overlayType: string, isFullscreen: boolean) => ({
          'data-asuka-overlay': overlayType,
          'data-asuka-module': def.id,
          'data-asuka-responsive-breakpoint': String(responsiveBreakpoint ?? 'desktop'),
          'data-asuka-overlay-original': String(mode),
          'data-asuka-overlay-fullscreen': isFullscreen ? 'true' : 'false',
        });

        const isStudio = def.id === 'studio';
        const responsiveMode = transformOverlayTypeForBreakpoint(mode as any, responsiveBreakpoint as any, responsiveConfig as any);
        const effectiveMode = (isStudio ? 'modal' : responsiveMode) as any;
        const fullscreen = isStudio || shouldUseFullscreenOverlayForBreakpoint(effectiveMode as any, responsiveBreakpoint as any, responsiveConfig as any);
        const explicitMountTarget = ((moduleMountTargetOverrides as any)?.[id] ?? def.mountTarget ?? null) as OverlayPortalTarget;
        const portalTarget = explicitMountTarget
          ? resolveDomTarget(explicitMountTarget) ?? explicitMountTarget
          : effectiveMode === 'drawer'
            ? defaultDrawerMountTarget
            : null;


        const canBack = isActive && (stack?.length ?? 0) > 1;
        const api = getI18n();
        const bundles = api.getTranslations?.() ?? {};
        const locale = api.getLocale?.() ?? 'en';
        const fallbackLocale = api.getFallbackLocale?.() ?? 'en';
        const getPath = (bundle: any, key: string): string | undefined => {
          if (!bundle || typeof bundle !== 'object') return undefined;
          if (typeof bundle[key] === 'string') return bundle[key];
          const firstDot = key.indexOf('.');
          if (firstDot > 0) {
            const ns = key.slice(0, firstDot);
            const scoped = bundle[ns];
            if (scoped && typeof scoped === 'object') {
              if (typeof scoped[key] === 'string') return scoped[key];
              const short = key.slice(firstDot + 1);
              if (typeof scoped[short] === 'string') return scoped[short];
            }
          }
          const parts = key.split('.');
          let cur: any = bundle;
          for (const part of parts) {
            if (!cur || typeof cur !== 'object' || !(part in cur)) return undefined;
            cur = cur[part];
          }
          return typeof cur === 'string' ? cur : undefined;
        };
        const title =
          getPath(bundles[locale], `menu.${String(def.id)}`) ??
          getPath(bundles[fallbackLocale], `menu.${String(def.id)}`) ??
          getPath(bundles[locale], `modules.${String(def.id)}`) ??
          getPath(bundles[fallbackLocale], `modules.${String(def.id)}`) ??
          def.label ??
          def.title ??
          String(def.id);
        const icon = def.icon;

        if (!ModuleComponent && !isBlockedWithoutView) return null;

        const handleClose = isActive
          ? () => {
              if (def.id === 'element-editor') closeElementEditor?.();
              if (def.id === 'element-variation-picker') closeElementVariationPicker?.();
              closeModule();
              if (def.id === 'studio' && pendingRestoreSelectionMenu) {
                resetSelectionMenuNav?.();
                openSelectionMenu?.();
                setPendingRestoreSelectionMenu?.(false);
              }
            }
          : undefined;

        return (
          <div key={def.id} style={isHidden ? { display: 'none' } : undefined}>
            <OverlayHost
              type={effectiveMode as OverlayType}
              title={title}
              icon={icon}
              zClass={isExiting ? 'ask-z-10' : undefined}
              isVisible={!isHidden}
              isExiting={isExiting}
              onExited={isExiting ? () => finalizeModuleExit(def.id) : undefined}
              canBack={canBack}
              onBack={canBack ? backOverlay : undefined}
              onClose={handleClose}
              anchorBox={isActive && !fullscreen ? anchorBox : null}
              attrs={overlayAttrs(String(effectiveMode), fullscreen)}
              compactHeader={effectiveMode !== 'modal'}
              fullscreen={fullscreen}
              portalTarget={portalTarget}
              allowBackgroundInteraction={def.allowCanvasInteraction === true}
            >
              {isBlockedWithoutView ? (
                <ModuleUnavailableState />
              ) : ModuleComponent ? (
                <ModuleComponent context={context} ui={context.ui} />
              ) : null}
            </OverlayHost>
          </div>
        );
      })}
    </Fragment>
  );
};
