// Zod schemas + domain types for stored transcripts.
//
// This module is the single source of truth for what a "valid" transcript
// looks like before it ever touches storage. Every write path (web Dexie repo,
// native SQLite repo) funnels user/engine-produced data through these schemas
// so malformed numbers (NaN/Infinity) and bad shapes can never be persisted.
//
// IMPORTANT: relative imports only inside src/lib (vitest has no '@/*' alias).

import { z } from 'zod';
import type { Segment, ModelId } from '../types';

// ---------------------------------------------------------------------------
// Reusable number guards
// ---------------------------------------------------------------------------

// A finite number: rejects NaN and ±Infinity. `z.number()` alone accepts both,
// which is dangerous for durations/timestamps that get serialized to JSON/SQL.
const zFinite = z.number().refine(Number.isFinite, {
  message: 'must be a finite number (no NaN/Infinity)',
});

// A finite number that is also >= 0, for durations and time offsets.
const zFiniteNonNeg = zFinite.refine((n) => n >= 0, {
  message: 'must be >= 0',
});

// ---------------------------------------------------------------------------
// Segment
// ---------------------------------------------------------------------------

/**
 * One transcribed segment as accepted from a draft.
 * Mirrors {@link Segment} from ../types but with runtime validation:
 *  - start/end: finite and >= 0 (seconds, absolute to the media)
 *  - text: any string (may be empty in pathological cases)
 *  - confidence: optional, finite and within [0, 1]
 */
export const zSegment = z.object({
  start: zFiniteNonNeg,
  end: zFiniteNonNeg,
  text: z.string(),
  confidence: zFinite
    .refine((n) => n >= 0 && n <= 1, { message: 'confidence must be in [0,1]' })
    .optional(),
});

// The 6 supported model ids, as a Zod enum. Kept in lockstep with ModelId in
// ../types. A compile-time assertion below guarantees they never drift.
export const zModelId = z.enum([
  'tiny.en',
  'tiny',
  'base.en',
  'base',
  'small.en',
  'small',
]);

// Compile-time guard: if ../types ModelId changes, this assignment fails to
// typecheck, forcing zModelId to be updated. (No runtime cost.)
type _ModelIdMatches = ModelId extends z.infer<typeof zModelId>
  ? z.infer<typeof zModelId> extends ModelId
    ? true
    : never
  : never;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _modelIdMatches: _ModelIdMatches = true;

// ---------------------------------------------------------------------------
// Transcript draft (input to create())
// ---------------------------------------------------------------------------

/**
 * The unsaved shape a caller hands to the repo to create a transcript.
 * No id/timestamps/segmentCount yet — the repo derives those.
 */
export const zTranscriptDraft = z.object({
  title: z.string(),
  durationSec: zFiniteNonNeg,
  modelId: zModelId,
  language: z.string().optional(),
  segments: z.array(zSegment),
});

/** Inferred TS type for a validated draft. */
export type TranscriptDraft = z.infer<typeof zTranscriptDraft>;

/**
 * Validate an unknown input as a TranscriptDraft, throwing (ZodError) on any
 * violation. This is the throwing gatekeeper the repos call on every write.
 */
export function parseDraft(input: unknown): TranscriptDraft {
  return zTranscriptDraft.parse(input);
}

// ---------------------------------------------------------------------------
// Stored types (output of the repo)
// ---------------------------------------------------------------------------

/**
 * Lightweight metadata for a stored transcript — everything except the
 * (potentially large) segments array. Returned by list()/search() so the UI
 * can render lists without loading full bodies.
 */
export interface TranscriptMeta {
  id: string;
  title: string;
  /** Creation time, ms since epoch (runtime clock). */
  createdAt: number;
  /** Last-update time, ms since epoch (runtime clock). */
  updatedAt: number;
  durationSec: number;
  modelId: ModelId;
  language?: string;
  /** Number of segments in the body (denormalized for list views). */
  segmentCount: number;
}

/** A full stored transcript: metadata plus the segment body. */
export interface Transcript extends TranscriptMeta {
  segments: Segment[];
}
