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

interface SignalInfo {
  name: string;
  type?: string;
  unit?: string;
  description?: string;
  writable?: boolean;
}

interface DsGroup {
  ds: string;
  signals: SignalInfo[];
  expanded: boolean;
  addingSignal: boolean; // show add-PV input
  addValue: string;
}

// Custom signals persisted in localStorage
const LS_KEY = 'uopi:custom-signals';

function loadCustom(): Array<{ ds: string; name: string }> {
  try {
    return JSON.parse(localStorage.getItem(LS_KEY) ?? '[]');
  } catch {
    return [];
  }
}

function saveCustom(signals: Array<{ ds: string; name: string }>) {
  localStorage.setItem(LS_KEY, JSON.stringify(signals));
}

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

export default function SignalTree({ onDragStart, width }: Props) {
  const [collapsed, setCollapsed] = useState(false);
  const [groups, setGroups] = useState<DsGroup[]>([]);
  const [customSignals, setCustomSignals] = useState<Array<{ ds: string; name: string }>>(loadCustom);
  const [filter, setFilter] = useState('');
  const [loading, setLoading] = useState(true);
  const [cfAvailable, setCfAvailable] = useState(false);
  const [cfSearching, setCfSearching] = useState(false);
  const [showWizard, setShowWizard] = useState(false);
  const [editSynthetic, setEditSynthetic] = useState<string | null>(null);
  const csvRef = useRef<HTMLInputElement>(null);

  async function loadGroups() {
    setLoading(true);
    try {
      const res = await fetch('/api/v1/datasources');
      if (!res.ok) return;
      const dsList: { name: string }[] = await res.json();
      const loaded: DsGroup[] = await Promise.all(
        dsList.map(async ({ name }) => {
          try {
            const r = await fetch(`/api/v1/signals?ds=${encodeURIComponent(name)}`);
            const sigs: SignalInfo[] = r.ok ? await r.json() : [];
            return { ds: name, signals: sigs, expanded: true, addingSignal: false, addValue: '' };
          } catch {
            return { ds: name, signals: [], expanded: true, addingSignal: false, addValue: '' };
          }
        })
      );
      setGroups(loaded);

      // Check if Channel Finder is available
      const cf = await fetch('/api/v1/channel-finder?q=').catch(() => null);
      setCfAvailable(cf?.status === 200);
    } catch (e) {
      console.error('Failed to load signals:', e);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => { loadGroups(); }, []);

  // Merge custom signals into groups
  const allGroups = groups.map(g => {
    const extra = customSignals.filter(c => c.ds === g.ds);
    const existing = new Set(g.signals.map(s => s.name));
    const merged = [
      ...g.signals,
      ...extra.filter(c => !existing.has(c.name)).map(c => ({ name: c.name, type: 'custom' } as SignalInfo)),
    ];
    return { ...g, signals: merged };
  });

  // Custom signals with no matching DS group get their own group
  const knownDs = new Set(groups.map(g => g.ds));
  const orphanCustom = customSignals.filter(c => !knownDs.has(c.ds));
  const orphanGroups: DsGroup[] = Object.entries(
    orphanCustom.reduce<Record<string, string[]>>((acc, { ds, name }) => {
      (acc[ds] ??= []).push(name);
      return acc;
    }, {})
  ).map(([ds, names]) => ({
    ds, signals: names.map(name => ({ name, type: 'custom' } as SignalInfo)),
    expanded: true, addingSignal: false, addValue: '',
  }));

  const visibleGroups = [...allGroups, ...orphanGroups];

  function toggleGroup(ds: string) {
    setGroups(gs => gs.map(g => g.ds === ds ? { ...g, expanded: !g.expanded } : g));
  }

  function setAdding(ds: string, val: boolean) {
    setGroups(gs => gs.map(g => g.ds === ds ? { ...g, addingSignal: val, addValue: '' } : g));
  }

  function setAddValue(ds: string, val: string) {
    setGroups(gs => gs.map(g => g.ds === ds ? { ...g, addValue: val } : g));
  }

  function commitAdd(ds: string, name: string) {
    name = name.trim();
    if (!name) { setAdding(ds, false); return; }
    const next = [...customSignals, { ds, name }];
    setCustomSignals(next);
    saveCustom(next);
    setAdding(ds, false);
  }

  function removeCustom(ds: string, name: string) {
    const next = customSignals.filter(c => !(c.ds === ds && c.name === name));
    setCustomSignals(next);
    saveCustom(next);
  }

  // ── Channel Finder search ────────────────────────────────────────────────

  async function handleCfSearch() {
    if (!filter) return;
    setCfSearching(true);
    try {
      const res = await fetch(`/api/v1/channel-finder?q=${encodeURIComponent(filter)}`);
      if (!res.ok) return;
      const names: string[] = await res.json();
      const next = [
        ...customSignals,
        ...names
          .filter(n => !customSignals.some(c => c.ds === 'epics' && c.name === n))
          .map(n => ({ ds: 'epics', name: n })),
      ];
      setCustomSignals(next);
      saveCustom(next);
    } finally {
      setCfSearching(false);
    }
  }

  // ── CSV import ────────────────────────────────────────────────────────────

  async function handleCsvImport(e: Event) {
    const input = e.target as HTMLInputElement;
    const file = input.files?.[0];
    input.value = '';
    if (!file) return;
    const text = await file.text();
    const added: Array<{ ds: string; name: string }> = [];
    for (const raw of text.split('\n')) {
      const line = raw.trim();
      if (!line || line.startsWith('#')) continue;
      const parts = line.split(',').map(p => p.trim());
      if (parts.length === 1 && parts[0]) {
        // bare PV name → default to epics
        added.push({ ds: 'epics', name: parts[0] });
      } else if (parts.length >= 2 && parts[0] && parts[1]) {
        added.push({ ds: parts[0], name: parts[1] });
      }
    }
    const existing = new Set(customSignals.map(c => `${c.ds}\0${c.name}`));
    const fresh = added.filter(a => !existing.has(`${a.ds}\0${a.name}`));
    const next = [...customSignals, ...fresh];
    setCustomSignals(next);
    saveCustom(next);
  }

  // ── Filter ────────────────────────────────────────────────────────────────

  const lf = filter.toLowerCase();
  const filtered = visibleGroups
    .map(g => ({
      ...g,
      signals: lf
        ? g.signals.filter(s =>
            s.name.toLowerCase().includes(lf) ||
            (s.description ?? '').toLowerCase().includes(lf)
          )
        : g.signals,
    }))
    .filter(g => g.signals.length > 0 || g.addingSignal || !lf);

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

  return (
    <aside class={`panel signal-tree-panel${collapsed ? ' collapsed' : ''}`} style={!collapsed && width ? `width:${width}px;min-width:${width}px;` : undefined}>
      <div class="panel-header">
        {!collapsed && <span class="panel-title">Signals</span>}
        <button class="icon-btn" onClick={() => setCollapsed(c => !c)} title={collapsed ? 'Expand' : 'Collapse'}>
          {collapsed ? '▶' : '◀'}
        </button>
      </div>

      {!collapsed && (
        <div class="signal-tree-body">
          {/* Search / filter bar */}
          <div class="signal-tree-search">
            <div class="signal-search-row">
              <input
                class="signal-filter"
                type="search"
                placeholder="Filter signals…"
                value={filter}
                onInput={(e) => setFilter((e.target as HTMLInputElement).value)}
              />
              {cfAvailable && filter && (
                <button class="icon-btn" title="Search Channel Finder" onClick={handleCfSearch} disabled={cfSearching}>
                  {cfSearching ? '…' : '🔍'}
                </button>
              )}
            </div>
          </div>

          {/* Toolbar: New Synthetic | Import CSV | Refresh */}
          <div class="signal-tree-toolbar">
            <button class="panel-btn" title="Create synthetic signal" onClick={() => setShowWizard(true)}>+ Synthetic</button>
            <button class="panel-btn" title="Import CSV (ds,name or bare PV names)" onClick={() => csvRef.current?.click()}>CSV</button>
            <button class="panel-btn" title="Refresh signal list" onClick={loadGroups}>↺</button>
            <input ref={csvRef} type="file" accept=".csv,text/csv" style="display:none" onChange={handleCsvImport} />
          </div>

          {/* Signal groups */}
          <div class="panel-list signal-list">
            {loading ? (
              <p class="hint">Loading signals…</p>
            ) : filtered.length === 0 ? (
              <p class="hint">No signals found.</p>
            ) : (
              filtered.map(group => {
                const groupInGroups = groups.find(g => g.ds === group.ds);
                return (
                  <div key={group.ds} class="signal-group">
                    <div class="signal-group-header">
                      <span class="signal-group-arrow" onClick={() => toggleGroup(group.ds)}>
                        {group.expanded ? '▾' : '▸'}
                      </span>
                      <span class="signal-group-name" onClick={() => toggleGroup(group.ds)}>
                        {group.ds}
                      </span>
                      <span class="signal-group-count">{group.signals.length}</span>
                      <button
                        class="icon-btn signal-add-btn"
                        title={`Add custom signal to ${group.ds}`}
                        onClick={() => setAdding(group.ds, true)}
                      >+</button>
                    </div>

                    {group.expanded && (
                      <div>
                        {group.signals.map(sig => {
                          const isCustom = !groups.find(g => g.ds === group.ds)?.signals.some(s => s.name === sig.name);
                          return (
                            <div
                              key={sig.name}
                              class={`signal-item${isCustom ? ' signal-custom' : ''}`}
                              title={`${group.ds}:${sig.name}${sig.unit ? ` [${sig.unit}]` : ''}${sig.description ? ` — ${sig.description}` : ''}`}
                              {...makeDraggable(group.ds, sig)}
                            >
                              <span class="signal-name">{sig.name}</span>
                              {sig.unit && <span class="signal-unit">{sig.unit}</span>}
                              {group.ds === 'synthetic' && (
                                <button
                                  class="icon-btn signal-edit-btn"
                                  title="Edit pipeline"
                                  onMouseDown={(e: MouseEvent) => e.stopPropagation()}
                                  onClick={(e: MouseEvent) => { e.stopPropagation(); setEditSynthetic(sig.name); }}
                                >✎</button>
                              )}
                              {isCustom && (
                                <button
                                  class="icon-btn signal-remove-btn"
                                  title="Remove custom signal"
                                  onMouseDown={(e: MouseEvent) => e.stopPropagation()}
                                  onClick={(e: MouseEvent) => { e.stopPropagation(); removeCustom(group.ds, sig.name); }}
                                >✕</button>
                              )}
                            </div>
                          );
                        })}

                        {/* Add-signal input row */}
                        {(groupInGroups?.addingSignal) && (
                          <div class="signal-add-row">
                            <input
                              class="signal-add-input"
                              placeholder="Signal / PV name…"
                              autoFocus
                              value={groupInGroups?.addValue ?? ''}
                              onInput={(e) => setAddValue(group.ds, (e.target as HTMLInputElement).value)}
                              onKeyDown={(e: KeyboardEvent) => {
                                if (e.key === 'Enter') commitAdd(group.ds, (e.target as HTMLInputElement).value);
                                if (e.key === 'Escape') setAdding(group.ds, false);
                              }}
                            />
                            <button class="icon-btn" onClick={() => commitAdd(group.ds, groupInGroups?.addValue ?? '')}>✓</button>
                            <button class="icon-btn" onClick={() => setAdding(group.ds, false)}>✕</button>
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                );
              })
            )}
          </div>
        </div>
      )}

      {showWizard && (
        <SyntheticWizard
          onClose={() => setShowWizard(false)}
          onCreated={loadGroups}
        />
      )}

      {editSynthetic && (
        <SyntheticEditor
          name={editSynthetic}
          onClose={() => setEditSynthetic(null)}
          onSaved={loadGroups}
        />
      )}
    </aside>
  );
}
