/* global React, TextArea, FieldLabel */
const { useState, useRef, useEffect } = React;

// ─────────────────────────────────────────────────────────────
// Animated waveform — bars whose heights drift (visual only)
// ─────────────────────────────────────────────────────────────
function Waveform({ active, t, color }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 2, height: 28, flex: 1,
      opacity: active ? 1 : 0.35,
      transition: 'opacity 200ms',
    }}>
      {Array.from({ length: 32 }).map((_, i) => (
        <div key={i} style={{
          flex: 1, height: '100%', display: 'flex', alignItems: 'center',
        }}>
          <div style={{
            width: '100%', borderRadius: 2,
            background: color || t.primary,
            animation: active ? `wave 0.9s ${i * 0.04}s ease-in-out infinite alternate` : 'none',
            height: active ? '40%' : `${20 + ((i * 7) % 60)}%`,
            transition: 'height 200ms',
          }} />
        </div>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Real-recording helpers
// ─────────────────────────────────────────────────────────────
function pickMime() {
  if (typeof MediaRecorder === 'undefined' || !MediaRecorder.isTypeSupported) return '';
  const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/aac', 'audio/mpeg'];
  for (const c of candidates) { if (MediaRecorder.isTypeSupported(c)) return c; }
  return '';
}
function extForMime(mime) {
  if (!mime) return 'webm';
  if (mime.indexOf('mp4') > -1) return 'm4a';
  if (mime.indexOf('mpeg') > -1) return 'mp3';
  if (mime.indexOf('aac') > -1) return 'aac';
  return 'webm';
}
window.extForMime = extForMime;

// ─────────────────────────────────────────────────────────────
// VoiceRecorder — real MediaRecorder
// states: idle / recording / recorded / error
// value shape: { duration, blob, url, mimeType } | null
// ─────────────────────────────────────────────────────────────
function VoiceRecorder({ value, onChange, t, onMicError }) {
  const [state, setState] = useState(value ? 'recorded' : 'idle');
  const [elapsed, setElapsed] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [playPos, setPlayPos] = useState(0);

  const timerRef = useRef(null);
  const recRef = useRef(null);      // MediaRecorder
  const streamRef = useRef(null);   // MediaStream
  const chunksRef = useRef([]);
  const startTsRef = useRef(0);
  const audioRef = useRef(null);    // <audio> element for real playback

  // sync external value → state
  useEffect(() => {
    if (value && state !== 'recording') setState('recorded');
    else if (!value && state !== 'recording' && state !== 'error') setState('idle');
  }, [value]);

  // recording timer
  useEffect(() => {
    if (state === 'recording') {
      timerRef.current = setInterval(() => setElapsed(e => e + 0.1), 100);
    } else {
      clearInterval(timerRef.current);
    }
    return () => clearInterval(timerRef.current);
  }, [state]);

  // cleanup on unmount — stop any live recording + release the mic
  useEffect(() => () => {
    try { if (recRef.current && recRef.current.state !== 'inactive') recRef.current.stop(); } catch (e) {}
    if (streamRef.current) streamRef.current.getTracks().forEach(tr => tr.stop());
  }, []);

  const fmt = (s) => `${Math.floor(s / 60)}:${Math.floor(s % 60).toString().padStart(2, '0')}`;

  const start = async () => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      setState('error');
      if (onMicError) onMicError(new Error('getUserMedia unavailable'));
      return;
    }
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;
      const mime = pickMime();
      const rec = mime ? new MediaRecorder(stream, { mimeType: mime }) : new MediaRecorder(stream);
      chunksRef.current = [];
      rec.ondataavailable = (e) => { if (e.data && e.data.size) chunksRef.current.push(e.data); };
      rec.onstop = () => {
        const type = rec.mimeType || mime || 'audio/webm';
        const blob = new Blob(chunksRef.current, { type });
        const url = URL.createObjectURL(blob);
        const dur = Math.max((Date.now() - startTsRef.current) / 1000, 0.5);
        if (streamRef.current) { streamRef.current.getTracks().forEach(tr => tr.stop()); streamRef.current = null; }
        if (value && value.url) { try { URL.revokeObjectURL(value.url); } catch (e) {} }
        setState('recorded');
        onChange({ duration: dur, blob, url, mimeType: type });
      };
      recRef.current = rec;
      startTsRef.current = Date.now();
      setElapsed(0);
      rec.start();
      setState('recording');
    } catch (err) {
      setState('error');
      if (onMicError) onMicError(err);
    }
  };

  const stop = () => {
    try { if (recRef.current && recRef.current.state !== 'inactive') recRef.current.stop(); } catch (e) {}
  };

  const del = () => {
    if (value && value.url) { try { URL.revokeObjectURL(value.url); } catch (e) {} }
    setState('idle'); setElapsed(0); setPlayPos(0); setPlaying(false);
    onChange(null);
  };
  const reRecord = () => { setPlaying(false); setPlayPos(0); start(); };

  const togglePlay = () => {
    const a = audioRef.current;
    if (!a) return;
    if (playing) { a.pause(); setPlaying(false); }
    else { a.play().then(() => setPlaying(true)).catch(() => setPlaying(false)); }
  };

  // ── ERROR ────────────────────────────────────────────
  if (state === 'error') {
    return (
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, padding: 12,
        borderRadius: 14, background: t.surface, border: `1px dashed ${t.line}`,
      }}>
        <div style={{ flex: 1, fontFamily: 'Geist, system-ui, sans-serif', fontSize: 13, color: t.inkSoft, lineHeight: 1.4 }}>
          Microphone unavailable — type your answer below instead.
        </div>
        <button
          onClick={() => { setState('idle'); }}
          style={{
            background: 'transparent', border: `1px solid ${t.line}`, borderRadius: 100,
            padding: '6px 12px', fontFamily: 'Geist, system-ui, sans-serif', fontSize: 12,
            color: t.ink, cursor: 'pointer', flexShrink: 0,
          }}
        >Retry mic</button>
      </div>
    );
  }

  // ── IDLE ─────────────────────────────────────────────
  if (state === 'idle') {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <button
          onClick={start}
          style={{
            width: 48, height: 48, borderRadius: 24, border: 'none',
            background: t.primary, color: '#fff', cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            boxShadow: `0 4px 14px ${t.primary}55`,
            transition: 'transform 120ms',
          }}
          onMouseDown={(e) => e.currentTarget.style.transform = 'scale(0.94)'}
          onMouseUp={(e) => e.currentTarget.style.transform = 'scale(1)'}
          onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
        >
          <MicIcon />
        </button>
        <div>
          <div style={{
            fontFamily: 'Geist, system-ui, sans-serif', fontSize: 14,
            fontWeight: 600, color: t.ink,
          }}>
            Record answer
          </div>
          <div style={{
            fontFamily: 'Geist, system-ui, sans-serif', fontSize: 12,
            color: t.inkSoft, marginTop: 2,
          }}>
            Tap to start · no time limit
          </div>
        </div>
      </div>
    );
  }

  // ── RECORDING ────────────────────────────────────────
  if (state === 'recording') {
    return (
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: 12, borderRadius: 16,
        background: t.primarySoft, border: `1.5px solid ${t.primaryMid}`,
      }}>
        <button
          onClick={stop}
          style={{
            width: 44, height: 44, borderRadius: 22, border: 'none',
            background: t.primary, cursor: 'pointer', position: 'relative',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            flexShrink: 0,
          }}
        >
          <span style={{
            position: 'absolute', inset: -4, borderRadius: '50%',
            border: `2px solid ${t.primary}`, opacity: 0.5,
            animation: 'ringPulse 1.4s ease-out infinite',
          }} />
          <span style={{ width: 14, height: 14, borderRadius: 3, background: '#fff' }} />
        </button>
        <div style={{ flex: 1, minWidth: 0 }}>
          <Waveform active t={t} />
        </div>
        <div style={{
          fontFamily: 'Geist Mono, monospace', fontSize: 13,
          color: t.primary, fontWeight: 600, fontVariantNumeric: 'tabular-nums',
          flexShrink: 0, width: 42, textAlign: 'right',
        }}>
          {fmt(elapsed)}
        </div>
      </div>
    );
  }

  // ── RECORDED ─────────────────────────────────────────
  const dur = value?.duration || 0;
  const pct = dur > 0 ? Math.min((playPos / dur) * 100, 100) : 0;
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: 12, borderRadius: 16,
      background: t.surface, border: `1.5px solid ${t.primary}`,
    }}>
      {/* real audio element (hidden) */}
      <audio
        ref={audioRef}
        src={value?.url}
        style={{ display: 'none' }}
        onTimeUpdate={(e) => setPlayPos(e.target.currentTime)}
        onEnded={() => { setPlaying(false); setPlayPos(0); }}
      />
      <button
        onClick={togglePlay}
        style={{
          width: 40, height: 40, borderRadius: 20, border: 'none',
          background: t.primary, color: '#fff', cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          flexShrink: 0,
        }}
      >
        {playing ? (
          <svg width="14" height="14" viewBox="0 0 14 14"><rect x="3" y="2" width="3" height="10" rx="1" fill="#fff"/><rect x="8" y="2" width="3" height="10" rx="1" fill="#fff"/></svg>
        ) : (
          <svg width="14" height="14" viewBox="0 0 14 14"><path d="M4 2.5v9l7-4.5-7-4.5z" fill="#fff"/></svg>
        )}
      </button>
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 4 }}>
        <div style={{
          height: 3, borderRadius: 2, background: t.line, position: 'relative',
        }}>
          <div style={{
            position: 'absolute', top: 0, left: 0, bottom: 0,
            width: `${pct}%`, background: t.primary, borderRadius: 2,
            transition: playing ? 'width 120ms linear' : 'none',
          }} />
        </div>
        <div style={{
          display: 'flex', justifyContent: 'space-between',
          fontFamily: 'Geist Mono, monospace', fontSize: 11,
          color: t.inkSoft,
        }}>
          <span>{fmt(playing ? playPos : 0)}</span>
          <span>{fmt(dur)}</span>
        </div>
      </div>
      <div style={{ display: 'flex', gap: 4, flexShrink: 0 }}>
        <IconButton t={t} onClick={reRecord} title="Re-record">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
            <path d="M2 7a5 5 0 119 3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/>
            <path d="M11.5 7.5l-.5 2.5 2.5-.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </IconButton>
        <IconButton t={t} onClick={del} title="Delete" danger>
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
            <path d="M3 4h8M5 4v-.5A.5.5 0 015.5 3h3a.5.5 0 01.5.5V4M4.5 4l.5 7a1 1 0 001 1h3a1 1 0 001-1l.5-7" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </IconButton>
      </div>
    </div>
  );
}

function IconButton({ children, onClick, t, danger, title }) {
  return (
    <button
      onClick={onClick}
      title={title}
      style={{
        width: 32, height: 32, borderRadius: 16, border: 'none',
        background: 'transparent', cursor: 'pointer',
        color: danger ? t.danger : t.inkSoft,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      {children}
    </button>
  );
}

function MicIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <rect x="7" y="2" width="6" height="10" rx="3" fill="#fff"/>
      <path d="M4 9a6 6 0 0012 0M10 15v3M7 18h6" stroke="#fff" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

// ─────────────────────────────────────────────────────────────
// VoiceQuestionCard — question + recorder + text fallback
// value: { text: string, audio: {duration,blob,url,mimeType} | null }
// ─────────────────────────────────────────────────────────────
function VoiceQuestionCard({ question, value, onChange, t, index, mode = 'voice+text' }) {
  const v = value || { text: '', audio: null };
  const setText = (text) => onChange({ ...v, text });
  const setAudio = (audio) => onChange({ ...v, audio });

  const hasRecording = !!v.audio;
  const [showText, setShowText] = useState(!hasRecording && !!v.text);

  useEffect(() => { if (v.text) setShowText(true); }, [v.text]);

  // If the mic fails, force the text fallback open so the client is never stuck
  const handleMicError = () => setShowText(true);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div>
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 6,
        }}>
          <span style={{
            fontFamily: 'Geist Mono, monospace', fontSize: 10,
            color: t.primary, fontWeight: 500, letterSpacing: '0.05em',
          }}>
            {question.id}
          </span>
          {question.label && (
            <span style={{
              fontFamily: 'Geist Mono, monospace', fontSize: 10,
              color: t.inkFaint, letterSpacing: '0.05em',
              textTransform: 'uppercase',
            }}>
              · {question.label}
            </span>
          )}
        </div>
        <h2 style={{
          fontFamily: '"Instrument Serif", Georgia, serif',
          fontSize: 26, lineHeight: 1.12, margin: 0, color: t.ink,
          letterSpacing: '-0.01em', fontWeight: 400, textWrap: 'pretty',
        }}>
          {question.q}
        </h2>
        {question.hint && (
          <div style={{
            fontFamily: 'Geist, system-ui, sans-serif',
            fontSize: 13.5, color: t.inkSoft, marginTop: 8, lineHeight: 1.5,
          }}>
            {question.hint}
          </div>
        )}
      </div>

      {/* Voice recorder */}
      <VoiceRecorder value={v.audio} onChange={setAudio} t={t} onMicError={handleMicError} />

      {/* Or-type divider + textarea */}
      {!hasRecording && (
        <>
          {!showText ? (
            <button
              onClick={() => setShowText(true)}
              style={{
                background: 'transparent', border: 'none', padding: 0,
                fontFamily: 'Geist, system-ui, sans-serif', fontSize: 13,
                color: t.inkSoft, cursor: 'pointer', textAlign: 'left',
                alignSelf: 'flex-start',
                display: 'flex', alignItems: 'center', gap: 6,
              }}
            >
              <span style={{ width: 28, height: 1, background: t.line, display: 'inline-block' }} />
              or type your answer
              <span style={{ width: 28, height: 1, background: t.line, display: 'inline-block' }} />
            </button>
          ) : (
            <TextArea
              value={v.text}
              onChange={setText}
              placeholder="Type your answer…"
              t={t}
              rows={4}
            />
          )}
        </>
      )}

      {/* If recording exists, allow inline text override too */}
      {hasRecording && v.text && (
        <TextArea
          value={v.text}
          onChange={setText}
          placeholder="Type your answer…"
          t={t}
          rows={3}
        />
      )}
    </div>
  );
}

Object.assign(window, { VoiceRecorder, VoiceQuestionCard, Waveform, MicIcon });
