// The platform-agnostic storage contract.
//
// Both the web (Dexie/IndexedDB) and native (expo-sqlite) implementations
// satisfy this interface, so the rest of the app depends only on StorageRepo
// and never on a specific backend. The correct implementation is selected at
// build time via platform file extensions (see repoImpl.web.ts / repoImpl.native.ts).

import type {
  TranscriptDraft,
  TranscriptMeta,
  Transcript,
  TranscriptPatch,
  Course,
  CourseDraft,
  Flashcard,
  FlashcardDraft,
  SrsState,
} from './schema';

/** Source of audio to persist for a transcript (web passes data, native a uri). */
export interface MediaInput {
  /** Raw bytes (web import path). */
  data?: ArrayBuffer;
  /** A file uri to copy into app storage (native import path). */
  uri?: string;
  /** MIME type, e.g. 'audio/mpeg'. */
  mime: string;
}

/** One stored embedding for a segment (vector is unit-normalized). */
export interface SegmentVector {
  segmentId: string;
  start: number;
  end: number;
  text: string;
  vector: Float32Array;
}

/** A semantic-search hit at segment granularity. */
export interface VectorHit {
  transcriptId: string;
  segmentId: string;
  start: number;
  end: number;
  courseId: string | null;
  text: string;
  /** Cosine similarity (dot product of unit vectors). */
  score: number;
}

export interface StorageRepo {
  // --- transcripts ---------------------------------------------------------
  /** All transcript metadatas, newest first (by createdAt desc). */
  list(): Promise<TranscriptMeta[]>;

  /** Full transcript by id, or undefined if it does not exist. */
  get(id: string): Promise<Transcript | undefined>;

  /** Validate the draft and persist a new transcript, returning the stored row. */
  create(draft: TranscriptDraft): Promise<Transcript>;

  /**
   * Patch a transcript's mutable fields (title/segments/course+lecture meta).
   * Re-derives denormalized fields (searchText, segmentCount, updatedAt),
   * re-validates segments, and assigns ids to any segments missing one.
   * Rejects if the id does not exist.
   */
  update(id: string, patch: TranscriptPatch): Promise<Transcript>;

  /** Delete by id (also removes its persisted media). No-op if absent. */
  remove(id: string): Promise<void>;

  /**
   * Case-insensitive substring search over title + segment text.
   * Returns metadatas, newest first.
   */
  search(query: string): Promise<TranscriptMeta[]>;

  /** Transcripts in a course (pass null for "Unsorted"), newest first. */
  listByCourse(courseId: string | null): Promise<TranscriptMeta[]>;

  /** Move a transcript to a course (or null for Unsorted). */
  reassign(transcriptId: string, courseId: string | null): Promise<void>;

  // --- courses -------------------------------------------------------------
  /** All courses, alphabetical by name. */
  listCourses(): Promise<Course[]>;

  /** Validate + create a course. */
  createCourse(draft: CourseDraft): Promise<Course>;

  /** Patch a course's fields. Rejects if absent. */
  updateCourse(id: string, patch: Partial<CourseDraft>): Promise<Course>;

  /** Delete a course; its transcripts are reassigned to Unsorted (courseId=null). */
  deleteCourse(id: string): Promise<void>;

  // --- media (persisted source audio) -------------------------------------
  /** Store (or replace) the source audio for a transcript. */
  putMedia(transcriptId: string, input: MediaInput): Promise<void>;

  /**
   * A playable URL for the persisted audio (object URL on web, file uri on
   * native), or undefined if no media is stored. Callers should revoke web
   * object URLs when done.
   */
  getMediaUrl(transcriptId: string): Promise<string | undefined>;

  /** Delete the persisted audio for a transcript. No-op if absent. */
  removeMedia(transcriptId: string): Promise<void>;

  // --- semantic search vectors (Phase 1) ----------------------------------
  /**
   * Replace all stored vectors for a transcript with `vectors`, tagged with the
   * embedding `model` id (so a model change can be detected and re-embedded).
   */
  upsertVectors(transcriptId: string, model: string, vectors: SegmentVector[]): Promise<void>;

  /**
   * Brute-force cosine search over stored vectors (optionally scoped to a
   * course; null = Unsorted), returning the top `limit` segment hits.
   */
  searchVectors(
    query: Float32Array,
    opts?: { courseId?: string | null; limit?: number },
  ): Promise<VectorHit[]>;

  /** Drop all vectors for a transcript. */
  clearVectors(transcriptId: string): Promise<void>;

  /** Transcript ids that have NO vectors for `model` (need embedding/backfill). */
  unembeddedIds(model: string): Promise<string[]>;

  // --- flashcards + spaced repetition (Phase 3) ---------------------------
  /** Validate + persist new flashcards (each gets an id, createdAt, initial SRS). */
  createFlashcards(drafts: FlashcardDraft[]): Promise<Flashcard[]>;

  /** All flashcards, optionally scoped to a transcript or course. */
  listFlashcards(opts?: { transcriptId?: string; courseId?: string | null }): Promise<Flashcard[]>;

  /** Cards due for review (srs.due <= now), optionally course-scoped. */
  listDueFlashcards(opts?: { courseId?: string | null; now?: number; limit?: number }): Promise<Flashcard[]>;

  /** Persist a recomputed SRS schedule after a review. */
  updateFlashcardSrs(id: string, srs: SrsState): Promise<Flashcard>;

  /** Delete a flashcard. */
  deleteFlashcard(id: string): Promise<void>;

  /** Counts for badges (optionally course-scoped). */
  flashcardCounts(courseId?: string | null): Promise<{ total: number; due: number }>;
}
