/* ══════════════════════════════════════════════════════════════════
   Slate — Tank audio tour authoring (CMS)
   ══════════════════════════════════════════════════════════════════
   Per-screen 45-second audio tour. Mounted inside the per-screen
   settings popup in displays.jsx via window.TankAudioSection.

   Backend: src/routes/screen-voiceover.js. See
   docs/tank-audio-tour-plan.md.
   ══════════════════════════════════════════════════════════════════ */

(function () {
  // Wrap the entire file in an IIFE + try/catch so a runtime error
  // during definition can't silently leave window.TankAudioSection
  // undefined — the catch logs loudly and assigns a fallback component
  // that surfaces the error in the popup itself, so curators don't see
  // a mysterious empty section.
  try {
    const { useState, useEffect, useMemo } = React;

    // Word→duration heuristic (Charlotte/V3 at speed:1.2 ≈ 3 words/sec).
    const WORDS_PER_SECOND = 3.0;
    const TARGET_WORDS_MIN = 80;
    const TARGET_WORDS_MAX = 130;
    const HARD_WORDS_MAX = 150;

    function wordCount(text) {
      return String(text || '').trim().split(/\s+/).filter(Boolean).length;
    }

    function WordMeter(props) {
      const words = props.words || 0;
      const dur = (words / WORDS_PER_SECOND).toFixed(1);
      const inBand = words >= TARGET_WORDS_MIN && words <= TARGET_WORDS_MAX;
      const over = words > TARGET_WORDS_MAX;
      const pct = Math.min(100, Math.round((words / HARD_WORDS_MAX) * 100));
      const color = inBand ? 'var(--aq-success)' : (over ? 'var(--aq-warn)' : 'var(--aq-text-faint)');
      return (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 }}>
          <div style={{ flex: 1, height: 4, borderRadius: 2, background: 'var(--aq-surface-2)', overflow: 'hidden' }}>
            <div style={{ width: pct + '%', height: '100%', background: color, transition: 'width 120ms linear' }} />
          </div>
          <div style={{ fontSize: 10.5, color: 'var(--aq-text-faint)', fontFamily: 'var(--aq-ff-mono)', minWidth: 100, textAlign: 'right' }}>
            {words} words · ~{dur}s {inBand ? '✓' : (over ? '(over)' : '(short)')}
          </div>
        </div>
      );
    }

    function TankAudioSection(props) {
      const screen = props.screen;
      const screenId = screen && screen.id;

      const [settings, setSettings] = useState({
        provider: 'elevenlabs',
        languageCode: 'en',
        voiceId: '',
        modelId: 'eleven_v3',
        voiceSettings: {
          stability: 0.55, similarity_boost: 0.75, style: 1.0,
          speed: 1.2, use_speaker_boost: false,
        },
        script: '',
        customFocus: '',
      });
      const [audio, setAudio] = useState(null);
      const [busy, setBusy] = useState(null);
      const [err, setErr] = useState(null);
      const [progress, setProgress] = useState(null);
      const [inhabitants, setInhabitants] = useState([]);
      const [stale, setStale] = useState(false);
      const [catalogue, setCatalogue] = useState(null);

      // Load voice catalogue (shared with the species voiceover surface).
      useEffect(function () {
        apiFetch('/api/species/voiceover/voices')
          .then(function (res) {
            setCatalogue(res || {});
            setSettings(function (s) {
              return Object.assign({}, s, {
                provider: s.provider || res.defaultProvider || 'elevenlabs',
                voiceId: s.voiceId || res.defaultVoiceId || '',
                modelId: s.modelId || res.defaultModelId || 'eleven_v3',
              });
            });
          })
          .catch(function () {});
      }, []);

      // Load existing tour state when screen is known.
      useEffect(function () {
        if (!screenId) return;
        apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover')
          .then(function (res) {
            if (!res) return;
            setSettings(function (s) {
              return Object.assign({}, s, {
                provider: res.provider || s.provider,
                languageCode: res.languageCode || s.languageCode,
                voiceId: res.voiceId || s.voiceId,
                modelId: res.modelId || s.modelId,
                voiceSettings: (res.voiceSettings && typeof res.voiceSettings === 'object')
                  ? Object.assign({}, s.voiceSettings, res.voiceSettings)
                  : s.voiceSettings,
                script: typeof res.script === 'string' ? res.script : s.script,
                customFocus: typeof res.customFocus === 'string' ? res.customFocus : s.customFocus,
              });
            });
            setInhabitants(Array.isArray(res.inhabitants) ? res.inhabitants : []);
            setStale(!!res.stale);
            if (res.url) {
              setAudio({ url: res.url, status: res.status || 'ready', language: res.languageCode || 'en' });
            } else {
              setAudio(null);
            }
          })
          .catch(function () {});
      }, [screenId]);

      function saveSettings(patch) {
        if (!screenId) return;
        const next = Object.assign({}, settings, patch);
        setSettings(next);
        apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover', {
          method: 'PUT',
          body: JSON.stringify(next),
        }).catch(function () {});
      }

      function updateVoiceSetting(key, value) {
        const nextVoice = Object.assign({}, settings.voiceSettings || {});
        nextVoice[key] = value;
        saveSettings({ voiceSettings: nextVoice });
      }

      function pollUntilReady(languageCode) {
        const deadline = Date.now() + 90000;
        function tick() {
          if (Date.now() > deadline) return { ok: false, error: 'Generation timed out after 90 s.' };
          return new Promise(function (resolve) { setTimeout(resolve, 1500); })
            .then(function () {
              return apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover?lang=' + encodeURIComponent(languageCode || 'en'));
            })
            .then(function (r) {
              if (!r) return tick();
              const stage = (r.meta && r.meta.stage) || r.status || null;
              if (stage) setProgress('Generating · ' + stage + '…');
              if (r.status === 'ready' && r.url) {
                setStale(false);
                return { ok: true, url: r.url, languageCode: r.languageCode };
              }
              if (r.status === 'error') {
                return { ok: false, error: (r.meta && r.meta.error) || 'Generation failed.' };
              }
              return tick();
            })
            .catch(function () { return tick(); });
        }
        return tick();
      }

      function suggestScript() {
        if (!screenId) return;
        setBusy('suggest'); setErr(null);
        apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover/suggest-script', {
          method: 'POST',
          body: JSON.stringify({
            languageCode: settings.languageCode,
            provider: settings.provider,
            voiceId: settings.voiceId,
            modelId: settings.modelId,
            customFocus: settings.customFocus,
          }),
        })
          .then(function (r) {
            setSettings(function (s) { return Object.assign({}, s, { script: r.script || s.script }); });
          })
          .catch(function (e) { setErr(e.message); })
          .then(function () { setBusy(null); });
      }

      function generateAudio() {
        if (!screenId) return;
        if (!settings.script || !settings.script.trim()) {
          setErr('Add a script first — Suggest can write one from the tank inhabitants.');
          return;
        }
        setBusy('generate'); setErr(null); setProgress('Generating · queued…');
        apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover/generate', {
          method: 'POST',
          body: JSON.stringify(settings),
        })
          .then(function () { return pollUntilReady(settings.languageCode); })
          .then(function (result) {
            if (result.ok) {
              setAudio({ url: result.url, status: 'ready', language: result.languageCode || settings.languageCode });
            } else {
              setErr(result.error);
            }
          })
          .catch(function (e) { setErr(e.message); })
          .then(function () { setBusy(null); setProgress(null); });
      }

      function clearAudio() {
        if (!screenId) return;
        if (!window.confirm('Delete the current tour audio? The script will stay.')) return;
        setBusy('clear'); setErr(null);
        apiFetch('/api/screens/' + encodeURIComponent(screenId) + '/voiceover', { method: 'DELETE' })
          .then(function () { setAudio(null); })
          .catch(function (e) { setErr(e.message); })
          .then(function () { setBusy(null); });
      }

      const providerOptions = useMemo(function () {
        const arr = (catalogue && catalogue.providers) || [];
        return arr.map(function (p) {
          return { value: p.id, label: p.configured ? p.label : (p.label + ' (needs API key)') };
        });
      }, [catalogue]);

      const languageOptions = useMemo(function () {
        const arr = (catalogue && catalogue.languages) || [{ code: 'en', label: 'English' }];
        return arr.map(function (l) { return { value: l.code, label: l.label || l.code }; });
      }, [catalogue]);

      const voiceOptions = useMemo(function () {
        if (!catalogue || !catalogue.voices) return [];
        const provider = settings.provider || catalogue.defaultProvider || 'elevenlabs';
        const list = catalogue.voices[provider] || [];
        return list.slice()
          .sort(function (a, b) { return Number(!!b.featured) - Number(!!a.featured); })
          .map(function (v) {
            return {
              value: v.id,
              label: (v.name || v.id) + (v.featured ? ' ★' : '') + (v.installed === false ? ' (not configured)' : ''),
            };
          });
      }, [catalogue, settings.provider]);

      const modelOptions = useMemo(function () {
        const arr = (catalogue && catalogue.models) || [{ id: 'eleven_v3', label: 'V3 · Expressive' }];
        return arr.map(function (m) { return { value: m.id, label: m.label || m.id }; });
      }, [catalogue]);

      const words = wordCount(settings.script);

      // ── Render ─────────────────────────────────────────────────────
      if (!screenId) {
        return (
          <div style={{ padding: '10px 12px', background: 'var(--aq-surface-2)', border: '1px solid var(--aq-line)', borderRadius: 6, fontSize: 11.5, color: 'var(--aq-text-faint)' }}>
            Save the screen first — tour audio attaches to the saved record.
          </div>
        );
      }

      const inputStyle = {
        width: '100%', padding: '8px 11px', fontSize: 13,
        background: 'var(--aq-surface-2)', border: '1px solid var(--aq-line)',
        borderRadius: 6, color: 'var(--aq-text)', font: 'inherit', outline: 0,
      };
      const btnPrimary = {
        padding: '8px 14px', fontSize: 13, fontWeight: 500,
        background: 'var(--aq-accent)', color: 'var(--aq-on-accent, white)',
        border: 0, borderRadius: 6, cursor: 'pointer',
      };
      const btnGhost = {
        padding: '8px 14px', fontSize: 13, fontWeight: 500,
        background: 'transparent', color: 'var(--aq-text)',
        border: '1px solid var(--aq-line)', borderRadius: 6, cursor: 'pointer',
      };

      return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {/* Inhabitants the AI will draw from. */}
          <div>
            <div style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--aq-text-faint)', marginBottom: 6 }}>
              The tour will mention these inhabitants
            </div>
            {inhabitants.length === 0 ? (
              <div style={{ fontSize: 12, color: 'var(--aq-text-faint)', padding: '8px 10px', background: 'var(--aq-surface-2)', border: '1px dashed var(--aq-line)', borderRadius: 6 }}>
                No species assigned to this tank yet — add some, then come back to generate the tour.
              </div>
            ) : (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {inhabitants.map(function (sp) {
                  return (
                    <a
                      key={sp.species_id}
                      href={'#species/' + sp.species_id}
                      title={sp.is_featured ? 'Featured — appears first in the tour' : 'Open species editor'}
                      style={{
                        display: 'inline-flex', alignItems: 'center', gap: 6,
                        padding: '5px 10px', borderRadius: 999,
                        background: sp.is_featured ? 'color-mix(in srgb, var(--aq-accent) 14%, transparent)' : 'var(--aq-surface-2)',
                        border: '1px solid ' + (sp.is_featured ? 'var(--aq-accent)' : 'var(--aq-line)'),
                        color: sp.is_featured ? 'var(--aq-accent)' : 'var(--aq-text)',
                        fontSize: 12, textDecoration: 'none',
                      }}
                    >
                      {sp.is_featured ? <span style={{ fontSize: 9 }}>★</span> : null}
                      {sp.common_name || sp.species_id}
                    </a>
                  );
                })}
              </div>
            )}
          </div>

          {/* Provider / language / voice are intentionally not surfaced
              here — every tour uses the org's standard narrator
              (Charlotte / V3 / English by default, configured via the
              shared voice catalogue at /api/species/voiceover/voices).
              The settings state still tracks them so PUT/POST bodies
              are complete; we just don't ask the curator to pick. */}

          {/* Tour focus. */}
          <label>
            <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--aq-text)', marginBottom: 5 }}>
              Tour focus <span style={{ color: 'var(--aq-text-faint)', fontWeight: 400 }}>· optional</span>
            </div>
            <input
              type="text"
              value={settings.customFocus || ''}
              onChange={function (e) {
                const v = e.target.value.slice(0, 200);
                setSettings(function (s) { return Object.assign({}, s, { customFocus: v }); });
              }}
              onBlur={function () { saveSettings({ customFocus: settings.customFocus }); }}
              placeholder='e.g. "Focus on the cleaner shrimp symbiosis"'
              style={inputStyle}
              maxLength={200}
            />
          </label>

          {/* Script. */}
          <label>
            <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--aq-text)', marginBottom: 5 }}>Tour script</div>
            <textarea
              rows={5}
              value={settings.script}
              onChange={function (e) {
                const v = e.target.value;
                setSettings(function (s) { return Object.assign({}, s, { script: v }); });
              }}
              onBlur={function () { saveSettings({ script: settings.script }); }}
              placeholder='Press "Suggest script" to draft one from the inhabitants, or write your own…'
              style={Object.assign({}, inputStyle, { fontFamily: 'var(--aq-ff-mono)', fontSize: 12.5, lineHeight: 1.5, resize: 'vertical', height: 'auto', padding: '10px 11px' })}
            />
            <WordMeter words={words} />
          </label>

          {audio && audio.url ? (
            <div style={{ padding: '10px 12px', background: 'var(--aq-surface-2)', border: '1px solid var(--aq-line)', borderRadius: 8, display: 'flex', gap: 12, alignItems: 'center' }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, color: 'var(--aq-text)', fontWeight: 500 }}>Tour audio ready</div>
                <div style={{ fontSize: 12, color: 'var(--aq-text-faint)' }}>
                  {(audio.language || '').toUpperCase()} · {audio.status}{stale ? ' · stale (inhabitants changed)' : ''}
                </div>
              </div>
              <audio src={audio.url} controls style={{ height: 30 }} />
            </div>
          ) : null}

          {err ? (
            <div style={{ padding: '8px 12px', borderRadius: 6, background: 'color-mix(in srgb, var(--aq-danger) 12%, transparent)', color: 'var(--aq-danger)', fontSize: 12 }}>{err}</div>
          ) : null}

          {progress && busy === 'generate' ? (
            <div style={{ padding: '8px 12px', borderRadius: 6, background: 'var(--aq-surface-2)', border: '1px solid var(--aq-line)', color: 'var(--aq-text-faint)', fontSize: 12, fontFamily: 'var(--aq-ff-mono)' }}>{progress}</div>
          ) : null}

          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <button
              type="button"
              disabled={busy != null || inhabitants.length === 0}
              onClick={suggestScript}
              style={btnGhost}
              title={inhabitants.length === 0 ? 'Add species to this tank first' : 'Draft a tour script from the inhabitants'}
            >
              {busy === 'suggest' ? 'Drafting…' : 'Suggest script'}
            </button>
            <button
              type="button"
              disabled={busy != null || !settings.script.trim()}
              onClick={generateAudio}
              style={btnPrimary}
            >
              {busy === 'generate' ? 'Generating…' : (audio ? 'Regenerate audio' : 'Generate audio')}
            </button>
            {audio ? (
              <button type="button" disabled={busy != null} onClick={clearAudio} style={btnGhost}>
                {busy === 'clear' ? 'Clearing…' : 'Clear audio'}
              </button>
            ) : null}
          </div>

          <div style={{ fontSize: 12, color: 'var(--aq-text-faint)', lineHeight: 1.45 }}>
            Visitors hear this on the mobile app after scanning a tank's species QR. Target 45 seconds.
          </div>
        </div>
      );
    }

    window.TankAudioSection = TankAudioSection;
    console.log('[tank-audio-section] loaded');
  } catch (e) {
    // Surface load-time errors so they don't disappear into the network/console noise.
    console.error('[tank-audio-section] failed to register:', e && e.message, e && e.stack);
    window.TankAudioSection = function TankAudioSectionError(props) {
      return (
        <div style={{ padding: 12, background: 'rgba(244,63,94,0.08)', border: '1px solid rgba(244,63,94,0.4)', borderRadius: 6, color: '#f87171', fontSize: 12 }}>
          <strong>Audio tour failed to load:</strong> {(e && e.message) || String(e)}
        </div>
      );
    };
  }
})();
