import { h, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks';
import { initLocalState } from './lib/localstate';
import { logicEngine, ensureArrayDecls } from './lib/logic';
import { getWidgetCmdStore, type WidgetCmd } from './lib/widgetCommands';
import type { Interface, Widget, SignalRef } from './lib/types';
import TextView from './widgets/TextView';
import TextLabel from './widgets/TextLabel';
import Gauge from './widgets/Gauge';
import BarH from './widgets/BarH';
import BarV from './widgets/BarV';
import Led from './widgets/Led';
import MultiLed from './widgets/MultiLed';
import SetValue from './widgets/SetValue';
import Toggle from './widgets/Toggle';
import Button from './widgets/Button';
import PlotWidget from './widgets/PlotWidget';
import ImageWidget from './widgets/ImageWidget';
import LinkWidget from './widgets/LinkWidget';
import ConfigSelect from './widgets/ConfigSelect';
import Container from './widgets/Container';
import TableWidget from './widgets/TableWidget';
import ContextMenu from './ContextMenu';
import InfoPanel from './InfoPanel';
import SplitLayout from './SplitLayout';
import LogicDialogs from './LogicDialogs';
import { centreInside, widgetTab, tabPanes } from './lib/containers';

const COMPONENTS: Record<string, any> = {
  textview:  TextView,
  textlabel: TextLabel,
  gauge:     Gauge,
  barh:      BarH,
  barv:      BarV,
  led:       Led,
  multiled:  MultiLed,
  setvalue:  SetValue,
  toggle:    Toggle,
  button:    Button,
  plot:      PlotWidget,
  image:     ImageWidget,
  link:      LinkWidget,
  configselect: ConfigSelect,
  container: Container,
  table:     TableWidget,
};

// Container panes are decorative frames placed behind other widgets; render them
// first so they paint underneath.
function renderOrder(widgets: Widget[]): Widget[] {
  const containers = widgets.filter(w => w.type === 'container');
  const rest = widgets.filter(w => w.type !== 'container');
  return [...containers, ...rest];
}

interface CtxState {
  visible: boolean;
  x: number;
  y: number;
  signal: SignalRef | null;
}

// Wraps a single view-mode widget so panel logic (action.widget) can drive it:
// `hidden` removes it from the view, `disabled` dims it and blocks interaction
// via a transparent overlay (which still surfaces the right-click menu). Plot
// pause/clear are handled inside PlotWidget itself.
function WidgetView({ widget, children, onContextMenu }: {
  widget: Widget;
  children: any;
  onContextMenu: (e: MouseEvent) => void;
  key?: string;
}) {
  const [cmd, setCmd] = useState<WidgetCmd>(() => getWidgetCmdStore(widget.id).get());
  useEffect(() => getWidgetCmdStore(widget.id).subscribe(setCmd), [widget.id]);
  if (cmd.hidden) return null;
  if (!cmd.disabled) return children;
  return (
    <Fragment>
      {children}
      <div
        class="widget-disable-overlay"
        style={`left:${widget.x}px;top:${widget.y}px;width:${widget.w}px;height:${widget.h}px;`}
        onContextMenu={onContextMenu}
      />
    </Fragment>
  );
}

interface Props {
  iface: Interface | null;
  onNavigate?: (interfaceId: string) => void;
  timeRange?: { start: string; end: string } | null;
}

export default function Canvas({ iface, onNavigate, timeRange }: Props) {
  const [ctxMenu, setCtxMenu] = useState<CtxState>({
    visible: false, x: 0, y: 0, signal: null,
  });
  const [infoSignal, setInfoSignal] = useState<SignalRef | null>(null);
  // View-mode collapse state for collapsible container panes (ephemeral, by id).
  const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
  // View-mode active-tab state for tabbed container panes (ephemeral, by id).
  const [activeTabs, setActiveTabs] = useState<Map<string, number>>(() => new Map());

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

  function selectTab(id: string, i: number) {
    setActiveTabs(prev => {
      const next = new Map(prev);
      next.set(id, i);
      return next;
    });
  }

  // Instantiate this panel's local state variables from their initial values.
  // ensureArrayDecls auto-declares any array referenced by logic nodes that
  // are not yet declared as state vars (backward-compat migration).
  useEffect(() => {
    initLocalState(ensureArrayDecls(iface?.logic, iface?.statevars ?? []));
  }, [iface?.id, iface?.statevars, iface?.logic]);

  // Activate panel logic for the live view; tear it down on unmount/panel switch.
  useEffect(() => {
    logicEngine.load(iface?.logic);
    return () => logicEngine.clear();
  }, [iface?.id, iface?.logic]);

  function onCtxMenu(e: MouseEvent, widget: Widget) {
    e.preventDefault();
    e.stopPropagation();
    setCtxMenu({ visible: true, x: e.clientX, y: e.clientY, signal: widget.signals[0] ?? null });
  }

  function closeCtxMenu() {
    setCtxMenu(c => ({ ...c, visible: false }));
  }

  function handleInfo() {
    if (ctxMenu.signal) setInfoSignal(ctxMenu.signal);
  }

  if (!iface) {
    return (
      <div class="canvas-container">
        <div class="placeholder">
          <p>Select an interface from the left panel, or import one.</p>
        </div>
      </div>
    );
  }

  // Plot panel: plots fill the viewport arranged in a recursive split layout.
  if (iface.kind === 'plot' && iface.layout) {
    const byId = new Map(iface.widgets.map(w => [w.id, w]));
    return (
      <div class="canvas-container">
        <div class="plot-panel-view">
          <SplitLayout
            layout={iface.layout}
            renderLeaf={(wid) => {
              const widget = byId.get(wid);
              if (!widget) return <div class="plot-pane-empty">missing plot</div>;
              return (
                <PlotWidget
                  widget={widget}
                  onContextMenu={(e: MouseEvent) => onCtxMenu(e, widget)}
                  timeRange={timeRange}
                />
              );
            }}
          />
        </div>

        <ContextMenu
          {...ctxMenu}
          onClose={closeCtxMenu}
          onInfo={handleInfo}
        />

        {infoSignal && (
          <InfoPanel signal={infoSignal} onClose={() => setInfoSignal(null)} />
        )}

        <LogicDialogs />
      </div>
    );
  }

  // Collapsible panes currently in the collapsed state — their contents are hidden.
  const collapsedPanes = iface.widgets.filter(
    w => w.type === 'container' && w.options['collapsible'] === 'true' && collapsed.has(w.id),
  );
  // Tabbed panes — widgets inside that aren't on the active tab are hidden.
  const tPanes = tabPanes(iface.widgets);

  return (
    <div class="canvas-container">
      <div class="canvas-area canvas-view-bare" style={`width:${iface.w}px;height:${iface.h}px;`}>
        {renderOrder(iface.widgets).map(widget => {
          // Hide widgets that fall inside a currently-collapsed container pane.
          const hidden = collapsedPanes.some(c => c.id !== widget.id && centreInside(widget, c));
          // Hide widgets inside a tabbed pane that aren't on its active tab.
          const tabHidden = tPanes.some(c =>
            c.id !== widget.id && centreInside(widget, c) && widgetTab(widget) !== (activeTabs.get(c.id) ?? 0),
          );
          if (hidden || tabHidden) return null;
          const Comp = COMPONENTS[widget.type];
          const inner = Comp
            ? <Comp
                widget={widget}
                onContextMenu={(e: MouseEvent) => onCtxMenu(e, widget)}
                onNavigate={onNavigate}
                timeRange={timeRange}
                collapsed={collapsed.has(widget.id)}
                onToggleCollapse={() => toggleCollapse(widget.id)}
                activeTab={activeTabs.get(widget.id) ?? 0}
                onSelectTab={(i: number) => selectTab(widget.id, i)}
              />
            : (
              <div
                class="unknown-widget"
                style={`left:${widget.x}px;top:${widget.y}px;width:${widget.w}px;height:${widget.h}px;`}
                title={`Unknown widget type: ${widget.type}`}
                onContextMenu={(e: MouseEvent) => onCtxMenu(e, widget)}
              >
                <span class="unknown-label">{widget.type}</span>
              </div>
            );
          return (
            <WidgetView
              key={widget.id}
              widget={widget}
              onContextMenu={(e: MouseEvent) => onCtxMenu(e, widget)}
            >
              {inner}
            </WidgetView>
          );
        })}
      </div>

      <ContextMenu
        {...ctxMenu}
        onClose={closeCtxMenu}
        onInfo={handleInfo}
      />

      {infoSignal && (
        <InfoPanel signal={infoSignal} onClose={() => setInfoSignal(null)} />
      )}

      <LogicDialogs />
    </div>
  );
}
