<script lang="ts">
  import { onMount } from 'svelte';

  interface Props {
    channelId: string;
  }
  let { channelId }: Props = $props();

  let isRecording = $state(false);
  let duration = $state(0);
  let micError = $state<string | null>(null);

  let mediaRecorder: MediaRecorder | null = $state(null);
  let audioChunks: Blob[] = [];
  let stream: MediaStream | null = null;
  let startTime = 0;
  let timerId: ReturnType<typeof setInterval> | null = null;
  const MIN_DURATION = 0.3;

  async function startRecording() {
    micError = null;
    try {
      stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
        ? 'audio/webm;codecs=opus'
        : 'audio/webm';
      mediaRecorder = new MediaRecorder(stream, { mimeType });
      audioChunks = [];

      mediaRecorder.ondataavailable = (e) => {
        if (e.data.size > 0) audioChunks.push(e.data);
      };

      mediaRecorder.onstop = async () => {
        const d = (Date.now() - startTime) / 1000;
        if (d < MIN_DURATION) {
          stream?.getTracks().forEach((t) => t.stop());
          return;
        }
        const blob = new Blob(audioChunks, { type: mimeType });
        const file = new File([blob], `voice-${Date.now()}.webm`, { type: mimeType });
        const formData = new FormData();
        formData.append('type', 'voice');
        formData.append('duration', String(Math.round(d)));
        formData.append('audio', file);
        try {
          const res = await fetch(`/api/channels/${channelId}/messages`, {
            method: 'POST',
            body: formData,
            credentials: 'include'
          });
          if (!res.ok) {
            const data = await res.json().catch(() => ({}));
            throw new Error(data.error ?? 'Upload failed');
          }
        } catch (err) {
          console.error('Upload failed', err);
        }
        audioChunks = [];
        duration = 0;
      };

      mediaRecorder.start(250);
      startTime = Date.now();
      timerId = setInterval(() => {
        duration = (Date.now() - startTime) / 1000;
      }, 100);
      isRecording = true;
    } catch (err) {
      micError = err instanceof Error ? err.message : 'Microphone access denied';
    }
  }

  function stopRecording() {
    if (mediaRecorder?.state === 'recording') {
      mediaRecorder.stop();
    }
    if (timerId) {
      clearInterval(timerId);
      timerId = null;
    }
    stream?.getTracks().forEach((t) => t.stop());
    stream = null;
    mediaRecorder = null;
    isRecording = false;
  }

  function handlePointerDown(e: PointerEvent) {
    e.preventDefault();
    startRecording();
  }

  function handlePointerUp(e: PointerEvent) {
    e.preventDefault();
    if (isRecording) stopRecording();
  }

  onMount(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code === 'Space') {
        e.preventDefault();
        startRecording();
      }
    };
    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code === 'Space') {
        e.preventDefault();
        if (isRecording) stopRecording();
      }
    };
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
      if (isRecording) stopRecording();
    };
  });
</script>

<div class="voice-ptt-container flex flex-col items-center gap-2">
  {#if micError}
    <p class="text-sm text-destructive">{micError}</p>
  {/if}
  <button
    type="button"
    class="ptt-button h-20 w-20 rounded-full border-none font-bold text-white cursor-pointer select-none touch-manipulation transition-transform
      {isRecording ? 'bg-destructive animate-pulse' : 'bg-muted-foreground/80 hover:bg-muted-foreground'}"
    onpointerdown={handlePointerDown}
    onpointerup={handlePointerUp}
    ontouchstart={handlePointerDown}
    ontouchend={handlePointerUp}
  >
    {#if isRecording}
      <span class="text-sm">Recording… {duration.toFixed(1)}s</span>
    {:else}
      <span class="text-sm">Hold to talk</span>
    {/if}
  </button>
</div>
