"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import type {
  DocumentEditorHostProps,
  LeptosTiptapHostBridgeState,
} from "@/components/editor/editor-host-types";
import {
  blocksFromTiptapDoc,
  editorBlockDocumentFromTiptapDoc,
  tiptapDocFromBlocks,
} from "@/lib/documents/tiptap-content-converter";
import { useEditorBridgeStore } from "@/store/editor-bridge";
import type { EditorReferenceBridge } from "@/store/editor-bridge";
import type { DocumentStats } from "@/types/page-options";
import type { Json } from "@/types/supabase";
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
import { executePageBodySavePayload } from "@/lib/documents/page-command-client";

const RUNTIME_PORT = 8123;
const RUNTIME_NAME = "8123-leptos-tiptap-runtime";
const RUNTIME_VERSION = "1.1.0";
const EVENT_PREFIX = "mnote:leptos-tiptap-spike";
const READY_EVENT = `${EVENT_PREFIX}:ready`;
const CHANGE_EVENT = `${EVENT_PREFIX}:change`;
const STATE_EVENT = `${EVENT_PREFIX}:state`;
const SAVE_REQUEST_EVENT = `${EVENT_PREFIX}:save-request`;
const HEIGHT_EVENT = `${EVENT_PREFIX}:height`;
const BOOTSTRAP_EVENT = `${EVENT_PREFIX}:bootstrap`;
const REPLACE_DOCUMENT_EVENT = `${EVENT_PREFIX}:replace-document`;
const PROTOCOL = "mnote.leptos_tiptap.bridge.v1";
const EMPTY_DOC = { type: "doc", content: [{ type: "paragraph", attrs: {}, content: [] }] } as const;
const FALLBACK_IFRAME_MIN_HEIGHT = 720;

type HostEnvelope<T = unknown> = {
  protocol?: string;
  runtime?: string;
  version?: string;
  source?: string;
  event?: string;
  payload?: T;
};

type HostDocumentPayload = {
  title?: string | null;
  content?: unknown;
  meta?: {
    dirty_count?: number;
    editor_focused?: boolean;
    slash_open?: boolean;
    toolbar_open?: boolean;
    revision?: number | null;
    conflict_detection_key?: string | null;
  };
};

type HeightPayload = {
  height?: number;
};

type BootstrapPayload = {
  documentId: string;
  workspaceId: string;
  title: string | null;
  content: unknown;
  revision: number | null;
  conflictDetectionKey: string | null;
  readOnly: boolean;
};

function resolveRuntimeUrl() {
  if (typeof window === "undefined") {
    return `http://localhost:${RUNTIME_PORT}/`;
  }
  try {
    const url = new URL(window.location.href);
    url.port = String(RUNTIME_PORT);
    url.pathname = "/";
    url.search = "";
    url.hash = "";
    return url.toString();
  } catch {
    return `http://localhost:${RUNTIME_PORT}/`;
  }
}

function normalizeRuntimeDoc(tiptapDoc: Json) {
  if (!tiptapDoc || typeof tiptapDoc !== "object") {
    return EMPTY_DOC;
  }
  const doc = tiptapDoc as { type?: unknown; content?: unknown };
  if (doc.type !== "doc") {
    return EMPTY_DOC;
  }
  if (!Array.isArray(doc.content) || doc.content.length === 0) {
    return EMPTY_DOC;
  }
  return tiptapDoc;
}

function buildIframeSrc(runtimeUrl: string, props: DocumentEditorHostProps, reloadKey: number) {
  const url = new URL(runtimeUrl);
  url.searchParams.set("embedded", "1");
  url.searchParams.set("host", "leptos_tiptap");
  url.searchParams.set("documentId", props.documentId);
  url.searchParams.set("workspaceId", props.workspaceId);
  url.searchParams.set("reloadKey", String(reloadKey));
  return url.toString();
}

function isBridgeEnvelope(value: unknown): value is HostEnvelope {
  if (!value || typeof value !== "object") {
    return false;
  }
  const maybe = value as HostEnvelope;
  return maybe.protocol === PROTOCOL && maybe.runtime === RUNTIME_NAME && maybe.source === EVENT_PREFIX;
}

function flattenText(value: unknown): string {
  if (typeof value === "string") return value;
  if (Array.isArray(value)) return value.map(flattenText).join("");
  if (value && typeof value === "object") {
    const obj = value as { text?: unknown; content?: unknown };
    return `${flattenText(obj.text)}${flattenText(obj.content)}`;
  }
  return "";
}

function buildStats(blocks: Json): DocumentStats {
  const items = Array.isArray(blocks) ? blocks : [];
  const text = items
    .map((item) => (item && typeof item === "object" ? flattenText((item as { content?: unknown }).content) : ""))
    .join("\n")
    .trim();
  const todoItems = items.filter((item) => item && typeof item === "object" && (item as { type?: unknown }).type === "todo");
  const todoDone = todoItems.filter(
    (item) => item && typeof item === "object" && Boolean((item as { props?: { checked?: unknown } }).props?.checked),
  ).length;
  return {
    wordCount: text ? text.split(/\s+/).filter(Boolean).length : 0,
    characterCount: text.length,
    blockCount: items.length,
    todoTotal: todoItems.length,
    todoDone,
  };
}

function toIsoNow() {
  return new Date().toISOString();
}

function coerceHeight(value: unknown): number {
  const height = Number(value);
  if (!Number.isFinite(height)) {
    return FALLBACK_IFRAME_MIN_HEIGHT;
  }
  return Math.max(FALLBACK_IFRAME_MIN_HEIGHT, Math.ceil(height));
}

function postBridgeMessage(targetWindow: Window | null | undefined, event: string, payload: unknown) {
  if (!targetWindow) return;
  const envelope: HostEnvelope = {
    protocol: PROTOCOL,
    runtime: RUNTIME_NAME,
    version: RUNTIME_VERSION,
    source: EVENT_PREFIX,
    event,
    payload,
  };
  targetWindow.postMessage(envelope, "*");
}

export function LeptosTiptapEditorHost(props: DocumentEditorHostProps) {
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
  const runtimeUrl = useMemo(() => resolveRuntimeUrl(), []);
  const registerBridge = useEditorBridgeStore((state) => state.registerBridge);
  const onSnapshotRef = useRef(props.onSnapshot);
  const onStatsChangeRef = useRef(props.onStatsChange);
  const onPersistedMetaChangeRef = useRef(props.onPersistedMetaChange);
  const revisionRef = useRef<number | null>(props.initialRevision ?? null);
  const conflictDetectionKeyRef = useRef<string | null>(props.initialConflictDetectionKey ?? null);
  const bootstrapPayloadRef = useRef<BootstrapPayload | null>(null);
  const runtimeDocRef = useRef<Json>(
    normalizeRuntimeDoc(tiptapDocFromBlocks(props.initialContent as Json) as Json) as Json,
  );
  const [runtimeDoc, setRuntimeDoc] = useState<Json>(() => tiptapDocFromBlocks(props.initialContent as Json) as Json);
  const [reloadKey, setReloadKey] = useState(0);
  const [iframeHeight, setIframeHeight] = useState(FALLBACK_IFRAME_MIN_HEIGHT);
  const [bridgeState, setBridgeState] = useState<LeptosTiptapHostBridgeState>({
    ready: false,
    runtimeName: RUNTIME_NAME,
    runtimeVersion: RUNTIME_VERSION,
    runtimeUrl,
    documentId: props.documentId,
    workspaceId: props.workspaceId,
    status: "booting",
    lastChangeAt: null,
    lastSaveRequestAt: null,
    lastError: null,
  });

  const iframeSrc = useMemo(
    () => buildIframeSrc(runtimeUrl, props, reloadKey),
    [props, reloadKey, runtimeUrl],
  );

  useEffect(() => {
    onSnapshotRef.current = props.onSnapshot;
    onStatsChangeRef.current = props.onStatsChange;
    onPersistedMetaChangeRef.current = props.onPersistedMetaChange;
    revisionRef.current = props.initialRevision ?? null;
    conflictDetectionKeyRef.current = props.initialConflictDetectionKey ?? null;
  }, [props.initialConflictDetectionKey, props.initialRevision, props.onPersistedMetaChange, props.onSnapshot, props.onStatsChange]);

  useEffect(() => {
    runtimeDocRef.current = runtimeDoc;
  }, [runtimeDoc]);

  useEffect(() => {
    const nextRuntimeDoc = tiptapDocFromBlocks(props.initialContent as Json) as Json;
    setRuntimeDoc(nextRuntimeDoc);
    bootstrapPayloadRef.current = {
      documentId: props.documentId,
      workspaceId: props.workspaceId,
      title: props.title ?? null,
      content: normalizeRuntimeDoc(nextRuntimeDoc),
      revision: props.initialRevision ?? null,
      conflictDetectionKey: props.initialConflictDetectionKey ?? null,
      readOnly: Boolean(props.readOnly),
    };
    setIframeHeight(FALLBACK_IFRAME_MIN_HEIGHT);
    setReloadKey((value) => value + 1);
    setBridgeState((prev) => ({
      ...prev,
      ready: false,
      documentId: props.documentId,
      workspaceId: props.workspaceId,
      status: "booting",
      lastError: null,
    }));
  }, [props.documentId, props.initialContent, props.initialConflictDetectionKey, props.initialRevision, props.readOnly, props.title, props.workspaceId]);

  useEffect(() => {
    const bridge: EditorReferenceBridge = {
      insertInlineReference: () => ({ blockId: null }),
      insertEmbedReference: () => ({ blockId: null }),
      undo: () => {
        // 现阶段 iframe runtime 还没有父->子撤销命令，先保持最小 bridge 兼容。
      },
      redo: () => {
        // 现阶段 iframe runtime 还没有父->子重做命令，先保持最小 bridge 兼容。
      },
      getCursorBlockId: () => null,
      replaceWithSnapshot: (blocks: Json) => {
        const nextRuntimeDoc = tiptapDocFromBlocks(blocks) as Json;
        setRuntimeDoc(nextRuntimeDoc);
        bootstrapPayloadRef.current = {
          documentId: props.documentId,
          workspaceId: props.workspaceId,
          title: props.title ?? null,
          content: normalizeRuntimeDoc(nextRuntimeDoc),
          revision: revisionRef.current,
          conflictDetectionKey: conflictDetectionKeyRef.current,
          readOnly: Boolean(props.readOnly),
        };
        postBridgeMessage(iframeRef.current?.contentWindow, REPLACE_DOCUMENT_EVENT, bootstrapPayloadRef.current);
        setBridgeState((prev) => ({
          ...prev,
          ready: false,
          status: "reloading",
          lastError: null,
        }));
      },
    };
    registerBridge(bridge);
    return () => registerBridge(null);
  }, [props.documentId, props.readOnly, props.title, props.workspaceId, registerBridge]);

  useEffect(() => {
    let disposed = false;
    const expectedOrigin = (() => {
      try {
        return new URL(runtimeUrl).origin;
      } catch {
        return "";
      }
    })();

    const publishSnapshot = (payload: HostDocumentPayload) => {
      const blocks = blocksFromTiptapDoc(payload.content) as Json;
      const stats = buildStats(blocks);
      const nextRuntimeDoc = normalizeRuntimeDoc(payload.content as Json) as Json;
      runtimeDocRef.current = nextRuntimeDoc;
      setRuntimeDoc(nextRuntimeDoc);
      onSnapshotRef.current?.({ blocks, stats });
      onStatsChangeRef.current?.(stats);
      return { blocks, stats };
    };

    const saveSnapshot = async (payload: HostDocumentPayload) => {
      const normalizedRuntimeDoc = normalizeRuntimeDoc(payload.content as Json) as Json;
      const { blocks } = publishSnapshot(payload);
      const persistedMeta = await executePageBodySavePayload(
        buildDocumentSavePayload({
          documentId: props.documentId,
          workspaceId: props.workspaceId,
          revision: payload.meta?.revision ?? revisionRef.current,
          editorDocument: editorBlockDocumentFromTiptapDoc(
            normalizedRuntimeDoc,
            props.documentId,
          ),
          content: blocks,
          tiptapDocument: normalizedRuntimeDoc,
          conflictDetectionKey:
            payload.meta?.conflict_detection_key ?? conflictDetectionKeyRef.current,
          snapshotCapturedAt: toIsoNow(),
          blockCount: Array.isArray(blocks) ? blocks.length : 0,
        }),
      );
      const nextRevision = persistedMeta.revision ?? payload.meta?.revision ?? null;
      const nextConflictDetectionKey =
        persistedMeta.conflictDetectionKey ?? payload.meta?.conflict_detection_key ?? null;
      revisionRef.current = nextRevision;
      conflictDetectionKeyRef.current = nextConflictDetectionKey;
      onPersistedMetaChangeRef.current?.({
        revision: nextRevision,
        conflictDetectionKey: nextConflictDetectionKey,
      });
    };

    const handleMessage = (event: MessageEvent<HostEnvelope>) => {
      if (!expectedOrigin || event.origin !== expectedOrigin) {
        return;
      }
      if (event.source !== iframeRef.current?.contentWindow) {
        return;
      }
      if (!isBridgeEnvelope(event.data)) {
        return;
      }
      const eventName = typeof event.data.event === "string" ? event.data.event : "";
      const payload = (event.data.payload ?? {}) as HostDocumentPayload & HeightPayload;
      if (eventName === READY_EVENT) {
        const bootstrapPayload = bootstrapPayloadRef.current;
        if (bootstrapPayload) {
          postBridgeMessage(iframeRef.current?.contentWindow, BOOTSTRAP_EVENT, bootstrapPayload);
        }
        setBridgeState((prev) => ({ ...prev, ready: true, status: "ready", lastError: null }));
        return;
      }
      if (eventName === STATE_EVENT) {
        setBridgeState((prev) => ({ ...prev, ready: true, status: "editing", lastError: null }));
        return;
      }
      if (eventName === HEIGHT_EVENT) {
        setIframeHeight(coerceHeight(payload.height));
        return;
      }
      if (eventName === CHANGE_EVENT) {
        publishSnapshot(payload);
        setBridgeState((prev) => ({ ...prev, ready: true, status: "dirty", lastChangeAt: toIsoNow(), lastError: null }));
        return;
      }
      if (eventName === SAVE_REQUEST_EVENT) {
        setBridgeState((prev) => ({
          ...prev,
          ready: true,
          status: "saving",
          lastSaveRequestAt: toIsoNow(),
          lastError: null,
        }));
        void saveSnapshot(payload)
          .then(() => {
            if (disposed) return;
            setBridgeState((prev) => ({ ...prev, ready: true, status: "saved", lastError: null }));
          })
          .catch((error) => {
            if (disposed) return;
            setBridgeState((prev) => ({
              ...prev,
              ready: true,
              status: "error",
              lastError: error instanceof Error ? error.message : "保存失败",
            }));
          });
      }
    };

    window.addEventListener("message", handleMessage);
    return () => {
      disposed = true;
      window.removeEventListener("message", handleMessage);
    };
  }, [props.documentId, props.workspaceId, runtimeUrl]);

  return (
    <div
      className="rounded-xl border border-amber-200/80 bg-amber-50/70"
      data-editor-host-kind="leptos_tiptap_iframe_debug"
    >
      <div className="flex items-center justify-between border-b border-amber-200 px-3 py-2 text-xs text-amber-800">
        <span>主编辑器：`leptos-tiptap` iframe debug host</span>
        <span>{bridgeState.status}</span>
      </div>
      <iframe
        key={`${props.documentId}:${reloadKey}`}
        ref={iframeRef}
        src={iframeSrc}
        title="Leptos Tiptap Main Editor Runtime"
        className="w-full border-0"
        style={{ height: `${iframeHeight}px`, minHeight: `${FALLBACK_IFRAME_MIN_HEIGHT}px` }}
        data-bridge-status={bridgeState.status}
        data-runtime-name={bridgeState.runtimeName}
        data-runtime-version={bridgeState.runtimeVersion}
      />
    </div>
  );
}
