import { h } from 'preact';
import { useState, useCallback, useRef } from 'preact/hooks';
import type { Interface, Widget } from './lib/types';
import { serializeInterface, parseInterface } from './lib/xml';
import SignalTree from './SignalTree';
import EditCanvas, { genWidgetId, DEFAULT_SIZES } from './EditCanvas';
import PropertiesPane from './PropertiesPane';

interface Props {
  initial: Interface | null;
  onDone: () => void;
}

function blankInterface(): Interface {
  return { id: '', name: 'New Interface', version: 1, w: 1200, h: 800, widgets: [] };
}

// ── Align / distribute helpers ──────────────────────────────────────────────

function alignWidgets(widgets: Widget[], ids: string[], mode: string): Widget[] {
  const sel = widgets.filter(w => ids.includes(w.id));
  if (sel.length < 2) return widgets;
  const minX = Math.min(...sel.map(w => w.x));
  const maxX = Math.max(...sel.map(w => w.x + w.w));
  const minY = Math.min(...sel.map(w => w.y));
  const maxY = Math.max(...sel.map(w => w.y + w.h));
  const cx   = (minX + maxX) / 2;
  const cy   = (minY + maxY) / 2;
  return widgets.map(w => {
    if (!ids.includes(w.id)) return w;
    switch (mode) {
      case 'left':     return { ...w, x: minX };
      case 'right':    return { ...w, x: maxX - w.w };
      case 'centerH':  return { ...w, x: Math.round(cx - w.w / 2) };
      case 'top':      return { ...w, y: minY };
      case 'bottom':   return { ...w, y: maxY - w.h };
      case 'centerV':  return { ...w, y: Math.round(cy - w.h / 2) };
      default:         return w;
    }
  });
}

function distributeWidgets(widgets: Widget[], ids: string[], axis: 'h' | 'v'): Widget[] {
  const sel = [...widgets.filter(w => ids.includes(w.id))];
  if (sel.length < 3) return widgets;
  if (axis === 'h') {
    sel.sort((a, b) => a.x - b.x);
    const totalW = sel.reduce((s, w) => s + w.w, 0);
    const span   = sel[sel.length - 1].x + sel[sel.length - 1].w - sel[0].x;
    const gap    = (span - totalW) / (sel.length - 1);
    let cx = sel[0].x;
    const positions = new Map<string, number>();
    for (const w of sel) { positions.set(w.id, cx); cx += w.w + gap; }
    return widgets.map(w => ids.includes(w.id) ? { ...w, x: Math.round(positions.get(w.id)!) } : w);
  } else {
    sel.sort((a, b) => a.y - b.y);
    const totalH = sel.reduce((s, w) => s + w.h, 0);
    const span   = sel[sel.length - 1].y + sel[sel.length - 1].h - sel[0].y;
    const gap    = (span - totalH) / (sel.length - 1);
    let cy = sel[0].y;
    const positions = new Map<string, number>();
    for (const w of sel) { positions.set(w.id, cy); cy += w.h + gap; }
    return widgets.map(w => ids.includes(w.id) ? { ...w, y: Math.round(positions.get(w.id)!) } : w);
  }
}

const STATIC_WIDGET_TYPES = [
  { type: 'textlabel', label: 'Text Label' },
  { type: 'image',     label: 'Image' },
  { type: 'link',      label: 'Link' },
];

export default function EditMode({ initial, onDone }: Props) {
  const [iface, setIface] = useState<Interface>(initial ?? blankInterface());
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [snapGrid, setSnapGrid] = useState(10);
  const [showSnapGrid, setShowSnapGrid] = useState(true);
  const [showInsertMenu, setShowInsertMenu] = useState(false);

  // Undo / redo
  const undoStack = useRef<Widget[][]>([]);
  const redoStack = useRef<Widget[][]>([]);
  const [historyLen, setHistoryLen] = useState(0); // drives canUndo/canRedo display
  const widgetsRef = useRef(iface.widgets);
  widgetsRef.current = iface.widgets;

  function pushUndo() {
    undoStack.current = [...undoStack.current.slice(-49), [...widgetsRef.current]];
    redoStack.current = [];
    setHistoryLen(undoStack.current.length);
  }

  function undo() {
    if (undoStack.current.length === 0) return;
    const prev = undoStack.current[undoStack.current.length - 1];
    redoStack.current = [widgetsRef.current, ...redoStack.current];
    undoStack.current = undoStack.current.slice(0, -1);
    setHistoryLen(undoStack.current.length);
    setIface(f => ({ ...f, widgets: prev }));
    setDirty(true);
  }

  function redo() {
    if (redoStack.current.length === 0) return;
    const next = redoStack.current[0];
    undoStack.current = [...undoStack.current, widgetsRef.current];
    redoStack.current = redoStack.current.slice(1);
    setHistoryLen(undoStack.current.length);
    setIface(f => ({ ...f, widgets: next }));
    setDirty(true);
  }

  const handleWidgetsChange = useCallback((widgets: Widget[]) => {
    pushUndo();
    setIface(f => ({ ...f, widgets }));
    setDirty(true);
  }, []);

  const handleWidgetChange = useCallback((updated: Widget) => {
    pushUndo();
    setIface(f => ({ ...f, widgets: f.widgets.map(w => w.id === updated.id ? updated : w) }));
    setDirty(true);
  }, []);

  const handleIfaceChange = useCallback((updated: Interface) => {
    setIface(updated);
    setDirty(true);
  }, []);

  // ── Align / distribute ─────────────────────────────────────────────────────

  function doAlign(mode: string) {
    const aligned = alignWidgets(iface.widgets, selectedIds, mode);
    handleWidgetsChange(aligned);
  }

  function doDistribute(axis: 'h' | 'v') {
    const distributed = distributeWidgets(iface.widgets, selectedIds, axis);
    handleWidgetsChange(distributed);
  }

  // ── Insert static widget ───────────────────────────────────────────────────

  function insertWidget(type: string) {
    setShowInsertMenu(false);
    const [defW, defH] = DEFAULT_SIZES[type] ?? [150, 60];
    const newWidget: Widget = {
      id: genWidgetId(),
      type,
      x: 40,
      y: 40,
      w: defW,
      h: defH,
      signals: [],
      options: type === 'textlabel' ? { label: 'Label' } : {},
    };
    handleWidgetsChange([...iface.widgets, newWidget]);
    setSelectedIds([newWidget.id]);
  }

  // ── Save / export / import ─────────────────────────────────────────────────

  async function handleSave() {
    setSaving(true);
    setError(null);
    try {
      const xml = serializeInterface(iface);
      const url = iface.id
        ? `/api/v1/interfaces/${encodeURIComponent(iface.id)}`
        : '/api/v1/interfaces';
      const method = iface.id ? 'PUT' : 'POST';
      const res = await fetch(url, {
        method,
        headers: { 'Content-Type': 'application/xml' },
        body: xml,
      });
      if (!res.ok) throw new Error(`Save failed (${res.status}): ${await res.text()}`);
      if (method === 'POST') {
        const json = await res.json();
        setIface(f => ({ ...f, id: json.id }));
      }
      setDirty(false);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setSaving(false);
    }
  }

  function handleExport() {
    const xml = serializeInterface(iface);
    const blob = new Blob([xml], { type: 'application/xml' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${iface.name.replace(/[^a-z0-9]/gi, '_')}.xml`;
    a.click();
    URL.revokeObjectURL(url);
  }

  function handleImport() {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = '.xml,application/xml,text/xml';
    input.onchange = async () => {
      const file = input.files?.[0];
      if (!file) return;
      try {
        setIface(parseInterface(await file.text()));
        setSelectedIds([]);
        setDirty(true);
        undoStack.current = [];
        redoStack.current = [];
        setHistoryLen(0);
      } catch (err) {
        setError(`Import failed: ${err instanceof Error ? err.message : err}`);
      }
    };
    input.click();
  }

  function handleLeave() {
    if (dirty && !confirm('You have unsaved changes. Leave without saving?')) return;
    onDone();
  }

  // ── Keyboard shortcuts ─────────────────────────────────────────────────────

  function handleKeyDown(e: KeyboardEvent) {
    const target = e.target as Element;
    if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') return;

    if ((e.key === 'Delete' || e.key === 'Backspace') && selectedIds.length > 0) {
      pushUndo();
      setIface(f => ({ ...f, widgets: f.widgets.filter(w => !selectedIds.includes(w.id)) }));
      setSelectedIds([]);
      setDirty(true);
      return;
    }
    if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) { e.preventDefault(); undo(); return; }
    if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) { e.preventDefault(); redo(); return; }
    if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
      e.preventDefault();
      setSelectedIds(iface.widgets.map(w => w.id));
      return;
    }
    // Arrow nudge
    if (['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(e.key) && selectedIds.length > 0) {
      e.preventDefault();
      const step = e.shiftKey ? (snapGrid || 10) * 2 : (snapGrid || 1);
      const dx = e.key === 'ArrowLeft' ? -step : e.key === 'ArrowRight' ? step : 0;
      const dy = e.key === 'ArrowUp'   ? -step : e.key === 'ArrowDown'  ? step : 0;
      handleWidgetsChange(iface.widgets.map(w =>
        selectedIds.includes(w.id) ? { ...w, x: w.x + dx, y: w.y + dy } : w
      ));
    }
  }

  const selectedWidget = selectedIds.length === 1
    ? iface.widgets.find(w => w.id === selectedIds[0]) ?? null
    : null;

  const canUndo = historyLen > 0;
  const canRedo = redoStack.current.length > 0;
  const multiSelected = selectedIds.length > 1;

  return (
    <div class="edit-mode-layout" onKeyDown={handleKeyDown} tabIndex={-1}>
      {/* ── Toolbar ── */}
      <header class="toolbar">
        <div class="toolbar-left">
          <span class="app-name">uopi</span>
          <span class="edit-badge">Edit</span>
          <input
            class="iface-name-input"
            value={iface.name}
            onInput={(e) => handleIfaceChange({ ...iface, name: (e.target as HTMLInputElement).value })}
          />
          {dirty && <span class="dirty-dot" title="Unsaved changes">●</span>}
        </div>

        <div class="toolbar-center edit-toolbar-tools">
          {/* Undo / redo */}
          <button class="toolbar-btn" onClick={undo} disabled={!canUndo} title="Undo (Ctrl+Z)">↩</button>
          <button class="toolbar-btn" onClick={redo} disabled={!canRedo} title="Redo (Ctrl+Y)">↪</button>

          <span class="toolbar-sep" />

          {/* Snap to grid */}
          <label class="snap-toggle" title="Snap to grid">
            <input
              type="checkbox"
              checked={showSnapGrid}
              onChange={(e) => {
                const on = (e.target as HTMLInputElement).checked;
                setShowSnapGrid(on);
                setSnapGrid(on ? 10 : 0);
              }}
            />
            <span>Grid</span>
          </label>

          {/* Insert static widget */}
          <div class="toolbar-dropdown">
            <button class="toolbar-btn" onClick={() => setShowInsertMenu(m => !m)}>+ Insert ▾</button>
            {showInsertMenu && (
              <div class="toolbar-dropdown-menu" onClick={() => setShowInsertMenu(false)}>
                {STATIC_WIDGET_TYPES.map(({ type, label }) => (
                  <button key={type} class="ctx-item" onClick={() => insertWidget(type)}>{label}</button>
                ))}
              </div>
            )}
          </div>

          {/* Align/distribute — only when ≥2 selected */}
          {selectedIds.length >= 2 && (
            <div class="align-toolbar">
              <span class="toolbar-sep" />
              <button class="toolbar-btn icon-only" onClick={() => doAlign('left')}   title="Align left">⬜◻◻</button>
              <button class="toolbar-btn icon-only" onClick={() => doAlign('centerH')} title="Align center H">◻⬜◻</button>
              <button class="toolbar-btn icon-only" onClick={() => doAlign('right')}  title="Align right">◻◻⬜</button>
              <button class="toolbar-btn icon-only" onClick={() => doAlign('top')}    title="Align top">⬆</button>
              <button class="toolbar-btn icon-only" onClick={() => doAlign('centerV')} title="Align center V">↕</button>
              <button class="toolbar-btn icon-only" onClick={() => doAlign('bottom')} title="Align bottom">⬇</button>
              {selectedIds.length >= 3 && (
                <div>
                  <button class="toolbar-btn icon-only" onClick={() => doDistribute('h')} title="Distribute H">↔</button>
                  <button class="toolbar-btn icon-only" onClick={() => doDistribute('v')} title="Distribute V">↕↕</button>
                </div>
              )}
            </div>
          )}

          {error && <span class="parse-error" title={error}>Save error</span>}
        </div>

        <div class="toolbar-right">
          <button class="toolbar-btn" onClick={handleImport}>Import</button>
          <button class="toolbar-btn" onClick={handleExport}>Export</button>
          <button class="toolbar-btn toolbar-btn-primary" onClick={handleSave} disabled={saving}>
            {saving ? 'Saving…' : 'Save'}
          </button>
          <button class="toolbar-btn" onClick={handleLeave}>✕ Close</button>
        </div>
      </header>

      {/* ── Three-panel body ── */}
      <div class="edit-body">
        <SignalTree />
        <EditCanvas
          iface={iface}
          selectedIds={selectedIds}
          onSelect={setSelectedIds}
          onChange={handleWidgetsChange}
          snapGrid={snapGrid}
        />
        <PropertiesPane
          selected={selectedWidget}
          multiCount={multiSelected ? selectedIds.length : 0}
          iface={iface}
          onChange={handleWidgetChange}
          onIfaceChange={handleIfaceChange}
        />
      </div>
    </div>
  );
}
