/* ============================================================
   Load-data panel: a single analysis JSON (one file holding
   every call + its score time-series) and, optionally, a folder
   of recordings matched by filename. Functional today: parses
   the JSON → table and attaches audio object URLs for playback.
   ============================================================ */
;(function(){
const { Icon, Button, Overlay, closeBtn, useIsMobile } = window.UI;
const { parseAnalysis, CALLS, DIM_KEYS, fileStem } = window.AIData;
const { resample } = window.AIWave;
const AIStore = window.AIStore;

const SCORE_COLS = DIM_KEYS; // risk_score + the six Tyto dimensions, in canonical order

function downloadFile(name, text, type) {
  const blob = new Blob([text], { type });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = name;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1500);
}

// representative calls for the example files (clean / flagged / noisy / dropouts)
function exampleCalls() {
  const byQ = [...CALLS].sort((a, b) => a.scores.risk_score - b.scores.risk_score);
  const noisy = [...CALLS].sort((a, b) => b.scores.noise - a.scores.noise)[0];
  const drop = [...CALLS].sort((a, b) => b.scores.packet_loss - a.scores.packet_loss)[0];
  const uniq = [];
  [byQ[0], byQ[byQ.length - 1], noisy, drop].forEach((c) => { if (c && !uniq.includes(c)) uniq.push(c); });
  return uniq.slice(0, 4);
}
function exampleJSON() {
  const calls = exampleCalls().map((c, i) => ({
    file: `rec_000${i + 1}.wav`,
    duration_sec: c.durationSec,
    frames: Object.fromEntries(SCORE_COLS.map((k) => [k, resample(c.frames[k], 48).map((v) => Math.round(v * 1000) / 1000)])),
  }));
  return JSON.stringify({ model: "Tyto", calls }, null, 2);
}

function ExampleLink({ icon, children, onClick }) {
  const [h, setH] = React.useState(false);
  return (
    <button onClick={onClick} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{
        display: "inline-flex", alignItems: "center", gap: 7, padding: "6px 11px", borderRadius: "var(--radius-sm)",
        border: `1px solid ${h ? "var(--blue-40)" : "var(--border-subtle)"}`, background: h ? "rgba(105,147,255,0.08)" : "transparent",
        color: h ? "#bcd0ff" : "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500,
        cursor: "pointer", transition: "all var(--dur-fast) var(--ease-standard)",
      }}>
      <Icon name={icon} size={14} color={h ? "var(--blue-40)" : "var(--text-tertiary)"} />
      {children}
    </button>
  );
}

function DropZone({ icon, title, hint, accept, multiple, directory, onFiles, filled }) {
  const inputRef = React.useRef(null);
  const [hover, setHover] = React.useState(false);
  return (
    <div
      onClick={() => inputRef.current && inputRef.current.click()}
      onDragOver={(e) => { e.preventDefault(); setHover(true); }}
      onDragLeave={() => setHover(false)}
      onDrop={(e) => { e.preventDefault(); setHover(false); onFiles([...e.dataTransfer.files]); }}
      style={{
        flex: 1, minWidth: 0, cursor: "pointer", textAlign: "center",
        padding: "26px 18px", borderRadius: "var(--radius-md)",
        border: `1.5px dashed ${hover || filled ? "var(--blue-40)" : "var(--border-subtle)"}`,
        background: hover ? "rgba(105,147,255,0.07)" : "var(--surface-sunken)",
        transition: "all var(--dur-fast) var(--ease-standard)",
      }}>
      <input
        ref={inputRef} type="file" accept={accept} multiple={multiple}
        {...(directory ? { webkitdirectory: "", directory: "" } : {})}
        style={{ display: "none" }}
        onChange={(e) => onFiles([...e.target.files])}
      />
      <div style={{ display: "inline-flex", width: 42, height: 42, borderRadius: "var(--radius-md)", alignItems: "center", justifyContent: "center", background: filled ? "rgba(0,191,166,0.16)" : "var(--surface-card)", border: "1px solid var(--border-subtle)", marginBottom: 12 }}>
        <Icon name={filled ? "checkmark.circle.fill" : icon} size={20} color={filled ? "var(--teal)" : "var(--blue-40)"} />
      </div>
      <div style={{ fontSize: 14, fontWeight: 600, color: "var(--text-primary)" }}>{title}</div>
      <div style={{ fontSize: 12.5, color: "var(--text-tertiary)", marginTop: 4, lineHeight: 1.5 }}>{hint}</div>
    </div>
  );
}

function LoaderModal({ onClose, onLoad }) {
  const isMobile = useIsMobile();
  const [jsonName, setJsonName] = React.useState(null);
  const [parsed, setParsed] = React.useState(null);
  const [rawText, setRawText] = React.useState(null); // persisted to IndexedDB, re-parsed on restore
  const [audioMap, setAudioMap] = React.useState({});
  const [audioFiles, setAudioFiles] = React.useState({}); // name → File, for persistence
  const [audioCount, setAudioCount] = React.useState(0);
  const [error, setError] = React.useState(null);

  const onJson = (files) => {
    const f = files.find((x) => /\.json$/i.test(x.name)) || files[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = () => {
      const text = String(reader.result);
      try {
        const rows = parseAnalysis(text);
        setParsed(rows); setRawText(text); setJsonName(f.name); setError(null);
      } catch (e) { setError("Could not read that JSON. Check it matches the format below."); }
    };
    reader.readAsText(f);
  };
  const onAudio = (files) => {
    const map = {}, fileMap = {};
    files.filter((f) => /\.(wav|mp3|m4a|ogg|flac|webm)$/i.test(f.name))
      .forEach((f) => { map[f.name] = URL.createObjectURL(f); fileMap[f.name] = f; });
    setAudioMap(map); setAudioFiles(fileMap); setAudioCount(Object.keys(map).length);
  };
  const apply = async () => {
    if (!parsed) return;
    // match audio by filename stem (ignore extension) so e.g. rec_0001.wav ↔ rec_0001.mp3
    const byStem = {};
    Object.keys(audioMap).forEach((n) => { byStem[fileStem(n)] = audioMap[n]; });
    const finalCalls = parsed.map((c) => ({ ...c, audioUrl: byStem[fileStem(c.file)] || c.audioUrl }));
    // persist locally (IndexedDB) so the session restores next visit (best effort, never blocks loading)
    if (AIStore && AIStore.available()) {
      try {
        await AIStore.clearAll();
        if (rawText) await AIStore.saveDataset(rawText);
        for (const name of Object.keys(audioFiles)) {
          try { await AIStore.saveAudio(name, audioFiles[name]); }
          catch (e) { console.warn("Tyto: could not persist audio", name, e); } // e.g. quota; scores still restore
        }
        AIStore.tryPersist();
      } catch (e) { console.warn("Tyto: could not persist dataset", e); }
    }
    onLoad(finalCalls);
    onClose();
  };

  return (
    <Overlay onClose={onClose}>
      <div style={{ width: isMobile ? "100%" : "min(640px, 94vw)" }} onClick={(e) => e.stopPropagation()}>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 4 }}>
          <div>
            <div className="aic-eyebrow" style={{ marginBottom: 8 }}>Audio Insight · ingest</div>
            <h2 style={{ fontFamily: "var(--font-display)", fontSize: 26, letterSpacing: "-0.02em", margin: 0 }}>Load call data</h2>
          </div>
          <button onClick={onClose} style={closeBtn}><Icon name="xmark" size={16} color="var(--text-secondary)" /></button>
        </div>
        <p style={{ fontSize: 14, color: "var(--text-secondary)", margin: "8px 0 20px", maxWidth: "56ch" }}>
          Drop the analysis JSON: one file holding every call and its scores. You build it from the ai-coustics SDK's raw per-window Tyto values{" "}
          {/* TODO: replace with a real link to the SDK→JSON tutorial once published */}
          <span style={{ color: "var(--text-tertiary)", fontStyle: "italic" }}>(tutorial coming soon)</span>. Optionally add the folder of recordings; they're matched to each call by filename.
        </p>

        <div style={{ display: "flex", flexDirection: isMobile ? "column" : "row", gap: 14 }}>
          <DropZone icon="chevron.left.forwardslash.chevron.right" title={jsonName || "Analysis JSON"} hint={parsed ? `${parsed.length} calls parsed` : "click to browse · or drop the .json"} accept=".json,application/json" filled={!!parsed} onFiles={onJson} />
          <DropZone icon="waveform" title={audioCount ? `${audioCount} recordings` : "Audio folder"} hint="wav · mp3 · m4a · optional" multiple directory filled={audioCount > 0} onFiles={onAudio} />
        </div>

        {error && (
          <div style={{ marginTop: 14, display: "flex", alignItems: "center", gap: 8, color: "#eaa597", fontSize: 13 }}>
            <Icon name="exclamationmark.triangle.fill" size={15} color="var(--clay)" /> {error}
          </div>
        )}

        <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 16, flexWrap: "wrap" }}>
          <span style={{ fontSize: 12.5, color: "var(--text-tertiary)" }}>Need the format?</span>
          <ExampleLink icon="chevron.left.forwardslash.chevron.right" onClick={() => downloadFile("tyto_analysis_example.json", exampleJSON(), "application/json")}>Download example JSON</ExampleLink>
        </div>

        <details style={{ marginTop: 18 }}>
          <summary style={{ cursor: "pointer", fontFamily: "var(--font-mono)", fontSize: 11.5, letterSpacing: "0.05em", textTransform: "uppercase", color: "var(--text-tertiary)" }}>Expected JSON format</summary>
          <pre style={{ marginTop: 10, padding: "12px 14px", background: "var(--surface-sunken)", border: "1px solid var(--border-subtle)", borderRadius: "var(--radius-sm)", fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--text-secondary)", overflowX: "auto", lineHeight: 1.65 }}>
{`{
  "model": "Tyto",
  "calls": [
    {
      "file": "rec_0001.wav",
      "duration_sec": 142,
      "frames": {
        "risk_score":       [0.05, 0.06, 0.04, ...],
        "speaker_reverb":   [0.08, 0.07, 0.09, ...],
        "speaker_loudness": [...], "interfering_speech": [...],
        "media_speech": [...], "noise": [...], "packet_loss": [...]
      }
    }
  ]
}`}
          </pre>
          <div style={{ fontSize: 12, color: "var(--text-tertiary)", marginTop: 10, lineHeight: 1.6 }}>
            The Tyto Risk Score plus its six dimensions, all in [0, 1]. Tyto emits one score set per fixed 5.0&nbsp;s analysis window; arrays map evenly across the call's duration, and these color the waveform, and the table shows each array's average (with p95 and % degraded for triage). Recordings are optional and matched to <span style={{ fontFamily: "var(--font-mono)" }}>file</span> by name; without audio the player uses an animated playhead.
          </div>
        </details>

        <div style={{ display: "flex", justifyContent: "flex-end", gap: 10, marginTop: 22 }}>
          <Button variant="ghost" size="md" onClick={onClose} style={{ fontWeight: 500 }}>Cancel</Button>
          <Button variant="primary" size="md" iconLeft="checkmark" disabled={!parsed} onClick={apply} style={{ fontWeight: 500 }}>Load {parsed ? `${parsed.length} calls` : "data"}</Button>
        </div>
      </div>
    </Overlay>
  );
}

window.AILoader = { LoaderModal, DropZone };
})();
