import { useState, useRef, useEffect } from 'react';
import { toast } from 'sonner';

export function useAudioRecording(onTranscription: (text: string) => void) {
  const [recording, setRecording] = useState(false);
  const [transcribing, setTranscribing] = useState(false);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);

  const toggleRecording = async () => {
    if (recording) {
      const recorder = mediaRecorderRef.current;
      if (!recorder) return;

      setRecording(false);

      try {
        if (recorder.state === 'inactive') {
          recorder.stream.getTracks().forEach((t) => t.stop());
          return;
        }

        const blob = await new Promise<Blob>((resolve, reject) => {
          const timeout = setTimeout(() => reject(new Error('Recording stop timed out')), 5000);
          recorder.onstop = () => {
            clearTimeout(timeout);
            resolve(new Blob(chunksRef.current, { type: recorder.mimeType }));
            chunksRef.current = [];
          };
          recorder.stop();
        });

        recorder.stream.getTracks().forEach((t) => t.stop());

        if (blob.size === 0) {
          toast.error('No audio was captured');
          return;
        }

        setTranscribing(true);
        try {
          const wav = await blobToWav(blob);
          const formData = new FormData();
          formData.append('file', wav, 'recording.wav');
          formData.append('temperature', '0.0');
          formData.append('temperature_inc', '0.2');
          formData.append('response_format', 'json');

          const token = localStorage.getItem('BEARER_TOKEN') ?? sessionStorage.getItem('BEARER_TOKEN');
          const res = await fetch('/api/chat/stt', {
            method: 'POST',
            body: formData,
            headers: token ? { Authorization: `Bearer ${token}` } : {},
          });
          if (!res.ok) throw new Error(`Whisper returned ${res.status}`);
          const json = await res.json();
          if (json.error) throw new Error(json.error);
          const text = (json.text ?? '').trim();
          if (text) onTranscription(text);
        } finally {
          setTranscribing(false);
        }
      } catch (err) {
        recorder.stream?.getTracks().forEach((t) => t.stop());
        chunksRef.current = [];
        toast.error(err instanceof Error ? err.message : 'Recording failed');
      }
      return;
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const recorder = new MediaRecorder(stream);
      mediaRecorderRef.current = recorder;
      chunksRef.current = [];

      recorder.ondataavailable = (ev) => {
        if (ev.data.size > 0) chunksRef.current.push(ev.data);
      };

      recorder.start(250);
      setRecording(true);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Could not access microphone');
    }
  };

  // Nothing here released the microphone on unmount, and a panel can go away mid-recording — closing the
  // Chat panel, switching dashboards, a layout change that remounts the slot. The recorder is only ever
  // stopped by the user pressing the button again, so without this the stream outlives the component and
  // the browser keeps showing the recording indicator for the life of the tab.
  useEffect(
    () => () => {
      const recorder = mediaRecorderRef.current;
      if (!recorder) return;
      if (recorder.state !== 'inactive') recorder.stop();
      recorder.stream?.getTracks().forEach((t) => t.stop());
      mediaRecorderRef.current = null;
      chunksRef.current = [];
    },
    [],
  );

  return { recording, transcribing, toggleRecording };
}

export type UseAudioRecordingType = ReturnType<typeof useAudioRecording>;

// ── Helpers ──

export const blobToWav = async (blob: Blob): Promise<Blob> => {
  const ctx = new AudioContext();
  const buf = await ctx.decodeAudioData(await blob.arrayBuffer());
  await ctx.close();

  const samples = buf.getChannelData(0);
  const len = samples.length;
  const sr = buf.sampleRate;
  const ab = new ArrayBuffer(44 + len * 2);
  const v = new DataView(ab);

  const s = (o: number, str: string) => {
    for (let i = 0; i < str.length; i++) v.setUint8(o + i, str.charCodeAt(i));
  };
  s(0, 'RIFF');
  v.setUint32(4, 36 + len * 2, true);
  s(8, 'WAVE');
  s(12, 'fmt ');
  v.setUint32(16, 16, true);
  v.setUint16(20, 1, true);
  v.setUint16(22, 1, true);
  v.setUint32(24, sr, true);
  v.setUint32(28, sr * 2, true);
  v.setUint16(32, 2, true);
  v.setUint16(34, 16, true);
  s(36, 'data');
  v.setUint32(40, len * 2, true);

  for (let i = 0; i < len; i++) {
    const val = Math.max(-1, Math.min(1, samples[i]!));
    v.setInt16(44 + i * 2, val < 0 ? val * 0x8000 : val * 0x7fff, true);
  }

  return new Blob([ab], { type: 'audio/wav' });
};
