"use client";

import { useCallback, useEffect, useMemo, useRef } from "react";
import { Handle, Node, NodeProps, Position, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { ArrayNodeData } from "@/types";
import { parseTextToArray } from "@/utils/arrayParser";

type ArrayNodeType = Node<ArrayNodeData, "array">;

function arraysEqual(a: string[], b: string[]): boolean {
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
  const nodeData = data;
  const commentNavigation = useCommentNavigation(id);
  const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
  const addNode = useWorkflowStore((state) => state.addNode);
  const onConnect = useWorkflowStore((state) => state.onConnect);
  const nodes = useWorkflowStore((state) => state.nodes);
  const getConnectedInputs = useWorkflowStore((state) => state.getConnectedInputs);
  const edges = useWorkflowStore((state) => state.edges);
  const { setNodes } = useReactFlow();
  const lastSyncedInputRef = useRef<string | null>(null);
  const lastDerivedWriteRef = useRef<string | null>(null);

  const hasIncomingTextConnection = useMemo(() => {
    return edges.some((edge) => {
      if (edge.target !== id) return false;
      const handle = edge.targetHandle || "text";
      return handle === "text" || handle.startsWith("text-") || handle.includes("prompt");
    });
  }, [edges, id]);

  // Pull upstream text into this node whenever the connected input changes.
  useEffect(() => {
    if (!hasIncomingTextConnection) return;
    const { text } = getConnectedInputs(id);
    if (
      text !== null &&
      text !== nodeData.inputText &&
      text !== lastSyncedInputRef.current
    ) {
      lastSyncedInputRef.current = text;
      updateNodeData(id, { inputText: text });
    }
  }, [hasIncomingTextConnection, getConnectedInputs, id, nodeData.inputText, updateNodeData]);

  const parsed = useMemo(() => {
    return parseTextToArray(nodeData.inputText, {
      splitMode: nodeData.splitMode,
      delimiter: nodeData.delimiter,
      regexPattern: nodeData.regexPattern,
      trimItems: nodeData.trimItems,
      removeEmpty: nodeData.removeEmpty,
    });
  }, [
    nodeData.inputText,
    nodeData.splitMode,
    nodeData.delimiter,
    nodeData.regexPattern,
    nodeData.trimItems,
    nodeData.removeEmpty,
  ]);

  // Keep derived outputs in node data so execution/edges always read the latest values.
  useEffect(() => {
    const nextOutputText = JSON.stringify(parsed.items);
    const writeSignature = `${parsed.error ?? ""}::${nextOutputText}`;
    const needsSync =
      parsed.error !== nodeData.error ||
      nextOutputText !== (nodeData.outputText ?? "[]") ||
      !arraysEqual(parsed.items, nodeData.outputItems || []);

    if (!needsSync) return;
    if (lastDerivedWriteRef.current === writeSignature) return;
    lastDerivedWriteRef.current = writeSignature;

    if (
      parsed.error !== nodeData.error ||
      nextOutputText !== (nodeData.outputText ?? "[]") ||
      !arraysEqual(parsed.items, nodeData.outputItems || [])
    ) {
      updateNodeData(id, {
        outputItems: parsed.items,
        outputText: nextOutputText,
        error: parsed.error,
      });
    }
  }, [id, nodeData.error, nodeData.outputItems, nodeData.outputText, parsed.error, parsed.items, updateNodeData]);

  const handleModeChange = useCallback(
    (e: React.ChangeEvent<HTMLSelectElement>) => {
      updateNodeData(id, { splitMode: e.target.value as ArrayNodeData["splitMode"] });
    },
    [id, updateNodeData]
  );

  const previewItems = parsed.items;

  const handleAutoRouteToPrompts = useCallback(() => {
    const items = nodeData.outputItems || [];
    if (items.length === 0) return;

    const sourceNode = nodes.find((n) => n.id === id);
    if (!sourceNode) return;

    const sourceWidth = (sourceNode.style?.width as number) || 360;
    const baseX = sourceNode.position.x + sourceWidth + 220;
    const baseY = sourceNode.position.y;
    const promptHeight = 220;
    const verticalGap = 24;
    const previousSelected = nodeData.selectedOutputIndex ?? null;

    items.forEach((item, index) => {
      const promptNodeId = addNode(
        "prompt",
        { x: baseX, y: baseY + index * (promptHeight + verticalGap) },
        { prompt: item }
      );

      // Force each generated edge to bind to the matching array item index.
      updateNodeData(id, { selectedOutputIndex: index });
      onConnect({
        source: id,
        sourceHandle: "text",
        target: promptNodeId,
        targetHandle: "text",
      });
    });

    updateNodeData(id, { selectedOutputIndex: previousSelected });
  }, [addNode, id, nodeData.outputItems, nodeData.selectedOutputIndex, nodes, onConnect, updateNodeData]);

  // Reset selection if it no longer points to a valid parsed item.
  useEffect(() => {
    const selected = nodeData.selectedOutputIndex;
    if (selected !== null && (selected < 0 || selected >= previewItems.length)) {
      updateNodeData(id, { selectedOutputIndex: null });
    }
  }, [id, nodeData.selectedOutputIndex, previewItems.length, updateNodeData]);

  // Auto-resize node height to fit all parsed lines so users don't need to scroll.
  useEffect(() => {
    const baseHeight = 360;
    const perItemHeight = 30;
    const newHeight = Math.max(baseHeight, 270 + previewItems.length * perItemHeight);

    setNodes((nodes) =>
      nodes.map((node) =>
        node.id === id ? { ...node, style: { ...node.style, height: newHeight } } : node
      )
    );
  }, [id, previewItems.length, setNodes]);

  return (
    <BaseNode
      id={id}
      title="Array"
      customTitle={nodeData.customTitle}
      comment={nodeData.comment}
      onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
      onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
      selected={selected}
      commentNavigation={commentNavigation ?? undefined}
      hasError={!!nodeData.error}
      minWidth={320}
      minHeight={300}
      headerButtons={
        <button
          type="button"
          onClick={handleAutoRouteToPrompts}
          disabled={previewItems.length === 0}
          className="nodrag nopan ml-2 shrink-0 p-1 rounded border border-neutral-600 text-neutral-300 hover:bg-neutral-700 hover:text-neutral-100 disabled:opacity-50 disabled:cursor-not-allowed"
          title="Auto-route to Prompts"
        >
          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M6 3v6m0 0a2 2 0 104 0 2 2 0 00-4 0zm0 0v7a3 3 0 003 3h6m0 0a2 2 0 100 4 2 2 0 000-4zm0 0v-6a3 3 0 013-3h0" />
          </svg>
        </button>
      }
    >
      <Handle type="target" position={Position.Left} id="text" data-handletype="text" />

      {/* Single text output point (each outgoing edge receives a separate item) */}
      <Handle type="source" position={Position.Right} id="text" data-handletype="text" style={{ top: 48 }} />

      <div className="flex flex-col gap-2 flex-1 min-h-0">
        <div className="grid grid-cols-[auto_1fr] gap-2 items-center">
          <label className="text-[11px] text-neutral-400">Split</label>
          <select
            value={nodeData.splitMode}
            onChange={handleModeChange}
            className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
          >
            <option value="delimiter">Delimiter</option>
            <option value="newline">Newline</option>
            <option value="regex">Regex</option>
          </select>
        </div>

        {nodeData.splitMode === "delimiter" && (
          <div className="grid grid-cols-[auto_1fr] gap-2 items-center">
            <label className="text-[11px] text-neutral-400">By</label>
            <input
              value={nodeData.delimiter}
              onChange={(e) => updateNodeData(id, { delimiter: e.target.value })}
              placeholder="*"
              className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
            />
          </div>
        )}

        {nodeData.splitMode === "regex" && (
          <div className="grid grid-cols-[auto_1fr] gap-2 items-center">
            <label className="text-[11px] text-neutral-400">Pattern</label>
            <input
              value={nodeData.regexPattern}
              onChange={(e) => updateNodeData(id, { regexPattern: e.target.value })}
              placeholder="/\\n+/"
              className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
            />
          </div>
        )}

        <div className="flex items-center gap-3">
          <label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
            <input
              type="checkbox"
              checked={nodeData.trimItems}
              onChange={(e) => updateNodeData(id, { trimItems: e.target.checked })}
              className="nodrag nopan w-3 h-3"
            />
            Trim
          </label>
          <label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
            <input
              type="checkbox"
              checked={nodeData.removeEmpty}
              onChange={(e) => updateNodeData(id, { removeEmpty: e.target.checked })}
              className="nodrag nopan w-3 h-3"
            />
            Remove empty
          </label>
        </div>

        <div className="text-[10px] uppercase tracking-wide text-neutral-500">
          Parsed Items ({previewItems.length})
        </div>
        <div className="relative flex-1 min-h-[90px] border border-neutral-700 rounded bg-neutral-900/40">
          {nodeData.error ? (
            <div className="p-2 text-[11px] text-red-400">{nodeData.error}</div>
          ) : previewItems.length === 0 ? (
            <div className="p-2 text-[11px] text-neutral-500">No items parsed</div>
          ) : (
            <div className="py-1">
              {previewItems.map((item, index) => {
                const isSelected = nodeData.selectedOutputIndex === index;
                return (
                  <div key={`${index}-${item}`} className="relative pr-8">
                    <button
                      type="button"
                      onClick={() =>
                        updateNodeData(id, {
                          selectedOutputIndex: isSelected ? null : index,
                        })
                      }
                      className={`nodrag nopan w-[calc(100%-1rem)] mx-2 my-1 rounded border px-2 py-1 text-[11px] text-left truncate transition-colors ${
                        isSelected
                          ? "border-blue-500 bg-blue-900/40 text-blue-200"
                          : "border-neutral-700 bg-neutral-900/80 text-neutral-200 hover:bg-neutral-800"
                      }`}
                      title={isSelected ? "Selected for next connection (click to unselect)" : "Click to select for next connection"}
                    >
                      {index + 1}. {item}
                    </button>
                  </div>
                );
              })}
            </div>
          )}
        </div>
        <div className="text-[10px] text-neutral-500">
          {nodeData.selectedOutputIndex !== null
            ? `Next wire uses item ${nodeData.selectedOutputIndex + 1}`
            : "No selection: wires advance in order from item 1"}
        </div>
      </div>
    </BaseNode>
  );
}
