import { createContext, useContext, type ReactNode } from "react";
import type { Orientation, NodeId, LeafShellSpec, Direction } from "./tree";
import type { PaneId, SshHost, SessionContext } from "../../ipc";

/**
 * Orchestration context — every piece of shared state and every operation
 * that a Pane / SplitNode / LeafPane might call. Lives in React context so
 * descendants can `useOrchestration()` without prop drilling.
 *
 * activeLeafId comes in as a plain value (re-derived by App's useState).
 * React's context is reactive: when the App-level Provider updates the
 * value, ALL consumers re-render. No Svelte-style props-don't-propagate
 * trap here.
 */
export interface Orchestration {
  // Read-only state
  activeLeafId: NodeId | null;
  /** WSL distros enumerated from `wsl.exe -l -q`. PowerShell is a separate
   *  shell kind, not in this list. */
  distros: string[];
  /** Saved SSH hosts loaded from `hosts.json`. Reactive — changes when the
   *  user edits hosts via {@link openHostManager}. */
  hosts: SshHost[];

  // Tree mutations
  split: (leafId: NodeId, orientation: Orientation) => void;
  close: (leafId: NodeId) => void;
  /** Change the shell on a leaf (WSL distro / PowerShell / SSH host).
   *  Always forces a respawn — the helper in tree.ts swaps the leaf id so
   *  the renderer remounts XtermPane. */
  setShell: (leafId: NodeId, spec: LeafShellSpec) => void;
  setLabel: (leafId: NodeId, label: string | undefined) => void;
  toggleBroadcast: (leafId: NodeId) => void;
  /** Flip the per-pane mcpAllow flag. Default-deny; chip in the pane
   *  toolbar drives this. */
  toggleMcpAllow: (leafId: NodeId) => void;

  // SSH host management
  openHostManager: () => void;

  // Per-pane orchestration
  setActive: (leafId: NodeId) => void;
  registerPaneId: (leafId: NodeId, paneId: PaneId | null) => void;
  broadcastFrom: (originLeafId: NodeId, dataB64: string) => void;
  notify: (message: string) => void;

  // Drag-header-to-swap. dragSourceId / dragOverId are reactive so leaves
  // can apply hover/source styling. The lifecycle methods are stable.
  dragSourceId: NodeId | null;
  dragOverId: NodeId | null;
  beginHeaderDrag: (leafId: NodeId) => void;
  setHeaderDragOver: (leafId: NodeId | null) => void;
  endHeaderDrag: (commitSwap: boolean) => void;

  // Per-leaf idle reporting. LeafPanes call reportLeafIdle when their
  // own quiet-state crosses the threshold; App aggregates so the titlebar
  // can show an "N idle" count without spamming toast notifications.
  reportLeafIdle: (leafId: NodeId, idle: boolean) => void;

  // Multi-window pane transfer ---------------------------------------------
  /** Pop a pane out of the current workspace into a fresh top-level window.
   *  The PTY stays alive across the move (the new window's XtermPane
   *  adopts the existing PaneId; scrollback ring is replayed). */
  moveToNewWindow: (leafId: NodeId) => void;
  /**
   * Navigate focus from within a pane's key-handler. XtermPane emits the
   * intent; LeafPane/App resolve the target leaf and set it active.
   *
   * `{ kind: "direction", dir }` — move to the spatial neighbour in that
   * direction using the same flattenLayout geometry as Ctrl+Shift+Arrow.
   * `{ kind: "index", n }` — focus the Nth leaf in DFS (walkLeaves) order,
   * 1-indexed, clamped to the leaf count (so Alt+9 with 3 panes picks pane 3).
   */
  navigateTo: (intent: NavigateIntent) => void;
  /** Returns a PaneId only for leaves that just arrived via a window
   *  transfer (so LeafPane can pass `existingPaneId` to XtermPane to skip
   *  the spawn). One-shot — App clears the entry once the pane has
   *  registered. */
  getInitialPaneIdFor: (leafId: NodeId) => PaneId | undefined;
  /** cwd -> the newest claude session's current context occupancy, for the
   *  per-pane context-fill indicator. A leaf looks itself up by `leaf.cwd`;
   *  absent for non-claude / unmatched panes. Polled by App. */
  paneContext: Map<string, SessionContext>;
}

/** Discriminated intent emitted by XtermPane's key handler. App resolves
 *  the actual target leaf from the current tree without XtermPane needing
 *  to know anything about layout geometry or leaf ordering. */
export type NavigateIntent =
  | { kind: "direction"; dir: Direction }
  | { kind: "index"; n: number };

const OrchestrationContext = createContext<Orchestration | null>(null);

export function OrchestrationProvider({
  value,
  children,
}: {
  value: Orchestration;
  children: ReactNode;
}) {
  return (
    <OrchestrationContext.Provider value={value}>
      {children}
    </OrchestrationContext.Provider>
  );
}

export function useOrchestration(): Orchestration {
  const orch = useContext(OrchestrationContext);
  if (!orch) {
    throw new Error(
      "useOrchestration() must be called inside <OrchestrationProvider>",
    );
  }
  return orch;
}
