/**
 * W7: Sync newly-discovered local models into opencode's config.
 *
 * Prefers `~/.config/opencode/opencode.jsonc` (hand-curated) over `.json`
 * (legacy auto-generated). Scans all existing provider sections for known
 * model IDs, then adds any NEW registry models to a `==boocode-auto==`
 * section. Existing hand-curated sections are never modified. The legacy
 * `boocode-local` section (if present) is removed.
 */
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { homedir } from 'node:os';
import { fetchRegistryModels } from './provider-snapshot.js';

const OPENCODE_CONFIG_DIR = join(homedir(), '.config', 'opencode');

function resolveConfigPath(): string {
  const jsoncPath = join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
  try {
    // Prefer .jsonc (hand-curated categories) over .json (auto-generated).
    readFileSync(jsoncPath);
    return jsoncPath;
  } catch {
    return join(OPENCODE_CONFIG_DIR, 'opencode.json');
  }
}

export interface OpencodeProviderConfig {
  enabled?: boolean;
  npm?: string;
  name?: string;
  options?: { baseURL?: string; [key: string]: unknown };
  models?: Record<string, { name?: string }>;
}

export interface OpencodeConfig {
  provider?: Record<string, OpencodeProviderConfig>;
  [key: string]: unknown;
}

const AUTO_SECTION_KEY = '==boocode-auto==';

/**
 * Read the current opencode config (preferring .jsonc over .json), scan all
 * existing provider sections for known model IDs, then add any NEW registry
 * models to a `==boocode-auto==` section. Existing hand-curated sections are
 * never modified. The legacy `boocode-local` section (if present) is removed.
 *
 * Idempotent — re-running drops no hand entries and only appends to
 * `==boocode-auto==`.
 */
export async function syncOpencodeConfig(
  gatewayUrl: string,
  log: { warn: (obj: unknown, msg: string) => void; info: (obj: unknown, msg: string) => void },
): Promise<OpencodeConfig | null> {
  const configPath = resolveConfigPath();

  let config: OpencodeConfig = {};
  try {
    config = JSON.parse(readFileSync(configPath, 'utf8')) as OpencodeConfig;
  } catch {
    // File missing or invalid — start fresh.
  }

  if (!config.provider) config.provider = {};

  // Collect every model ID already present in any provider section.
  const knownIds = new Set<string>();
  for (const section of Object.values(config.provider)) {
    if (section.models) {
      for (const id of Object.keys(section.models)) knownIds.add(id);
    }
  }

  // Remove legacy boocode-local (replaced by ==boocode-auto== + hand sections).
  delete config.provider['boocode-local'];

  // Discover new models from the registry.
  const registryModels = await fetchRegistryModels();
  const newModels = registryModels.filter((m) => !knownIds.has(m.id));

  if (newModels.length > 0) {
    const autoSection = config.provider[AUTO_SECTION_KEY] ?? {} as OpencodeProviderConfig;
    if (!autoSection.models) autoSection.models = {};
    for (const m of newModels) {
      autoSection.models[m.id] = { name: m.label };
    }
    config.provider[AUTO_SECTION_KEY] = {
      enabled: true,
      npm: '@ai-sdk/openai-compatible',
      name: 'Auto (local registry)',
      options: { baseURL: `${gatewayUrl}/v1` },
      ...autoSection,
      models: autoSection.models,
    };
  }

  try {
    mkdirSync(dirname(configPath), { recursive: true });
    writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
    log.info(
      { path: configPath, newModelCount: newModels.length },
      'opencode-config-sync: wrote config',
    );
    return config;
  } catch (err) {
    log.warn(
      { err: err instanceof Error ? err.message : String(err), path: configPath },
      'opencode-config-sync: failed to write config',
    );
    return null;
  }
}
