/**
 * RevealManager — mounts as a child of <Article> and reactively applies
 * reveal classes (non-GM) or injects action buttons (GM) whenever the
 * content DOM, stream connection state, role, or completions change.
 *
 * All action buttons are proper Solid components rendered via Portal.
 * Nothing is injected into the custom element DOM — listeners are attached
 * via addEventListener with proper cleanup.
 */

import {
  Component,
  createEffect,
  createSignal,
  onCleanup,
  Show,
} from "solid-js";
import { Portal } from "solid-js/web";
import { useLocation } from "@solidjs/router";
import { useArticleDom } from "./Article";
import { journalStreamState } from "./stores/journalStream";
import { useJournalCompletions } from "./journal/completions";
import {
  addRevealedClasses,
  cleanupInjections,
  setActionPrefill,
  normalizePath,
} from "./stores/reveal";

// ---------------------------------------------------------------------------
// SVG icons
// ---------------------------------------------------------------------------

const LINK_SVG =
  '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" ' +
  'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
  'stroke-linecap="round" stroke-linejoin="round">' +
  '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>' +
  '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>' +
  "</svg>";

const SPARK_SVG =
  '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" ' +
  'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
  'stroke-linecap="round" stroke-linejoin="round">' +
  '<path d="M12 2l1.5 6.5L18 7l-4.5 4.5L16 16l-4-2.5L8 16l1.5-4.5L5 7l4.5-.5z"/>' +
  "</svg>";

// ---------------------------------------------------------------------------
// Floating button state
// ---------------------------------------------------------------------------

interface FloatingButton {
  rect: DOMRect;
  action: () => void;
  title: string;
  svg: string;
  color: string;
}

const [floating, setFloating] = createSignal<FloatingButton | null>(null);
let hideTimer: ReturnType<typeof setTimeout> | undefined;

function show(btn: FloatingButton) {
  clearTimeout(hideTimer);
  setFloating(btn);
}
function hide() {
  hideTimer = setTimeout(() => setFloating(null), 200);
}

// ---------------------------------------------------------------------------
// RevealManager
// ---------------------------------------------------------------------------

export const RevealManager: Component = () => {
  const contentDom = useArticleDom();
  const location = useLocation();
  const comp = useJournalCompletions();

  // ---- Reveal / conceal classes ----
  createEffect(() => {
    const dom = contentDom();
    if (!dom) return;

    const state = journalStreamState;
    void state.connected;
    void state.myRole;

    addRevealedClasses(dom, location.pathname);
  });

  onCleanup(() => {
    const dom = contentDom();
    if (dom) cleanupInjections(dom);
  });

  // ---- GM hover listeners (only when connected as GM) ----
  createEffect(() => {
    const dom = contentDom();
    const state = journalStreamState;
    if (!dom || state.myRole !== "gm" || !state.connected) return;

    const normalized = normalizePath(location.pathname);
    const data = comp.data;

    // Spark tables on this page — matched by suffix on column slug.
    // filePath in completions may have a leading slash; normalize both sides.
    const pageSparkSlugs = data.sparkTables
      .filter((st) => st.filePath.replace(/^\//, "") === normalized)
      .map((st) => st.slug);

    // Single delegated handler for both link and spark
    const onMouseOver = (e: MouseEvent) => {
      const target = e.target as HTMLElement;

      // Spark table
      const sparkTable = target.closest("md-table[data-spark]");
      if (sparkTable) {
        const colSlug = sparkTable.getAttribute("data-spark");
        const combinedSlug = colSlug
          ? pageSparkSlugs.find((s) => s.endsWith(`-${colSlug}`))
          : undefined;
        if (combinedSlug) {
          show({
            rect: sparkTable.getBoundingClientRect(),
            action: () =>
              setActionPrefill({
                command: "/spark",
                text: `/spark ${combinedSlug}`,
              }),
            title: "Roll spark table",
            svg: SPARK_SVG,
            color: "text-purple-400 hover:text-purple-600 hover:bg-purple-50",
          });
          return;
        }
      }

      // Heading
      const heading = target.closest("h1, h2, h3, h4, h5, h6");
      if (heading) {
        const text = heading.id || heading.textContent?.trim() || "";
        if (text) {
          show({
            rect: heading.getBoundingClientRect(),
            action: () =>
              setActionPrefill({
                command: "/link",
                text: `/link ${normalized}#${text}`,
              }),
            title: "Send /link to stream",
            svg: LINK_SVG,
            color: "text-blue-500 hover:bg-blue-50",
          });
        }
      }
    };

    const onMouseOut = (e: MouseEvent) => {
      // Hide only if the mouse actually left the content area
      const related = e.relatedTarget as HTMLElement | null;
      if (!dom.contains(related)) hide();
    };

    dom.addEventListener("mouseover", onMouseOver);
    dom.addEventListener("mouseout", onMouseOut);

    onCleanup(() => {
      dom.removeEventListener("mouseover", onMouseOver);
      dom.removeEventListener("mouseout", onMouseOut);
    });
  });

  return (
    <Show when={floating()}>
      {(btn) => (
        <Portal>
          <button
            class={`fixed z-50 inline-flex items-center justify-center w-5 h-5 rounded cursor-pointer ${btn().color}`}
            style={{
              top: `${btn().rect.top + window.scrollY - 6}px`,
              left: `${btn().rect.left + window.scrollX - 20}px`,
            }}
            title={btn().title}
            innerHTML={btn().svg}
            onClick={(e) => {
              e.stopPropagation();
              btn().action();
            }}
            onMouseEnter={() => clearTimeout(hideTimer)}
            onMouseLeave={hide}
          />
        </Portal>
      )}
    </Show>
  );
};

export default RevealManager;
