import type {
  AgentInfo,
  AgentDiagnostic,
  AgentFixIntent,
  AgentCliEnvPrefs,
  AgentModelPrefs,
  AgentTestRequest,
  AppVersionInfo,
  AppVersionResponse,
  AudioKind,
  ChatAttachment,
  ChatCommentAttachment,
  ChatCommentSelectionKind,
  ChatMessageFeedback,
  ChatMessageFeedbackRating,
  ChatMessageFeedbackReasonCode,
  ChatMessage,
  ConnectionTestKind,
  ConnectionTestProtocol,
  ConnectionTestRequest,
  ConnectionTestResponse,
  Conversation,
  DeployConfigResponse,
  DeployProjectFileResponse,
  DesignSystemDetail,
  DesignSystemFileDetail,
  DesignSystemFileSummary,
  DesignSystemGenerationJob,
  DesignSystemPackageAudit,
  DesignSystemPackageAuditIssue,
  DesignSystemProvenance,
  DesignSystemRevision,
  DesignSystemRevisionJobRequest,
  DesignSystemRevisionStatus,
  DesignSystemSummary,
  LiveArtifact,
  LiveArtifactDetailResponse,
  LiveArtifactListResponse,
  LiveArtifactPreview,
  LiveArtifactRefreshLogEntry,
  LiveArtifactRefreshStatus,
  LiveArtifactStatus,
  LiveArtifactSummary,
  MediaAspect,
  OrbitRunSummary,
  OrbitStatusResponse,
  ProjectDeploymentsResponse,
  ProviderTestRequest,
  PersistedAgentEvent,
  ProviderModelOption,
  ProviderModelsKind,
  ProviderModelsRequest,
  ProviderModelsResponse,
  Project,
  ProjectLocationPrefs,
  ProjectPlatform,
  ProjectBrowserWorkspaceTab,
  ProjectTabsState,
  PreviewCommentMember,
  PreviewAnnotationStyle,
  PreviewCommentSelectionKind,
  PreviewComment,
  PreviewCommentAttachment,
  PreviewCommentStatus,
  PreviewCommentTarget,
  PreviewCommentUpsertRequest,
  PreviewVisualMarkKind,
  ProjectDisplayStatus,
  ProjectFile,
  ProjectFolder,
  ProjectFileKind,
  ProjectKind,
  ProjectMetadata,
  ProjectTemplate,
  RenameProjectFileResponse,
  CodexPetSummary,
  CodexPetsResponse,
  SyncCommunityPetsRequest,
  SyncCommunityPetsResponse,
  SkillDetail,
  SkillSummary,
  InstallInput,
  InstallSkillResponse,
  InstallDesignSystemResponse,
  UninstallResponse,
  UpdateDeployConfigRequest,
} from '@open-design/contracts';

export type {
  CloudflarePagesDeploySelection,
  CloudflarePagesDeploymentInfo,
  CloudflarePagesZonesResponse,
  ChatCommentSelectionKind,
  OrbitRunSummary,
  OrbitStatusResponse,
  ProjectLocation,
  PreviewCommentMember,
  PreviewAnnotationStyle,
  PreviewCommentSelectionKind,
  PreviewVisualMarkKind,
} from '@open-design/contracts';

export type ExecMode = 'daemon' | 'api';
export type ApiProtocol = 'anthropic' | 'openai' | 'azure' | 'google' | 'ollama' | 'senseaudio' | 'aihubmix';

export type LiveArtifactTabId = `live:${string}`;
// Tab ids are arbitrary strings; the template-literal members below are
// conventions FileWorkspace's `.ws-body` switch keys off (`live:` → live
// artifact viewer, `chat:` → Side Chat tab). See `SideChatTabId` below.
export type ProjectWorkspaceTabId =
  | string
  | LiveArtifactTabId
  | SideChatTabId
  | TerminalTabId;

export function liveArtifactTabId(artifactId: string): LiveArtifactTabId {
  return `live:${artifactId}`;
}

export function isLiveArtifactTabId(tabId: string): tabId is LiveArtifactTabId {
  return tabId.startsWith('live:') && tabId.length > 'live:'.length;
}

export function liveArtifactIdFromTabId(tabId: LiveArtifactTabId): string {
  return tabId.slice('live:'.length);
}

// Side Chat tab convention. A `chat:<conversationId>` tab mounts a secondary
// ChatPane bound to that conversation (Stage 2), mirroring the `live:` scheme
// above. The conversation is a normal conversation, so it also shows up in the
// header ConversationsMenu.
export type SideChatTabId = `chat:${string}`;

export function sideChatTabId(conversationId: string): SideChatTabId {
  return `chat:${conversationId}`;
}

export function isSideChatTabId(tabId: string): tabId is SideChatTabId {
  return tabId.startsWith('chat:') && tabId.length > 'chat:'.length;
}

export function conversationIdFromSideChatTabId(tabId: SideChatTabId): string {
  return tabId.slice('chat:'.length);
}

// Terminal tab convention. A `terminal:<terminalId>` tab mounts an xterm.js
// surface bound to a daemon PTY session (Stage 3), mirroring the `chat:` and
// `live:` schemes above. The terminal id is the session id returned by
// `POST /api/projects/:id/terminals`.
export type TerminalTabId = `terminal:${string}`;

export function terminalTabId(terminalId: string): TerminalTabId {
  return `terminal:${terminalId}`;
}

export function isTerminalTabId(tabId: string): tabId is TerminalTabId {
  return tabId.startsWith('terminal:') && tabId.length > 'terminal:'.length;
}

export function terminalIdFromTabId(tabId: TerminalTabId): string {
  return tabId.slice('terminal:'.length);
}

export type LiveArtifactViewerTab =
  | 'preview'
  | 'code'
  | 'data'
  | 'refresh-history';

export interface ProjectFileWorkspaceEntry {
  kind: 'file';
  tabId: string;
  name: string;
  file: ProjectFile;
}

export interface LiveArtifactWorkspaceEntry {
  kind: 'live-artifact';
  tabId: LiveArtifactTabId;
  artifactId: string;
  projectId: string;
  title: string;
  slug: string;
  status: LiveArtifactStatus;
  refreshStatus: LiveArtifactRefreshStatus;
  pinned: boolean;
  preview: LiveArtifactPreview;
  hasDocument: boolean;
  updatedAt: string;
  lastRefreshedAt?: string;
}

export type ProjectWorkspaceEntry = ProjectFileWorkspaceEntry | LiveArtifactWorkspaceEntry;

export function liveArtifactSummaryToWorkspaceEntry(
  liveArtifact: LiveArtifactSummary,
): LiveArtifactWorkspaceEntry {
  const entry: LiveArtifactWorkspaceEntry = {
    kind: 'live-artifact',
    tabId: liveArtifactTabId(liveArtifact.id),
    artifactId: liveArtifact.id,
    projectId: liveArtifact.projectId,
    title: liveArtifact.title,
    slug: liveArtifact.slug,
    status: liveArtifact.status,
    refreshStatus: liveArtifact.refreshStatus,
    pinned: liveArtifact.pinned,
    preview: liveArtifact.preview,
    hasDocument: liveArtifact.hasDocument,
    updatedAt: liveArtifact.updatedAt,
  };
  if (liveArtifact.lastRefreshedAt) entry.lastRefreshedAt = liveArtifact.lastRefreshedAt;
  return entry;
}

export interface LiveArtifactPreviewRequest {
  projectId: string;
  artifactId: string;
  previewUrl: string;
}

export interface MediaProviderCredentials {
  apiKey: string;
  baseUrl: string;
  model?: string;
  apiKeyConfigured?: boolean;
  apiKeyTail?: string;
  source?: string;
}

export interface ApiProtocolConfig {
  apiKey: string;
  baseUrl: string;
  model: string;
  apiVersion?: string;
  apiProviderBaseUrl?: string | null;
  /** SenseAudio BYOK only — default image model the daemon-side
   *  `generate_image` tool uses when the LLM doesn't pass one. Carries
   *  one of the SenseAudio image model ids (`senseaudio-image-2.0-260319`,
   *  `senseaudio-image-1.0-260319`, `doubao-seedream-5-0-260128`). Stored
   *  per-protocol so flipping between BYOK tabs doesn't reset the
   *  SenseAudio image-model choice. */
  byokImageModel?: string;
  /** BYOK only — default video model the daemon-side `generate_video` tool
   *  uses when the LLM doesn't pass one. Carries an `aihubmix-` prefixed
   *  video model id. Stored per-protocol, like byokImageModel. */
  byokVideoModel?: string;
  /** BYOK only — default speech (TTS) model for the daemon-side generate_speech
   *  tool (`aihubmix-` prefixed). Stored per-protocol, like byokImageModel. */
  byokSpeechModel?: string;
  /** BYOK only — default speech voice id for the generate_speech tool. */
  byokSpeechVoice?: string;
}

// Per-CLI model + reasoning the user picked in the model menu. Each agent
// keeps its own slot so flipping between Codex and Gemini doesn't reset the
// other one's choice. Missing entries fall back to the agent's first
// declared model (`'default'` — let the CLI pick).
export type AgentModelChoice = AgentModelPrefs;
export type AgentCliEnvConfig = AgentCliEnvPrefs;

export type AppTheme = 'system' | 'light' | 'dark';

// One animation row inside a pet's sprite atlas. Mirrors the Codex
// hatch-pet `animation-rows.md` reference — `id` lets the overlay map
// interaction states (idle / hover / drag direction / waiting) to the
// correct row regardless of how many rows a particular pet ships.
export interface PetAtlasRowDef {
  // Row index in the atlas, top to bottom.
  index: number;
  // Stable id used by the interaction state machine and i18n keys.
  // Matches the canonical Codex row ids: 'idle', 'running-right', etc.
  id: string;
  // Number of leading frames the row uses. The remaining cells in the
  // row are expected to be transparent / empty.
  frames: number;
  // Frames-per-second the row plays at. Per-row tuning lets idle stay
  // calm while running-* / jumping feel snappy.
  fps: number;
}

// Sprite atlas layout — when present on `PetCustom`, `imageUrl` is the
// full grid (cols × rows) instead of a single horizontal strip. The
// overlay then picks one row to render based on user interaction.
export interface PetAtlasLayout {
  cols: number;
  rows: number;
  // Per-row playback definitions. Order matches the row index.
  rowsDef: PetAtlasRowDef[];
}

// User-tunable companion that floats over the workspace. The full catalog
// lives in `components/pet/pets.ts`; this shape is what gets persisted to
// localStorage so we can roundtrip a customized pet across reloads.
export interface PetCustom {
  // Display name shown in the overlay tooltip and settings card.
  name: string;
  // Single emoji or 1–2 char glyph rendered as the sprite. We render text,
  // not an image, so any user keyboard input works without uploads.
  glyph: string;
  // Hex color used as the overlay halo accent.
  accent: string;
  // Short greeting line shown in the speech bubble on hover / first wake.
  greeting: string;
  // Optional uploaded sprite. Stored as a base64 data URL so it survives
  // localStorage roundtrips without depending on daemon storage. When
  // present, the overlay / rail / settings render the image instead of
  // the text glyph. Cleared when the user picks "Remove image".
  imageUrl?: string;
  // Legacy single-row spritesheet config — when `frames > 1` we treat
  // `imageUrl` as a horizontal strip of `frames` equally-sized cells and
  // step through them at `fps` frames per second using a CSS `steps()`
  // animation, matching the codex-pets-react sheet shape (e.g.
  // tater/spritesheet). `frames === 1` (default) renders the image as a
  // single static cell with the same gentle float animation as the
  // emoji glyph. Ignored when `atlas` is set.
  frames?: number;
  fps?: number;
  // Optional sprite atlas layout. When present, `imageUrl` is the full
  // atlas grid and the overlay renders the active row chosen by the
  // interaction state machine (idle / hover → wave / drag → run / etc.).
  atlas?: PetAtlasLayout;
}

export interface NotificationsConfig {
  // Master switch for the completion sound. Default false — first-run users
  // hear nothing until they opt in.
  soundEnabled: boolean;
  // Sound id played when a turn ends with `runStatus === 'succeeded'`.
  successSoundId: string;
  // Sound id played when a turn ends with `runStatus === 'failed'`.
  failureSoundId: string;
  // Master switch for the browser Notification API banner. Default false.
  desktopEnabled: boolean;
}

export interface OrbitConfig {
  enabled: boolean;
  /** Local 24-hour clock time in HH:mm format. */
  time: string;
  /** Optional skill id from the examples gallery where scenario === "orbit". */
  templateSkillId?: string | null;
}

export interface PetConfig {
  // True once the user has explicitly picked a pet (built-in or custom).
  // Until then, the entry view shows an "adopt" callout to drive discovery.
  adopted: boolean;
  // Floating overlay visibility — the wake/tuck toggle lives in Settings
  // and on the overlay itself. Defaults to true after adoption.
  enabled: boolean;
  // 'custom' or a built-in id from `BUILT_IN_PETS`. We tolerate unknown ids
  // (e.g. older builds) and fall back to the first built-in.
  petId: string;
  // Free-form custom pet definition. Always present so the customize panel
  // has stable state to bind against, even when a built-in is active.
  custom: PetCustom;
}

export interface AppConfig {
  mode: ExecMode;
  apiKey: string;
  baseUrl: string;
  model: string;
  apiProtocol?: ApiProtocol;
  apiVersion?: string;
  /** SenseAudio BYOK only — default image model for the daemon-side
   *  generate_image tool. Mirrors apiProtocolConfigs.senseaudio.byokImageModel
   *  so the active protocol's value lives at the top level (consistent
   *  with how apiKey / baseUrl / model are projected onto AppConfig). */
  byokImageModel?: string;
  /** BYOK only — default video model for the daemon-side generate_video tool.
   *  Mirrors apiProtocolConfigs.<protocol>.byokVideoModel onto AppConfig. */
  byokVideoModel?: string;
  /** BYOK only — default speech model + voice for the generate_speech tool. */
  byokSpeechModel?: string;
  byokSpeechVoice?: string;
  apiProtocolConfigs?: Partial<Record<ApiProtocol, ApiProtocolConfig>>;
  /** Internal config schema/migration version for localStorage upgrades. */
  configMigrationVersion?: number;
  /** Base URL of the selected known provider; cleared once the user customizes provider fields. */
  apiProviderBaseUrl?: string | null;
  agentId: string | null;
  skillId: string | null;
  designSystemId: string | null;
  theme?: AppTheme;
  accentColor?: string;
  // True once the user has been through the welcome onboarding modal at
  // least once (saved or skipped). Bootstrap skips the auto-popup when
  // this is set so refreshing the page doesn't re-prompt.
  onboardingCompleted?: boolean;
  mediaProviders?: Record<string, MediaProviderCredentials>;
  composio?: ComposioSettings;
  // Per-CLI model picker state, keyed by agent id (e.g. `gemini`, `codex`).
  // Pre-existing configs without this field fall through to the agent's
  // declared default.
  agentModels?: Record<string, AgentModelChoice>;
  // Per-agent non-secret CLI config locations injected into detection and runs.
  agentCliEnv?: AgentCliEnvConfig;
  // Caps the upstream completion length in API mode. Defaults to 8192 when
  // unset; raise it for providers (e.g. MiMo) that allow longer responses.
  maxTokens?: number;
  // Optional Codex-style animated companion. Older configs that pre-date
  // the feature land at `undefined`, which the loader normalizes to a
  // safe default (un-adopted, hidden until the user opts in).
  pet?: PetConfig;
  // Optional task-completion sound + browser notification settings. Older
  // configs that pre-date the feature land at `undefined`, which the loader
  // normalizes to a safe default (everything off).
  notifications?: NotificationsConfig;
  // Daily connector activity digest. When enabled, the daemon runs this once
  // per day at the configured local time; defaults to 08:00.
  orbit?: OrbitConfig;
  // IDs of skills/design-systems the user has explicitly disabled.
  disabledSkills?: string[];
  disabledDesignSystems?: string[];
  // Anonymous install identifier for telemetry. Generated locally the first
  // time a user opts in via Settings → Privacy. `null` after the user
  // explicitly opts out (or rotates "Delete my data"); `undefined` when the
  // daemon has not assigned an anonymous id yet.
  installationId?: string | null;
  // Unix-millis timestamp recording that the first-run privacy prompt was
  // resolved. This is independent from installationId so Delete my data can
  // rotate or clear the anonymous id without re-opening the consent banner.
  privacyDecisionAt?: number | null;
  // Privacy preferences governing what (if anything) is shipped to the
  // PostHog / Langfuse telemetry endpoints. `metrics` and `content`
  // default ON (set by `DEFAULT_CONFIG.telemetry` in state/config.ts) so
  // the onboarding funnel actually captures the first-run events the
  // user hasn't had a chance to consent to yet; the post-onboarding
  // disclosure modal explains this and Settings → Privacy is the
  // one-click opt-out. `artifactManifest` stays off until the user
  // turns it on explicitly. A daemon-stored override always wins over
  // these client defaults — once the user picks a value the modal /
  // PrivacySection persist it through `syncConfigToDaemon`.
  telemetry?: TelemetryConfig;
  customInstructions?: string;
  projectLocations?: ProjectLocationPrefs[];
  defaultProjectLocationId?: string | null;
}

export interface TelemetryConfig {
  metrics?: boolean;
  content?: boolean;
  artifactManifest?: boolean;
}

export interface ComposioSettings {
  apiKey?: string;
  apiKeyConfigured?: boolean;
  apiKeyTail?: string;
}

export type AgentEvent = PersistedAgentEvent;

export interface LiveArtifactEventItem {
  id: number;
  event: Extract<AgentEvent, { kind: 'live_artifact' | 'live_artifact_refresh' }>;
}

export type ChatMessageFeedbackChange =
  | ({
      rating: ChatMessageFeedbackRating;
    } & Partial<
      Pick<
        ChatMessageFeedback,
        'reasonCodes' | 'customReason' | 'reasonsSubmittedAt'
      >
    >)
  | null;

export type {
  ChatAttachment,
  ChatCommentAttachment,
  ChatMessage,
  ChatMessageFeedbackRating,
  ChatMessageFeedbackReasonCode,
};

export type {
  ProjectBrowserWorkspaceTab,
};

export interface Artifact {
  identifier: string;
  artifactType?: string;
  title: string;
  html: string;
  savedUrl?: string;
}

export interface ExamplePreview {
  source: 'skill' | 'design-system';
  id: string;
  title: string;
  html: string;
}

export interface AgentModelOption {
  id: string;
  label: string;
}

export type Surface = 'web' | 'image' | 'video' | 'audio';

export interface PromptTemplateSource {
  repo: string;
  license: string;
  author?: string;
  url?: string;
}

export interface PromptTemplateSummary {
  id: string;
  surface: 'image' | 'video';
  title: string;
  summary: string;
  category: string;
  tags?: string[];
  model?: string;
  aspect?: MediaAspect;
  previewImageUrl?: string;
  previewVideoUrl?: string;
  source: PromptTemplateSource;
}

export interface PromptTemplateDetail extends PromptTemplateSummary {
  prompt: string;
}

export type {
  AgentInfo,
  AgentDiagnostic,
  AgentFixIntent,
  AgentTestRequest,
  AppVersionInfo,
  AppVersionResponse,
  AudioKind,
  ConnectionTestKind,
  ConnectionTestProtocol,
  ConnectionTestRequest,
  ConnectionTestResponse,
  Conversation,
  DeployConfigResponse,
  DeployProjectFileResponse,
  DesignSystemDetail,
  DesignSystemFileDetail,
  DesignSystemFileSummary,
  DesignSystemGenerationJob,
  DesignSystemPackageAudit,
  DesignSystemPackageAuditIssue,
  DesignSystemProvenance,
  DesignSystemRevision,
  DesignSystemRevisionJobRequest,
  DesignSystemRevisionStatus,
  DesignSystemSummary,
  LiveArtifact,
  LiveArtifactDetailResponse,
  LiveArtifactListResponse,
  LiveArtifactRefreshLogEntry,
  LiveArtifactRefreshStatus,
  LiveArtifactStatus,
  LiveArtifactSummary,
  MediaAspect,
  ProjectDeploymentsResponse,
  Project,
  ProjectPlatform,
  PreviewComment,
  PreviewCommentAttachment,
  PreviewCommentStatus,
  PreviewCommentTarget,
  PreviewCommentUpsertRequest,
  ProjectDisplayStatus,
  ProjectFile,
  ProjectFolder,
  ProjectFileKind,
  ProjectKind,
  ProjectMetadata,
  ProjectTemplate,
  RenameProjectFileResponse,
  ProviderTestRequest,
  ProviderModelOption,
  ProviderModelsKind,
  ProviderModelsRequest,
  ProviderModelsResponse,
  CodexPetSummary,
  CodexPetsResponse,
  SyncCommunityPetsRequest,
  SyncCommunityPetsResponse,
  SkillDetail,
  SkillSummary,
  InstallInput,
  InstallSkillResponse,
  InstallDesignSystemResponse,
  UninstallResponse,
  UpdateDeployConfigRequest,
};

export type OpenTabsState = ProjectTabsState;
