import type { ModelInfo } from './types';
import { getOpenCodeServerUrl } from './opencode/sidecar-server';

// The Claude harness runs the `claude` CLI, so its tiers are a fixed set.
const CLAUDE_CODE_MODELS: ModelInfo[] = [
  {
    id: 'claude-code/opus',
    name: 'opus',
    provider: 'claude-code',
    contextWindow: 200000,
    maxTokens: 16000,
    reasoning: true,
    images: true,
  },
  {
    id: 'claude-code/sonnet',
    name: 'sonnet',
    provider: 'claude-code',
    contextWindow: 200000,
    maxTokens: 16000,
    reasoning: true,
    images: true,
  },
  {
    id: 'claude-code/haiku',
    name: 'haiku',
    provider: 'claude-code',
    contextWindow: 200000,
    maxTokens: 8192,
    reasoning: false,
    images: true,
  },
];

// Cache the OpenCode catalog; it's stable for a session.
let openCodeCache: ModelInfo[] | null = null;

export function invalidateModelCache(): void {
  openCodeCache = null;
}

type OpenCodeModel = {
  capabilities?: { input?: { image?: boolean }; reasoning?: boolean };
};

type ProvidersResponse = {
  providers?: Array<{ id?: string; models?: Record<string, OpenCodeModel> }>;
};

// Enumerate OpenCode models from the fixed server's GET /config/providers (reliable — no subprocess).
// Model ids are `providerID/modelID` (e.g. `opencode/claude-haiku-4-5`); metadata is left at neutral
// defaults for now.
async function listOpenCodeModels(): Promise<ModelInfo[]> {
  if (openCodeCache) return openCodeCache;
  const baseUrl = getOpenCodeServerUrl();
  if (!baseUrl) return []; // sidecar hasn't reported its server yet
  try {
    const res = await fetch(`${baseUrl}/config/providers`, { signal: AbortSignal.timeout(5000) });
    if (!res.ok) return [];
    const data = (await res.json()) as ProvidersResponse;

    const models: ModelInfo[] = [];
    for (const provider of data.providers ?? []) {
      const providerId = provider.id ?? '';
      for (const [modelId, model] of Object.entries(provider.models ?? {})) {
        models.push({
          id: `${providerId}/${modelId}`,
          name: modelId,
          provider: providerId,
          contextWindow: 200000,
          maxTokens: 8192,
          reasoning: model?.capabilities?.reasoning ?? false,
          // Was hardcoded `false`, correctly, while nothing carried images — the composer gates on this
          // flag, so advertising `true` offered a capability that did not exist. Images are now plumbed
          // through `OpenCodeRunParams` to `opencode run --file`, so the honest answer is the model's
          // own: OpenCode publishes it per model and we had never read it. Defaults to false, so a model
          // that does not declare the capability keeps the affordance hidden rather than offering it.
          images: model?.capabilities?.input?.image ?? false,
        });
      }
    }

    openCodeCache = models;
    return models;
  } catch {
    return [];
  }
}

export async function listChatModels(): Promise<ModelInfo[]> {
  const openCode = await listOpenCodeModels();
  return [...CLAUDE_CODE_MODELS, ...openCode];
}
