import { h } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import { useAuth, canWrite } from './lib/auth';
import type { Interface, InterfaceListItem, Folder } from './lib/types';
import { ScopeFilter, bucketOf, type Bucket } from './lib/scope';
import ShareDialog from './ShareDialog';

interface Props {
  onLoad: (xml: string) => void;
  onSelect?: (id: string) => void;
  onEdit?: (iface?: Interface) => void;
  onNewPlot?: () => void;
  onEditId?: (id: string) => void;
  width?: number;
  collapsed: boolean;
  onToggleCollapse: () => void;
}

// Monochrome (currentColor) panel-kind glyph. Inline SVG avoids font-dependent
// emoji that may render in colour or as a missing-glyph box.
function KindIcon({ kind }: { kind?: 'panel' | 'plot' }) {
  const plot = kind === 'plot';
  return (
    <span class="iface-item-icon" title={plot ? 'Plot panel' : 'HMI panel'}>
      {plot ? (
        <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M2.5 2.5 v11 h11" />
          <path d="M4.5 11 L7 7.5 L9 9.5 L13 4.5" />
        </svg>
      ) : (
        <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <rect x="2" y="2.5" width="12" height="11" rx="1.5" />
          <path d="M4.5 6 h7" />
          <circle cx="6" cy="6" r="1.2" fill="currentColor" stroke="none" />
          <path d="M4.5 10 h7" />
          <circle cx="10" cy="10" r="1.2" fill="currentColor" stroke="none" />
        </svg>
      )}
    </span>
  );
}

export default function InterfaceList({ onLoad, onSelect, onEdit, onNewPlot, onEditId, width, collapsed, onToggleCollapse }: Props) {
  const [interfaces, setInterfaces] = useState<InterfaceListItem[]>([]);
  const [folders, setFolders] = useState<Folder[]>([]);
  const [loading, setLoading] = useState(true);
  const [share, setShare] = useState<{ id: string; name: string } | null>(null);
  const [expanded, setExpanded] = useState<Record<string, boolean>>({});
  const [dropTarget, setDropTarget] = useState<string | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const dragId = useRef<string | null>(null);
  const me = useAuth();
  const writable = canWrite(me.level);
  // Mine/Group/Global visibility filter. Default to Global so the primary
  // navigation list isn't empty for users who own no panels; legacy/public
  // panels live there.
  const [bucket, setBucket] = useState<Bucket>('global');
  const [scopeGroup, setScopeGroup] = useState('');

  // Panels in a folder ('' = root), sorted by their stored order then name.
  function panelsIn(folder: string): InterfaceListItem[] {
    return interfaces
      .filter(i => (i.folder || '') === folder)
      .sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || (a.name || a.id).localeCompare(b.name || b.id));
  }

  // Whether a panel falls in the active visibility bucket. In group mode a
  // chosen group narrows to panels shared with that specific group.
  function inScope(item: InterfaceListItem): boolean {
    if (bucketOf(item, me) !== bucket) return false;
    if (bucket === 'group' && scopeGroup) return (item.groups ?? []).includes(scopeGroup);
    return true;
  }

  // Panels in a folder that pass the active visibility filter.
  function visiblePanelsIn(folder: string): InterfaceListItem[] {
    return panelsIn(folder).filter(inScope);
  }

  // A folder is shown when it (recursively) contains any in-scope panel, so
  // empty branches collapse out under the active filter.
  function folderHasVisible(id: string): boolean {
    if (visiblePanelsIn(id).length > 0) return true;
    return folders.some(f => f.parent === id && folderHasVisible(f.id));
  }

  // Persist a new ordering for a folder's panels (also moves panels between folders).
  async function reorder(folder: string, ids: string[]) {
    const res = await fetch('/api/v1/interfaces/reorder', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ folder, ids }),
    });
    if (res.ok) refresh();
  }

  function endDrag() {
    dragId.current = null;
    setDropTarget(null);
  }

  // Drop dragged panel just before `target` within target's folder.
  function dropOnPanel(target: InterfaceListItem, e: DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    const id = dragId.current;
    if (!id || id === target.id) { endDrag(); return; }
    const dest = target.folder || '';
    const ids = panelsIn(dest).map(p => p.id).filter(pid => pid !== id);
    const idx = ids.indexOf(target.id);
    ids.splice(idx < 0 ? ids.length : idx, 0, id);
    reorder(dest, ids);
    endDrag();
  }

  // Drop dragged panel at the end of `folder` ('' = root).
  function dropInFolder(folder: string, e: DragEvent) {
    e.preventDefault();
    e.stopPropagation();
    const id = dragId.current;
    if (!id) { endDrag(); return; }
    const ids = panelsIn(folder).map(p => p.id).filter(pid => pid !== id);
    ids.push(id);
    reorder(folder, ids);
    endDrag();
  }

  function refresh() {
    setLoading(true);
    Promise.all([
      fetch('/api/v1/interfaces').then(r => r.ok ? r.json() : []),
      fetch('/api/v1/folders').then(r => r.ok ? r.json() : []),
    ])
      .then(([ifaceData, folderData]) => {
        const list = Array.isArray(ifaceData) ? ifaceData : [];
        const normalized: InterfaceListItem[] = list.map((item: any) => ({
          id: String(item.id || item.ID || ''),
          name: String(item.name || item.Name || ''),
          version: Number(item.version || item.Version || 0),
          kind: item.kind === 'plot' ? ('plot' as const) : undefined,
          owner: item.owner ? String(item.owner) : '',
          folder: item.folder ? String(item.folder) : '',
          order: Number(item.order || 0),
          perm: (item.perm as InterfaceListItem['perm']) || 'write',
          scope: item.scope ? String(item.scope) : '',
          groups: Array.isArray(item.groups) ? item.groups.map(String) : [],
        })).filter((item: InterfaceListItem) => item.id);
        setInterfaces(normalized);
        setFolders(Array.isArray(folderData) ? folderData : []);
      })
      .catch(() => {})
      .finally(() => setLoading(false));
  }

  useEffect(() => {
    refresh();
    const handleRefresh = () => refresh();
    window.addEventListener('uopi:refresh-interfaces', handleRefresh);
    return () => window.removeEventListener('uopi:refresh-interfaces', handleRefresh);
  }, []);

  function triggerImport() {
    fileInputRef.current?.click();
  }

  async function handleFileChange(ev: Event) {
    const input = ev.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;
    try {
      const xml = await file.text();
      onLoad(xml);
    } catch (err) {
      console.error('Failed to read interface file:', err);
    } finally {
      input.value = '';
    }
  }

  async function handleDelete(id: string, name: string) {
    if (!confirm(`Delete interface "${name}"?`)) return;
    const res = await fetch(`/api/v1/interfaces/${encodeURIComponent(id)}`, { method: 'DELETE' });
    if (res.ok) refresh();
  }

  async function handleClone(id: string) {
    const res = await fetch(`/api/v1/interfaces/${encodeURIComponent(id)}/clone`, { method: 'POST' });
    if (res.ok) refresh();
  }

  function handleFullscreen(id: string) {
    window.open(`?fs=${encodeURIComponent(id)}`, '_blank');
  }

  async function handleNewFolder(parent: string) {
    const name = prompt('New folder name:');
    if (!name || !name.trim()) return;
    const res = await fetch('/api/v1/folders', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: name.trim(), parent }),
    });
    if (res.ok) refresh();
  }

  async function handleDeleteFolder(id: string, name: string) {
    if (!confirm(`Delete folder "${name}"? Its panels and subfolders move up one level.`)) return;
    const res = await fetch(`/api/v1/folders/${encodeURIComponent(id)}`, { method: 'DELETE' });
    if (res.ok) refresh();
  }

  // The caller may manage sharing when they can write and either own the panel
  // or it is unmanaged (no owner recorded yet).
  function canShare(item: InterfaceListItem): boolean {
    return writable && (!item.owner || item.owner === me.user);
  }

  function toggle(id: string) {
    setExpanded(prev => ({ ...prev, [id]: !prev[id] }));
  }

  function renderPanel(item: InterfaceListItem) {
    const drag = writable && item.perm === 'write';
    return (
      <li
        key={item.id}
        class={`iface-item iface-item-clickable${dropTarget === `p:${item.id}` ? ' drop-target' : ''}`}
        draggable={drag}
        onClick={() => onSelect?.(item.id)}
        onDragStart={drag ? (e) => { dragId.current = item.id; e.dataTransfer!.effectAllowed = 'move'; } : undefined}
        onDragEnd={endDrag}
        onDragOver={(e) => { if (dragId.current) { e.preventDefault(); e.stopPropagation(); setDropTarget(`p:${item.id}`); } }}
        onDragLeave={() => setDropTarget(t => t === `p:${item.id}` ? null : t)}
        onDrop={(e) => dropOnPanel(item, e)}
      >
        <span class="iface-item-name" title={item.owner ? `Owner: ${item.owner}` : undefined}>
          <KindIcon kind={item.kind} />
          {item.name || item.id}
          {item.perm === 'read' && <span class="iface-badge" title="Read-only">ro</span>}
        </span>
        <div class="iface-item-actions" onClick={(e) => e.stopPropagation()}>
          {item.perm === 'write' && <button class="icon-btn" title="Edit" onClick={() => onEditId?.(item.id)}>✎</button>}
          <button class="icon-btn" title="Open fullscreen in new tab" onClick={() => handleFullscreen(item.id)}>⛶</button>
          {writable && <button class="icon-btn" title="Clone" onClick={() => handleClone(item.id)}>⎘</button>}
          {canShare(item) && <button class="icon-btn" title="Share" onClick={() => setShare({ id: item.id, name: item.name })}>⚹</button>}
          {item.perm === 'write' && <button class="icon-btn iface-delete" title="Delete" onClick={() => handleDelete(item.id, item.name)}>✕</button>}
        </div>
      </li>
    );
  }

  // Render a folder and everything beneath it (subfolders first, then panels).
  // Folders with no in-scope descendant panels are hidden under the filter.
  function renderFolder(folder: Folder) {
    if (!folderHasVisible(folder.id)) return null;
    const children = folders.filter(f => f.parent === folder.id);
    const panels = visiblePanelsIn(folder.id);
    const open = expanded[folder.id] !== false; // default expanded
    const canDrop = folder.perm === 'write';
    return (
      <li key={folder.id} class="iface-folder">
        <div
          class={`iface-folder-header${dropTarget === `f:${folder.id}` ? ' drop-target' : ''}`}
          onDragOver={canDrop ? (e) => { if (dragId.current) { e.preventDefault(); e.stopPropagation(); setDropTarget(`f:${folder.id}`); } } : undefined}
          onDragLeave={() => setDropTarget(t => t === `f:${folder.id}` ? null : t)}
          onDrop={canDrop ? (e) => dropInFolder(folder.id, e) : undefined}
        >
          <span class="iface-folder-name" onClick={() => toggle(folder.id)}>
            {open ? '▾' : '▸'} {folder.name}
          </span>
          {folder.perm === 'write' && (
            <div class="iface-item-actions">
              <button class="icon-btn" title="New subfolder" onClick={() => handleNewFolder(folder.id)}>＋</button>
              <button class="icon-btn iface-delete" title="Delete folder" onClick={() => handleDeleteFolder(folder.id, folder.name)}>✕</button>
            </div>
          )}
        </div>
        {open && (
          <ul class="iface-list iface-sublist">
            {children.map(renderFolder)}
            {panels.map(renderPanel)}
          </ul>
        )}
      </li>
    );
  }

  const rootFolders = folders.filter(f => !f.parent);
  const rootPanels = visiblePanelsIn('');

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

      {!collapsed && (
        <div class="panel-body">
          {writable && (
            <div class="panel-actions">
              <button class="panel-btn" onClick={() => onEdit?.()}>+ New</button>
              <button class="panel-btn" onClick={() => onNewPlot?.()}>+ Plot</button>
              <button class="panel-btn" onClick={() => handleNewFolder('')}>+ Folder</button>
              <button class="panel-btn" onClick={triggerImport}>Import</button>
              <input
                type="file"
                accept=".xml,application/xml,text/xml"
                style="display:none"
                ref={fileInputRef}
                onChange={handleFileChange}
              />
            </div>
          )}

          <ScopeFilter me={me} value={bucket} onChange={setBucket}
            group={scopeGroup} onGroupChange={setScopeGroup} />

          <div class="panel-list">
            {loading ? (
              <p class="hint">Loading…</p>
            ) : interfaces.length === 0 && folders.length === 0 ? (
              <p class="hint">No interfaces yet. Create one or import XML.</p>
            ) : (
              <ul
                class={`iface-list${dropTarget === 'f:' ? ' drop-target' : ''}`}
                onDragOver={(e) => { if (dragId.current) { e.preventDefault(); setDropTarget('f:'); } }}
                onDragLeave={() => setDropTarget(t => t === 'f:' ? null : t)}
                onDrop={(e) => dropInFolder('', e)}
              >
                {rootFolders.map(renderFolder)}
                {rootPanels.map(renderPanel)}
              </ul>
            )}
          </div>
        </div>
      )}

      {share && (
        <ShareDialog
          ifaceId={share.id}
          ifaceName={share.name}
          folders={folders}
          onClose={() => setShare(null)}
          onSaved={refresh}
        />
      )}
    </aside>
  );
}
