import { useEffect, useMemo, useState } from 'preact/hooks';
import { useAsukaStore } from '../../store/asukaStore';
import { registerModule, type AsukaModuleComponent } from '../../core/modules';
import { getModuleEngine } from '../../core/moduleContext';
import { DEFAULT_PERMISSIONS } from '../../core/permissions';
import { getI18n } from '../../core/i18n';
import { useT } from '../../hooks/useT';

import { Card, CardHeader, CardTitle, CardContent } from '../core/Card';
import { Button } from '../core/Button';
import { Field } from '../core/Field';
import { List, ListItem } from '../core/List';
import { onAsukaEvent } from '../../core/events';
import { asukaHistoryDebug, isAsukaHistoryDebugEnabled, summarizeHistoryInfo, summarizeHistoryTrack } from '../../core/historyDebug';
import { normalizeRenderEngineHistoryInfo, type AsukaHistoryInfo } from '../../core/renderEngine';

const DOCUMENT_FABRIC_HISTORY_KEY = '__document__';
const DEFAULT_HISTORY_MAX_SNAPSHOTS = 8;

type HistoryInfo = AsukaHistoryInfo;

function normalizeHistoryInfo(input: any): HistoryInfo {
  return normalizeRenderEngineHistoryInfo(input, { maxSnapshots: DEFAULT_HISTORY_MAX_SNAPSHOTS });
}

const HistoryModule: AsukaModuleComponent = ({ context }) => {
  const t = useT();
  const engine = getModuleEngine(context);
  const storeHistoryInfo = useAsukaStore((state: any) => state.documentHistoryInfo);
  const activeRenderEngineKind = useAsukaStore((state: any) => state.activeRenderEngineKind ?? 'fabric');
  const activeViewId = useAsukaStore((state: any) => state.activeViewId ?? null);
  const active3dHistoryMarker = useAsukaStore((state: any) => {
    const viewId = String(state?.activeViewId ?? '').trim();
    const track = viewId ? state?.document3dHistoryByView?.[viewId] : null;
    const length = Array.isArray(track?.snapshots) ? track.snapshots.length : 0;
    return `${viewId}:${Number(track?.index ?? -1)}:${length}`;
  });
  const activeFabricHistoryMarker = useAsukaStore((state: any) => {
    const viewId = String(state?.activeViewId ?? '').trim();
    const track = state?.documentFabricHistoryByView?.[DOCUMENT_FABRIC_HISTORY_KEY] ?? (viewId ? state?.documentFabricHistoryByView?.[viewId] : null);
    const length = Array.isArray(track?.snapshots) ? track.snapshots.length : 0;
    return `${String(track?.viewId ?? viewId)}:${Number(track?.index ?? -1)}:${length}`;
  });
  const [runtimeHistoryInfo, setRuntimeHistoryInfo] = useState<HistoryInfo>(normalizeHistoryInfo(storeHistoryInfo));
  const permissions = useAsukaStore((state: any) => state.permissions ?? DEFAULT_PERMISSIONS);

  useEffect(() => {
    const normalized = normalizeHistoryInfo(storeHistoryInfo);
    asukaHistoryDebug('module.history.storeHistoryInfo', {
      activeViewId,
      activeRenderEngineKind,
      storeHistoryInfo: summarizeHistoryInfo(storeHistoryInfo),
      normalized: summarizeHistoryInfo(normalized),
    });
    setRuntimeHistoryInfo(normalized);
  }, [storeHistoryInfo, activeRenderEngineKind, activeViewId]);

  useEffect(() => {
    let disposed = false;
    let raf: number | undefined;

    const apply = (next: HistoryInfo) => {
      if (disposed) return;
      asukaHistoryDebug('module.history.apply', {
        activeViewId: (useAsukaStore.getState() as any)?.activeViewId ?? activeViewId,
        activeRenderEngineKind: (useAsukaStore.getState() as any)?.activeRenderEngineKind ?? activeRenderEngineKind,
        next: summarizeHistoryInfo(next),
      });
      setRuntimeHistoryInfo((prev) => (
        prev.index === next.index &&
        prev.length === next.length &&
        prev.canUndo === next.canUndo &&
        prev.canRedo === next.canRedo &&
        prev.maxSnapshots === next.maxSnapshots &&
        prev.engine === next.engine
      ) ? prev : next);
    };

    const historyInfoFromTrack = (engineKind: '3d' | 'fabric', viewId: string | null | undefined, track: any): HistoryInfo | null => {
      if (!viewId || !track || !Array.isArray(track?.snapshots) || track.snapshots.length === 0) return null;
      const index = Math.max(0, Math.min(track.snapshots.length - 1, Number(track.index ?? track.snapshots.length - 1) || 0));
      const isDocumentTrack = String(track?.scope ?? '').trim() === 'document' || String(track?.viewId ?? viewId ?? '').trim() === DOCUMENT_FABRIC_HISTORY_KEY;
      return normalizeHistoryInfo({
        engine: engineKind,
        scope: isDocumentTrack ? 'document' : 'view',
        viewId: isDocumentTrack ? DOCUMENT_FABRIC_HISTORY_KEY : viewId,
        index,
        length: track.snapshots.length,
        canUndo: index > 0,
        canRedo: index < track.snapshots.length - 1,
        maxSnapshots: Number(track.maxSnapshots ?? (useAsukaStore.getState() as any)?.documentHistoryInfo?.maxSnapshots ?? DEFAULT_HISTORY_MAX_SNAPSHOTS) || DEFAULT_HISTORY_MAX_SNAPSHOTS,
      });
    };

    const sync = () => {
      const engine = getModuleEngine(context) as any;
      try {
        const store = useAsukaStore.getState() as any;
        const viewId = String(store?.activeViewId ?? activeViewId ?? '').trim();
        let raw = engine?.history
          ? engine.history.getInfo()
          : store?.documentHistoryInfo;
        asukaHistoryDebug('module.history.sync.start', {
          viewId,
          activeRenderEngineKind: store?.activeRenderEngineKind ?? activeRenderEngineKind,
          hasEngine: Boolean(engine),
          engineKind: engine?.kind ?? null,
          rawEngineInfo: summarizeHistoryInfo(raw),
          storeInfo: summarizeHistoryInfo(store?.documentHistoryInfo),
          store3dTrack: summarizeHistoryTrack(viewId ? store?.document3dHistoryByView?.[viewId] : null),
        });
        if (String(store?.activeRenderEngineKind ?? activeRenderEngineKind) === '3d') {
          const storeTrack = viewId ? store?.document3dHistoryByView?.[viewId] : null;
          const trackInfo = historyInfoFromTrack('3d', viewId, storeTrack);
          const rawViewId = String((raw as any)?.viewId ?? '').trim();
          const engineInfoMatchesView = Boolean(raw && (!rawViewId || rawViewId === viewId));
          raw = engineInfoMatchesView
            ? { ...(raw as any), viewId, engine: '3d', scope: 'view' }
            : (trackInfo
              ?? { engine: '3d', scope: 'view', viewId, index: -1, length: 0, canUndo: false, canRedo: false, maxSnapshots: store?.documentHistoryInfo?.maxSnapshots ?? DEFAULT_HISTORY_MAX_SNAPSHOTS });
          asukaHistoryDebug('module.history.sync.3dResolved', {
            viewId,
            trackInfo: summarizeHistoryInfo(trackInfo),
            resolvedRaw: summarizeHistoryInfo(raw),
          });
        }
        if (String(store?.activeRenderEngineKind ?? activeRenderEngineKind) !== '3d') {
          const storeTrack = store?.documentFabricHistoryByView?.[DOCUMENT_FABRIC_HISTORY_KEY] ?? (viewId ? store?.documentFabricHistoryByView?.[viewId] : null);
          const trackViewId = String(storeTrack?.viewId ?? (String(storeTrack?.scope ?? '').trim() === 'document' ? DOCUMENT_FABRIC_HISTORY_KEY : viewId)).trim();
          const trackInfo = historyInfoFromTrack('fabric', trackViewId || viewId, storeTrack);
          const rawViewId = String((raw as any)?.viewId ?? '').trim();
          const rawLength = Number((raw as any)?.length ?? 0) || 0;
          const rawIsDocument = rawViewId === DOCUMENT_FABRIC_HISTORY_KEY || String((raw as any)?.scope ?? '').trim() === 'document';
          raw = trackInfo
            ?? (rawLength > 0 && (rawIsDocument || !rawViewId || rawViewId === viewId)
              ? { ...(raw as any), viewId: rawIsDocument ? DOCUMENT_FABRIC_HISTORY_KEY : viewId, engine: 'fabric', scope: rawIsDocument ? 'document' : 'view' }
              : { engine: 'fabric', scope: 'document', viewId: DOCUMENT_FABRIC_HISTORY_KEY, index: -1, length: 0, canUndo: false, canRedo: false, maxSnapshots: store?.documentHistoryInfo?.maxSnapshots ?? DEFAULT_HISTORY_MAX_SNAPSHOTS });
          asukaHistoryDebug('module.history.sync.fabricResolved', {
            viewId,
            trackInfo: summarizeHistoryInfo(trackInfo),
            resolvedRaw: summarizeHistoryInfo(raw),
            storeFabricTrack: summarizeHistoryTrack(storeTrack),
          });
        }
        const normalized = normalizeHistoryInfo({
          ...(raw ?? {}),
          maxSnapshots: (raw as any)?.maxSnapshots ?? store?.documentHistoryInfo?.maxSnapshots ?? DEFAULT_HISTORY_MAX_SNAPSHOTS,
          engine: (raw as any)?.engine ?? engine?.kind ?? activeRenderEngineKind,
        });
        asukaHistoryDebug('module.history.sync.apply', { viewId, normalized: summarizeHistoryInfo(normalized) });
        apply(normalized);
      } catch (error) {
        asukaHistoryDebug('module.history.sync.error', { error: String((error as any)?.message ?? error) });
        apply(normalizeHistoryInfo({ engine: activeRenderEngineKind }));
      }
    };

    const scheduleSync = () => {
      if (disposed) return;
      if (raf !== undefined) {
        try { window.cancelAnimationFrame(raf); } catch {}
      }
      raf = window.requestAnimationFrame(() => {
        raf = undefined;
        sync();
      }) as unknown as number;
    };

    const unsubscribers = [
      engine?.events.subscribe((event) => {
        if (event.type === 'history' || event.type === 'ready') scheduleSync();
      }),
      onAsukaEvent('renderEngine.changed', scheduleSync),
      onAsukaEvent('after:state.restore', scheduleSync),
      onAsukaEvent('after:views.switch', scheduleSync),
    ];

    scheduleSync();

    return () => {
      disposed = true;
      unsubscribers.forEach((off) => { try { off?.(); } catch {} });
      if (raf !== undefined) {
        try { window.cancelAnimationFrame(raf); } catch {}
      }
    };
  }, [context, engine, activeRenderEngineKind, activeViewId, active3dHistoryMarker, activeFabricHistoryMarker]);

  const historyInfo = useMemo(() => normalizeHistoryInfo(runtimeHistoryInfo), [runtimeHistoryInfo]);
  const hasHistory = historyInfo.length > 0;
  const currentStep = Math.max(0, historyInfo.index + 1);

  const canUseUndo = Boolean((permissions as any)?.canUndo);
  const canUseRedo = Boolean((permissions as any)?.canRedo);
  const canUseReset = Boolean((permissions as any)?.canReset);

  const ensureMounted3DHistory = (runtimeEngine: any): string | null => {
    const store = useAsukaStore.getState() as any;
    const viewId = String(store?.activeViewId ?? activeViewId ?? '').trim();
    asukaHistoryDebug('module.history.ensureMounted3DHistory.start', {
      viewId,
      activeRenderEngineKind: store?.activeRenderEngineKind ?? activeRenderEngineKind,
      hasEngine: Boolean(runtimeEngine),
      storeTrack: summarizeHistoryTrack(viewId ? store?.document3dHistoryByView?.[viewId] : null),
    });
    if (!viewId || String(store?.activeRenderEngineKind ?? activeRenderEngineKind) !== '3d') return viewId || null;
    let engineLength = 0;
    let engineIndex = -1;
    let engineMatchesView = false;
    let info: any = null;
    try {
      info = runtimeEngine?.history?.getInfo() ?? null;
      const infoViewId = String((info as any)?.viewId ?? '').trim();
      engineLength = Number((info as any)?.length ?? 0) || 0;
      engineIndex = Number((info as any)?.index ?? -1);
      engineMatchesView = engineLength > 0 && (!infoViewId || infoViewId === viewId);
    } catch (error) {
      asukaHistoryDebug('module.history.ensureMounted3DHistory.engine.error', { viewId, error: String((error as any)?.message ?? error) });
    }
    try {
      const track = store?.document3dHistoryByView?.[viewId] ?? null;
      const trackLength = Array.isArray(track?.snapshots) ? Number(track.snapshots.length) || 0 : 0;
      const trackIndex = Number(track?.index ?? -1);
      const shouldImportStoredTrack = trackLength > 0 && !engineMatchesView;
      asukaHistoryDebug('module.history.ensureMounted3DHistory.compare', {
        viewId,
        engineInfo: summarizeHistoryInfo(info),
        engineLength,
        engineIndex,
        engineMatchesView,
        trackIndex,
        track: summarizeHistoryTrack(track),
        shouldImportStoredTrack,
        canImport: Boolean(runtimeEngine?.history),
      });
      if (shouldImportStoredTrack && runtimeEngine?.history) {
        void runtimeEngine.history.importTrack(viewId, track).catch((error: unknown) => {
          asukaHistoryDebug('module.history.ensureMounted3DHistory.import.error', { viewId, error: String((error as any)?.message ?? error) });
        });
        asukaHistoryDebug('module.history.ensureMounted3DHistory.imported', { viewId, track: summarizeHistoryTrack(track) });
      }
    } catch (error) {
      asukaHistoryDebug('module.history.ensureMounted3DHistory.import.error', { viewId, error: String((error as any)?.message ?? error) });
    }
    return viewId;
  };

  const persist3DHistoryAfterAction = async (runtimeEngine: any, viewId: string | null) => {
    asukaHistoryDebug('module.history.persistAfter.start', { viewId, hasEngine: Boolean(runtimeEngine), activeRenderEngineKind: (useAsukaStore.getState() as any)?.activeRenderEngineKind ?? activeRenderEngineKind });
    if (!runtimeEngine || !viewId || String((useAsukaStore.getState() as any)?.activeRenderEngineKind ?? activeRenderEngineKind) !== '3d') return;
    const store = useAsukaStore.getState() as any;
    try {
      const info = runtimeEngine.history.getInfo();
      if (info) store?.setDocumentHistoryInfo?.({ ...(info as any), viewId, engine: '3d', scope: 'view' });
      const track = runtimeEngine.history.exportTrack(viewId);
      if (track) store?.setDocument3dHistoryForView?.(viewId, track);
      const scene = await Promise.resolve(runtimeEngine.lifecycle.exportView(viewId));
      if (scene) store?.setDocument3dSceneForView?.(viewId, scene);
      asukaHistoryDebug('module.history.persistAfter.done', {
        viewId,
        info: summarizeHistoryInfo(info),
        track: summarizeHistoryTrack(track),
      });
    } catch (error) {
      asukaHistoryDebug('module.history.persistAfter.error', { viewId, error: String((error as any)?.message ?? error) });
    }
  };

  const runHistoryAction = async (action: 'undo' | 'redo' | 'reset') => {
    const runtimeEngine = getModuleEngine(context) as any;
    const store = useAsukaStore.getState() as any;
    asukaHistoryDebug('module.history.action.start', {
      action,
      activeViewId: store?.activeViewId ?? activeViewId,
      activeRenderEngineKind: store?.activeRenderEngineKind ?? activeRenderEngineKind,
      hasEngine: Boolean(runtimeEngine),
      canAction: Boolean(runtimeEngine?.history),
      engineInfo: summarizeHistoryInfo(runtimeEngine?.history?.getInfo() ?? null),
      storeInfo: summarizeHistoryInfo(store?.documentHistoryInfo),
      storeTrack: summarizeHistoryTrack(store?.document3dHistoryByView?.[String(store?.activeViewId ?? activeViewId ?? '').trim()]),
    });
    const viewId = ensureMounted3DHistory(runtimeEngine);
    if (runtimeEngine?.history) {
      await Promise.resolve(runtimeEngine.history[action]());
      asukaHistoryDebug('module.history.action.afterEngine', {
        action,
        viewId,
        engineInfo: summarizeHistoryInfo(runtimeEngine.history.getInfo()),
        engineTrack: summarizeHistoryTrack(isAsukaHistoryDebugEnabled() ? runtimeEngine.history.exportTrack(viewId) : null),
      });
      await persist3DHistoryAfterAction(runtimeEngine, viewId);
    } else {
      asukaHistoryDebug('module.history.action.missingMethod', { action, viewId, engineKeys: Object.keys(runtimeEngine ?? {}).filter((k) => /history|undo|redo|reset/i.test(k)) });
    }
  };

  const handleUndo = () => {
    if (!canUseUndo) return;
    void runHistoryAction('undo');
  };
  const handleRedo = () => {
    if (!canUseRedo) return;
    void runHistoryAction('redo');
  };
  const handleReset = () => {
    if (!canUseReset) return;
    void runHistoryAction('reset');
  };

  return (
    <Card>
      <CardHeader className="ask-flex ask-items-center ask-justify-between">
        <CardTitle>{t('modules.history', 'History')}</CardTitle>
        <div className="ask-text-[10px] ask-text-textMuted">
          {hasHistory
            ? t('history.stepOf', { step: currentStep, total: historyInfo.length })
            : t('history.empty', 'Empty')}
        </div>
      </CardHeader>

      <CardContent className="ask-flex ask-flex-col ask-gap-4">
        <Field label={t('history.actions', 'Actions')}>
          <div className="ask-flex ask-items-center ask-gap-2">
            {canUseUndo ? (
              <Button size="sm" disabled={!hasHistory || !historyInfo.canUndo || !canUseUndo} onClick={handleUndo}>
                {t('common.undo', 'Undo')}
              </Button>
            ) : null}
            {canUseRedo ? (
              <Button size="sm" disabled={!hasHistory || !historyInfo.canRedo || !canUseRedo} onClick={handleRedo}>
                {t('common.redo', 'Redo')}
              </Button>
            ) : null}
            {canUseReset ? (
              <Button size="sm" variant="ghost" disabled={!hasHistory || !canUseReset} onClick={handleReset}>
                {t('common.reset', 'Reset')}
              </Button>
            ) : null}
          </div>
        </Field>

        <Field label={t('history.snapshots', 'Snapshots')}>
          <div className="ask-flex ask-items-center ask-justify-between ask-text-[10px] ask-text-textMuted">
            <span>{hasHistory ? `${currentStep} / ${historyInfo.length}` : t('history.hint', 'No snapshots yet')}</span>
            <span>{t('history.maxSnapshots', 'Max')}: {historyInfo.maxSnapshots}</span>
          </div>
        </Field>

        {hasHistory ? (
          <List>
            {Array.from({ length: historyInfo.length }).map((_, i) => (
              <ListItem
                key={i}
                active={i === historyInfo.index}
                onClick={() => {
                  const runtimeEngine = getModuleEngine(context) as any;
                  const viewId = ensureMounted3DHistory(runtimeEngine);
                  void Promise.resolve(runtimeEngine?.history?.goTo(i)).then(() => persist3DHistoryAfterAction(runtimeEngine, viewId));
                }}
              >
                {t('history.snapshotN', { n: i + 1 })}
              </ListItem>
            ))}
          </List>
        ) : (
          <p className="ask-text-[11px] ask-text-textMuted">{t('history.hint', 'Create or modify something to add a snapshot.')}</p>
        )}
      </CardContent>
    </Card>
  );
};

registerModule({
  id: 'history',
  label: getI18n().t('modules.history'),
  icon: 'reset',
  displayMode: 'floating',
  supportedEngines: ['fabric', '3d'],
  render: HistoryModule,
});

export default HistoryModule;
