'use client'

import { useEffect, useRef, useState, useTransition } from 'react'
import { Sparkles, Plus, X, Mic, Square, Trash2, Check } from 'lucide-react'
import { setPendingNote, setVoiceNote, deleteVoiceNote } from '../actions'

interface Props {
  itemId: string
  initialNote: string | null
  initialVoicePath: string | null
  initialVoiceMime: string | null
  needsAIReview: boolean
}

export function PendingNoteSection({
  itemId,
  initialNote,
  initialVoicePath,
  initialVoiceMime,
  needsAIReview,
}: Props) {
  const hasNote = (initialNote ?? '').length > 0
  const hasVoice = !!initialVoicePath
  const hasAny = hasNote || hasVoice

  const [editing, setEditing] = useState(false)
  const [draft, setDraft] = useState(initialNote ?? '')
  const [voiceBlob, setVoiceBlob] = useState<Blob | null>(null)
  const [voiceMime, setVoiceMime] = useState<string>('')
  const [voicePreview, setVoicePreview] = useState<string | null>(null)
  const [recording, setRecording] = useState(false)
  const [finalizing, setFinalizing] = useState(false)
  const [recordSeconds, setRecordSeconds] = useState(0)
  const [error, setError] = useState<string | null>(null)
  const [savedFlash, setSavedFlash] = useState(false)
  const [pending, startTransition] = useTransition()

  const recorderRef = useRef<MediaRecorder | null>(null)
  const streamRef = useRef<MediaStream | null>(null)
  const chunksRef = useRef<BlobPart[]>([])
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
  const finalizedRef = useRef(false)
  const fallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  useEffect(() => {
    return () => {
      if (voicePreview) URL.revokeObjectURL(voicePreview)
      if (timerRef.current) clearInterval(timerRef.current)
      if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current)
    }
  }, [voicePreview])

  // Build the final blob from accumulated chunks. Called either by onstop or
  // by a fallback timeout — iOS Safari sometimes doesn't fire onstop on short
  // clips, so we defend by finalizing manually after a delay. The finalizedRef
  // guard makes it safe to call from both paths.
  const finalizeRecording = (mimeType: string) => {
    if (finalizedRef.current) return
    finalizedRef.current = true
    if (fallbackTimerRef.current) {
      clearTimeout(fallbackTimerRef.current)
      fallbackTimerRef.current = null
    }
    const blob = new Blob(chunksRef.current, { type: mimeType })
    if (voicePreview) URL.revokeObjectURL(voicePreview)
    if (blob.size === 0) {
      setError('Recording was empty — try again, and hold for at least a second.')
      setFinalizing(false)
      streamRef.current?.getTracks().forEach(t => t.stop())
      streamRef.current = null
      return
    }
    setVoiceBlob(blob)
    setVoiceMime(mimeType || 'audio/webm')
    setVoicePreview(URL.createObjectURL(blob))
    setFinalizing(false)
    streamRef.current?.getTracks().forEach(t => t.stop())
    streamRef.current = null
  }

  const startRecording = async () => {
    setError(null)
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
      const mimePrefs = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg;codecs=opus']
      const mime = mimePrefs.find(m => MediaRecorder.isTypeSupported(m)) || ''
      const recorder = mime ? new MediaRecorder(stream, { mimeType: mime }) : new MediaRecorder(stream)
      chunksRef.current = []
      finalizedRef.current = false
      recorder.ondataavailable = e => {
        if (e.data && e.data.size > 0) chunksRef.current.push(e.data)
      }
      recorder.onstop = () => finalizeRecording(recorder.mimeType)
      // Emit a chunk every second so we always have data — even on iOS Safari
      // where onstop on short clips is unreliable.
      recorder.start(1000)
      recorderRef.current = recorder
      streamRef.current = stream
      setRecording(true)
      setRecordSeconds(0)
      if (timerRef.current) clearInterval(timerRef.current)
      timerRef.current = setInterval(() => setRecordSeconds(s => s + 1), 1000)
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Microphone unavailable')
    }
  }

  const stopRecording = () => {
    const recorder = recorderRef.current
    if (!recorder) return
    setFinalizing(true)
    setRecording(false)
    if (timerRef.current) clearInterval(timerRef.current)
    try {
      recorder.stop()
    } catch {
      // ignore — recorder may already be inactive
    }
    // Fallback: if onstop doesn't fire within 1.5s (iOS Safari short-clip
    // quirk), finalize from whatever chunks we have so the user isn't stuck.
    if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current)
    fallbackTimerRef.current = setTimeout(() => {
      finalizeRecording(recorder.mimeType || 'audio/webm')
    }, 1500)
  }

  const discardLocalVoice = () => {
    if (voicePreview) URL.revokeObjectURL(voicePreview)
    setVoiceBlob(null)
    setVoicePreview(null)
    setVoiceMime('')
  }

  const flash = () => {
    setSavedFlash(true)
    setTimeout(() => setSavedFlash(false), 6000)
  }

  const save = () => {
    setError(null)
    const trimmed = draft.trim()
    const noteChanged = trimmed !== (initialNote ?? '')
    if (!noteChanged && !voiceBlob) {
      setEditing(false)
      return
    }
    startTransition(async () => {
      if (noteChanged) {
        await setPendingNote(itemId, trimmed)
      }
      if (voiceBlob) {
        const ext = voiceMime.includes('mp4') ? 'm4a' : voiceMime.includes('ogg') ? 'ogg' : 'webm'
        const fd = new FormData()
        fd.set('itemId', itemId)
        fd.set('voice', voiceBlob, `voice.${ext}`)
        fd.set('voiceMime', voiceMime)
        const res = await setVoiceNote(fd)
        if (!res.ok) {
          setError(res.error)
          return
        }
      }
      discardLocalVoice()
      flash()
      setEditing(false)
    })
  }

  const clearAll = () => {
    startTransition(async () => {
      if (hasNote) await setPendingNote(itemId, '')
      if (hasVoice) await deleteVoiceNote(itemId)
      setDraft('')
      discardLocalVoice()
      setEditing(false)
    })
  }

  // === Collapsed empty state ===
  if (!editing && !hasAny) {
    return (
      <button
        type="button"
        onClick={() => setEditing(true)}
        className="w-full flex items-center justify-center gap-2 p-3 rounded-xl border-2 border-dashed text-sm text-muted-foreground hover:bg-accent/40"
      >
        <Plus className="size-4" />
        Add a note for the next AI pass
      </button>
    )
  }

  // === Collapsed filled state ===
  if (!editing && hasAny) {
    const amber = needsAIReview
    const label = amber
      ? 'Saved · Queued for next AI pass'
      : hasNote && hasVoice
      ? 'Saved voice & note'
      : hasVoice
      ? 'Saved voice note'
      : 'Saved note'
    return (
      <div className="space-y-2">
        {savedFlash && (
          <div className="p-2 rounded-lg bg-emerald-100 dark:bg-emerald-950/50 text-emerald-900 dark:text-emerald-200 text-sm flex items-center gap-2 border border-emerald-200 dark:border-emerald-900">
            <Check className="size-4 shrink-0" />
            <span>Saved — queued for the next AI pass.</span>
          </div>
        )}
        <button
          type="button"
          onClick={() => setEditing(true)}
          className={
            amber
              ? 'w-full p-3 rounded-xl border bg-amber-50/60 dark:bg-amber-950/20 border-amber-200/60 dark:border-amber-900/40 text-sm flex items-center justify-between hover:bg-amber-100/60 dark:hover:bg-amber-950/40'
              : 'w-full p-3 rounded-xl border bg-card text-sm flex items-center justify-between hover:bg-accent/40'
          }
        >
          <span
            className={
              amber
                ? 'flex items-center gap-1.5 text-xs uppercase tracking-wide text-amber-700 dark:text-amber-400'
                : 'flex items-center gap-1.5 text-xs uppercase tracking-wide text-muted-foreground'
            }
          >
            <Sparkles className="size-3.5" />
            {label}
          </span>
          <span className="text-xs text-muted-foreground underline">Edit</span>
        </button>
      </div>
    )
  }

  // === Editing state ===
  return (
    <div className="p-3 rounded-xl border bg-card space-y-3">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-1.5 text-xs uppercase tracking-wide text-muted-foreground">
          <Sparkles className="size-3.5" />
          Note for next AI pass
        </div>
        {hasAny && (
          <button
            type="button"
            onClick={() => {
              setDraft(initialNote ?? '')
              discardLocalVoice()
              setEditing(false)
            }}
            aria-label="Cancel"
            className="text-muted-foreground hover:text-foreground"
          >
            <X className="size-4" />
          </button>
        )}
      </div>

      <textarea
        value={draft}
        onChange={e => setDraft(e.target.value)}
        rows={3}
        placeholder="Type, or tap the mic on the iOS keyboard to dictate."
        className="w-full text-base resize-none rounded-lg border bg-background px-3 py-2 outline-none focus:ring-2 focus:ring-ring"
      />

      <div className="space-y-2">
        {recording ? (
          <button
            type="button"
            onClick={stopRecording}
            className="w-full h-10 rounded-lg bg-rose-600 text-white flex items-center justify-center gap-2 text-sm font-medium"
          >
            <Square className="size-4 fill-current" />
            Stop ({formatSeconds(recordSeconds)})
          </button>
        ) : finalizing ? (
          <div className="w-full h-10 rounded-lg border bg-muted text-muted-foreground flex items-center justify-center gap-2 text-sm">
            Finalizing recording…
          </div>
        ) : voiceBlob && voicePreview ? (
          <div className="space-y-2">
            <div className="text-xs text-muted-foreground">
              New recording {hasVoice ? '(will replace existing on Save)' : '(unsaved — tap Save below)'}
            </div>
            <div className="flex items-center gap-2">
              {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
              <audio src={voicePreview} controls className="flex-1 h-9" />
              <button
                type="button"
                onClick={discardLocalVoice}
                aria-label="Discard recording"
                className="size-9 rounded-lg border text-muted-foreground hover:text-foreground flex items-center justify-center"
              >
                <Trash2 className="size-4" />
              </button>
            </div>
          </div>
        ) : (
          <button
            type="button"
            onClick={startRecording}
            className="w-full h-10 rounded-lg border flex items-center justify-center gap-2 text-sm"
          >
            <Mic className="size-4" />
            {hasVoice ? 'Record (replaces existing)' : 'Record voice note'}
          </button>
        )}
      </div>

      {error && <div className="text-xs text-rose-600">{error}</div>}
      {savedFlash && (
        <div className="flex items-center gap-1.5 text-xs text-emerald-700 dark:text-emerald-400">
          <Check className="size-3.5" />
          Saved — queued for the next AI pass.
        </div>
      )}

      <div className="flex gap-2 items-center">
        {hasAny && (
          <button
            type="button"
            disabled={pending}
            onClick={clearAll}
            className="h-11 px-4 rounded-lg border text-sm text-muted-foreground"
          >
            Clear
          </button>
        )}
        <button
          type="button"
          disabled={
            pending ||
            recording ||
            finalizing ||
            (draft.trim() === (initialNote ?? '') && !voiceBlob)
          }
          onClick={save}
          className="flex-1 h-11 px-4 rounded-lg bg-primary text-primary-foreground text-base font-semibold disabled:opacity-50"
        >
          {pending ? 'Saving…' : 'Save'}
        </button>
      </div>
    </div>
  )
}

function formatSeconds(s: number) {
  const m = Math.floor(s / 60)
  const r = Math.floor(s % 60)
  return `${m}:${String(r).padStart(2, '0')}`
}
