/**
 * Sync the boocode-local provider into Pi's config file.
 *
 * Pi (~/.pi/agent/models.json) defines custom OpenAI-compatible providers as
 * `providers.<id> = { baseUrl, api, apiKey, models: [{ id, name, ... }] }`.
 * This writes/updates a `boocode-local` entry pointing at the BooCoder local
 * gateway with the composite local model ids, so Pi can target every machine
 * in the llama-providers registry (same identity story as opencode, D-6).
 *
 * Merge semantics: other providers are untouched; within boocode-local,
 * per-model contextWindow/maxTokens/cost overrides on existing entries are
 * preserved (we only own id/name and the provider-level routing fields).
 */
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 PI_MODELS_FILE = join(homedir(), '.pi', 'agent', 'models.json');

interface PiModelEntry {
  id: string;
  name: string;
  contextWindow?: number;
  maxTokens?: number;
  cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
  [key: string]: unknown;
}

export interface PiProviderConfig {
  baseUrl?: string;
  api?: string;
  apiKey?: string;
  compat?: Record<string, unknown>;
  models?: PiModelEntry[];
  [key: string]: unknown;
}

export interface PiModelsConfig {
  providers?: Record<string, PiProviderConfig>;
  [key: string]: unknown;
}

// Conservative defaults for llama-swap models; Pi treats these as caps, and a
// model whose real window differs can be hand-tuned — the merge preserves it.
const DEFAULT_CONTEXT_WINDOW = 131_072;
const DEFAULT_MAX_TOKENS = 32_768;
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };

/** Build the boocode-local provider entry for Pi. */
export async function buildPiProviderEntry(
  gatewayUrl: string,
  existing?: PiProviderConfig,
): Promise<PiProviderConfig> {
  const registryModels = await fetchRegistryModels();
  const prior = new Map((existing?.models ?? []).map((m) => [m.id, m]));
  return {
    ...existing,
    baseUrl: `${gatewayUrl}/v1`,
    api: 'openai-completions',
    apiKey: 'dummy',
    compat: existing?.compat ?? {
      supportsDeveloperRole: false,
      supportsReasoningEffort: false,
    },
    models: registryModels.map((m) => {
      const old = prior.get(m.id);
      return {
        contextWindow: DEFAULT_CONTEXT_WINDOW,
        maxTokens: DEFAULT_MAX_TOKENS,
        cost: ZERO_COST,
        ...old,
        id: m.id,
        name: m.label,
      };
    }),
  };
}

/**
 * Read Pi's models.json, merge the boocode-local provider, write it back.
 * Never throws — returns null on failure (logged).
 */
export async function syncPiConfig(
  gatewayUrl: string,
  log: { warn: (obj: unknown, msg: string) => void; info: (obj: unknown, msg: string) => void },
): Promise<PiModelsConfig | null> {
  let config: PiModelsConfig = {};
  try {
    config = JSON.parse(readFileSync(PI_MODELS_FILE, 'utf8')) as PiModelsConfig;
  } catch {
    // Missing or invalid — start fresh (Pi tolerates a providers-only file).
  }

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

  try {
    config.providers['boocode-local'] = await buildPiProviderEntry(
      gatewayUrl,
      config.providers['boocode-local'],
    );
    mkdirSync(dirname(PI_MODELS_FILE), { recursive: true });
    writeFileSync(PI_MODELS_FILE, JSON.stringify(config, null, 2) + '\n', 'utf8');
    log.info(
      {
        path: PI_MODELS_FILE,
        modelCount: config.providers['boocode-local'].models?.length ?? 0,
      },
      'pi-config-sync: wrote boocode-local provider',
    );
    return config;
  } catch (err) {
    log.warn(
      { err: err instanceof Error ? err.message : String(err), path: PI_MODELS_FILE },
      'pi-config-sync: failed to write config',
    );
    return null;
  }
}
