import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Graph } from "@cosmos.gl/graph";
import { fetchBrowseNodes, fetchRemotePage, type NetworkNode } from "@/api/client";
import { renderMicron } from "@/components/editor/micronRenderer";
import axisMundiUrl from "@/assets/axis-mundi.min.svg";

// ---------------------------------------------------------------------------
// Theme color maps — hex values matching index.css OKLCH definitions
// ---------------------------------------------------------------------------

interface ThemeColors {
  primary: string;
  muted: string;
  border: string;
}

const THEME_COLORS: Record<string, ThemeColors> = {
  dark: {   // .dark (terra)
    primary: "#c47a32",
    muted: "#8a7560",
    border: "#6b5a42",
  },
  azure: {  // .theme-azure
    primary: "#5aa0d4",
    muted: "#6d8a9e",
    border: "#4a6e88",
  },
};

function getThemeId(): string {
  const cl = document.documentElement.classList;
  if (cl.contains("theme-azure")) return "azure";
  return "dark";
}

function hexToRgba255(hex: string): [number, number, number, number] {
  return [
    parseInt(hex.slice(1, 3), 16),
    parseInt(hex.slice(3, 5), 16),
    parseInt(hex.slice(5, 7), 16),
    255,
  ];
}

// ---------------------------------------------------------------------------
// Data builders — convert NetworkNode[] to Float32Arrays for cosmos.gl
// ---------------------------------------------------------------------------

function buildBuffers(nodes: NetworkNode[], colors: ThemeColors) {
  const sorted = [...nodes].sort((a, b) => (a.is_self ? -1 : b.is_self ? 1 : 0));

  const n = sorted.length;
  const positions = new Float32Array(n * 2);
  const pointColors = new Float32Array(n * 4);
  const sizes = new Float32Array(n);
  const primaryRgba = hexToRgba255(colors.primary);
  const mutedRgba = hexToRgba255(colors.muted);

  for (let i = 0; i < n; i++) {
    if (sorted[i].is_self) {
      positions[i * 2] = 0;
      positions[i * 2 + 1] = 0;
    } else {
      const angle = ((i - 1) / Math.max(1, n - 1)) * Math.PI * 2;
      positions[i * 2] = Math.cos(angle) * 100;
      positions[i * 2 + 1] = Math.sin(angle) * 100;
    }

    const rgba = sorted[i].is_self ? primaryRgba : mutedRgba;
    pointColors[i * 4] = rgba[0];
    pointColors[i * 4 + 1] = rgba[1];
    pointColors[i * 4 + 2] = rgba[2];
    pointColors[i * 4 + 3] = rgba[3];

    sizes[i] = sorted[i].is_self ? 14 : 7;
  }

  const linkCount = Math.max(0, n - 1);
  const links = new Float32Array(linkCount * 2);
  const borderRgba = hexToRgba255(colors.border);
  const linkColors = new Float32Array(linkCount * 4);
  for (let i = 0; i < linkCount; i++) {
    links[i * 2] = 0;
    links[i * 2 + 1] = i + 1;
    linkColors[i * 4] = borderRgba[0];
    linkColors[i * 4 + 1] = borderRgba[1];
    linkColors[i * 4 + 2] = borderRgba[2];
    linkColors[i * 4 + 3] = 180;
  }

  return { sorted, positions, pointColors, sizes, links, linkColors };
}

/** Re-apply theme colors to an existing graph instance */
function applyThemeToGraph(graph: Graph, nodes: NetworkNode[], colors: ThemeColors) {
  graph.setConfig({
    pointDefaultColor: colors.primary,
    linkDefaultColor: colors.border,
    hoveredPointRingColor: colors.primary,
  });

  const n = nodes.length;
  if (n === 0) return;

  const pointColors = new Float32Array(n * 4);
  const primaryRgba = hexToRgba255(colors.primary);
  const mutedRgba = hexToRgba255(colors.muted);
  for (let i = 0; i < n; i++) {
    const rgba = nodes[i].is_self ? primaryRgba : mutedRgba;
    pointColors[i * 4] = rgba[0];
    pointColors[i * 4 + 1] = rgba[1];
    pointColors[i * 4 + 2] = rgba[2];
    pointColors[i * 4 + 3] = rgba[3];
  }
  graph.setPointColors(pointColors);

  const linkCount = Math.max(0, n - 1);
  if (linkCount > 0) {
    const borderRgba = hexToRgba255(colors.border);
    const linkColors = new Float32Array(linkCount * 4);
    for (let i = 0; i < linkCount; i++) {
      linkColors[i * 4] = borderRgba[0];
      linkColors[i * 4 + 1] = borderRgba[1];
      linkColors[i * 4 + 2] = borderRgba[2];
      linkColors[i * 4 + 3] = 180;
    }
    graph.setLinkColors(linkColors);
  }

  graph.render();
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export default function BrowseView() {
  const [nodes, setNodes] = useState<NetworkNode[]>([]);
  const [filter, setFilter] = useState("");
  const [selectedNode, setSelectedNode] = useState<NetworkNode | null>(null);
  const [pageHtml, setPageHtml] = useState<string | null>(null);
  const [pageLoading, setPageLoading] = useState(false);
  const [pageError, setPageError] = useState<string | null>(null);
  const [labelPositions, setLabelPositions] = useState<{ x: number; y: number }[]>([]);
  const [themeId, setThemeId] = useState(getThemeId);

  const containerRef = useRef<HTMLDivElement>(null);
  const graphRef = useRef<Graph | null>(null);
  const nodesRef = useRef<NetworkNode[]>([]);
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const rafRef = useRef(0);

  const colors = THEME_COLORS[themeId] ?? THEME_COLORS.dark;

  const filteredNodes = useMemo(() => {
    if (!filter) return nodes;
    const q = filter.toLowerCase();
    return nodes.filter(
      (n) => n.name.toLowerCase().includes(q) || n.hash.toLowerCase().includes(q),
    );
  }, [nodes, filter]);

  // ── Watch for theme changes ──
  useEffect(() => {
    const observer = new MutationObserver(() => {
      const id = getThemeId();
      setThemeId(id);
      const graph = graphRef.current;
      if (graph) {
        const c = THEME_COLORS[id] ?? THEME_COLORS.dark;
        applyThemeToGraph(graph, nodesRef.current, c);
      }
    });
    observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
    return () => observer.disconnect();
  }, []);

  // ── Initialize cosmos.gl graph ──
  useEffect(() => {
    if (!containerRef.current) return;

    const graph = new Graph(containerRef.current, {
      backgroundColor: [0, 0, 0, 0],
      pointDefaultColor: colors.primary,
      pointDefaultSize: 12,
      linkDefaultColor: colors.border,
      linkDefaultWidth: 1,
      linkOpacity: 0.5,
      enableSimulation: true,
      enableDrag: true,
      enableZoom: true,
      fitViewOnInit: false,
      spaceSize: 1024,
      simulationGravity: 0.15,
      simulationRepulsion: 0.6,
      simulationLinkSpring: 0.3,
      simulationLinkDistance: 60,
      simulationFriction: 0.85,
      simulationDecay: 8000,
      renderHoveredPointRing: true,
      hoveredPointRingColor: colors.primary,
      hoveredPointCursor: "pointer",
      onPointClick: (index: number) => {
        const node = nodesRef.current[index];
        if (node) handleNodeClick(node);
      },
      onClick: () => {
        setSelectedNode(null);
        setPageHtml(null);
        setPageError(null);
      },
      onSimulationTick: () => updateLabels(),
      onZoom: () => updateLabels(),
    });

    graphRef.current = graph;

    return () => {
      cancelAnimationFrame(rafRef.current);
      graph.destroy();
      graphRef.current = null;
    };
  }, []);

  // ── Update labels from graph positions ──
  const updateLabels = useCallback(() => {
    const graph = graphRef.current;
    if (!graph || nodesRef.current.length === 0) return;

    const positions = graph.getPointPositions();
    const next: { x: number; y: number }[] = [];
    for (let i = 0; i < nodesRef.current.length; i++) {
      const sx = positions[i * 2];
      const sy = positions[i * 2 + 1];
      if (sx === undefined) break;
      const [px, py] = graph.spaceToScreenPosition([sx, sy]);
      next.push({ x: px, y: py });
    }
    setLabelPositions(next);
  }, []);

  // ── Feed node data into graph when nodes change ──
  useEffect(() => {
    const graph = graphRef.current;
    if (!graph) return;
    if (filteredNodes.length === 0) {
      nodesRef.current = [];
      setLabelPositions([]);
      graph.setPointPositions(new Float32Array(0));
      graph.setPointColors(new Float32Array(0));
      graph.setPointSizes(new Float32Array(0));
      graph.setLinks(new Float32Array(0));
      graph.setLinkColors(new Float32Array(0));
      graph.render();
      return;
    }

    const c = THEME_COLORS[getThemeId()] ?? THEME_COLORS.dark;
    const { sorted, positions, pointColors, sizes, links, linkColors } = buildBuffers(filteredNodes, c);
    nodesRef.current = sorted;

    graph.setPointPositions(positions);
    graph.setPointColors(pointColors);
    graph.setPointSizes(sizes);
    if (links.length > 0) {
      graph.setLinks(links);
      graph.setLinkColors(linkColors);
    }
    graph.setPinnedPoints([0]);
    graph.render();
    graph.start();

    setTimeout(() => {
      graph.fitView(400, 0.4);
      updateLabels();
    }, 200);
  }, [filteredNodes, updateLabels]);

  // ── Poll for nodes ──
  useEffect(() => {
    const load = () => {
      fetchBrowseNodes().then(setNodes).catch(() => { });
    };
    load();
    pollRef.current = setInterval(load, 30_000);
    return () => {
      if (pollRef.current) clearInterval(pollRef.current);
    };
  }, []);

  // ── Node click → fetch page ──
  const handleNodeClick = useCallback((node: NetworkNode) => {
    setSelectedNode(node);
    setPageHtml(null);
    setPageError(null);
    setPageLoading(true);

    fetchRemotePage(node.hash)
      .then((res) => {
        if (res.content) {
          setPageHtml(renderMicron(res.content, true));
        } else {
          setPageError(res.error ?? "No content");
        }
      })
      .catch((e) => setPageError(String(e)))
      .finally(() => setPageLoading(false));
  }, []);

  return (
    <div className="flex flex-col" style={{ height: "100%" }}>
      {/* Header */}
      <div className="flex items-center gap-3 px-4 py-2 border-b-2 border-border shrink-0">
        <h1 className="text-sm font-semibold">Browse</h1>
        <input
          type="text"
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
          placeholder="Filter nodes..."
          className="flex-1 h-8 px-2 text-xs bg-muted/50 border border-border rounded placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
        />
        <span className="text-[10px] text-muted-foreground uppercase tracking-wider whitespace-nowrap">
          {filteredNodes.length}/{nodes.length} node{nodes.length !== 1 && "s"}
        </span>
      </div>

      {/* Graph + labels */}
      <div
        ref={containerRef}
        className="relative shrink-0 bg-background overflow-hidden"
        style={{ height: 500 }}
      >
        {nodesRef.current.map((node, i) => {
          const lp = labelPositions[i];
          if (!lp) return null;
          return (
            <span
              key={node.hash}
              className="absolute text-[10px] font-mono pointer-events-none select-none whitespace-nowrap"
              style={{
                left: lp.x,
                top: lp.y - (node.is_self ? 24 : 12),
                transform: "translate(-50%, -100%)",
                color: node.is_self ? colors.primary : colors.muted,
              }}
            >
              {node.name}
            </span>
          );
        })}

        {nodes.length === 0 && (
          <div className="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm">
            Listening for nodes on the Reticulum network...
          </div>
        )}
        {nodes.length > 0 && filteredNodes.length === 0 && (
          <div className="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm">
            No nodes match "{filter}"
          </div>
        )}
      </div>

      {/* Page viewer */}
      <div className="flex-1 min-h-0 border-t-2 border-border flex flex-col">
        <div className="flex items-center px-4 py-2 border-b border-border bg-background shrink-0">
          <span className="text-xs font-semibold flex-1 truncate">
            {selectedNode ? (
              <>
                {selectedNode.name}
                <span className="ml-2 text-[10px] text-muted-foreground font-normal">
                  {selectedNode.hash.slice(0, 12)}
                </span>
              </>
            ) : (
              <span className="text-muted-foreground font-normal">Page</span>
            )}
          </span>
        </div>

        <div className="flex-1 min-h-0 overflow-auto p-3">
          {!selectedNode && !pageLoading && (
            <div className="flex flex-col items-center justify-center h-full gap-2 text-muted-foreground">
              <div
                className="w-80 h-64"
                style={{
                  backgroundColor: colors.primary,
                  mask: `url(${axisMundiUrl}) center/contain no-repeat`,
                  WebkitMask: `url(${axisMundiUrl}) center/contain no-repeat`,
                }}
              />
              <span className="text-xs">Click a node to view its page</span>
            </div>
          )}
          {pageLoading && (
            <span className="text-muted-foreground text-xs animate-pulse">
              Requesting page...
            </span>
          )}
          {pageError && (
            <span className="text-destructive text-xs">{pageError}</span>
          )}
          {pageHtml && (
            <div
              className="font-mono text-[11px] leading-tight"
              dangerouslySetInnerHTML={{ __html: pageHtml }}
            />
          )}
        </div>
      </div>
    </div>
  );
}
