import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';
import type { GroupNode, SignalRef } from './lib/types';

interface Props {
  onDragStart?: (sig: SignalRef) => void;
}

// ── Pure tree helpers ──────────────────────────────────────────────────────────

function genId(): string {
  return Math.random().toString(36).slice(2, 10);
}

/** Insert a child node at the end of the folder at `path` (empty = root). */
function insertAt(nodes: GroupNode[], path: number[], child: GroupNode): GroupNode[] {
  if (path.length === 0) return [...nodes, child];
  return nodes.map((n, i) => {
    if (i !== path[0] || n.kind !== 'folder') return n;
    return { ...n, children: insertAt(n.children, path.slice(1), child) };
  });
}

/** Remove the node at `path`. */
function removeAt(nodes: GroupNode[], path: number[]): GroupNode[] {
  if (path.length === 1) return nodes.filter((_, i) => i !== path[0]);
  return nodes.map((n, i) => {
    if (i !== path[0] || n.kind !== 'folder') return n;
    return { ...n, children: removeAt(n.children, path.slice(1)) };
  });
}

/** Update the folder label at `path`. */
function renameAt(nodes: GroupNode[], path: number[], label: string): GroupNode[] {
  if (path.length === 1) {
    return nodes.map((n, i) =>
      i === path[0] && n.kind === 'folder' ? { ...n, label } : n
    );
  }
  return nodes.map((n, i) => {
    if (i !== path[0] || n.kind !== 'folder') return n;
    return { ...n, children: renameAt(n.children, path.slice(1), label) };
  });
}

// ── Parse signal input ─────────────────────────────────────────────────────────

/** Parse "ds:name" or bare "name" (defaults to epics). */
function parseSignal(input: string): { ds: string; name: string } | null {
  const s = input.trim();
  if (!s) return null;
  const colon = s.indexOf(':');
  if (colon > 0) return { ds: s.slice(0, colon), name: s.slice(colon + 1) };
  return { ds: 'epics', name: s };
}

// ── Component ──────────────────────────────────────────────────────────────────

export default function GroupsTree({ onDragStart }: Props) {
  const [nodes, setNodes] = useState<GroupNode[]>([]);
  const [loading, setLoading] = useState(true);
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set());

  // What the user is currently adding inline (null = nothing)
  const [addingAt, setAddingAt] = useState<{ path: number[]; kind: 'signal' | 'folder' } | null>(null);
  const [addInput, setAddInput] = useState('');

  // Inline folder rename
  const [renamingPath, setRenamingPath] = useState<number[] | null>(null);
  const [renameInput, setRenameInput] = useState('');

  // ── Load / save ─────────────────────────────────────────────────────────────

  useEffect(() => {
    fetch('/api/v1/groups')
      .then(r => r.json())
      .then((data: GroupNode[]) => { setNodes(data); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  function save(next: GroupNode[]) {
    fetch('/api/v1/groups', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(next),
    }).catch(e => console.error('groups save failed:', e));
  }

  function update(next: GroupNode[]) {
    setNodes(next);
    save(next);
  }

  // ── Drag ────────────────────────────────────────────────────────────────────

  function makeDraggable(ds: string, name: string) {
    return {
      draggable: true as const,
      onDragStart: (e: DragEvent) => {
        const ref: SignalRef = { ds, name };
        e.dataTransfer?.setData('application/json', JSON.stringify(ref));
        onDragStart?.(ref);
      },
    };
  }

  // ── Inline add ──────────────────────────────────────────────────────────────

  function startAdding(path: number[], kind: 'signal' | 'folder') {
    setAddingAt({ path, kind });
    setAddInput('');
  }

  function commitAdd() {
    if (!addingAt) return;
    const { path, kind } = addingAt;
    const val = addInput.trim();
    setAddingAt(null);
    setAddInput('');
    if (!val) return;
    if (kind === 'folder') {
      const node: GroupNode = { kind: 'folder', id: genId(), label: val, children: [] };
      update(insertAt(nodes, path, node));
    } else {
      const sig = parseSignal(val);
      if (!sig) return;
      const node: GroupNode = { kind: 'signal', ds: sig.ds, name: sig.name };
      update(insertAt(nodes, path, node));
    }
  }

  function cancelAdd() {
    setAddingAt(null);
    setAddInput('');
  }

  // ── Rename ──────────────────────────────────────────────────────────────────

  function startRename(path: number[], label: string) {
    setRenamingPath(path);
    setRenameInput(label);
  }

  function commitRename() {
    if (!renamingPath || !renameInput.trim()) { cancelRename(); return; }
    update(renameAt(nodes, renamingPath, renameInput.trim()));
    setRenamingPath(null);
  }

  function cancelRename() {
    setRenamingPath(null);
    setRenameInput('');
  }

  // ── Collapse ────────────────────────────────────────────────────────────────

  function toggleCollapse(id: string) {
    setCollapsed(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  // ── Render ──────────────────────────────────────────────────────────────────

  function renderAddRow(path: number[], kind: 'signal' | 'folder') {
    const isActive = addingAt &&
      addingAt.kind === kind &&
      JSON.stringify(addingAt.path) === JSON.stringify(path);
    if (!isActive) return null;
    return (
      <div class="group-add-row">
        <input
          class="group-add-input"
          autoFocus
          placeholder={kind === 'signal' ? 'ds:name or PV name…' : 'Folder name…'}
          value={addInput}
          onInput={(e) => setAddInput((e.target as HTMLInputElement).value)}
          onKeyDown={(e: KeyboardEvent) => {
            if (e.key === 'Enter') commitAdd();
            if (e.key === 'Escape') cancelAdd();
          }}
        />
        <button class="icon-btn" onClick={commitAdd}>✓</button>
        <button class="icon-btn" onClick={cancelAdd}>✕</button>
      </div>
    );
  }

  function renderNode(node: GroupNode, path: number[]) {
    const pathKey = JSON.stringify(path);

    if (node.kind === 'signal') {
      return (
        <div
          key={pathKey}
          class="group-signal-item"
          title={`${node.ds}:${node.name}`}
          {...makeDraggable(node.ds, node.name)}
        >
          <span class="group-signal-ds">{node.ds}</span>
          <span class="group-signal-name">{node.name}</span>
          <button
            class="icon-btn group-node-remove"
            title="Remove from group"
            onMouseDown={(e: MouseEvent) => e.stopPropagation()}
            onClick={(e: MouseEvent) => { e.stopPropagation(); update(removeAt(nodes, path)); }}
          >✕</button>
        </div>
      );
    }

    // folder node
    const isCollapsed = collapsed.has(node.id);
    const isRenaming = renamingPath !== null && JSON.stringify(renamingPath) === pathKey;

    return (
      <div key={pathKey} class="group-folder">
        <div class="group-folder-header">
          <span
            class="group-folder-arrow"
            onClick={() => toggleCollapse(node.id)}
          >
            {isCollapsed ? '▸' : '▾'}
          </span>

          {isRenaming ? (
            <input
              class="group-rename-input"
              autoFocus
              value={renameInput}
              onInput={(e) => setRenameInput((e.target as HTMLInputElement).value)}
              onKeyDown={(e: KeyboardEvent) => {
                if (e.key === 'Enter') commitRename();
                if (e.key === 'Escape') cancelRename();
              }}
              onBlur={commitRename}
            />
          ) : (
            <span
              class="group-folder-name"
              onDblClick={() => startRename(path, node.label)}
              title="Double-click to rename"
            >
              {node.label}
            </span>
          )}

          <span class="group-folder-count">{node.children.length}</span>

          <div class="group-folder-actions">
            <button
              class="icon-btn"
              title="Add signal"
              onClick={() => { if (!isCollapsed) toggleCollapse(node.id); startAdding(path, 'signal'); }}
            >+📶</button>
            <button
              class="icon-btn"
              title="Add subfolder"
              onClick={() => { if (!isCollapsed) toggleCollapse(node.id); startAdding(path, 'folder'); }}
            >+📁</button>
            <button
              class="icon-btn group-node-remove"
              title="Delete folder"
              onClick={() => update(removeAt(nodes, path))}
            >🗑</button>
          </div>
        </div>

        {!isCollapsed && (
          <div class="group-folder-children">
            {node.children.map((child, i) => renderNode(child, [...path, i]))}
            {renderAddRow(path, 'signal')}
            {renderAddRow(path, 'folder')}
          </div>
        )}
      </div>
    );
  }

  const isAddingRoot = addingAt !== null && addingAt.path.length === 0;

  return (
    <div class="groups-tree">
      {/* Root-level toolbar */}
      <div class="groups-toolbar">
        <button
          class="panel-btn"
          title="New root group"
          onClick={() => startAdding([], 'folder')}
        >
          + Group
        </button>
      </div>

      {/* Inline input for new root folder */}
      {isAddingRoot && addingAt?.kind === 'folder' && (
        <div class="group-add-row group-add-root">
          <input
            class="group-add-input"
            autoFocus
            placeholder="Group name…"
            value={addInput}
            onInput={(e) => setAddInput((e.target as HTMLInputElement).value)}
            onKeyDown={(e: KeyboardEvent) => {
              if (e.key === 'Enter') commitAdd();
              if (e.key === 'Escape') cancelAdd();
            }}
          />
          <button class="icon-btn" onClick={commitAdd}>✓</button>
          <button class="icon-btn" onClick={cancelAdd}>✕</button>
        </div>
      )}

      <div class="panel-list group-list">
        {loading ? (
          <p class="hint">Loading groups…</p>
        ) : nodes.length === 0 && !isAddingRoot ? (
          <p class="hint">No groups yet — click "+ Group" to create one.</p>
        ) : (
          nodes.map((node, i) => renderNode(node, [i]))
        )}
      </div>
    </div>
  );
}
