@extends('layouts.app')

@section('title', 'AI 음성녹음')

@push('styles')
<style>
    .rec-pulse { animation: recPulse 1.5s ease-in-out infinite; }
    @keyframes recPulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.6;transform:scale(1.08)} }
    .fade-in { animation: fadeIn .25s ease-out; }
    @keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} }
    .slide-down { animation: slideDown .3s ease-out; }
    @keyframes slideDown { from{opacity:0;max-height:0} to{opacity:1;max-height:600px} }
    .progress-shine { background: linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent); animation: shine 1.5s infinite; }
    @keyframes shine { from{transform:translateX(-100%)} to{transform:translateX(100%)} }
    .drop-zone { border:2px dashed #d1d5db; transition:all .2s; }
    .drop-zone:hover,.drop-zone.active { border-color:#7c3aed; background:#f5f3ff; }
    .modal-backdrop { background:rgba(0,0,0,.45); backdrop-filter:blur(2px); }
    .ring-recording { box-shadow:0 0 0 6px rgba(239,68,68,.25); }
</style>
@endpush

@section('content')
<div id="ai-voice-recording-root"></div>
@endsection

@push('scripts')
@include('partials.react-cdn')
<script src="https://unpkg.com/lucide@0.469.0?v={{ time() }}"></script>
@verbatim
<script type="text/babel">
(function() {
const { useState, useEffect, useRef, useCallback, useMemo } = React;

const CSRF = document.querySelector('meta[name="csrf-token"]')?.content;
const API = '/system/ai-voice-recording';

// ─── fetch helpers ───
const http = {
    h: () => ({ 'Content-Type':'application/json','X-CSRF-TOKEN':CSRF,'Accept':'application/json' }),
    async get(u){ const r=await fetch(u,{headers:this.h()}); if(!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); },
    async post(u,d){ const r=await fetch(u,{method:'POST',headers:this.h(),body:JSON.stringify(d)}); if(!r.ok){const e=await r.json().catch(()=>({})); throw new Error(e.message||`HTTP ${r.status}`);} return r.json(); },
    async del(u){ const r=await fetch(u,{method:'DELETE',headers:this.h()}); if(!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); },
    async upload(u,fd){ const r=await fetch(u,{method:'POST',headers:{'X-CSRF-TOKEN':CSRF,'Accept':'application/json'},body:fd}); if(!r.ok){const e=await r.json().catch(()=>({})); throw new Error(e.message||`HTTP ${r.status}`);} return r.json(); },
};

// ─── Lucide icon helper ───
const kebabToPascal = (s) => s.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
const iconAliases = {'check-circle':'CircleCheck','alert-circle':'CircleAlert','alert-triangle':'TriangleAlert'};
const getIconDef = (name) => {
    if (iconAliases[name] && lucide[iconAliases[name]]) return lucide[iconAliases[name]];
    const p = kebabToPascal(name);
    if (lucide[p]) return lucide[p];
    if (lucide.icons && lucide.icons[name]) return lucide.icons[name];
    return null;
};
const Icon = ({ name, className = 'w-5 h-5', ...p }) => {
    const ref = useRef(null);
    useEffect(() => {
        const def = getIconDef(name);
        if (ref.current && def) {
            ref.current.innerHTML = '';
            const s = document.createElementNS("http://www.w3.org/2000/svg","svg"); const [,da={},ch=[]]=def; Object.entries(da).forEach(([k,v])=>s.setAttribute(k,String(v))); (ch||[]).forEach(([t,a={}])=>{const e=document.createElementNS("http://www.w3.org/2000/svg",t);Object.entries(a).forEach(([k,v])=>e.setAttribute(k,String(v)));s.appendChild(e);});
            s.setAttribute('class', className);
            ref.current.appendChild(s);
        }
    }, [name, className]);
    return <span ref={ref} className="inline-flex items-center justify-center" {...p} />;
};

// ─── formatters ───
const fmtTime = s => `${String(Math.floor(s/60)).padStart(2,'0')}:${String(s%60).padStart(2,'0')}`;
const fmtDate = d => d ? new Date(d).toLocaleString('ko-KR',{year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}) : '-';

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ToastContainer
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function ToastContainer({ items, onRemove }) {
    return (
        <div className="fixed top-4 right-4 z-[60] space-y-2">
            {items.map(t => (
                <div key={t.id} className={`fade-in flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg text-white text-sm
                    ${t.type==='error'?'bg-red-500':t.type==='warning'?'bg-amber-500':'bg-emerald-500'}`}>
                    <Icon name={t.type==='error'?'alert-circle':t.type==='warning'?'alert-triangle':'check-circle'} className="w-4 h-4 flex-shrink-0" />
                    <span className="flex-1">{t.msg}</span>
                    <button onClick={()=>onRemove(t.id)} className="ml-1 opacity-70 hover:opacity-100"><Icon name="x" className="w-3.5 h-3.5" /></button>
                </div>
            ))}
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// StatusBadge
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function StatusBadge({ status, size = 'sm' }) {
    const m = {
        PENDING:    { l:'대기중', bg:'bg-gray-100 text-gray-600', dot:'bg-gray-400' },
        PROCESSING: { l:'분석중', bg:'bg-blue-50 text-blue-700', dot:'bg-blue-500' },
        COMPLETED:  { l:'완료',   bg:'bg-emerald-50 text-emerald-700', dot:'bg-emerald-500' },
        FAILED:     { l:'실패',   bg:'bg-red-50 text-red-600', dot:'bg-red-500' },
    };
    const s = m[status] || { l:status, bg:'bg-gray-100 text-gray-500', dot:'bg-gray-400' };
    const sz = size === 'sm' ? 'text-xs px-2 py-0.5' : 'text-sm px-2.5 py-1';
    return (
        <span className={`inline-flex items-center gap-1.5 rounded-full font-medium ${s.bg} ${sz}`}>
            <span className={`w-1.5 h-1.5 rounded-full ${s.dot} ${status==='PROCESSING'?'animate-pulse':''}`}/>
            {s.l}
        </span>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// RecorderWidget - Canvas 파형 + 원형 버튼
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function RecorderWidget({ onDone, categories }) {
    const [phase, setPhase] = useState('idle');        // idle | recording | paused | saving
    const [seconds, setSeconds] = useState(0);
    const [title, setTitle] = useState('');
    const [saveProgress, setSaveProgress] = useState(0);
    const [transcript, setTranscript] = useState('');
    const [interimTranscript, setInterimTranscript] = useState('');
    const [selectedTemplateId, setSelectedTemplateId] = useState('');

    const canvasRef = useRef(null);
    const mediaRef = useRef(null);
    const chunksRef = useRef([]);
    const streamRef = useRef(null);
    const analyserRef = useRef(null);
    const audioCtxRef = useRef(null);
    const timerRef = useRef(null);
    const rafRef = useRef(null);
    const recognitionRef = useRef(null);
    const confirmedRef = useRef([]);
    const accumulatedTextRef = useRef('');

    // Canvas 파형 그리기
    const drawWaveform = useCallback(() => {
        const canvas = canvasRef.current;
        const analyser = analyserRef.current;
        if (!canvas || !analyser) return;

        const ctx = canvas.getContext('2d');
        const W = canvas.width = canvas.offsetWidth * 2;
        const H = canvas.height = canvas.offsetHeight * 2;
        ctx.scale(2, 2);
        const w = W / 2, h = H / 2;

        const data = new Uint8Array(analyser.frequencyBinCount);
        analyser.getByteTimeDomainData(data);

        ctx.clearRect(0, 0, w, h);

        // 중앙 기준선
        ctx.strokeStyle = '#e5e7eb';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(0, h/2);
        ctx.lineTo(w, h/2);
        ctx.stroke();

        // 파형
        ctx.strokeStyle = '#7c3aed';
        ctx.lineWidth = 2;
        ctx.beginPath();
        const sliceW = w / data.length;
        for (let i = 0; i < data.length; i++) {
            const v = data[i] / 128.0;
            const y = v * h / 2;
            i === 0 ? ctx.moveTo(0, y) : ctx.lineTo(i * sliceW, y);
        }
        ctx.stroke();

        rafRef.current = requestAnimationFrame(drawWaveform);
    }, []);

    // Web Speech API - 실시간 음성인식 (세션 간 텍스트 누적 보존)
    const startSpeechRecognition = () => {
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        if (!SpeechRecognition) return;

        const recognition = new SpeechRecognition();
        recognition.lang = 'ko-KR';
        recognition.continuous = true;
        recognition.interimResults = true;
        recognition.maxAlternatives = 1;

        // 현재 세션의 확정 결과만 리셋 (누적 텍스트는 유지)
        confirmedRef.current = [];

        recognition.onresult = (event) => {
            let interim = '';
            for (let i = 0; i < event.results.length; i++) {
                const result = event.results[i];
                const text = result[0].transcript;
                if (result.isFinal) {
                    confirmedRef.current[i] = text;
                } else {
                    interim += text;
                }
            }
            // 이전 세션 누적 + 현재 세션 결합
            const sessionText = confirmedRef.current.filter(Boolean).join(' ');
            const fullText = accumulatedTextRef.current
                ? accumulatedTextRef.current + ' ' + sessionText
                : sessionText;
            setTranscript(fullText);
            setInterimTranscript(interim);
        };

        recognition.onerror = (event) => {
            if (event.error === 'no-speech' || event.error === 'aborted') return;
            console.warn('Speech recognition error:', event.error);
        };

        recognition.onend = () => {
            // 녹음 중이면 현재 세션 텍스트 누적 후 자동 재시작
            if (recognitionRef.current) {
                const sessionText = confirmedRef.current.filter(Boolean).join(' ');
                if (sessionText) {
                    accumulatedTextRef.current = accumulatedTextRef.current
                        ? accumulatedTextRef.current + ' ' + sessionText
                        : sessionText;
                }
                confirmedRef.current = [];
                // 재시작 지연으로 급속 재시작 루프 방지
                setTimeout(() => {
                    if (recognitionRef.current) {
                        try { recognitionRef.current.start(); } catch (e) {}
                    }
                }, 300);
            }
        };

        recognition.start();
        recognitionRef.current = recognition;
    };

    const stopSpeechRecognition = () => {
        if (recognitionRef.current) {
            // 현재 세션 텍스트를 누적에 저장
            const sessionText = confirmedRef.current.filter(Boolean).join(' ');
            if (sessionText) {
                accumulatedTextRef.current = accumulatedTextRef.current
                    ? accumulatedTextRef.current + ' ' + sessionText
                    : sessionText;
            }
            confirmedRef.current = [];

            const ref = recognitionRef.current;
            recognitionRef.current = null;
            // stop() 후 발생하는 이벤트로 인한 중복 방지
            ref.onresult = null;
            ref.onend = null;
            ref.onerror = null;
            try { ref.stop(); } catch (e) {}
        }
    };

    const startRecording = async () => {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            streamRef.current = stream;

            const audioCtx = new AudioContext();
            audioCtxRef.current = audioCtx;
            const source = audioCtx.createMediaStreamSource(stream);
            const analyser = audioCtx.createAnalyser();
            analyser.fftSize = 2048;
            source.connect(analyser);
            analyserRef.current = analyser;

            const mr = new MediaRecorder(stream, {
                mimeType: MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm'
            });
            mediaRef.current = mr;
            chunksRef.current = [];

            mr.ondataavailable = e => { if (e.data.size > 0) chunksRef.current.push(e.data); };
            mr.start(1000);

            setPhase('recording');
            setSeconds(0);
            setTranscript('');
            setInterimTranscript('');
            accumulatedTextRef.current = '';
            timerRef.current = setInterval(() => setSeconds(s => s + 1), 1000);
            drawWaveform();
            startSpeechRecognition();
        } catch {
            alert('마이크 접근 권한이 필요합니다.\n브라우저 주소창의 자물쇠 아이콘에서 마이크를 허용해주세요.');
        }
    };

    const pauseRecording = () => {
        if (mediaRef.current?.state === 'recording') {
            mediaRef.current.pause();
            clearInterval(timerRef.current);
            if (rafRef.current) cancelAnimationFrame(rafRef.current);
            stopSpeechRecognition();
            setPhase('paused');
        }
    };

    const resumeRecording = () => {
        if (mediaRef.current?.state === 'paused') {
            mediaRef.current.resume();
            timerRef.current = setInterval(() => setSeconds(s => s + 1), 1000);
            drawWaveform();
            startSpeechRecognition();
            setPhase('recording');
        }
    };

    const stopAndSave = () => {
        if (!mediaRef.current || mediaRef.current.state === 'inactive') return;

        clearInterval(timerRef.current);
        if (rafRef.current) cancelAnimationFrame(rafRef.current);
        stopSpeechRecognition();

        const duration = seconds;

        mediaRef.current.onstop = () => {
            const blob = new Blob(chunksRef.current, { type: 'audio/webm' });
            const reader = new FileReader();
            reader.onloadend = async () => {
                setPhase('saving');
                setSaveProgress(0);

                // 프로그레스 시뮬레이션
                const iv = setInterval(() => setSaveProgress(p => Math.min(p + Math.random()*12, 90)), 300);

                try {
                    const storeData = { title: title || '무제 음성녹음' };
                    if (selectedTemplateId) storeData.interview_template_id = parseInt(selectedTemplateId);
                    const create = await http.post(API, storeData);
                    if (!create.success) throw new Error(create.message);

                    setSaveProgress(40);

                    // 비동기 처리 시작
                    http.post(`${API}/${create.data.id}/process`, {
                        audio: reader.result,
                        duration,
                    }).catch(() => {});

                    clearInterval(iv);
                    setSaveProgress(100);

                    setTimeout(() => {
                        cleanup();
                        setPhase('idle');
                        setSeconds(0);
                        setTitle('');
                        setSaveProgress(0);
                        setTranscript('');
                        setInterimTranscript('');
                        setSelectedTemplateId('');
                        onDone(create.data.id);
                    }, 600);
                } catch (err) {
                    clearInterval(iv);
                    alert('저장 실패: ' + err.message);
                    setPhase('idle');
                    setSaveProgress(0);
                }
            };
            reader.readAsDataURL(blob);
        };

        mediaRef.current.stop();
        streamRef.current?.getTracks().forEach(t => t.stop());
    };

    const cancelRecording = () => {
        clearInterval(timerRef.current);
        if (rafRef.current) cancelAnimationFrame(rafRef.current);
        stopSpeechRecognition();
        if (mediaRef.current && mediaRef.current.state !== 'inactive') {
            mediaRef.current.onstop = null;
            mediaRef.current.ondataavailable = null;
            mediaRef.current.stop();
        }
        cleanup();
        setPhase('idle');
        setSeconds(0);
        setTranscript('');
        setInterimTranscript('');
        accumulatedTextRef.current = '';
    };

    const cleanup = () => {
        streamRef.current?.getTracks().forEach(t => t.stop());
        audioCtxRef.current?.close().catch(()=>{});
    };

    useEffect(() => () => { stopSpeechRecognition(); cleanup(); clearInterval(timerRef.current); if(rafRef.current) cancelAnimationFrame(rafRef.current); }, []);

    const isActive = phase === 'recording' || phase === 'paused';

    return (
        <div className="bg-white border border-gray-200 rounded-xl overflow-hidden relative fade-in">
            {/* 저장중 오버레이 */}
            {phase === 'saving' && (
                <div className="absolute inset-0 z-20 bg-white/95 flex flex-col items-center justify-center gap-4">
                    <div className="p-4 bg-purple-100 rounded-full">
                        <Icon name="cloud-upload" className="w-8 h-8 text-purple-600 animate-pulse" />
                    </div>
                    <div className="text-center">
                        <p className="font-medium text-gray-800">녹음을 저장하고 있습니다</p>
                        <p className="text-sm text-gray-500 mt-1">잠시만 기다려주세요...</p>
                    </div>
                    <div className="w-48 bg-gray-200 rounded-full h-2 overflow-hidden">
                        <div className="h-full bg-purple-600 rounded-full transition-all duration-300 relative"
                             style={{ width: `${saveProgress}%` }}>
                            <div className="absolute inset-0 progress-shine" />
                        </div>
                    </div>
                </div>
            )}

            {/* 헤더 */}
            <div className="flex items-center justify-between px-5 py-3 border-b border-gray-100">
                <div className="flex items-center gap-2">
                    <Icon name="mic" className="w-4 h-4 text-purple-600" />
                    <span className="font-semibold text-gray-800 text-sm">음성 녹음</span>
                    {phase === 'recording' && (
                        <span className="flex items-center gap-1 px-2 py-0.5 bg-red-100 text-red-600 text-xs font-medium rounded-full">
                            <span className="w-1.5 h-1.5 bg-red-500 rounded-full animate-pulse" />
                            녹음 중
                        </span>
                    )}
                    {phase === 'paused' && (
                        <span className="px-2 py-0.5 bg-amber-100 text-amber-600 text-xs font-medium rounded-full">일시정지</span>
                    )}
                </div>
                {!isActive && phase !== 'saving' && (
                    <button onClick={cancelRecording} className="text-gray-400 hover:text-gray-600 transition-colors">
                        <Icon name="x" className="w-4 h-4" />
                    </button>
                )}
            </div>

            <div className="px-5 py-4 space-y-4">
                {/* 제목 + 카테고리 입력 */}
                {!isActive && phase !== 'saving' && (
                    <div className="space-y-2">
                        <input type="text" value={title} onChange={e => setTitle(e.target.value)}
                            placeholder="녹음 제목을 입력하세요 (선택)"
                            className="w-full text-sm border border-gray-200 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:border-purple-400 transition" />
                        {categories && categories.length > 0 && (
                            <select value={selectedTemplateId} onChange={e => setSelectedTemplateId(e.target.value)}
                                className="w-full text-sm border border-gray-200 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:border-purple-400 transition text-gray-600 bg-white">
                                <option value="">시나리오 템플릿 선택 (선택)</option>
                                {categories.map(cat => (
                                    <optgroup key={cat.id} label={cat.name}>
                                        {(cat.templates || []).map(tpl => (
                                            <option key={tpl.id} value={tpl.id}>{tpl.title || tpl.name}</option>
                                        ))}
                                    </optgroup>
                                ))}
                            </select>
                        )}
                    </div>
                )}

                {/* Canvas 파형 */}
                <div className="relative bg-gray-50 rounded-lg overflow-hidden" style={{ height: '80px' }}>
                    <canvas ref={canvasRef} className="w-full h-full" />
                    {isActive && (
                        <div className="absolute top-2 right-2 flex items-center gap-1.5 bg-red-500 text-white px-2 py-0.5 rounded-full text-xs font-medium">
                            <span className="w-1.5 h-1.5 bg-white rounded-full animate-pulse" />
                            {fmtTime(seconds)}
                        </div>
                    )}
                    {phase === 'idle' && (
                        <div className="absolute inset-0 flex items-center justify-center">
                            <span className="text-gray-400 text-sm">마이크 버튼을 눌러 녹음을 시작하세요</span>
                        </div>
                    )}
                </div>

                {/* 실시간 음성인식 텍스트 */}
                {isActive && (
                    <div className="bg-gray-50 border border-gray-200 rounded-lg p-3 min-h-[80px] max-h-[200px] overflow-y-auto">
                        <div className="flex items-center justify-between mb-1.5">
                            <span className="text-xs font-medium text-gray-500 flex items-center gap-1">
                                <Icon name="message-square" className="w-3 h-3" />
                                실시간 음성인식
                            </span>
                            <span className="text-xs text-gray-400">{(transcript + ' ' + interimTranscript).trim().length}자</span>
                        </div>
                        <div className="text-sm text-gray-700 leading-relaxed whitespace-pre-wrap">
                            {transcript}
                            {interimTranscript && (
                                <span className="text-purple-500 italic">{transcript ? ' ' : ''}{interimTranscript}</span>
                            )}
                            {!transcript && !interimTranscript && (
                                <span className="text-gray-400 italic">말씀하시면 여기에 텍스트가 표시됩니다...</span>
                            )}
                        </div>
                    </div>
                )}

                {/* 타이머 (녹음중에만) */}
                {isActive && (
                    <div className="text-center">
                        <span className="text-3xl font-mono font-bold text-gray-800 tracking-wider">{fmtTime(seconds)}</span>
                    </div>
                )}

                {/* 컨트롤 버튼 */}
                <div className="flex items-center justify-center gap-4">
                    {phase === 'idle' && (
                        <button onClick={startRecording}
                            className="flex items-center justify-center w-16 h-16 rounded-full bg-purple-600 hover:bg-purple-700 text-white shadow-lg transition-all transform hover:scale-105 active:scale-95">
                            <Icon name="mic" className="w-7 h-7" />
                        </button>
                    )}

                    {phase === 'recording' && (
                        <>
                            <button onClick={pauseRecording}
                                className="flex items-center justify-center w-11 h-11 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 transition-all">
                                <Icon name="pause" className="w-5 h-5" />
                            </button>
                            <button onClick={stopAndSave}
                                className="flex items-center justify-center w-16 h-16 rounded-full bg-red-500 hover:bg-red-600 text-white shadow-lg ring-recording transition-all transform hover:scale-105 active:scale-95 rec-pulse">
                                <Icon name="square" className="w-6 h-6" />
                            </button>
                            <button onClick={cancelRecording}
                                className="flex items-center justify-center w-11 h-11 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 transition-all">
                                <Icon name="x" className="w-5 h-5" />
                            </button>
                        </>
                    )}

                    {phase === 'paused' && (
                        <>
                            <button onClick={resumeRecording}
                                className="flex items-center justify-center w-11 h-11 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 transition-all">
                                <Icon name="play" className="w-5 h-5" />
                            </button>
                            <button onClick={stopAndSave}
                                className="flex items-center justify-center w-16 h-16 rounded-full bg-emerald-500 hover:bg-emerald-600 text-white shadow-lg transition-all transform hover:scale-105 active:scale-95">
                                <Icon name="check" className="w-7 h-7" />
                            </button>
                            <button onClick={cancelRecording}
                                className="flex items-center justify-center w-11 h-11 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 transition-all">
                                <Icon name="x" className="w-5 h-5" />
                            </button>
                        </>
                    )}
                </div>

                {/* 안내 */}
                <p className="text-center text-xs text-gray-400">
                    {phase === 'idle' && '보라색 버튼을 눌러 녹음을 시작합니다'}
                    {phase === 'recording' && '빨간 정지 버튼을 누르면 녹음이 저장됩니다'}
                    {phase === 'paused' && '재생 버튼으로 이어서 녹음하거나, 체크 버튼으로 저장합니다'}
                </p>
            </div>
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// FileUploadWidget
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function FileUploadWidget({ onDone, onCancel, categories }) {
    const [title, setTitle] = useState('');
    const [file, setFile] = useState(null);
    const [selectedTemplateId, setSelectedTemplateId] = useState('');
    const [dragOver, setDragOver] = useState(false);
    const [uploading, setUploading] = useState(false);
    const [progress, setProgress] = useState(0);
    const inputRef = useRef(null);

    const handleDrop = e => { e.preventDefault(); setDragOver(false); if(e.dataTransfer.files[0]) setFile(e.dataTransfer.files[0]); };

    const submit = async () => {
        if (!file) return;
        setUploading(true);
        setProgress(0);
        const iv = setInterval(() => setProgress(p => Math.min(p + Math.random()*10, 90)), 400);
        try {
            const fd = new FormData();
            fd.append('audio_file', file);
            fd.append('title', title || file.name.replace(/\.[^.]+$/, ''));
            if (selectedTemplateId) fd.append('interview_template_id', selectedTemplateId);
            const res = await http.upload(`${API}/upload`, fd);
            clearInterval(iv);
            setProgress(100);
            setTimeout(() => onDone(res), 400);
        } catch (err) {
            clearInterval(iv);
            setUploading(false);
            setProgress(0);
            alert('업로드 실패: ' + err.message);
        }
    };

    return (
        <div className="bg-white border border-gray-200 rounded-xl overflow-hidden relative fade-in">
            {uploading && (
                <div className="absolute inset-0 z-20 bg-white/95 flex flex-col items-center justify-center gap-4">
                    <div className="p-4 bg-blue-100 rounded-full">
                        <Icon name="cloud-upload" className="w-8 h-8 text-blue-600 animate-pulse" />
                    </div>
                    <p className="font-medium text-gray-800">파일을 업로드하고 분석 중입니다</p>
                    <p className="text-sm text-gray-500">음성 인식과 AI 분석이 완료되면 알려드립니다</p>
                    <div className="w-48 bg-gray-200 rounded-full h-2 overflow-hidden">
                        <div className="h-full bg-blue-600 rounded-full transition-all duration-300 relative" style={{width:`${progress}%`}}>
                            <div className="absolute inset-0 progress-shine" />
                        </div>
                    </div>
                </div>
            )}

            <div className="flex items-center justify-between px-5 py-3 border-b border-gray-100">
                <div className="flex items-center gap-2">
                    <Icon name="upload" className="w-4 h-4 text-blue-600" />
                    <span className="font-semibold text-gray-800 text-sm">파일 업로드</span>
                </div>
                <button onClick={onCancel} className="text-gray-400 hover:text-gray-600"><Icon name="x" className="w-4 h-4" /></button>
            </div>

            <div className="px-5 py-4 space-y-3">
                <input type="text" value={title} onChange={e=>setTitle(e.target.value)}
                    placeholder="제목 (비워두면 파일명 사용)"
                    className="w-full text-sm border border-gray-200 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-300 focus:border-blue-400 transition" />

                {categories && categories.length > 0 && (
                    <select value={selectedTemplateId} onChange={e => setSelectedTemplateId(e.target.value)}
                        className="w-full text-sm border border-gray-200 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-300 focus:border-blue-400 transition text-gray-600 bg-white">
                        <option value="">시나리오 템플릿 선택 (선택)</option>
                        {categories.map(cat => (
                            <optgroup key={cat.id} label={cat.name}>
                                {(cat.templates || []).map(tpl => (
                                    <option key={tpl.id} value={tpl.id}>{tpl.title || tpl.name}</option>
                                ))}
                            </optgroup>
                        ))}
                    </select>
                )}

                <div className={`drop-zone rounded-lg p-8 text-center cursor-pointer ${dragOver?'active':''}`}
                    onDragOver={e=>{e.preventDefault();setDragOver(true)}}
                    onDragLeave={()=>setDragOver(false)}
                    onDrop={handleDrop}
                    onClick={()=>inputRef.current?.click()}>
                    <input ref={inputRef} type="file" accept=".webm,.wav,.mp3,.ogg,.m4a,.mp4" className="hidden"
                        onChange={e=>setFile(e.target.files[0])} />
                    {file ? (
                        <div className="space-y-2">
                            <div className="inline-flex items-center justify-center w-12 h-12 bg-blue-100 rounded-full">
                                <Icon name="file-audio" className="w-6 h-6 text-blue-600" />
                            </div>
                            <p className="font-medium text-gray-800 text-sm">{file.name}</p>
                            <p className="text-xs text-gray-500">{(file.size/1024/1024).toFixed(1)} MB</p>
                            <button onClick={e=>{e.stopPropagation();setFile(null)}}
                                className="text-xs text-red-500 hover:text-red-700">파일 변경</button>
                        </div>
                    ) : (
                        <div className="space-y-2">
                            <div className="inline-flex items-center justify-center w-12 h-12 bg-gray-100 rounded-full">
                                <Icon name="upload" className="w-6 h-6 text-gray-400" />
                            </div>
                            <p className="text-sm text-gray-500">파일을 드래그하거나 <span className="text-purple-600 font-medium">클릭</span>하여 선택</p>
                            <p className="text-xs text-gray-400">webm, wav, mp3, ogg, m4a, mp4 (최대 100MB)</p>
                        </div>
                    )}
                </div>

                <div className="flex justify-end">
                    <button onClick={submit} disabled={!file || uploading}
                        className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 text-white text-sm font-medium rounded-lg transition">
                        <Icon name="sparkles" className="w-4 h-4" /> 업로드 및 AI 분석
                    </button>
                </div>
            </div>
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// RecordingCard - 목록 아이템
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function RecordingCard({ item, onView, onDelete, onDownload }) {
    return (
        <div className="flex items-center gap-4 px-4 py-3 hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-b-0 group">
            {/* 아이콘 */}
            <div className={`flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center
                ${item.status==='COMPLETED'?'bg-emerald-100':item.status==='PROCESSING'?'bg-blue-100':item.status==='FAILED'?'bg-red-100':'bg-gray-100'}`}>
                <Icon name={item.status==='COMPLETED'?'check-circle':item.status==='PROCESSING'?'loader':item.status==='FAILED'?'alert-circle':'clock'}
                    className={`w-5 h-5 ${item.status==='COMPLETED'?'text-emerald-600':item.status==='PROCESSING'?'text-blue-600 animate-spin':item.status==='FAILED'?'text-red-500':'text-gray-400'}`} />
            </div>

            {/* 정보 */}
            <div className="flex-1 min-w-0">
                <div className="flex items-center gap-2">
                    <span className="font-medium text-gray-800 text-sm truncate cursor-pointer hover:text-purple-600 transition-colors"
                          onClick={()=>onView(item.id)}>{item.title}</span>
                    <StatusBadge status={item.status} />
                </div>
                <div className="flex items-center gap-3 mt-0.5 text-xs text-gray-400">
                    {item.duration_seconds > 0 && (
                        <span className="flex items-center gap-1"><Icon name="clock" className="w-3 h-3" />{fmtTime(item.duration_seconds)}</span>
                    )}
                    <span>{item.user?.name || '-'}</span>
                    <span>{fmtDate(item.created_at)}</span>
                </div>
            </div>

            {/* 액션 */}
            <div className="flex items-center gap-1">
                {item.status === 'COMPLETED' && (
                    <button onClick={()=>onView(item.id)}
                        className="px-2.5 py-1 text-xs font-medium text-purple-700 bg-purple-50 hover:bg-purple-100 rounded-lg transition-colors">
                        결과 보기
                    </button>
                )}
                {item.audio_file_path && (
                    <button onClick={()=>onDownload(item.id)}
                        className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
                        title="음성파일 다운로드">
                        <Icon name="download" className="w-4 h-4" />
                    </button>
                )}
                <button onClick={()=>onDelete(item)}
                    className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors"
                    title="삭제">
                    <Icon name="trash-2" className="w-4 h-4" />
                </button>
            </div>
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DetailModal
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function DetailModal({ data, onClose }) {
    const [copied, setCopied] = useState(null);
    const [tab, setTab] = useState('analysis');

    const copy = (text, key) => {
        navigator.clipboard.writeText(text).then(() => { setCopied(key); setTimeout(()=>setCopied(null), 2000); });
    };

    if (!data) return null;

    return (
        <div className="modal-backdrop fixed inset-0 z-50 flex items-center justify-center p-4" onClick={onClose}>
            <div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[85vh] flex flex-col fade-in" onClick={e=>e.stopPropagation()}>
                {/* Header */}
                <div className="px-6 py-4 border-b border-gray-100">
                    <div className="flex items-start justify-between">
                        <div>
                            <h3 className="font-bold text-lg text-gray-900">{data.title}</h3>
                            <div className="flex items-center gap-2 mt-1.5">
                                <StatusBadge status={data.status} />
                                {data.duration_seconds > 0 && (
                                    <span className="text-xs text-gray-500 flex items-center gap-1">
                                        <Icon name="clock" className="w-3 h-3" /> {fmtTime(data.duration_seconds)}
                                    </span>
                                )}
                                <span className="text-xs text-gray-400">{fmtDate(data.created_at)}</span>
                            </div>
                        </div>
                        <button onClick={onClose} className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors">
                            <Icon name="x" className="w-5 h-5 text-gray-400" />
                        </button>
                    </div>

                    {/* 탭 */}
                    {data.status === 'COMPLETED' && (
                        <div className="flex gap-1 mt-3 bg-gray-100 rounded-lg p-1">
                            <button onClick={()=>setTab('analysis')}
                                className={`flex-1 text-xs font-medium py-1.5 rounded-md transition-colors ${tab==='analysis'?'bg-white text-purple-700 shadow-sm':'text-gray-500 hover:text-gray-700'}`}>
                                AI 분석 결과
                            </button>
                            <button onClick={()=>setTab('transcript')}
                                className={`flex-1 text-xs font-medium py-1.5 rounded-md transition-colors ${tab==='transcript'?'bg-white text-purple-700 shadow-sm':'text-gray-500 hover:text-gray-700'}`}>
                                녹취록 원문
                            </button>
                        </div>
                    )}
                </div>

                {/* 오디오 플레이어 */}
                {data.audio_file_path && (
                    <div className="px-6 py-3 bg-gray-50 border-b border-gray-100">
                        <div className="flex items-center gap-3">
                            <Icon name="volume-2" className="w-4 h-4 text-purple-500 flex-shrink-0" />
                            <audio controls className="flex-1 h-9" style={{minWidth:0}}>
                                <source src={`${API}/${data.id}/download?inline=1`} type="audio/webm" />
                            </audio>
                            <a href={`${API}/${data.id}/download`} download
                                className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-100 rounded-lg transition-colors flex-shrink-0"
                                title="파일 다운로드">
                                <Icon name="download" className="w-4 h-4" />
                            </a>
                        </div>
                    </div>
                )}

                {/* Body */}
                <div className="flex-1 overflow-y-auto px-6 py-4">
                    {data.status === 'PROCESSING' && (
                        <div className="flex flex-col items-center justify-center py-12 gap-3">
                            <div className="p-4 bg-blue-50 rounded-full"><Icon name="loader" className="w-8 h-8 text-blue-500 animate-spin" /></div>
                            <p className="font-medium text-gray-700">음성을 분석하고 있습니다</p>
                            <p className="text-sm text-gray-400">STT 변환 및 AI 분석이 진행 중입니다...</p>
                        </div>
                    )}

                    {data.status === 'FAILED' && (
                        <div className="flex flex-col items-center justify-center py-12 gap-3">
                            <div className="p-4 bg-red-50 rounded-full"><Icon name="alert-circle" className="w-8 h-8 text-red-400" /></div>
                            <p className="font-medium text-gray-700">처리 중 오류가 발생했습니다</p>
                            <p className="text-sm text-gray-400">다시 시도하거나 관리자에게 문의해주세요</p>
                        </div>
                    )}

                    {data.status === 'PENDING' && (
                        <div className="flex flex-col items-center justify-center py-12 gap-3">
                            <div className="p-4 bg-gray-50 rounded-full"><Icon name="clock" className="w-8 h-8 text-gray-400" /></div>
                            <p className="font-medium text-gray-700">처리 대기 중입니다</p>
                        </div>
                    )}

                    {data.status === 'COMPLETED' && tab === 'analysis' && data.analysis_text && (
                        <div>
                            <div className="flex justify-end mb-2">
                                <button onClick={()=>copy(data.analysis_text,'a')}
                                    className="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-purple-600 transition-colors">
                                    <Icon name={copied==='a'?'check':'copy'} className="w-3.5 h-3.5" />
                                    {copied==='a'?'복사됨':'복사'}
                                </button>
                            </div>
                            <div className="prose prose-sm max-w-none text-gray-700 whitespace-pre-wrap leading-relaxed">
                                {data.analysis_text}
                            </div>
                        </div>
                    )}

                    {data.status === 'COMPLETED' && tab === 'transcript' && data.transcript_text && (
                        <div>
                            <div className="flex justify-end mb-2">
                                <button onClick={()=>copy(data.transcript_text,'t')}
                                    className="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-purple-600 transition-colors">
                                    <Icon name={copied==='t'?'check':'copy'} className="w-3.5 h-3.5" />
                                    {copied==='t'?'복사됨':'복사'}
                                </button>
                            </div>
                            <div className="bg-gray-50 rounded-lg p-4 text-sm text-gray-700 whitespace-pre-wrap leading-relaxed">
                                {data.transcript_text}
                            </div>
                        </div>
                    )}
                </div>

                {/* Footer */}
                <div className="px-6 py-3 border-t border-gray-100 bg-gray-50 rounded-b-2xl flex justify-end">
                    <button onClick={onClose}
                        className="px-4 py-1.5 text-sm font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-200 rounded-lg transition-colors">
                        닫기
                    </button>
                </div>
            </div>
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DeleteConfirmModal
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function DeleteConfirmModal({ item, onConfirm, onCancel, deleting }) {
    if (!item) return null;
    return (
        <div className="modal-backdrop fixed inset-0 z-50 flex items-center justify-center p-4" onClick={onCancel}>
            <div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm fade-in" onClick={e=>e.stopPropagation()}>
                <div className="px-6 pt-6 pb-2 text-center">
                    <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-red-100 flex items-center justify-center">
                        <Icon name="trash-2" className="w-7 h-7 text-red-500" />
                    </div>
                    <h3 className="text-lg font-bold text-gray-900 mb-1">녹음 삭제</h3>
                    <p className="text-sm text-gray-500">
                        <span className="font-medium text-gray-700">"{item.title}"</span>을(를) 삭제하시겠습니까?
                    </p>
                    <p className="text-xs text-gray-400 mt-1">삭제된 녹음과 음성 파일은 복구할 수 없습니다.</p>
                </div>
                <div className="px-6 py-4 flex gap-2">
                    <button onClick={onCancel} disabled={deleting}
                        className="flex-1 px-4 py-2.5 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 disabled:opacity-50 rounded-lg transition-colors">
                        취소
                    </button>
                    <button onClick={()=>onConfirm(item.id)} disabled={deleting}
                        className="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-red-500 hover:bg-red-600 disabled:opacity-50 rounded-lg transition-colors inline-flex items-center justify-center gap-1.5">
                        {deleting ? (
                            <><Icon name="loader" className="w-4 h-4 animate-spin" /> 삭제 중...</>
                        ) : (
                            <><Icon name="trash-2" className="w-4 h-4" /> 삭제</>
                        )}
                    </button>
                </div>
            </div>
        </div>
    );
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// App
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function App() {
    const [items, setItems] = useState([]);
    const [paging, setPaging] = useState(null);
    const [loading, setLoading] = useState(true);
    const [search, setSearch] = useState('');
    const [status, setStatus] = useState('');
    const [page, setPage] = useState(1);

    const [mode, setMode] = useState('list'); // list | record | upload
    const [detail, setDetail] = useState(null);
    const [toasts, setToasts] = useState([]);
    const [categories, setCategories] = useState([]);
    const [deleteTarget, setDeleteTarget] = useState(null);
    const [deleting, setDeleting] = useState(false);
    const pollingRef = useRef({});
    const toastId = useRef(0);

    // 카테고리 목록 로드
    useEffect(() => {
        http.get(`${API}/categories`).then(r => {
            if (r.success) setCategories(r.data || []);
        }).catch(() => {});
    }, []);

    const toast = (msg, type='success') => {
        const id = ++toastId.current;
        setToasts(ts => [...ts, { id, msg, type }]);
        setTimeout(() => setToasts(ts => ts.filter(t => t.id !== id)), 4000);
    };

    // Fetch list
    const fetchList = useCallback(async (silent) => {
        if (!silent) setLoading(true);
        try {
            const p = new URLSearchParams();
            if (search) p.set('search', search);
            if (status) p.set('status', status);
            p.set('per_page', '10');
            p.set('page', page);
            const res = await http.get(`${API}/list?${p}`);
            if (res.success) {
                setItems(res.data.data || []);
                setPaging({ cur: res.data.current_page, last: res.data.last_page, total: res.data.total });
                (res.data.data || []).forEach(r => { if(r.status==='PROCESSING') pollItem(r.id); });
            }
        } catch (e) { toast('목록 조회 실패','error'); }
        setLoading(false);
    }, [search, status, page]);

    useEffect(() => { fetchList(); }, [fetchList]);

    // Polling
    const pollItem = id => {
        if (pollingRef.current[id]) return;
        pollingRef.current[id] = setInterval(async () => {
            try {
                const r = await http.get(`${API}/${id}/status`);
                if (r.success && !r.data.is_processing) {
                    clearInterval(pollingRef.current[id]);
                    delete pollingRef.current[id];
                    fetchList(true);
                    if (r.data.is_completed) toast('음성 분석이 완료되었습니다');
                    if (r.data.status === 'FAILED') toast('분석 처리 실패','error');
                }
            } catch { clearInterval(pollingRef.current[id]); delete pollingRef.current[id]; }
        }, 5000);
    };

    useEffect(() => () => Object.values(pollingRef.current).forEach(clearInterval), []);

    // Handlers
    const handleRecordDone = (id) => {
        setMode('list');
        toast('녹음이 저장되었습니다. AI 분석을 시작합니다...');
        fetchList(true);
        pollItem(id);
    };

    const handleUploadDone = (res) => {
        setMode('list');
        if (res.success) {
            toast(res.message || '업로드 완료');
            fetchList(true);
            if (res.data?.id) pollItem(res.data.id);
        } else {
            toast(res.message || '업로드 실패','error');
        }
    };

    const handleView = async id => {
        try {
            const r = await http.get(`${API}/${id}`);
            if (r.success) setDetail(r.data);
        } catch { toast('조회 실패','error'); }
    };

    const handleDeleteClick = (item) => {
        setDeleteTarget(item);
    };

    const handleDeleteConfirm = async (id) => {
        setDeleting(true);
        try {
            await http.del(`${API}/${id}`);
            toast('삭제되었습니다');
            setDeleteTarget(null);
            fetchList(true);
        } catch { toast('삭제 실패','error'); }
        setDeleting(false);
    };

    const handleDownload = (id) => {
        window.open(`${API}/${id}/download`, '_blank');
    };

    const completedCount = items.filter(i => i.status==='COMPLETED').length;
    const processingCount = items.filter(i => i.status==='PROCESSING').length;

    return (
        <div className="py-2 px-4">
            <ToastContainer items={toasts} onRemove={id => setToasts(ts => ts.filter(t => t.id !== id))} />

            {/* 페이지 헤더 + 버튼 + 검색 한 행 */}
            <div className="flex items-center justify-between mb-5 flex-wrap gap-3">
                <div>
                    <h1 className="text-xl font-bold text-gray-900">AI 음성녹음</h1>
                    <p className="text-sm text-gray-500 mt-0.5">음성을 녹음하거나 파일을 업로드하면 AI가 자동으로 분석합니다</p>
                </div>
                <div className="flex items-center gap-2">
                    {paging && <span className="text-sm text-gray-500 whitespace-nowrap">전체 <span className="font-semibold text-gray-800">{paging.total}</span>건</span>}
                    {processingCount > 0 && (
                        <span className="flex items-center gap-1 text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded-full whitespace-nowrap">
                            <Icon name="loader" className="w-3 h-3 animate-spin" /> {processingCount}건 분석중
                        </span>
                    )}
                    <div className="relative">
                        <Icon name="search" className="w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
                        <input type="text" value={search}
                            onChange={e=>{setSearch(e.target.value);setPage(1)}}
                            placeholder="검색..."
                            className="pl-10 pr-3 py-1.5 text-sm border border-gray-200 rounded-lg w-48 focus:outline-none focus:ring-2 focus:ring-purple-200 focus:border-purple-400 transition" />
                    </div>
                    <select value={status} onChange={e=>{setStatus(e.target.value);setPage(1)}}
                        className="text-sm border border-gray-200 rounded-lg px-2.5 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-200 text-gray-600 bg-white">
                        <option value="">전체</option>
                        <option value="COMPLETED">완료</option>
                        <option value="PROCESSING">분석중</option>
                        <option value="PENDING">대기중</option>
                        <option value="FAILED">실패</option>
                    </select>
                    <button onClick={()=>fetchList()} className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
                        <Icon name="refresh-cw" className="w-4 h-4" />
                    </button>
                    <button onClick={() => setMode(mode==='record'?'list':'record')}
                        className={`inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-all shadow-sm
                            ${mode==='record'
                                ? 'bg-red-500 hover:bg-red-600 text-white'
                                : 'bg-purple-600 hover:bg-purple-700 text-white'}`}>
                        <Icon name="mic" className="w-4 h-4" />
                        {mode==='record' ? '녹음 닫기' : '새 녹음'}
                    </button>
                    <button onClick={() => setMode(mode==='upload'?'list':'upload')}
                        className={`inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-all
                            ${mode==='upload'
                                ? 'bg-gray-200 text-gray-700'
                                : 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50'}`}>
                        <Icon name="upload" className="w-4 h-4" />
                        파일 업로드
                    </button>
                </div>
            </div>

            {/* 녹음/업로드 위젯 */}
            {mode === 'record' && (
                <div className="mb-5">
                    <RecorderWidget onDone={handleRecordDone} categories={categories} />
                </div>
            )}
            {mode === 'upload' && (
                <div className="mb-5">
                    <FileUploadWidget onDone={handleUploadDone} onCancel={()=>setMode('list')} categories={categories} />
                </div>
            )}

            {/* 목록 */}
            <div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
                {loading ? (
                    <div className="flex items-center justify-center py-16">
                        <Icon name="loader" className="w-6 h-6 text-gray-300 animate-spin" />
                    </div>
                ) : items.length === 0 ? (
                    <div className="flex flex-col items-center justify-center py-16 gap-3">
                        <div className="p-4 bg-gray-50 rounded-full">
                            <Icon name="mic-off" className="w-8 h-8 text-gray-300" />
                        </div>
                        <p className="text-gray-400 text-sm">녹음 데이터가 없습니다</p>
                        <button onClick={()=>setMode('record')}
                            className="text-sm text-purple-600 hover:text-purple-700 font-medium">
                            첫 번째 녹음 시작하기 &rarr;
                        </button>
                    </div>
                ) : (
                    <div>
                        {items.map(item => (
                            <RecordingCard key={item.id} item={item} onView={handleView} onDelete={handleDeleteClick} onDownload={handleDownload} />
                        ))}
                    </div>
                )}

                {/* 페이지네이션 */}
                {paging && paging.last > 1 && (
                    <div className="flex items-center justify-center py-3 border-t border-gray-100 gap-1">
                        <button onClick={()=>setPage(Math.max(1,page-1))} disabled={page===1}
                            className="p-1.5 text-gray-400 hover:text-gray-600 disabled:opacity-30 rounded transition-colors">
                            <Icon name="chevron-left" className="w-4 h-4" />
                        </button>
                        {Array.from({length:paging.last},(_,i)=>i+1).map(p => (
                            <button key={p} onClick={()=>setPage(p)}
                                className={`min-w-[28px] h-7 text-xs rounded-md font-medium transition-colors
                                    ${p===paging.cur?'bg-purple-600 text-white':'text-gray-500 hover:bg-gray-100'}`}>
                                {p}
                            </button>
                        ))}
                        <button onClick={()=>setPage(Math.min(paging.last,page+1))} disabled={page===paging.last}
                            className="p-1.5 text-gray-400 hover:text-gray-600 disabled:opacity-30 rounded transition-colors">
                            <Icon name="chevron-right" className="w-4 h-4" />
                        </button>
                    </div>
                )}
            </div>

            {/* 상세 모달 */}
            {detail && <DetailModal data={detail} onClose={()=>setDetail(null)} />}

            {/* 삭제 확인 모달 */}
            {deleteTarget && (
                <DeleteConfirmModal
                    item={deleteTarget}
                    onConfirm={handleDeleteConfirm}
                    onCancel={()=>setDeleteTarget(null)}
                    deleting={deleting}
                />
            )}
        </div>
    );
}

// Mount
const root = ReactDOM.createRoot(document.getElementById('ai-voice-recording-root'));
root.render(<App />);
})();
</script>
@endverbatim
@endpush
