'use client';

import * as React from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';
import { cn } from '../../lib/utils';
import { StatusBadge } from '../badge/StatusBadge';
import { getCategoryToken } from './theme/categories';
import { getCategoryIcon } from './node-icons';
import { NODE_STATUS_BADGE } from './exec-status';
import type { NodeStatus, WfNode } from './types';

/**
 * Data payload carried on an xyflow node rendered by {@link WorkflowNode}.
 * The serializer stores the canonical {@link WfNode} under `node`; the canvas
 * layers presentation extras (category, status overlay, port labels, rename
 * callback) on top.
 */
export interface WorkflowNodeData {
  /** Canonical graph node. */
  node: WfNode;
  /** Category token key (resolved via theme/categories). */
  category?: string;
  /** Runtime status overlay (defaults to "idle" in the editor). */
  status?: NodeStatus;
  /** Number of inputs (default 1). */
  inputCount?: number;
  /** Number of outputs (default 1). */
  outputCount?: number;
  /** Labels for each output port (e.g. ["true", "false"]). */
  outputLabels?: string[];
  /** Labels for each input port. */
  inputLabels?: string[];
  /** Called when the node is renamed inline. */
  onRename?: (nodeId: string, name: string) => void;
  [key: string]: unknown;
}

/** Build evenly-spaced top offsets (as %) for `count` handles. */
function handleOffsets(count: number): string[] {
  if (count <= 1) return ['50%'];
  return Array.from({ length: count }, (_, i) =>
    `${((i + 1) / (count + 1)) * 100}%`
  );
}

/**
 * Custom xyflow node for the workflow editor: category accent color + icon,
 * an editable title, labelled input/output ports (handles), a runtime status
 * badge, and disabled styling. Renaming is inline (double-click the title)
 * and surfaced via `data.onRename`.
 */
export function WorkflowNode({ id, data, selected }: NodeProps) {
  const d = data as WorkflowNodeData;
  const node = d.node;
  const token = getCategoryToken(d.category ?? deriveCategory(node.type));
  const Icon = getCategoryIcon(token.icon);
  const status: NodeStatus = d.status ?? 'idle';
  const disabled = Boolean(node.disabled);

  const inputCount = d.inputCount ?? 1;
  const outputCount = d.outputCount ?? 1;
  const inputOffsets = handleOffsets(inputCount);
  const outputOffsets = handleOffsets(outputCount);

  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(node.name);
  React.useEffect(() => {
    setDraft(node.name);
  }, [node.name]);

  const commit = () => {
    setEditing(false);
    const next = draft.trim();
    if (next && next !== node.name) {
      d.onRename?.(id, next);
    } else {
      setDraft(node.name);
    }
  };

  return (
    <div
      data-node-id={id}
      data-status={status}
      data-disabled={disabled ? 'true' : undefined}
      className={cn(
        'relative min-w-[180px] rounded-lg border-2 bg-card shadow-sm transition-shadow',
        selected && 'ring-2 ring-offset-1',
        disabled && 'opacity-50 grayscale'
      )}
      style={{
        borderColor: token.color,
        // selection ring picks up the category accent
        ['--tw-ring-color' as string]: token.color,
      }}
    >
      {/* Input handles */}
      {inputOffsets.map((top, i) => (
        <React.Fragment key={`in-${i}`}>
          <Handle
            id={String(i)}
            type="target"
            position={Position.Left}
            data-handle-kind="input"
            style={{ top, background: token.color }}
          />
          {d.inputLabels?.[i] && (
            <span
              className="absolute left-2 -translate-y-1/2 text-[10px] text-muted-foreground"
              style={{ top }}
            >
              {d.inputLabels[i]}
            </span>
          )}
        </React.Fragment>
      ))}

      {/* Header: icon + category label */}
      <div
        className="flex items-center gap-1.5 rounded-t-md px-2.5 py-1"
        style={{ background: token.background, color: token.foreground }}
      >
        <Icon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
        <span className="text-[11px] font-medium uppercase tracking-wide">
          {token.label}
        </span>
        <StatusBadge
          status={status}
          config={NODE_STATUS_BADGE}
          size="sm"
          className="ml-auto"
        />
      </div>

      {/* Title (inline-editable) */}
      <div className="px-2.5 py-2">
        {editing ? (
          <input
            autoFocus
            role="textbox"
            aria-label="Node name"
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            onBlur={commit}
            onKeyDown={(e) => {
              if (e.key === 'Enter') commit();
              if (e.key === 'Escape') {
                setDraft(node.name);
                setEditing(false);
              }
            }}
            className="w-full rounded border border-border bg-background px-1 py-0.5 text-sm font-medium text-foreground outline-none"
          />
        ) : (
          <button
            type="button"
            onDoubleClick={() => d.onRename && setEditing(true)}
            className="block w-full truncate text-left text-sm font-medium text-foreground"
            title={node.name}
          >
            {node.name}
          </button>
        )}
        <span className="mt-0.5 block truncate text-[11px] text-muted-foreground">
          {node.type}
        </span>
      </div>

      {/* Output handles + labels */}
      {outputOffsets.map((top, i) => (
        <React.Fragment key={`out-${i}`}>
          <Handle
            id={String(i)}
            type="source"
            position={Position.Right}
            data-handle-kind="output"
            style={{ top, background: token.color }}
          />
          {d.outputLabels?.[i] && (
            <span
              className="absolute right-2 -translate-y-1/2 text-[10px] text-muted-foreground"
              style={{ top }}
            >
              {d.outputLabels[i]}
            </span>
          )}
        </React.Fragment>
      ))}
    </div>
  );
}

/** Best-effort category from a "category.subtype" slug (e.g. "action.email"). */
function deriveCategory(type: string | undefined): string | undefined {
  if (!type) return undefined;
  const head = type.split('.')[0];
  return head || undefined;
}
