import { h, Fragment } from 'preact';
import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
import type { SignalRef, StateVar } from './lib/types';
import SyntheticGraphEditor from './SyntheticGraphEditor';
import GroupsTree from './GroupsTree';
import ChannelFinderModal from './ChannelFinderModal';
import SignalPicker from './SignalPicker';

type TreeTab = 'sources' | 'groups';
type GroupBy = 'datasource' | 'area' | 'system' | 'ioc';

interface SignalInfo {
  name: string;
  type?: string;
  unit?: string;
  description?: string;
  writable?: boolean;
  properties?: Record<string, string>;
  tags?: string[];
}

interface DisplayGroup {
  id: string;
  label: string;
  signals: Array<SignalInfo & { ds: string }>;
  expanded: boolean;
  addingSignal?: boolean;
  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;
  // Current interface id — scopes panel-visibility synthetic signals.
  panelId?: string;
  // Panel-local state variables and a setter (edit mode only).
  statevars?: StateVar[];
  onStateVarsChange?: (vars: StateVar[]) => void;
}

export default function SignalTree({ onDragStart, width, panelId, statevars, onStateVarsChange }: Props) {
  const [treeTab, setTreeTab] = useState<TreeTab>('sources');
  const [groupBy, setGroupBy] = useState<GroupBy>('datasource');
  const [collapsed, setCollapsed] = useState(false);
  const [rawSignals, setRawSignals] = useState<Array<SignalInfo & { ds: string }>>([]);
  const [customSignals, setCustomSignals] = useState<Array<{ ds: string; name: string }>>(loadCustom);
  const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>({});
  const [filter, setFilter] = useState('');
  const [loading, setLoading] = useState(true);
  const [cfAvailable, setCfAvailable] = useState(false);
  const [showWizard, setShowWizard] = useState(false);
  const [showCfModal, setShowCfModal] = useState(false);
  const [showManualAdd, setShowManualAdd] = useState(false);
  const [manualDs, setManualDs] = useState('epics');
  const [manualName, setManualName] = useState('');
  const [addingTo, setAddingTo] = useState<string | null>(null);
  const [addValue, setAddValue] = useState('');
  const [editSynthetic, setEditSynthetic] = useState<string | null>(null);
  const csvRef = useRef<HTMLInputElement>(null);

  async function loadAllSignals() {
    setLoading(true);
    try {
      const res = await fetch('/api/v1/datasources');
      if (!res.ok) return;
      const dsList: { name: string }[] = await res.json();
      
      const all: Array<SignalInfo & { ds: string }> = [];
      await Promise.all(
        dsList.map(async ({ name }) => {
          try {
            // Synthetic signals are filtered server-side by panel scope.
            const url = name === 'synthetic' && panelId
              ? `/api/v1/signals?ds=synthetic&panel=${encodeURIComponent(panelId)}`
              : `/api/v1/signals?ds=${encodeURIComponent(name)}`;
            const r = await fetch(url);
            if (r.ok) {
              const data = await r.json();
              const sigs = Array.isArray(data) ? data : (data && typeof data === 'object' && Array.isArray(data.signals) ? data.signals : []);
              all.push(...sigs.map(s => ({ ...s, ds: name })));
            }
          } catch (e) { console.error(`Failed to load ${name}:`, e); }
        })
      );
      setRawSignals(all);

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

  useEffect(() => { loadAllSignals(); }, [panelId]);

  // ── Local state variables (edit mode) ──────────────────────────────────────
  const [showAddLocal, setShowAddLocal] = useState(false);
  const [localOpen, setLocalOpen] = useState(true);
  const [lvName, setLvName] = useState('');
  const [lvType, setLvType] = useState<'number' | 'bool' | 'string' | 'array'>('number');
  const [lvInitial, setLvInitial] = useState('0');
  // array-specific fields (mirror LogicEditor LocalVars)
  const [lvElem, setLvElem] = useState<'number' | 'bool' | 'array'>('number');
  const [lvSizing, setLvSizing] = useState<'dynamic' | 'capped' | 'fixed'>('dynamic');
  const [lvCapacity, setLvCapacity] = useState('100');
  const [lvJsonErr, setLvJsonErr] = useState('');

  function handleLvTypeChange(t: 'number' | 'bool' | 'string' | 'array') {
    setLvType(t);
    setLvInitial(t === 'array' ? '[]' : t === 'bool' ? 'false' : t === 'string' ? '' : '0');
    setLvJsonErr('');
  }

  function handleLvInitialChange(v: string) {
    setLvInitial(v);
    if (lvType === 'array') {
      try { JSON.parse(v); setLvJsonErr(''); }
      catch (e: any) { setLvJsonErr(e.message); }
    }
  }

  function addLocal() {
    const name = lvName.trim();
    if (!name || !onStateVarsChange) return;
    if (lvType === 'array' && lvJsonErr) return;
    const base: StateVar = { name, type: lvType, initial: lvInitial };
    if (lvType === 'array') {
      base.elem = lvElem;
      base.sizing = lvSizing;
      if (lvSizing !== 'dynamic') base.capacity = parseInt(lvCapacity, 10) || 100;
    }
    const next = (statevars ?? []).filter(v => v.name !== name);
    next.push(base);
    onStateVarsChange(next);
    setLvName('');
    setLvInitial('0');
    setLvType('number');
    setLvElem('number');
    setLvSizing('dynamic');
    setLvCapacity('100');
    setLvJsonErr('');
    setShowAddLocal(false);
  }

  function removeLocal(name: string) {
    if (!onStateVarsChange) return;
    onStateVarsChange((statevars ?? []).filter(v => v.name !== name));
  }

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

  // Merge custom signals that might not be in ListSignals yet
  const allSignals = useMemo(() => {
    const existing = new Set(rawSignals.map(s => `${s.ds}\0${s.name}`));
    const extra = customSignals
      .filter(c => !existing.has(`${c.ds}\0${c.name}`))
      .map(c => ({ name: c.name, ds: c.ds, type: 'custom' } as SignalInfo & { ds: string }));
    return [...rawSignals, ...extra];
  }, [rawSignals, customSignals]);

  // Group signals based on selected criteria
  const visibleGroups = useMemo(() => {
    const lf = filter.toLowerCase();
    const filtered = allSignals.filter(s => 
      s.name.toLowerCase().includes(lf) || 
      (s.description ?? '').toLowerCase().includes(lf)
    );

    const groups: Record<string, Array<SignalInfo & { ds: string }>> = {};

    filtered.forEach(s => {
      let key = 'unknown';
      if (groupBy === 'datasource') {
        key = s.ds;
      } else if (groupBy === 'area') {
        key = s.properties?.area || 'no-area';
      } else if (groupBy === 'system') {
        key = s.properties?.system || 'no-system';
      } else if (groupBy === 'ioc') {
        key = s.properties?.iocName || 'no-ioc';
      }
      (groups[key] ??= []).push(s);
    });

    return Object.entries(groups).map(([id, signals]) => ({
      id,
      label: id,
      signals: signals.sort((a, b) => a.name.localeCompare(b.name)),
      expanded: expandedGroups[id] ?? true,
    })).sort((a, b) => a.label.localeCompare(b.label));
  }, [allSignals, groupBy, expandedGroups, filter]);

  function toggleGroup(id: string) {
    setExpandedGroups(prev => ({ ...prev, [id]: !(prev[id] ?? true) }));
  }

  function startAdding(ds: string) {
    setAddingTo(ds);
    setAddValue('');
    setExpandedGroups(prev => ({ ...prev, [ds]: true }));
  }

  function commitAddManually() {
    const name = addValue.trim();
    if (!name || !addingTo) { setAddingTo(null); return; }
    const next = [...customSignals, { ds: addingTo, name }];
    setCustomSignals(next);
    saveCustom(next);
    setAddingTo(null);
  }

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

  function handleAddCfSignals(refs: SignalRef[]) {
    const existing = new Set(customSignals.map(c => `${c.ds}\0${c.name}`));
    const fresh = refs.filter(r => !existing.has(`${r.ds}\0${r.name}`));
    if (fresh.length === 0) return;
    const next = [...customSignals, ...fresh];
    setCustomSignals(next);
    saveCustom(next);
    loadAllSignals(); // Refresh to fetch metadata for new ones
  }

  function handleManualAdd() {
    if (!manualName.trim()) return;
    handleAddCfSignals([{ ds: manualDs, name: manualName.trim() }]);
    setManualName('');
    setShowManualAdd(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]) {
        added.push({ ds: 'epics', name: parts[0] });
      } else if (parts.length >= 2 && parts[0] && parts[1]) {
        added.push({ ds: parts[0], name: parts[1] });
      }
    }
    handleAddCfSignals(added);
  }

  function makeDraggable(sig: SignalInfo & { ds: string }) {
    return {
      draggable: true as const,
      onDragStart: (e: DragEvent) => {
        const ref: SignalRef = { ds: sig.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">
          <div class="signal-tree-tabs">
            <button
              class={`stab${treeTab === 'sources' ? ' stab-active' : ''}`}
              onClick={() => setTreeTab('sources')}
            >Sources</button>
            <button
              class={`stab${treeTab === 'groups' ? ' stab-active' : ''}`}
              onClick={() => setTreeTab('groups')}
            >Groups</button>
          </div>

          {treeTab === 'groups' && (
            <GroupsTree onDragStart={onDragStart} />
          )}

          {treeTab === 'sources' && onStateVarsChange && (
            <div class="signal-group local-state-group">
              <div class="signal-group-header">
                <span class="signal-group-arrow" onClick={() => setLocalOpen(o => !o)}>
                  {localOpen ? '▾' : '▸'}
                </span>
                <span class="signal-group-name" title="Panel-local state variables" onClick={() => setLocalOpen(o => !o)}>
                  Local State
                </span>
                <span class="signal-group-count">{(statevars ?? []).length}</span>
                <button
                  class="icon-btn signal-add-btn"
                  title="Add local state variable"
                  onClick={(e) => { e.stopPropagation(); setShowAddLocal(s => !s); setLocalOpen(true); }}
                >+</button>
              </div>
              {localOpen && (
                <div>
                  {showAddLocal && (
                    <div class="signal-add-row" style="flex-wrap: wrap; gap: 4px;">
                      <input
                        class="signal-add-input"
                        placeholder="Variable name…"
                        autoFocus
                        value={lvName}
                        onInput={(e) => setLvName((e.target as HTMLInputElement).value)}
                        onKeyDown={(e: KeyboardEvent) => {
                          if (e.key === 'Enter') addLocal();
                          if (e.key === 'Escape') setShowAddLocal(false);
                        }}
                      />
                      <select
                        class="prop-select"
                        style="font-size: 0.7rem; height: 1.6rem; padding: 0 4px;"
                        value={lvType}
                        onChange={(e) => handleLvTypeChange((e.target as HTMLSelectElement).value as any)}
                      >
                        <option value="number">number</option>
                        <option value="bool">bool</option>
                        <option value="string">string</option>
                        <option value="array">array</option>
                      </select>
                      {lvType === 'array' && (
                        <Fragment>
                          <select
                            class="prop-select"
                            style="font-size: 0.7rem; height: 1.6rem; padding: 0 4px;"
                            value={lvElem}
                            onChange={(e) => setLvElem((e.target as HTMLSelectElement).value as any)}
                          >
                            <option value="number">elem: number</option>
                            <option value="bool">elem: bool</option>
                            <option value="array">elem: array</option>
                          </select>
                          <select
                            class="prop-select"
                            style="font-size: 0.7rem; height: 1.6rem; padding: 0 4px;"
                            value={lvSizing}
                            onChange={(e) => setLvSizing((e.target as HTMLSelectElement).value as any)}
                          >
                            <option value="dynamic">sizing: dynamic</option>
                            <option value="capped">sizing: capped</option>
                            <option value="fixed">sizing: fixed</option>
                          </select>
                          {lvSizing !== 'dynamic' && (
                            <input
                              class="signal-add-input"
                              type="number"
                              style="width: 5rem;"
                              placeholder="capacity"
                              value={lvCapacity}
                              onInput={(e) => setLvCapacity((e.target as HTMLInputElement).value)}
                            />
                          )}
                        </Fragment>
                      )}
                      <input
                        class={`signal-add-input${lvJsonErr ? ' prop-input-error' : ''}`}
                        style="flex: 1;"
                        placeholder={lvType === 'array' ? 'initial (JSON)' : 'initial'}
                        value={lvInitial}
                        onInput={(e) => handleLvInitialChange((e.target as HTMLInputElement).value)}
                        onKeyDown={(e: KeyboardEvent) => { if (e.key === 'Enter') addLocal(); }}
                      />
                      {lvJsonErr && <p class="wizard-error" style="flex-basis: 100%; margin: 0;">{lvJsonErr}</p>}
                      <button class="icon-btn" title="Add" onClick={addLocal}>✓</button>
                      <button class="icon-btn" title="Cancel" onClick={() => setShowAddLocal(false)}>✕</button>
                    </div>
                  )}
                  {(statevars ?? []).length === 0 && !showAddLocal && (
                    <p class="hint" style="padding: 0.25rem 0.5rem;">No local variables.</p>
                  )}
                  {(statevars ?? []).map(v => (
                    <div
                      key={v.name}
                      class="signal-item signal-custom"
                      title={`local:${v.name} (${v.type ?? 'number'}, initial ${v.initial})`}
                      {...makeLocalDraggable(v.name)}
                    >
                      <span class="signal-name">{v.name}</span>
                      <span class="signal-unit">{v.type ?? 'number'}</span>
                      <button
                        class="icon-btn signal-remove-btn"
                        title="Remove local variable"
                        onMouseDown={(e: MouseEvent) => e.stopPropagation()}
                        onClick={(e: MouseEvent) => { e.stopPropagation(); removeLocal(v.name); }}
                      >✕</button>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}

          {treeTab === 'sources' && (
            <Fragment>
              <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 && (
                    <button class="icon-btn" title="Advanced Search" onClick={() => setShowCfModal(true)}>
                      🔍
                    </button>
                  )}
                </div>
              </div>

              <div class="signal-tree-toolbar" style="gap: 4px;">
                <select 
                  class="prop-select" 
                  style="font-size: 0.7rem; height: 1.6rem; padding: 0 4px; flex: 1;"
                  value={groupBy}
                  onChange={e => setGroupBy((e.target as HTMLSelectElement).value as any)}
                >
                  <option value="datasource">By Source</option>
                  <option value="area">By Area</option>
                  <option value="system">By System</option>
                  <option value="ioc">By IOC</option>
                </select>
                <button class="panel-btn icon-only" title="Add Signal Manually" onClick={() => setShowManualAdd(true)}>+</button>
                <button class="panel-btn icon-only" title="Create synthetic signal" onClick={() => setShowWizard(true)}>Σ</button>
                <button class="panel-btn icon-only" title="Import CSV" onClick={() => csvRef.current?.click()}>CSV</button>
                <button class="panel-btn icon-only" title="Refresh signal list" onClick={loadAllSignals}>↺</button>
                <input ref={csvRef} type="file" accept=".csv,text/csv" style="display:none" onChange={handleCsvImport} />
              </div>

              <div class="panel-list signal-list">
                {loading ? (
                  <p class="hint">Loading signals…</p>
                ) : visibleGroups.length === 0 ? (
                  <p class="hint">No signals found.</p>
                ) : (
                  visibleGroups.map(group => (
                    <div key={group.id} class="signal-group">
                      <div class="signal-group-header">
                        <span class="signal-group-arrow" onClick={() => toggleGroup(group.id)}>
                          {group.expanded ? '▾' : '▸'}
                        </span>
                        <span class="signal-group-name" title={group.label} onClick={() => toggleGroup(group.id)}>
                          {group.label}
                        </span>
                        <span class="signal-group-count">{group.signals.length}</span>
                        {groupBy === 'datasource' && group.id !== 'synthetic' && (
                          <button
                            class="icon-btn signal-add-btn"
                            title={`Add custom signal to ${group.id}`}
                            onClick={(e) => { e.stopPropagation(); startAdding(group.id); }}
                          >+</button>
                        )}
                      </div>

                      {group.expanded && (
                        <div>
                          {addingTo === group.id && (
                            <div class="signal-add-row">
                              <input
                                class="signal-add-input"
                                placeholder="Signal / PV name…"
                                autoFocus
                                value={addValue}
                                onInput={(e) => setAddValue((e.target as HTMLInputElement).value)}
                                onKeyDown={(e: KeyboardEvent) => {
                                  if (e.key === 'Enter') commitAddManually();
                                  if (e.key === 'Escape') setAddingTo(null);
                                }}
                              />
                              <button class="icon-btn" onClick={commitAddManually}>✓</button>
                              <button class="icon-btn" onClick={() => setAddingTo(null)}>✕</button>
                            </div>
                          )}
                          {group.signals.map(sig => {
                            const isCustom = sig.type === 'custom' || !rawSignals.some(rs => rs.ds === sig.ds && rs.name === sig.name);
                            return (
                              <div
                                key={`${sig.ds}:${sig.name}`}
                                class={`signal-item${isCustom ? ' signal-custom' : ''}`}
                                title={`${sig.ds}:${sig.name}${sig.unit ? ` [${sig.unit}]` : ''}${sig.description ? ` — ${sig.description}` : ''}`}
                                {...makeDraggable(sig)}
                              >
                                <span class="signal-name">{sig.name}</span>
                                {sig.unit && <span class="signal-unit">{sig.unit}</span>}
                                {sig.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(sig.ds, sig.name); }}
                                  >✕</button>
                                )}
                              </div>
                            );
                          })}
                        </div>
                      )}
                    </div>
                  ))
                )}
              </div>
            </Fragment>
          )}
        </div>
      )}

      {showWizard && (
        <SyntheticGraphEditor
          create
          panelId={panelId}
          onClose={() => setShowWizard(false)}
          onSaved={loadAllSignals}
        />
      )}

      {editSynthetic && (
        <SyntheticGraphEditor
          name={editSynthetic}
          onClose={() => setEditSynthetic(null)}
          onSaved={loadAllSignals}
        />
      )}

      {showCfModal && (
        <ChannelFinderModal 
          cfURL="" // fetched by backend API
          onClose={() => setShowCfModal(false)}
          onAddSignals={handleAddCfSignals}
        />
      )}

      {showManualAdd && (
        <div class="wizard-backdrop" onClick={() => setShowManualAdd(false)}>
          <div class="wizard" onClick={e => e.stopPropagation()} style="max-width: 400px;">
            <div class="wizard-header">
              <span>Add Signal Manually</span>
              <button class="icon-btn" onClick={() => setShowManualAdd(false)}>✕</button>
            </div>
            <div class="wizard-body">
              <div class="wizard-field">
                <label>Signal</label>
                <SignalPicker
                  value={manualName ? `${manualDs}:${manualName}` : ''}
                  options={allSignals}
                  allowFreeform
                  placeholder="Search or type ds:NAME…"
                  onChange={(ref) => {
                    const i = ref.indexOf(':');
                    if (i < 0) { setManualDs(ref); setManualName(''); }
                    else { setManualDs(ref.slice(0, i)); setManualName(ref.slice(i + 1)); }
                  }} />
                <p class="hint">Pick an existing signal, or type <code>epics:MY:PV:NAME</code> to add a new one.</p>
              </div>
            </div>
            <div class="wizard-footer">
              <button class="toolbar-btn" onClick={() => setShowManualAdd(false)}>Cancel</button>
              <button class="toolbar-btn toolbar-btn-primary" onClick={handleManualAdd}>Add Signal</button>
            </div>
          </div>
        </div>
      )}
    </aside>
  );
}
