/**
 * NechAPI — typed client for the Nech.PL APIs platform (api.nech.pl).
 *
 * Zero dependencies: uses the global `fetch` / `FormData` / `Blob` available in
 * Node 18+, Vercel Functions, and the Edge runtime.
 *
 * One client, namespaced per sub-API:
 *
 *   import { NechAPI } from "./nech";
 *   const nech = new NechAPI({ token: process.env.NECHPL_TOKEN! });
 *   const mood = await nech.audio.emotion(clip);       // { emotion: {…} }
 *   const spec = await nech.audio.spectrum(clip, { bands: 64, frames: 200 });
 *   // future sub-APIs add namespaces (nech.geo.…) — your import never changes.
 *
 * Prefer a smaller bundle? Import just the sub-client you need:
 *
 *   import { NechAudio } from "./nech";
 *   const audio = new NechAudio({ token });
 *   const mood = await audio.emotion(clip);
 *
 * Every audio call takes a clip (a `Blob` — e.g. `await fetch(url).then(r =>
 * r.blob())`, or a `File` from an upload) and returns typed JSON. Identical audio
 * is cached server-side: check `result._cache === "hit"`.
 */

export interface NechOptions {
  /** Platform bearer token (provisioned for your tenant). */
  token: string;
  /** Platform base; defaults to the public gateway. */
  baseUrl?: string;
  /** Inject a custom fetch (tests / non-standard runtimes). */
  fetchImpl?: typeof fetch;
}

/** Options for a standalone sub-client; baseUrl is that API's versioned root. */
export interface SubClientOptions {
  token: string;
  baseUrl?: string;
  fetchImpl?: typeof fetch;
}

export type Cache = "hit" | "miss";
type WithMeta<T> = T & { filename: string; content_id: string; _cache: Cache };

export interface Emotion {
  valence: number; arousal: number;
  top: [string, number][]; dist: Record<string, number>;
  confidence: number; engine: string;
}
export interface Features { features: Record<string, number | string>; tiers: Record<string, string[]>; sr: number; engine: string; }
export interface SampleProfile {
  profile: { rms_db: number; peak_db: number; centroid: number; bands: Record<string, number> };
  role: "percs" | "bass" | "melodic" | "tops" | "atmos";
  centroid: number | null; broadband: boolean; time_activity: number;
  name_hint: string | null; sr: number; engine: string;
}
export interface Grade {
  grade: { path: string; kind: "loop" | "one_shot"; grade: number; tier: "S" | "A" | "B" | "C" | "D";
    sub: Record<string, number>; metrics: Record<string, number>; flags: string[] };
}
export interface Onsets { tempo_bpm: number; onsets: number[]; n_onsets: number; onset_rate: number; duration_s: number; sr: number; engine: string; }
export interface Waveform { bins: number; peaks: [number, number][]; rms: number[]; duration_s: number; sr: number; engine: string; }
export interface Analyze { emotion: Emotion; features: Record<string, number | string>; sample: SampleProfile; }
export interface Loudness {
  lufs_integrated: number | null; peak_dbfs: number; true_peak_dbfs: number; rms_dbfs: number;
  crest_db: number; gain_to_target_db: { streaming: number; club: number } | null;
  channels: number; sr: number; engine: string;
}
export interface Spectrum { bands: number; frames: number; mel: boolean; band_hz: number[]; spec: number[][]; duration_s: number; sr: number; engine: string; }
export interface Naming { suggested_name: string; role: string; character: string; stem: string; lint: string[]; }

export class NechError extends Error {
  constructor(public status: number, public detail: unknown) {
    super(`NechAPI ${status}: ${typeof detail === "string" ? detail : JSON.stringify(detail)}`);
  }
}

/**
 * The `audio` sub-API client — usable standalone or via `NechAPI#audio`.
 * Public path: https://api.nech.pl/audio/v1
 */
export class NechAudio {
  private base: string;
  private token: string;
  private f: typeof fetch;

  constructor(opts: SubClientOptions) {
    this.token = opts.token;
    this.base = (opts.baseUrl ?? "https://api.nech.pl/audio/v1").replace(/\/$/, "");
    this.f = opts.fetchImpl ?? fetch;
  }

  private async post<T>(path: string, clip: Blob, query: Record<string, unknown> = {},
                        filename = "clip.wav"): Promise<WithMeta<T>> {
    const qs = Object.entries(query)
      .filter(([, v]) => v !== undefined && v !== null && v !== "")
      .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
    const form = new FormData();
    form.append("file", clip, filename);
    const res = await this.f(`${this.base}${path}${qs ? `?${qs}` : ""}`, {
      method: "POST",
      headers: { Authorization: `Bearer ${this.token}` },
      body: form,
    });
    const body = await res.json().catch(() => ({}));
    if (!res.ok) throw new NechError(res.status, (body as any)?.detail ?? body);
    (body as any)._cache = (res.headers.get("X-Nech-Cache") as Cache) ?? "miss";
    return body as WithMeta<T>;
  }

  /** Service health + per-engine availability (unauthenticated). */
  async health(): Promise<any> {
    const res = await this.f(`${this.base}/healthz`);
    return res.json();
  }

  emotion(clip: Blob, filename?: string)               { return this.post<Emotion & { emotion: Emotion }>("/analyze/emotion", clip, {}, filename); }
  features(clip: Blob, o: { rhythm?: boolean } = {})   { return this.post<Features>("/features", clip, o); }
  samples(clip: Blob, o: { name?: string } = {})       { return this.post<SampleProfile>("/analyze/samples", clip, o); }
  grade(clip: Blob, o: { role?: string } = {})         { return this.post<Grade>("/grade", clip, o); }
  onsets(clip: Blob)                                   { return this.post<Onsets>("/onsets", clip); }
  waveform(clip: Blob, o: { bins?: number } = {})      { return this.post<Waveform>("/waveform", clip, o); }
  analyze(clip: Blob)                                  { return this.post<Analyze>("/analyze", clip); }
  loudness(clip: Blob)                                 { return this.post<Loudness>("/loudness", clip); }
  spectrum(clip: Blob, o: { bands?: number; frames?: number; mel?: boolean } = {}) { return this.post<Spectrum>("/spectrum", clip, o); }
  naming(clip: Blob, o: { index?: number } = {})       { return this.post<Naming>("/naming", clip, o); }
}

/**
 * The umbrella platform client. Sub-APIs hang off it as namespaces; today that's
 * `nech.audio`, tomorrow `nech.geo`, … — with no change to your import.
 */
export class NechAPI {
  /** The Audio Intelligence API (api.nech.pl/audio/v1). */
  readonly audio: NechAudio;

  constructor(opts: NechOptions) {
    const base = (opts.baseUrl ?? "https://api.nech.pl").replace(/\/$/, "");
    this.audio = new NechAudio({ token: opts.token, baseUrl: `${base}/audio/v1`, fetchImpl: opts.fetchImpl });
  }
}

export default NechAPI;
