/**
 * MatchPrior API — zero-dependency TypeScript / JavaScript client.
 *
 *   import { MatchPrior } from './matchprior.js';
 *   const mp = new MatchPrior({ apiKey: 'mp_live_…' });
 *   const { data } = await mp.predictions({ sport: 'football', limit: 5 });
 *   for await (const p of mp.iterate('/v1/predictions', { sport: 'tennis' })) console.log(p.pick);
 *
 * No dependencies — uses the global fetch (Node 18+, Deno, browsers, workers).
 * Docs: https://matchprior.com/docs.html · OpenAPI: https://matchprior.com/openapi.yaml
 */

export interface MatchPriorOptions {
  apiKey: string;
  /** Defaults to https://api.matchprior.com */
  baseUrl?: string;
  /** Inject a fetch implementation (tests, custom agents). Defaults to global fetch. */
  fetch?: typeof fetch;
}

export interface Page<T> {
  data: T[];
  page: { count: number; nextCursor: string | null };
}

export interface FairOdds { H?: number; D?: number; A?: number }
export interface Probs { H: number; D: number; A: number }

export interface Prediction {
  fixture: {
    id: string; sport: string; league: string; date: string; time?: string;
    home: string; away: string; startMs?: number; surface?: string; tournament?: string; round?: string;
  };
  probs: Probs;
  fairOdds: FairOdds;
  pick: 'H' | 'D' | 'A';
  confidence: number;
  contrarian?: boolean;
  /** Pro tier and above. */
  over25?: number; btts?: number; likelyScore?: { home: number; away: number };
  spread?: number; totalPoints?: number;
  rationale?: Record<string, unknown>;
}

/** A non-2xx response. `status` 429 = over your monthly quota (see `retryAfter`). */
export class MatchPriorError extends Error {
  constructor(public status: number, public body: unknown, public retryAfter?: number) {
    super(`MatchPrior API error ${status}${retryAfter ? ` (retry after ${retryAfter}s)` : ''}`);
    this.name = 'MatchPriorError';
  }
}

export interface QueryParams {
  sport?: string; league?: string; date?: string; limit?: number; since?: string;
}

export class MatchPrior {
  private key: string;
  private base: string;
  private f: typeof fetch;

  constructor(opts: MatchPriorOptions) {
    if (!opts?.apiKey) throw new Error('MatchPrior: apiKey is required');
    this.key = opts.apiKey;
    this.base = (opts.baseUrl ?? 'https://api.matchprior.com').replace(/\/+$/, '');
    this.f = opts.fetch ?? globalThis.fetch;
    if (!this.f) throw new Error('MatchPrior: no fetch available — pass one via options.fetch');
  }

  private async get<T>(path: string, params: QueryParams = {}): Promise<T> {
    const url = new URL(this.base + path);
    for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
    const res = await this.f(url.toString(), { headers: { 'X-API-Key': this.key, Accept: 'application/json' } });
    const body = await res.json().catch(() => ({}));
    if (!res.ok) {
      const ra = Number(res.headers.get('Retry-After'));
      throw new MatchPriorError(res.status, body, Number.isFinite(ra) ? ra : undefined);
    }
    return body as T;
  }

  /** Service health + data freshness (public — no key needed, but sent anyway). */
  health(): Promise<{ ok: boolean; version: string; ageMs: number | null; stale: boolean }> {
    return this.get('/v1/health');
  }
  predictions(params?: QueryParams): Promise<Page<Prediction>> { return this.get('/v1/predictions', params); }
  prediction(fixtureId: string): Promise<Prediction> { return this.get(`/v1/predictions/${encodeURIComponent(fixtureId)}`); }
  fixtures(params?: QueryParams): Promise<Page<Prediction['fixture']>> { return this.get('/v1/fixtures', params); }
  /** Dev tier and above. */
  ratings(params?: QueryParams): Promise<Page<{ team: string; league: string; sport: string; rating: number; form: string[] }>> {
    return this.get('/v1/ratings', params);
  }
  accuracy(params?: QueryParams): Promise<Record<string, unknown>> { return this.get('/v1/accuracy', params); }
  /** Pro tier and above. */
  backtest(params?: QueryParams): Promise<{ data: Record<string, unknown> }> { return this.get('/v1/backtest', params); }

  /** Async-iterate every item across all pages of a paginated endpoint. */
  async *iterate<T = Prediction>(path: '/v1/predictions' | '/v1/fixtures' | '/v1/ratings', params: QueryParams = {}): AsyncGenerator<T> {
    let since: string | undefined = params.since;
    do {
      const page: Page<T> = await this.get(path, { ...params, since });
      for (const item of page.data) yield item;
      since = page.page.nextCursor ?? undefined;
    } while (since);
  }
}
