/**
 * v2.3 resolved provider registry — single in-memory source of truth after
 * merging the hardcoded built-ins (provider-registry.ts) with the config file
 * (provider-config.ts). Mirrors Paseo's buildProviderRegistry/addDerivedProviders.
 *
 * Phase 1 scope: build + expose the resolved registry. `launchCommand` is null
 * for built-ins (the default argv is resolved at dispatch time in Phase 3) and
 * is the config `command` for custom ACP entries. No DB columns (design.md §3.3);
 * `enabled` lives in memory only.
 */
import { PROVIDERS, type ProviderDef } from "./provider-registry.js";
import { load, type CoderProvidersFile } from './provider-config.js';

export interface ResolvedProviderDef extends ProviderDef {
  id: string;
  enabled: boolean;
  isBuiltin: boolean;
  isCustomAcp: boolean;
  /** Full argv for spawn: [binary, ...args]. Null for built-ins (resolved at dispatch). */
  launchCommand: [string, ...string[]] | null;
  env: Record<string, string> | undefined;
  configLabel?: string;
  configDescription?: string;
  /** Config `models` — REPLACES the discovered/static model list when present. */
  configModels?: Array<{ id: string; label: string }>;
  /** Config `additionalModels` — MERGED on top of the resolved model list. */
  configAdditionalModels?: Array<{ id: string; label: string }>;
}

/**
 * Merge built-ins with config overrides into the resolved registry.
 * Algorithm verbatim from design.md §3.1.
 */
export function buildResolvedRegistry(
  builtins: ProviderDef[],
  config: CoderProvidersFile,
): Map<string, ResolvedProviderDef> {
  const out = new Map<string, ResolvedProviderDef>();
  const overrides = config.providers ?? {};
  const builtinNames = new Set(builtins.map((b) => b.name));

  // 1. Built-ins, applying a config override if one is present.
  for (const def of builtins) {
    const ov = overrides[def.name];
    let enabled = ov?.enabled !== false;

    // 3. boocode is always enabled; an enabled:false override is ignored + warned.
    if (def.name === 'boocode' && ov?.enabled === false) {
      console.warn("provider-config: ignoring enabled:false for built-in 'boocode' (always enabled)");
      enabled = true;
    }

    const launchCommand =
      ov?.command && ov.command.length > 0 ? (ov.command as [string, ...string[]]) : null;

    out.set(def.name, {
      ...def,
      label: ov?.label ?? def.label,
      id: def.name,
      enabled,
      isBuiltin: true,
      isCustomAcp: false,
      launchCommand,
      env: ov?.env,
      configLabel: ov?.label,
      configDescription: ov?.description,
      configModels: ov?.models,
      configAdditionalModels: ov?.additionalModels,
    });
  }

  // 2. Config ids that are not built-ins → custom ACP entries.
  for (const [id, ov] of Object.entries(overrides)) {
    if (builtinNames.has(id)) continue;
    // §2.2 rules: "New id without extends → Reject at load with log."
    if (ov.extends !== 'acp' || !ov.label || !ov.command || ov.command.length === 0) {
      console.warn(
        `provider-config: skipping custom provider '${id}' — requires extends:'acp', label, and command`,
      );
      continue;
    }
    out.set(id, {
      name: id,
      label: ov.label,
      transport: 'acp',
      modelSource: 'probe',
      id,
      enabled: ov.enabled !== false,
      isBuiltin: false,
      isCustomAcp: true,
      launchCommand: ov.command as [string, ...string[]],
      env: ov.env,
      configLabel: ov.label,
      configDescription: ov.description,
      configModels: ov.models,
      configAdditionalModels: ov.additionalModels,
    });
  }

  return out;
}

let cachedRegistry: Map<string, ResolvedProviderDef> | null = null;
let cachedPath: string | null = null;

/** Load the config file at `path`, rebuild, and cache the resolved registry. */
export function loadProviderConfig(path: string): Map<string, ResolvedProviderDef> {
  cachedPath = path;
  cachedRegistry = buildResolvedRegistry(PROVIDERS, load(path));
  return cachedRegistry;
}

/** Re-read the last-loaded config file and rebuild (Phase 4 calls this after PATCH). */
export function reloadProviderConfig(): Map<string, ResolvedProviderDef> {
  if (cachedPath == null) {
    cachedRegistry = buildResolvedRegistry(PROVIDERS, { providers: {} });
    return cachedRegistry;
  }
  return loadProviderConfig(cachedPath);
}

/** The cached resolved registry (built-ins only if nothing has been loaded yet). */
export function getResolvedRegistry(): Map<string, ResolvedProviderDef> {
  return cachedRegistry ?? buildResolvedRegistry(PROVIDERS, { providers: {} });
}

