import { useEffect, useRef, useState } from 'react'; import { Paperclip } from 'lucide-react'; import type { RecordingMode } from '@/stores/media-settings-store'; import { CenteredWaveform } from '@/components/audio/centered-waveform'; import { useAudioSource } from '@/components/audio/use-audio-source'; import { useObjectUrl } from '@/hooks/use-object-url'; import { AttachmentStrip } from '@/features/compose/attachment-strip'; import type { PendingAttachment } from '@/features/compose/attachment-strip'; import { cn } from '@/lib/utils'; import { RECORDING_MAX_DURATION_SECONDS, RECORDING_WARNING_SECONDS, } from '@/lib/constants'; import { Button } from '@/components/ui/button'; import { KeyHint } from '@/components/key-hint'; import { useComposeIntentStore } from '@/stores/compose-intent-store'; interface RecordingOverlayProps { step: 'recording' | 'reviewing'; mediaStream: MediaStream | null; recordingMode: RecordingMode; reviewBlob: Blob | null; error: string | null; onClose: () => void; attachments: PendingAttachment[]; onRemoveAttachment: (id: string) => void; onAddFiles: () => void; isDragging: boolean; dropZoneProps: { onDragOver: (e: React.DragEvent) => void; onDragEnter: (e: React.DragEvent) => void; onDragLeave: (e: React.DragEvent) => void; onDrop: (e: React.DragEvent) => void; }; /** Mirror the video horizontally. Defaults to true (selfie-view for webcam). */ mirror?: boolean; /** How video fills its container. Defaults to "cover". Use "contain" for screen recordings. */ objectFit?: 'cover' | 'contain'; } const WARNING_AT_SECONDS = RECORDING_MAX_DURATION_SECONDS - RECORDING_WARNING_SECONDS; /** * Tracks elapsed recording time and drives the time-limit UI. Keeps the * recorder itself unaware of limits: when the cap is reached it dispatches the * standard `stop` intent (the same path as releasing the ` key), which finishes * the recording into the review step. */ function useRecordingCountdown(active: boolean) { const [elapsed, setElapsed] = useState(0); const requestIntent = useComposeIntentStore((s) => s.request); useEffect(() => { if (!active) return; const start = Date.now(); let stopped = false; // Tick faster than 1s so the auto-stop lands within ~250ms of the cap, but // only re-render when the whole-second value actually changes. const interval = setInterval(() => { const seconds = Math.floor((Date.now() - start) / 1000); setElapsed((prev) => (prev === seconds ? prev : seconds)); if (seconds >= RECORDING_MAX_DURATION_SECONDS && !stopped) { stopped = true; requestIntent('stop'); } }, 250); return () => { clearInterval(interval); setElapsed(0); }; }, [active, requestIntent]); return { elapsed, isWarning: elapsed >= WARNING_AT_SECONDS }; } function RecordingTimer({ elapsed, isWarning, }: { elapsed: number; isWarning: boolean; }) { const minutes = Math.floor(elapsed / 60); const seconds = elapsed % 60; const display = `${minutes}:${seconds.toString().padStart(2, '0')}`; return (