/**
 * Sync the pi model catalog into Bloby.
 *
 * Reads upstream pi's `packages/ai/src/models.generated.ts` (vendored as a
 * sibling checkout at ../pi-main) and emits a filtered, alphabetised TS file
 * the wizard imports. Run on demand when you want to pull in newer model IDs.
 *
 *   npm run sync:pi-models
 *
 * The OUTPUT (`supervisor/harnesses/pi/models-catalog.generated.ts`) is the
 * file that ships with bloby. The pi-main checkout is dev-only and is not
 * required at runtime.
 */
import fs from 'fs';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const PI_MODELS_PATH = path.join(REPO_ROOT, 'pi-main', 'packages', 'ai', 'src', 'models.generated.ts');
const OUTPUT_PATH = path.join(REPO_ROOT, 'supervisor', 'harnesses', 'pi', 'models-catalog.generated.ts');

if (!fs.existsSync(PI_MODELS_PATH)) {
  console.error(`✗  pi catalog not found at ${PI_MODELS_PATH}`);
  console.error('   Clone or download earendil-works/pi into ../pi-main first.');
  process.exit(1);
}

// Bloby sub-provider id → pi MODELS top-level key.
// Sub-providers without a pi mapping (ollama, lm-studio, custom) stay dynamic.
const PROVIDER_MAP: Record<string, string> = {
  google: 'google',
  deepseek: 'deepseek',
  groq: 'groq',
  xai: 'xai',
  cerebras: 'cerebras',
  mistral: 'mistral',
  'openai-api': 'openai',
  'anthropic-api': 'anthropic',
  // openrouter intentionally skipped — 270+ entries is wizard-hostile. Stays dynamic.
};

// Drop noisy variants: date-suffixed previews, custom-tool forks, live-audio,
// model-snapshot aliases. We keep the canonical id (e.g. "gemini-3.1-pro-preview")
// and the rolling "*-latest" handles, which is what users actually want to pick.
const HIDDEN_PATTERNS: RegExp[] = [
  /-\d{2}-\d{2}$/,             // ...-04-17
  /-\d{2}-\d{4}$/,             // ...-09-2025
  /-\d{4}-\d{2}-\d{2}$/,       // ...-2025-08-07
  /-\d{8}$/,                   // ...-20250805
  /-customtools$/,
  /-live-/,
  /-search-preview/,
  /-realtime/,
  /-audio/,
  /-tts$/,
  /-transcribe$/,
  /^text-embedding-/,
  /^omni-moderation/,
  /^dall-e/,
  /^whisper/,
  /^gpt-3\.5/,                 // legacy
  /^gpt-4-/,                   // legacy variants of plain gpt-4
  /^o1-/,                      // dated o1 variants
  /^gemma-/,                   // separate open-weight family — better served via Ollama
];

function isHidden(id: string): boolean {
  return HIDDEN_PATTERNS.some((re) => re.test(id));
}

async function loadPiModels(): Promise<Record<string, Record<string, { id: string; name?: string }>>> {
  // pi's file has `import type { Model } from "./types.js"` — strip that line
  // so the module loads without needing pi's full types graph at sync time.
  const raw = fs.readFileSync(PI_MODELS_PATH, 'utf-8');
  const sanitised = raw
    .replace(/^import type[^;]+;\s*$/m, '')
    .replace(/ satisfies Model<[^>]+>/g, '');

  // Drop into a temp file next to the original so any relative paths in errors
  // still make sense, then dynamic-import via file:// URL.
  const tmpPath = `${PI_MODELS_PATH}.bloby-sync.tmp.ts`;
  fs.writeFileSync(tmpPath, sanitised);
  try {
    const mod = await import(pathToFileURL(tmpPath).href);
    return (mod as any).MODELS;
  } finally {
    fs.rmSync(tmpPath, { force: true });
  }
}

function versionScore(id: string): number {
  // Cheap "newer first" ordering: parse the first major.minor pair we find.
  const m = id.match(/(\d+)(?:\.(\d+))?/);
  if (!m) return 0;
  const major = parseInt(m[1], 10);
  const minor = m[2] ? parseInt(m[2], 10) : 0;
  return major * 1000 + minor;
}

async function main() {
  const MODELS = await loadPiModels();

  const out: Record<string, { id: string; label: string }[]> = {};
  let total = 0;
  for (const [blobyId, piKey] of Object.entries(PROVIDER_MAP)) {
    const provider = MODELS[piKey];
    if (!provider) {
      console.warn(`!  no pi provider "${piKey}" (mapped from bloby "${blobyId}")`);
      continue;
    }
    const entries: { id: string; label: string }[] = [];
    for (const [id, m] of Object.entries(provider)) {
      if (isHidden(id)) continue;
      entries.push({ id, label: m?.name || id });
    }
    // Newest version first; alphabetical inside the same version.
    entries.sort((a, b) => {
      const dv = versionScore(b.id) - versionScore(a.id);
      return dv !== 0 ? dv : a.id.localeCompare(b.id);
    });
    out[blobyId] = entries;
    total += entries.length;
  }

  const banner =
    `// Auto-generated by scripts/sync-pi-models.ts — DO NOT EDIT MANUALLY.\n` +
    `// Source: earendil-works/pi @ packages/ai/src/models.generated.ts\n` +
    `// Last sync: ${new Date().toISOString()}\n` +
    `\n`;
  const body =
    `export interface PiCatalogModel { id: string; label: string }\n` +
    `export const PI_MODELS_CATALOG: Record<string, PiCatalogModel[]> = ${JSON.stringify(out, null, 2)};\n`;
  fs.writeFileSync(OUTPUT_PATH, banner + body);

  console.log(`✓ wrote ${OUTPUT_PATH}`);
  console.log(`  ${Object.keys(out).length} providers, ${total} models`);
  for (const [k, v] of Object.entries(out)) {
    console.log(`  · ${k}: ${v.length}`);
  }
}

main().catch((err) => {
  console.error('✗ sync failed:', err);
  process.exit(1);
});
