import { RegistrySource } from '../base.js';
import {
  OfficialRegistryResponseSchema,
  sanitizeString,
  type OfficialServerEntry,
  type RegistryServer,
} from '../types.js';
import { withRetry } from '../retry.js';

const BASE_URL = 'https://registry.modelcontextprotocol.io/v0/servers';

export class OfficialRegistrySource extends RegistrySource {
  readonly name = 'official' as const;

  async search(query: string, limit: number): Promise<RegistryServer[]> {
    const results: RegistryServer[] = [];
    let cursor: string | null | undefined;

    while (results.length < limit) {
      const url = new URL(BASE_URL);
      url.searchParams.set('search', query);
      url.searchParams.set('limit', String(Math.min(limit - results.length, 100)));
      if (cursor !== undefined && cursor !== null) {
        url.searchParams.set('cursor', cursor);
      }

      const response = await withRetry(() => fetch(url.toString()));
      if (!response.ok) {
        throw new Error(`Official registry returned ${String(response.status)}`);
      }

      const raw: unknown = await response.json();
      const parsed = OfficialRegistryResponseSchema.parse(raw);

      for (const entry of parsed.servers) {
        results.push(this.normalizeResult(entry));
      }

      cursor = parsed.metadata?.nextCursor;
      if (cursor === null || cursor === undefined || parsed.servers.length === 0) break;
    }

    return results.slice(0, limit);
  }

  protected normalizeResult(raw: unknown): RegistryServer {
    const entry = raw as OfficialServerEntry;
    const server = entry.server;

    // Extract env vars from packages
    const envTemplate = server.packages.flatMap((pkg: { environmentVariables: Array<{ name: string; description?: string; isSecret?: boolean }> }) =>
      pkg.environmentVariables.map((ev: { name: string; description?: string; isSecret?: boolean }) => ({
        name: ev.name,
        description: sanitizeString(ev.description ?? ''),
        isSecret: ev.isSecret ?? false,
      })),
    );

    // Determine transport from packages or remotes
    let transport: RegistryServer['transport'] = 'stdio';
    if (server.packages.length > 0) {
      const pkgTransport = server.packages[0]?.transport?.type;
      if (pkgTransport === 'stdio') transport = 'stdio';
    }
    if (server.remotes.length > 0) {
      const remoteType = server.remotes[0]?.type;
      if (remoteType === 'sse') transport = 'sse';
      else if (remoteType === 'streamable-http') transport = 'streamable-http';
    }

    // Extract npm package identifier
    const npmPkg = server.packages.find((p: { registryType: string }) => p.registryType === 'npm');
    const dockerPkg = server.packages.find((p: { registryType: string }) => p.registryType === 'oci');

    // Extract dates from _meta
    const meta = entry._meta as Record<string, Record<string, unknown>> | undefined;
    const officialMeta = meta?.['io.modelcontextprotocol.registry/official'];
    const updatedAt = officialMeta?.['updatedAt'];

    const packages: RegistryServer['packages'] = {};
    if (npmPkg !== undefined) {
      packages.npm = npmPkg.identifier;
    }
    if (dockerPkg !== undefined) {
      packages.docker = dockerPkg.identifier;
    }

    const result: RegistryServer = {
      name: sanitizeString(server.title ?? server.name),
      description: sanitizeString(server.description),
      packages,
      envTemplate,
      transport,
      popularityScore: 0, // Official registry has no popularity data
      verified: false, // Official registry has no verified badges
      sourceRegistry: 'official',
    };
    if (server.repository?.url !== undefined) {
      result.repositoryUrl = server.repository.url;
    }
    if (typeof updatedAt === 'string') {
      result.lastUpdated = new Date(updatedAt);
    }
    return result;
  }
}
