import { marked } from "marked";
import { useEffect, useRef, useState } from "preact/hooks";
import {
  loadRuntimeAssets,
  materializeRuntimeHtml,
  syncRuntimeStateScript,
} from "../cardRuntime/runtimeAssets";
import { useRuntimeHost } from "../cardRuntime/runtimeHost";
import type { MountedRuntimeCard, RuntimeItem, RuntimeSurface } from "../cardRuntime/runtimeTypes";
import { looksLikeHtml, normalizeTemplateState, runtimeItemId } from "../cardRuntime/runtimeUtils";
import { disposeCardRuntimeEntry } from "../cardRuntime/store";
import type { JsonValue } from "../types";

function StaticCardTextBody({
  content,
  bodyClass = "card-body",
}: {
  content: string;
  bodyClass?: string;
}) {
  const html = looksLikeHtml(content) ? content : (marked.parse(content) as string);
  return <div class={bodyClass} dangerouslySetInnerHTML={{ __html: html }} />;
}

export function DynamicCardBody({
  item,
  surface,
  bodyClass = "card-body",
}: {
  item: RuntimeItem;
  surface: RuntimeSurface;
  bodyClass?: string;
}) {
  const templateKey = item.templateKey?.trim() || "";
  const identity = runtimeItemId(item);
  const rootRef = useRef<HTMLDivElement>(null);
  const mountedRef = useRef<MountedRuntimeCard | null>(null);
  const [runtimeAvailable, setRuntimeAvailable] = useState<boolean | null>(
    templateKey ? null : false,
  );
  const [runtimeState, setRuntimeState] = useState<Record<string, JsonValue>>(
    normalizeTemplateState(item.templateState),
  );
  const runtimeStateRef = useRef(runtimeState);
  runtimeStateRef.current = runtimeState;
  const host = useRuntimeHost(surface, item, runtimeStateRef, setRuntimeState);

  useEffect(() => {
    const nextState = normalizeTemplateState(item.templateState);
    const activeElement = document.activeElement;
    const editingInside =
      activeElement instanceof Node && !!rootRef.current?.contains(activeElement);
    if (editingInside) return;
    const currentJson = JSON.stringify(runtimeStateRef.current);
    const nextJson = JSON.stringify(nextState);
    if (currentJson === nextJson) return;
    runtimeStateRef.current = nextState;
    setRuntimeState(nextState);
  }, [item.updatedAt, item.templateState]);

  useEffect(() => {
    let cancelled = false;

    const mountRuntime = async () => {
      if (!templateKey) {
        setRuntimeAvailable(false);
        return;
      }

      setRuntimeAvailable(null);
      const assets = await loadRuntimeAssets(templateKey);
      if (cancelled) return;
      if (!assets || !rootRef.current) {
        setRuntimeAvailable(false);
        return;
      }

      mountedRef.current?.destroy?.();
      rootRef.current.innerHTML = materializeRuntimeHtml(
        assets.html || "",
        item,
        runtimeStateRef.current,
      );
      mountedRef.current =
        assets.module.mount({
          root: rootRef.current,
          item,
          state: runtimeStateRef.current,
          host,
        }) || null;
      setRuntimeAvailable(true);
    };

    void mountRuntime();

    return () => {
      cancelled = true;
      mountedRef.current?.destroy?.();
      mountedRef.current = null;
      host.setRefreshHandler(null);
      host.setLiveContent(undefined);
      host.clearSelection();
      disposeCardRuntimeEntry(identity);
    };
  }, [host, identity, templateKey]);

  useEffect(() => {
    if (!runtimeAvailable || !rootRef.current) return;
    syncRuntimeStateScript(rootRef.current, runtimeState);
    if (!mountedRef.current?.update) return;
    mountedRef.current.update({
      root: rootRef.current,
      item,
      state: runtimeState,
      host,
    });
  }, [host, item, runtimeAvailable, runtimeState]);

  if (!templateKey || runtimeAvailable === false) {
    return <StaticCardTextBody content={item.content} bodyClass={bodyClass} />;
  }

  return <div ref={rootRef} class={bodyClass} />;
}
