// Tiny in-app event bus for session metadata changes that need to propagate
// across hooks (e.g. AI rename arriving via WS in the session view needs to
// also refresh the sidebar's session list).

import type {
  ArenaState,
  BattleShape,
  Chat,
  ContestantShape,
  CrossExaminationShape,
  ErrorReason,
  HtmlArtifactState,
  MarkdownArtifactState,
  OrchestratorState,
  Project,
  Session,
} from '@/api/types';
import type { Attachment } from '@/lib/attachments';

export interface SessionRenamedEvent {
  type: 'session_renamed';
  session_id: string;
  name: string;
}

export interface ProjectCreatedEvent {
  type: 'project_created';
  project: Project;
}

export interface ProjectDeletedEvent {
  type: 'project_deleted';
  project_id: string;
}

export interface SessionCreatedEvent {
  type: 'session_created';
  session: Session;
  project_id: string;
}

export interface SessionDeletedEvent {
  type: 'session_deleted';
  session_id: string;
  project_id: string;
}

export interface SessionUpdatedEvent {
  type: 'session_updated';
  session_id: string;
  project_id: string;
  name: string;
  updated_at: string;
}

export interface SessionWorkspaceUpdatedEvent {
  type: 'session_workspace_updated';
  session_id: string;
  // Legacy bare array OR the new envelope — useWorkspacePanes normalizes both
  // via toWorkspaceState.
  workspace_panes:
    | import('@/api/types').WorkspacePane[]
    | import('@/api/types').WorkspaceState;
}

export interface SessionLoadedEvent {
  type: 'session_loaded';
  session_id: string;
  project_id: string;
}

export interface OpenFileInBrowserEvent {
  type: 'open_file_in_browser';
  path: string; // project-relative
}

export interface AttachChatFileEvent {
  type: 'attach_chat_file';
  attachment: Omit<Attachment, 'id'>;
}

export interface OpenChatInActivePaneEvent {
  type: 'open_chat_in_active_pane';
  chat_id: string;
}

// Open a whole chat in a fresh split pane (vs the active pane). Emitted by the
// ChatTabBar tab context menu ("Open in new pane") and by MessageBubble.fork()
// so a fork lands beside the original. useWorkspacePanes subscribes.
export interface OpenChatInNewPaneEvent {
  type: 'open_chat_in_new_pane';
  chat_id: string;
}

// v1.14.x-html-artifact-panes: ActionRow's "Open in pane" button emits one of
// these; useWorkspacePanes subscribes and inserts the corresponding artifact
// pane (or focuses an existing one keyed by message_id).
export interface OpenMarkdownArtifactPaneEvent {
  type: 'open_markdown_artifact_pane';
  state: MarkdownArtifactState;
}

export interface OpenHtmlArtifactPaneEvent {
  type: 'open_html_artifact_pane';
  state: HtmlArtifactState;
}

// Client-side event fired by the sidebar Settings button when a session is
// currently mounted. Session.tsx subscribes and calls
// panesHook.toggleSettingsPane() (open on first click, close on second).
// Sidebar handles the no-session case by navigating to /settings directly.
export interface OpenSettingsPaneEvent {
  type: 'open_settings_pane';
}

export interface SessionArchivedEvent {
  type: 'session_archived';
  session_id: string;
  project_id: string;
}

export interface ChatCreatedEvent {
  type: 'chat_created';
  chat: Chat;
  session_id: string;
}

export interface ChatUpdatedEvent {
  type: 'chat_updated';
  chat_id: string;
  session_id: string;
  name: string | null;
  updated_at: string;
}

export interface ChatArchivedEvent {
  type: 'chat_archived';
  chat_id: string;
  session_id: string;
}

export interface ChatUnarchivedEvent {
  type: 'chat_unarchived';
  chat: Chat;
}

export interface ChatDeletedEvent {
  type: 'chat_deleted';
  chat_id: string;
  session_id: string;
}

export interface ProjectArchivedEvent {
  type: 'project_archived';
  project_id: string;
}

export interface ProjectUnarchivedEvent {
  type: 'project_unarchived';
  project: Project;
}

export interface ProjectUpdatedEvent {
  type: 'project_updated';
  project_id: string;
  name: string;
}

// v1.8 mobile-tabs: broadcast on user channel from inference.ts so any device
// subscribed sees a chat working/idle/error. Frontend stores per-chat; panes
// derive their dot from pane.activeChatId.
// v1.8.2: optional `reason` carries a machine-readable code when status is
// 'error'. UI prefers reason for inline error rendering.
export interface ChatStatusEvent {
  type: 'chat_status';
  chat_id: string;
  status: 'streaming' | 'tool_running' | 'waiting_for_input' | 'idle' | 'error';
  at: string;
  reason?: ErrorReason;
}

export interface RefetchMessagesEvent {
  type: 'refetch_messages';
}

// git-diff-panel Phase 1: emitted client-side to trigger a panel refresh.
// Not a WS frame — no @boocode/contracts change required.
export interface GitDiffRefreshEvent {
  type: 'git_diff_refresh';
}

// Orchestrator: emitted client-side to open an orchestrator pane.
// useWorkspacePanes subscribes and inserts the pane (or focuses an existing one).
// placement carries the surface context: 'new' (+ menu) or 'split' (split-pane
// menu). addOrchestratorPane appends in both cases; the hint is available for
// future positional differentiation.
export interface OpenOrchestratorPaneEvent {
  type: 'open_orchestrator_pane';
  state: OrchestratorState;
  placement?: 'new' | 'split';
}

// Orchestrator: emitted by the Workflow button on ChatInput (or "New Orchestrator"
// menu items) to request the flow launcher dialog. Carries the current pane's
// project and the placement context ('new' from the + menu, 'split' from the
// split-pane menu) so the resulting open_orchestrator_pane can be placed correctly.
export interface OpenFlowLauncherEvent {
  type: 'open_flow_launcher';
  project_id: string;
  placement?: 'new' | 'split';
}

// Orchestrator: run-level frames forwarded from the coder user channel by
// useCoderUserEvents. OrchestratorPane subscribes to update its roster/report.
export interface FlowRunStartedEvent {
  type: 'flow_run_started';
  run_id: string;
  flow_name: string;
  band: 'small' | 'medium' | 'large';
  steps: Array<{
    step_id: string;
    agent: string;
    kind: 'agent' | 'code';
    chat_id: string;
    label: string;
  }>;
}

export interface FlowRunStepUpdatedEvent {
  type: 'flow_run_step_updated';
  run_id: string;
  step_id: string;
  status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'cancelled';
  run_status?: 'running' | 'completed' | 'failed' | 'cancelled';
  report?: string;
}

// Arena: emitted by "New Arena" menu items to request the launcher dialog.
export interface OpenArenaLauncherEvent {
  type: 'open_arena_launcher';
  project_id: string;
  placement?: 'new' | 'split';
}

// Arena: emitted after a battle is created to open/focus the arena pane.
export interface OpenArenaPaneEvent {
  type: 'open_arena_pane';
  state: ArenaState;
  placement?: 'new' | 'split';
}

// Arena: battle lifecycle frames forwarded from the coder user channel.
export interface BattleStartedEvent {
  type: 'battle_started';
  battle_id: string;
  battle_type: 'coding' | 'qa';
  prompt: string;
  contestants: Array<{ id: string; identity: string; model: string; lane: 'local' | 'cloud' }>;
}

export interface ContestantUpdatedEvent {
  type: 'contestant_updated';
  battle_id: string;
  contestant_id: string;
  status?: 'queued' | 'running' | 'done' | 'error';
  duration_ms?: number;
  tokens_per_sec?: number;
  battle_status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
  delta?: string;
  error?: string;
}

export interface BattleUpdatedEvent {
  type: 'battle_updated';
  battle_id: string;
  status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
  winner_contestant_id?: string | null;
  analysis_ready?: boolean;
  cross_exam_id?: string;
}

// Collision warning: published when the BooCoder detects multiple agents
// editing the same file concurrently. Advisory only — writes are not blocked.
export interface CollisionWarningEvent {
  type: 'collision_warning';
  file_path: string;
  agents: string[];
}

// Inter-agent message: one agent step sends a live message to another step
// in the same flow run.
export interface AgentMessageEvent {
  type: 'agent_message';
  from_agent: string;
  to_agent: string;
  content: string;
}

// Re-export arena API shapes for consumers that need the full battle data.
export type { BattleShape, ContestantShape, CrossExaminationShape };

export type SessionEvent =
  | SessionRenamedEvent
  | ProjectCreatedEvent
  | ProjectDeletedEvent
  | SessionCreatedEvent
  | SessionDeletedEvent
  | SessionUpdatedEvent
  | SessionWorkspaceUpdatedEvent
  | SessionLoadedEvent
  | OpenFileInBrowserEvent
  | AttachChatFileEvent
  | OpenChatInActivePaneEvent
  | OpenChatInNewPaneEvent
  | OpenMarkdownArtifactPaneEvent
  | OpenHtmlArtifactPaneEvent
  | OpenSettingsPaneEvent
  | SessionArchivedEvent
  | ChatCreatedEvent
  | ChatUpdatedEvent
  | ChatArchivedEvent
  | ChatUnarchivedEvent
  | ChatDeletedEvent
  | ProjectArchivedEvent
  | ProjectUnarchivedEvent
  | ProjectUpdatedEvent
  | ChatStatusEvent
  | RefetchMessagesEvent
  | GitDiffRefreshEvent
  | OpenOrchestratorPaneEvent
  | FlowRunStartedEvent
  | FlowRunStepUpdatedEvent
  | OpenFlowLauncherEvent
  | OpenArenaLauncherEvent
  | OpenArenaPaneEvent
  | BattleStartedEvent
  | ContestantUpdatedEvent
  | BattleUpdatedEvent
  | CollisionWarningEvent
  | AgentMessageEvent;
type Listener = (event: SessionEvent) => void;

const listeners = new Set<Listener>();

export const sessionEvents = {
  emit(event: SessionEvent) {
    for (const listener of listeners) {
      try {
        listener(event);
      } catch {
        // swallow — one bad listener shouldn't break others
      }
    }
  },
  subscribe(listener: Listener): () => void {
    listeners.add(listener);
    return () => {
      listeners.delete(listener);
    };
  },
};
