/**
 * Config Schema — canonical shape and defaults for storyboard.config.json.
 *
 * Every consumer of storyboard.config.json should import `getConfig()` to get
 * a fully defaulted, validated config object. New keys added here are safe for
 * existing projects — they always have defaults.
 *
 * @module configSchema
 */

/**
 * @typedef {object} PasteRule
 * @property {string} match    — regex string tested against pasted URLs
 * @property {string} widget   — widget type to create (e.g. "figma-embed", "link-preview")
 * @property {Record<string, string>} [propsMap] — static props merged into widget props
 */

/**
 * @typedef {object} CanvasTerminalConfig
 * @property {boolean} [resizable]   — whether terminal widgets can be resized (default false)
 * @property {number}  [defaultWidth]  — default width for new terminal widgets
 * @property {number}  [defaultHeight] — default height for new terminal widgets
 * @property {number}  [fontSize]    — terminal font size
 * @property {string}  [fontFamily]  — terminal font family
 * @property {string}  [prompt]      — shell prompt string
 * @property {string|null} [startupCommand] — skip welcome screen: "copilot", "shell", or a custom command. null shows welcome.
 * @property {object|null} [defaultStartupSequence] — sequence of steps to run after terminal opens
 */

/**
 * @typedef {object} CanvasZoomConfig
 * @property {number} min  — minimum zoom percentage (default 10)
 * @property {number} max  — maximum zoom percentage (default 250)
 * @property {number} step — zoom increment/decrement step (default 10)
 */

/**
 * @typedef {object} CanvasConfig
 * @property {PasteRule[]} pasteRules       — URL→widget conversion rules (evaluated in order, first match wins)
 * @property {{ embedBehavior: string, ghGuard: string }} github — GitHub-specific embed settings
 * @property {CanvasTerminalConfig} [terminal] — terminal widget settings
 * @property {Record<string, CanvasAgentConfig>} [agents] — per-agent overrides
 * @property {CanvasZoomConfig} [zoom] — zoom min/max/step settings
 * @property {CanvasMessagingConfig} [messaging] — hub messaging settings
 */

/**
 * @typedef {object} CanvasMessagingConfig
 * @property {number} [conversationTimeoutMinutes] — timeout for idle conversations (default: 30)
 * @property {number} [messageTokenTimeoutSeconds] — timeout for message tokens (default: 120)
 * @property {number} [pollIntervalMs] — agent polling interval (default: 2000)
 * @property {number} [maxMessagesPerHub] — max messages per hub channel (default: 200)
 */

/**
 * @typedef {object} CanvasAgentConfig
 * @property {string}  [label]         — display label
 * @property {string}  [icon]          — icon name
 * @property {string}  [startupCommand] — command to run on startup
 * @property {string}  [postStartup]   — command sent after agent readiness (e.g. "/allow-all on")
 * @property {string}  [readinessSignal] — tmux pane text that signals the agent is ready (fragile, prefer readinessFile)
 * @property {string}  [resumeCommand]  — full command template to resume a session, with `{id}` placeholder (e.g. `"copilot --resume={id} --agent terminal-agent"`). Used both for auto-resume on cold restart (with `{id}` substituted) and for the interactive "Browse existing sessions" flow (binary derived, `--resume` appended).
 * @property {boolean} [readinessFile]  — use a file-based SessionStart hook for readiness (writes --settings with hook, polls for signal file)
 * @property {string}  [sessionIdEnv]   — env var exposed in the agent's SessionStart hook payload that holds its session id (e.g. "COPILOT_AGENT_SESSION_ID"). When set, the server captures the id per-widget so cold restarts can auto-resume.
 * @property {string}  [sessionStateDir] — directory where the agent stores per-session state, used to pre-flight `--resume` (e.g. "~/.copilot/session-state"). Pass `null` to skip the fs check (UUID-only validation).
 * @property {string}  [sessionStateGlob] — alternative to sessionStateDir for agents that store sessions under a per-project subdir, with `{id}` placeholder (e.g. "~/.claude/projects/*‍/{id}.jsonl").
 * @property {boolean} [resumeFallback]  — when true (default), the resume command is shell-chained with `|| <startupCommand>` so a runtime resume failure falls through to a fresh session instead of leaving the widget with a dead terminal. Set false to opt out.
 * @property {boolean} [resizable]     — override terminal resizability for this agent
 * @property {number}  [defaultWidth]  — override default width
 * @property {number}  [defaultHeight] — override default height
 */

/**
 * @typedef {object} HotPoolConfig
 * @property {boolean} [enabled]       — enable/disable all pools (default: true)
 * @property {boolean} [verbose]       — log to Vite terminal (default: false)
 * @property {number}  [default_pool_size]     — default baseline per pool (default: 1)
 * @property {number}  [default_max_pool_size] — default surge cap per pool (default: 3)
 * @property {boolean} [load_balancer] — enable auto-scaling (default: true)
 * @property {number}  [load_balancer_cooldown_mins] — minutes idle before scale-down (default: 10)
 * @property {Record<string, { pool_size?: number, max_pool_size?: number }>} [pools] — per-pool overrides (terminal, prompt, copilot, claude, codex).
 */

/**
 * @typedef {object} CommandPaletteConfig
 * @property {string[]} providers — provider IDs to enable
 * @property {string}   ranking   — result ranking strategy
 * @property {CommandPaletteSection[]} [sections] — declarative palette sections
 */

/**
 * @typedef {object} CommandPaletteSection
 * @property {string}  id       — unique section identifier
 * @property {string}  [title]  — section heading in the palette
 * @property {string}  [type]   — "tool-menu" for sub-page entries
 * @property {string}  [label]  — display label (for tool-menu entries)
 * @property {string[]} [keywords] — search keywords
 * @property {CommandPaletteSectionItem[]} [items]   — static entries
 * @property {string}  [source] — dynamic data source: "canvases" | "prototypes" | "stories"
 * @property {string}  [order]  — ordering: "recent" | "alphabetical" | "recent-changes"
 * @property {number}  [limit]  — max items from dynamic source
 * @property {CommandPaletteOption[]} [options] — sub-page options (for tool-menu type)
 */

/**
 * @typedef {object} CommandPaletteSectionItem
 * @property {string} type     — "link" | "action"
 * @property {string} label    — display text
 * @property {string} [url]    — navigation URL (for links)
 * @property {string} [action] — command action ID (for actions)
 * @property {string[]} [keywords] — search keywords
 */

/**
 * @typedef {object} CommandPaletteOption
 * @property {string} label   — display text
 * @property {string} action  — command action ID
 * @property {*}      [value] — action payload
 */

/**
 * @typedef {object} CustomerModeConfig
 * @property {boolean} enabled        — master toggle for customer mode
 * @property {boolean} hideChrome     — hides all toolbars (except canvas tools), branchbar, cmd+k, cmd+.
 * @property {boolean} hideHomepage   — removes the storyboard homepage (leaves empty page)
 * @property {string}  protoHomepage  — internal /path that replaces homepage; redirects from / and /workspace
 * @property {string}  canvasHomepage — canvas id (e.g. "landing") that replaces homepage. Mutually exclusive with `protoHomepage` — when both are set, this wins.
 */

/**
 * @typedef {object} ThemeDefinition
 * @property {string} label                       — display name shown in theme switchers
 * @property {Record<string,string>} [attrs]      — companion data attributes written onto each surface element when this theme is active (e.g. Primer's `data-color-mode`, `data-light-theme`). Storyboard core writes no companion attrs by default; consumers (Primer-using repos, etc.) add them via their own storyboard.config.json.
 */

/**
 * @typedef {object} SurfaceDefinition
 * @property {string}  label   — display label shown in the "Apply theme to" sub-menu
 * @property {boolean} sync    — default value for whether this surface follows the global theme (true) or stays light (false). Persisted per-user in localStorage.
 */

/**
 * @typedef {object} ThemesConfig
 * @property {Record<string, ThemeDefinition>}   themes    — registry of available themes. Theme IDs are the values written to `data-sb-<surface>-theme`. Storyboard core ships only `light` and `dark`; consumers add more (e.g. `dark_dimmed`, `light_colorblind`).
 * @property {Record<string, SurfaceDefinition>} surfaces  — registry of theming surfaces. Each surface ID is matched against `[data-sb-surface="<id>"]` elements in the DOM. The runtime writes `data-sb-<id>-theme` onto those elements plus the companion `attrs` from the active theme. Core ships `prototype`, `canvas`, `toolbar`, `codeBoxes`.
 * @property {string}  [default]  — default global theme ID. Use `"system"` to follow OS preference.
 */

/**
 * @typedef {object} StoryboardConfig
 * @property {string}   [customDomain]
 * @property {string}   [prodDomain] — production host (and optional base path) for the deployed env, e.g. "dfosco.github.io/storyboard/"
 * @property {number}   [port] — fixed dev server port for this storyboard instance. When set, `storyboard dev` always tries this exact port and exits if it's taken (no fallback).
 * @property {string}   [devDomainColor] — CSS color for the BranchBar in local dev (default: blue)
 * @property {{ owner: string, name: string }} [repository]
 * @property {{ enabled: boolean }} [modes]
 * @property {{ discussions: { category: string } }} [comments]
 * @property {Record<string, boolean>} [plugins]
 * @property {{ enabled?: boolean, features?: Record<string, boolean>, partials?: Array }} [workshop]
 * @property {Record<string, boolean>} [featureFlags]
 * @property {{ hide?: string[] }} [ui]
 * @property {object}   [toolbar]
 * @property {CanvasConfig} [canvas]
 * @property {HotPoolConfig} [hotPool]
 * @property {CommandPaletteConfig} [commandPalette]
 * @property {CustomerModeConfig} [customerMode]
 * @property {ThemesConfig} [themes]
 */

/** Built-in paste rules shipped with storyboard. */
export const builtinPasteRules = [
  {
    id: 'figma',
    match: 'https?://(?:www\\.)?figma\\.com/',
    widget: 'figma-embed',
    propsMap: { width: 800, height: 450 },
  },
]

/** Default config values. Every key here is safe to access without null checks. */
export const configDefaults = {
  customDomain: '',
  prodDomain: '',
  port: 0,
  devDomainColor: '',
  repository: { owner: '', name: '' },
  modes: { enabled: false },
  comments: { discussions: { category: 'Comments' } },
  plugins: {},
  workshop: {
    enabled: false,
    // Each `create<X>` flag controls whether the corresponding item appears
    // in the workspace Create-Artifact menu. Default true — set to false in
    // a consumer's storyboard.config.json to hide that item.
    features: {
      createPrototype: true,
      createCanvas: true,
      createComponent: true,
      createFlow: true,
      createPage: true,
      createObject: true,
      createRecord: true,
    },
  },
  featureFlags: {},
  ui: {},
  toolbar: {},
  canvas: {
    pasteRules: builtinPasteRules,
    github: {
      embedBehavior: 'link-preview', // "link-preview" | "rich-embed"
      ghGuard: 'copy',               // "copy" | "link" | "off"
    },
    zoom: {
      min: 10,
      max: 250,
      step: 10,
      gestures: true, // when false, disables cmd+wheel and pinch-to-zoom; +/- buttons still work
    },
    scroll: {
      axis: 'both', // "both" | "vertical" | "horizontal" | "none"
    },
    terminal: {
      resizable: false,
      defaultWidth: 800,
      defaultHeight: 450,
    },
    messaging: {
      conversationTimeoutMinutes: 30,
      messageTokenTimeoutSeconds: 120,
      pollIntervalMs: 2000,
      maxMessagesPerHub: 200,
    },
  },
  commandPalette: {
    providers: ['prototypes', 'flows', 'canvases', 'pages'],
    ranking: 'frecency',
    sections: [],
  },
  customerMode: {
    enabled: false,
    hideChrome: false,
    hideHomepage: false,
    protoHomepage: '',
    canvasHomepage: '',
  },
  theming: {
    // Minimal default theme registry. Consumers add more in their own
    // storyboard.config.json — see README#theming. IDs starting with "dark"
    // are picked up by the Tailwind `dark:` variant via prefix match.
    themes: {
      light: { label: 'Light', attrs: {} },
      dark:  { label: 'Dark',  attrs: {} },
    },
    // Surface registry. Each surface ID is looked up in the DOM as
    // `[data-sb-surface="<id>"]`. The runtime writes
    // `data-sb-<id>-theme="<resolved>"` plus the active theme's companion
    // `attrs` onto every matching element.
    surfaces: {
      prototype: { label: 'Prototype',  sync: true  },
      canvas:    { label: 'Canvas',     sync: true  },
      toolbar:   { label: 'Tools',      sync: false },
      codeBoxes: { label: 'Code boxes', sync: true  },
    },
    // Default global theme. "system" follows OS prefers-color-scheme.
    default: 'system',
  },
}

/**
 * Deep-merge helper that replaces arrays instead of concatenating.
 * Objects are recursively merged; all other values are overwritten.
 */
function mergeConfig(defaults, overrides) {
  if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) {
    return overrides ?? defaults
  }
  const result = { ...defaults }
  for (const key of Object.keys(overrides)) {
    const val = overrides[key]
    if (val === undefined) continue
    if (Array.isArray(val)) {
      // Arrays replace (e.g. pasteRules, providers) — no concat
      result[key] = val
    } else if (val && typeof val === 'object' && !Array.isArray(val) && typeof defaults[key] === 'object' && !Array.isArray(defaults[key])) {
      result[key] = mergeConfig(defaults[key], val)
    } else {
      result[key] = val
    }
  }
  return result
}

/**
 * Return a fully defaulted config by merging user-provided values over defaults.
 * Safe to call with an empty object or undefined — returns full defaults.
 *
 * @param {Partial<StoryboardConfig>} [raw={}]
 * @returns {StoryboardConfig}
 */
export function getConfig(raw = {}) {
  return mergeConfig(configDefaults, raw)
}

/**
 * Return a copy of the bare defaults (no user overrides).
 * @returns {StoryboardConfig}
 */
export function getConfigDefaults() {
  return JSON.parse(JSON.stringify(configDefaults))
}
