// Web microphone recorder (MediaRecorder) for in-app lecture capture (ROADMAP
// Phase 2). Records to a Blob, exposes elapsed time + a live input level, holds
// a screen Wake Lock while recording, and supports pause/resume. On stop it
// returns the recording as an ArrayBuffer that flows straight into the existing
// transcribe pipeline (decode -> chunk -> Whisper -> save).
//
// Web-only: getUserMedia/MediaRecorder/AudioContext are browser APIs. On native
// `supported` is false and the controls no-op (native realtime capture via
// whisper.rn is a follow-up).
import { useCallback, useEffect, useRef, useState } from 'react';
import { Platform } from 'react-native';

export type RecorderStatus = 'idle' | 'recording' | 'paused' | 'stopped';

export interface RecordingResult {
  data: ArrayBuffer;
  mime: string;
  name: string;
}

export interface Recorder {
  supported: boolean;
  status: RecorderStatus;
  elapsedMs: number;
  /** Live input level in [0,1], for a meter. */
  level: number;
  error?: string;
  start: () => Promise<void>;
  pause: () => void;
  resume: () => void;
  /** Stop and return the recording (null if nothing captured / unsupported). */
  stop: () => Promise<RecordingResult | null>;
}

function isSupported(): boolean {
  return (
    Platform.OS === 'web' &&
    typeof navigator !== 'undefined' &&
    !!navigator.mediaDevices?.getUserMedia &&
    typeof MediaRecorder !== 'undefined'
  );
}

export function useRecorder(): Recorder {
  const supported = isSupported();
  const [status, setStatus] = useState<RecorderStatus>('idle');
  const [elapsedMs, setElapsedMs] = useState(0);
  const [level, setLevel] = useState(0);
  const [error, setError] = useState<string | undefined>(undefined);

  const recorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);
  const streamRef = useRef<MediaStream | null>(null);
  const audioCtxRef = useRef<AudioContext | null>(null);
  const analyserRef = useRef<AnalyserNode | null>(null);
  const rafRef = useRef<number | null>(null);
  const wakeRef = useRef<WakeLockSentinel | null>(null);
  // Elapsed bookkeeping across pause/resume.
  const segStartRef = useRef(0);
  const accumRef = useRef(0);

  const cleanup = useCallback(() => {
    if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
    rafRef.current = null;
    streamRef.current?.getTracks().forEach((t) => t.stop());
    streamRef.current = null;
    void audioCtxRef.current?.close().catch(() => {});
    audioCtxRef.current = null;
    analyserRef.current = null;
    void wakeRef.current?.release().catch(() => {});
    wakeRef.current = null;
  }, []);

  useEffect(() => cleanup, [cleanup]);

  const tick = useCallback(() => {
    // Elapsed: accumulated paused time + current running segment.
    const running = status === 'recording' ? Date.now() - segStartRef.current : 0;
    setElapsedMs(accumRef.current + running);

    const an = analyserRef.current;
    if (an) {
      const buf = new Uint8Array(an.fftSize);
      an.getByteTimeDomainData(buf);
      let sum = 0;
      for (let i = 0; i < buf.length; i++) {
        const v = (buf[i]! - 128) / 128;
        sum += v * v;
      }
      setLevel(Math.min(1, Math.sqrt(sum / buf.length) * 3));
    }
    rafRef.current = requestAnimationFrame(tick);
  }, [status]);

  // Keep the rAF loop bound to the latest `status` via tick's identity.
  useEffect(() => {
    if (status === 'recording' || status === 'paused') {
      rafRef.current = requestAnimationFrame(tick);
      return () => {
        if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
      };
    }
    return undefined;
  }, [status, tick]);

  const start = useCallback(async () => {
    if (!supported) {
      setError('Recording is only available in the browser for now.');
      return;
    }
    try {
      setError(undefined);
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;

      const mr = new MediaRecorder(stream);
      chunksRef.current = [];
      mr.ondataavailable = (e) => {
        if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
      };
      mr.start();
      recorderRef.current = mr;

      // Level metering off an AnalyserNode.
      const ctx = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();
      const src = ctx.createMediaStreamSource(stream);
      const an = ctx.createAnalyser();
      an.fftSize = 512;
      src.connect(an);
      audioCtxRef.current = ctx;
      analyserRef.current = an;

      // Keep the screen awake while recording (best-effort).
      try {
        wakeRef.current = (await navigator.wakeLock?.request('screen')) ?? null;
      } catch {
        /* wake lock optional */
      }

      accumRef.current = 0;
      segStartRef.current = Date.now();
      setElapsedMs(0);
      setStatus('recording');
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Could not start recording (mic permission?).');
      cleanup();
      setStatus('idle');
    }
  }, [supported, cleanup]);

  const pause = useCallback(() => {
    const mr = recorderRef.current;
    if (!mr || status !== 'recording') return;
    mr.pause();
    accumRef.current += Date.now() - segStartRef.current;
    setStatus('paused');
  }, [status]);

  const resume = useCallback(() => {
    const mr = recorderRef.current;
    if (!mr || status !== 'paused') return;
    mr.resume();
    segStartRef.current = Date.now();
    setStatus('recording');
  }, [status]);

  const stop = useCallback(async (): Promise<RecordingResult | null> => {
    const mr = recorderRef.current;
    if (!mr) return null;
    const mime = mr.mimeType || 'audio/webm';
    const blob: Blob = await new Promise((resolve) => {
      mr.onstop = () => resolve(new Blob(chunksRef.current, { type: mime }));
      mr.stop();
    });
    recorderRef.current = null;
    cleanup();
    setStatus('stopped');
    if (blob.size === 0) return null;
    const data = await blob.arrayBuffer();
    const ext = mime.includes('ogg') ? 'ogg' : mime.includes('mp4') ? 'mp4' : 'webm';
    return { data, mime, name: `Recording.${ext}` };
  }, [cleanup]);

  return { supported, status, elapsedMs, level, error, start, pause, resume, stop };
}
