// Drives a single active transcription: decode -> run the pipeline on-device ->
// save to the local library. Holds live progress + the partial transcript so
// the UI can "fill in" as chunks complete, plus an object URL for playback.
import { Platform } from 'react-native';
import { create } from 'zustand';

import { getDecoder, type AudioFileInput } from '@/lib/audio';
import { getRepo } from '@/lib/db';
import { DEFAULT_MODEL } from '@/lib/models/catalog';
import { getEngine } from '@/lib/transcription';
import { transcribe } from '@/lib/transcription/pipeline';
import type { ModelId, Segment } from '@/lib/types';

type Status = 'idle' | 'loading' | 'transcribing' | 'done' | 'error';

interface StartOptions {
  title?: string;
  language?: string;
  translate?: boolean;
}

interface TranscribeState {
  status: Status;
  stage?: 'loading' | 'transcribing';
  progress: number;
  partial: Segment[];
  error?: string;
  modelId: ModelId;
  lastTranscriptId?: string;
  /** Playable source for the just-finished audio (object URL on web, uri on native). */
  audioUrl?: string;
  _abort?: AbortController;

  setModel: (m: ModelId) => void;
  start: (input: AudioFileInput, opts?: StartOptions) => Promise<string | undefined>;
  cancel: () => void;
  reset: () => void;
}

function makeAudioUrl(input: AudioFileInput): string | undefined {
  if (Platform.OS === 'web' && input.data) {
    return URL.createObjectURL(new Blob([input.data]));
  }
  return input.uri;
}

export const useTranscribe = create<TranscribeState>((set, get) => ({
  status: 'idle',
  progress: 0,
  partial: [],
  modelId: DEFAULT_MODEL,

  setModel: (m) => set({ modelId: m }),

  start: async (input, opts) => {
    // Clean up any previous object URL.
    const prev = get().audioUrl;
    if (prev && Platform.OS === 'web' && prev.startsWith('blob:')) URL.revokeObjectURL(prev);

    const abort = new AbortController();
    set({
      status: 'loading',
      stage: 'loading',
      progress: 0,
      partial: [],
      error: undefined,
      lastTranscriptId: undefined,
      audioUrl: makeAudioUrl(input),
      _abort: abort,
    });

    const { modelId } = get();
    try {
      const pcm = await getDecoder().decode(input);
      const segments = await transcribe({
        audio: pcm,
        options: { modelId, language: opts?.language, translate: opts?.translate },
        engine: getEngine(),
        signal: abort.signal,
        onProgress: (p) =>
          set({ stage: p.stage, progress: p.progress, partial: p.partial, status: p.stage }),
      });

      const durationSec = pcm.samples.length / pcm.sampleRate;
      const saved = await getRepo().create({
        title: opts?.title?.trim() || defaultTitle(),
        durationSec,
        modelId,
        language: opts?.language,
        segments,
      });

      set({ status: 'done', progress: 1, partial: segments, lastTranscriptId: saved.id });
      return saved.id;
    } catch (err) {
      if (err instanceof DOMException && err.name === 'AbortError') {
        set({ status: 'idle', progress: 0 });
        return undefined;
      }
      set({ status: 'error', error: err instanceof Error ? err.message : String(err) });
      return undefined;
    }
  },

  cancel: () => {
    get()._abort?.abort();
  },

  reset: () => {
    const prev = get().audioUrl;
    if (prev && Platform.OS === 'web' && prev.startsWith('blob:')) URL.revokeObjectURL(prev);
    set({ status: 'idle', stage: undefined, progress: 0, partial: [], error: undefined, audioUrl: undefined });
  },
}));

function defaultTitle(): string {
  const d = new Date();
  const pad = (n: number) => String(n).padStart(2, '0');
  return `Transcript ${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
