import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { NodeViewWrapper } from "@tiptap/react";
import type { NodeViewProps } from "@tiptap/react";
import type { ComponentDef, ComponentField } from "../components/registry";
import { buildDoc, DEFAULT_EMBED_HEIGHT } from "./build-doc";
import type { EmbedStore } from "./embed-store";
import { useTheme } from "../../hooks/useTheme";

/**
 * Resolve embed store from the editor's storage.
 * The store is injected by Editor.tsx into editor.storage.htmlEmbed.embedStore.
 */
function useEmbedStore(editor: NodeViewProps["editor"]): EmbedStore | null {
  return (editor.storage.htmlEmbed as any)?.embedStore ?? null;
}

function FieldRow({
  field,
  value,
  onChange,
}: {
  field: ComponentField;
  value: unknown;
  onChange: (val: unknown) => void;
}) {
  if (field.type === "boolean") {
    return (
      <label className="embed-field-row embed-field-checkbox">
        <input
          type="checkbox"
          checked={!!value}
          onChange={(e) => onChange(e.target.checked)}
        />
        {field.label}
      </label>
    );
  }

  return (
    <div className="embed-field-row">
      <span className="embed-field-label">{field.label}</span>
      <input
        type="text"
        value={String(value ?? "")}
        placeholder={field.placeholder || field.label}
        onChange={(e) => onChange(e.target.value)}
        className="embed-field-input"
      />
    </div>
  );
}

/** Safely parse the stored height attribute (legacy: string; new: number). */
function parseStoredHeight(raw: unknown): number {
  if (typeof raw === "number" && raw > 0) return Math.round(raw);
  const n = parseInt(String(raw ?? ""), 10);
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_EMBED_HEIGHT;
}

export function makeHtmlEmbedView(def: ComponentDef) {
  function HtmlEmbedNodeView({ node, updateAttributes, editor }: NodeViewProps) {
    const src = (node.attrs.src as string) || "";
    const title = (node.attrs.title as string) || "";
    const storedHeight = parseStoredHeight(node.attrs.height);
    const { isDark, primaryColor } = useTheme();
    const embedStore = useEmbedStore(editor);

    const [html, setHtml] = useState("");
    const [iframeHeight, setIframeHeight] = useState(storedHeight);
    const [showSettings, setShowSettings] = useState(false);
    const iframeRef = useRef<HTMLIFrameElement>(null);

    // Sync from embed store
    useEffect(() => {
      if (!embedStore || !src) return;
      setHtml(embedStore.get(src));
      return embedStore.observeKey(src, setHtml);
    }, [embedStore, src]);

    // Build full document for srcdoc.
    // NOTE: only depends on `html` – theme changes are pushed via postMessage
    // below so we don't reload the iframe (which would lose chart state).
    const srcdoc = useMemo(() => {
      if (!html) return "";
      return buildDoc(html, { isDark, primaryColor });
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [html]);

    // Hot-swap theme inside the iframe without reload.
    useEffect(() => {
      const frame = iframeRef.current;
      if (!frame || !html) return;
      const win = frame.contentWindow;
      if (!win) return;
      const send = () => {
        try {
          win.postMessage(
            {
              type: "setTheme",
              theme: isDark ? "dark" : "light",
              primaryColor,
            },
            "*",
          );
        } catch {
          /* cross-origin / not yet loaded – will retry on load */
        }
      };
      send();
      const onLoad = () => send();
      frame.addEventListener("load", onLoad);
      return () => frame.removeEventListener("load", onLoad);
    }, [isDark, primaryColor, html]);

    // Listen for height reports from the iframe.
    // - Apply immediately to the visible iframe (React state) for smooth resize.
    // - Debounce the PM transaction (node attribute) so document history isn't
    //   polluted with noisy updates during animations.
    const lastPersistedRef = useRef<number>(storedHeight);
    const persistTimerRef = useRef(0);
    useEffect(() => {
      const handler = (e: MessageEvent) => {
        if (e.data?.type !== "embedResize") return;
        const frame = iframeRef.current;
        if (!frame || e.source !== frame.contentWindow) return;

        const h = Math.max(0, Math.ceil(e.data.height));
        if (!h) return;

        setIframeHeight((prev) => (prev === h ? prev : h));

        if (h !== lastPersistedRef.current) {
          clearTimeout(persistTimerRef.current);
          persistTimerRef.current = window.setTimeout(() => {
            if (h !== lastPersistedRef.current) {
              lastPersistedRef.current = h;
              updateAttributes({ height: h });
            }
          }, 800);
        }
      };
      window.addEventListener("message", handler);
      return () => {
        window.removeEventListener("message", handler);
        clearTimeout(persistTimerRef.current);
      };
    }, [updateAttributes]);

    // Reset persisted tracker when the underlying node attribute changes from
    // the outside (undo/redo, collab remote update).
    useEffect(() => {
      lastPersistedRef.current = storedHeight;
    }, [storedHeight]);

    const handleFieldChange = useCallback(
      (fieldName: string, value: unknown) => {
        updateAttributes({ [fieldName]: value });
      },
      [updateAttributes],
    );

    const hasContent = !!html;

    // Metadata fields (exclude height - managed automatically from iframe)
    const editableFields = def.fields.filter((f) => f.name !== "height");

    return (
      <NodeViewWrapper data-component="htmlEmbed">
        <div contentEditable={false} className="embed-view">
          {/* Header */}
          <div className="embed-header">
            <div className="embed-header-left">
              <span className="embed-header-icon">📊</span>
              <span className="embed-header-label">
                {title || src || "HTML Embed"}
              </span>
              {src && title && (
                <span className="embed-header-src">{src}</span>
              )}
            </div>
            <div className="embed-header-actions">
              <button
                className="embed-btn"
                onClick={() => setShowSettings(!showSettings)}
                title="Settings"
              >
                {showSettings ? "Close" : "Settings"}
              </button>
              {hasContent && (
                <button
                  className="embed-btn embed-btn-primary"
                  onClick={() => {
                    window.dispatchEvent(
                      new CustomEvent("open-embed-studio", { detail: { src } }),
                    );
                  }}
                  title="Open Embed Studio"
                >
                  Edit
                </button>
              )}
            </div>
          </div>

          {/* Settings panel */}
          {showSettings && (
            <div className="embed-settings">
              {editableFields.map((f) => (
                <FieldRow
                  key={f.name}
                  field={f}
                  value={node.attrs[f.name]}
                  onChange={(v) => handleFieldChange(f.name, v)}
                />
              ))}
            </div>
          )}

          {/* Preview */}
          {hasContent ? (
            <div className="embed-preview">
              <iframe
                ref={iframeRef}
                srcDoc={srcdoc}
                title={title || src || "Chart preview"}
                sandbox="allow-scripts allow-same-origin"
                className="embed-iframe"
                style={{
                  height: iframeHeight,
                  minHeight: Math.min(storedHeight, 120),
                }}
              />
            </div>
          ) : (
            <div className="embed-empty">
              {src ? (
                <>
                  <span className="embed-empty-icon">📊</span>
                  <span>
                    No content for <code>{src}</code>
                  </span>
                  <button
                    className="embed-btn embed-btn-primary"
                    onClick={() => {
                      window.dispatchEvent(
                        new CustomEvent("open-embed-studio", { detail: { src } }),
                      );
                    }}
                  >
                    Create Chart
                  </button>
                </>
              ) : (
                <>
                  <span className="embed-empty-icon">📊</span>
                  <span>Set a source filename in settings to link an embed</span>
                  <button
                    className="embed-btn"
                    onClick={() => setShowSettings(true)}
                  >
                    Open Settings
                  </button>
                </>
              )}
            </div>
          )}
        </div>
      </NodeViewWrapper>
    );
  }

  HtmlEmbedNodeView.displayName = "HtmlEmbedView";
  return HtmlEmbedNodeView;
}
