import type { ComponentType, ReactNode } from 'react';
import type { LucideIcon } from 'lucide-react';

export type LayoutGroup = {
  type: 'group';
  id: string;
  direction: 'horizontal' | 'vertical';
  children: { node: LayoutNode; size: number }[];
};

export type LayoutPanel = {
  type: 'panel';
  id: string;
  appType: string | null;
  fitContent?: boolean;
  // Per-panel content scale (1 = 100%). Lives on the node rather than in its own useDashboardState key
  // because the layout is already persisted per panel — a separate key would seed a row per panel on
  // mount (useDashboardState seeds defaults to the server), and would need its own dispatcher branch.
  zoom?: number;
  // Opaque per-panel settings owned by whichever app the panel is running. The framework stores, moves
  // and deletes it and never reads a field — the shape is the app's business (see `usePanelConfig`).
  //
  // Here for the same reason `zoom` is: the layout is the one thing already persisted per panel, and it
  // is persisted server-side, which is what makes a panel's configuration outlive the tab. It also means
  // the config is deleted exactly when the panel is, with no separate lifecycle to get wrong.
  //
  // It travels with the panel through swap and move — see `swapPanels`/`movePanel`, which carry
  // `{ appType, config }` as one unit precisely because an app's identity is both.
  config?: PanelConfig;
};

/** Opaque to the framework. Each app defines and validates its own shape. */
export type PanelConfig = Record<string, unknown>;

export type LayoutNode = LayoutGroup | LayoutPanel;

export type HomeRoot = 'home' | '~' | 'officer.dev';

export type DashboardDefinition = {
  id: string;
  name: string;
  cwd: string;
  root?: HomeRoot;
  description?: string;
  templateIdx?: number;
};

export type DashboardState = {
  key: string;
  value: LayoutNode;
  setValue: (value: LayoutNode | ((prev: LayoutNode) => LayoutNode)) => void;
  isLoaded: boolean;
};

export type AppRegistryEntry = {
  name: string;
  icon: LucideIcon;
  component: ComponentType<{ panelId: string }>;
  header?: ComponentType<{ panelId: string }>;
  provider?: ComponentType<{ panelId: string; children: ReactNode }>;
  transparent?: boolean;
  availableOnPanel?: boolean;
  // Opt out of the panel zoom control. For apps that scale badly under a CSS transform: a terminal
  // (its own font sizing, and scale blurs the glyph grid) or a remote-desktop canvas (noVNC does its
  // own autoscale, and a wrapping transform desyncs its pointer mapping).
  zoomable?: boolean;
};

export type AppRegistryMap = Record<string, AppRegistryEntry>;

// A panel supplied by a screen rather than by the registry. `PanelSlot` renders all three of these with
// `panelId`, exactly as it does the registry's — they were typed with no props at all, so a
// `components`-supplied panel was handed an id it could not see, and a screen wanting one had to reach
// for `useParams` or a channel instead. A component that takes no props is still assignable here, so the
// existing screens are unaffected.
export type PanelComponentEntry = {
  component: ComponentType<{ panelId: string }>;
  header?: ComponentType<{ panelId: string }>;
  provider?: ComponentType<{ panelId: string; children: ReactNode }>;
  onClose?: () => void;
};

export type PanelComponents = Record<string, ComponentType<{ panelId: string }> | PanelComponentEntry>;

export type EphemeralPanels = {
  layout: LayoutNode;
  components: PanelComponents;
  defaultBaseSize?: number;
  onClose?: () => void;
};
