const { useState, useEffect, useRef, useMemo, useCallback } = React;

const SUPPORTED = [
  "mp3","alac","aac","m4a","mp4",
  "wav","aiff","aif",
  "flac","ape","tta","wv",
  "ogg","opus","wma","dsf","dff","caf",
];

const OUTPUT_FORMATS = [
  { id: "mp3",  label: "MP3",  ext: "mp3",  lossless: false, kbps: [128,192,256,320], sub: "Universal"      },
  { id: "aac",  label: "AAC",  ext: "m4a",  lossless: false, kbps: [128,192,256,320], sub: "Apple / stream" },
  { id: "ogg",  label: "OGG",  ext: "ogg",  lossless: false, kbps: [128,192,256,320], sub: "Open · web"     },
  { id: "opus", label: "OPUS", ext: "opus", lossless: false, kbps: [64,128,192,256],  sub: "Eficiente"      },
  { id: "flac", label: "FLAC", ext: "flac", lossless: true,  kbps: null, sub: "Sin pérdida"                 },
  { id: "wav",  label: "WAV",  ext: "wav",  lossless: true,  kbps: null, sub: "Estudio · PCM"              },
  { id: "aiff", label: "AIFF", ext: "aiff", lossless: true,  kbps: null, sub: "DJ · Rekordbox"             },
  { id: "alac", label: "ALAC", ext: "m4a",  lossless: true,  kbps: null, sub: "Apple lossless"             },
];

const QUALITY_LABELS = {
  64:  { label: "Ligero",       sub: "voz / podcast" },
  128: { label: "Estándar",     sub: "balance"        },
  192: { label: "Alta calidad", sub: "nítido"         },
  256: { label: "Muy alta",     sub: "detalle fino"   },
  320: { label: "Máxima",       sub: "recomendado"    },
};

const MAX_BYTES = 350 * 1024 * 1024;
const MAX_FILES = 25;

function fmtBytes(n) {
  if (n < 1024 * 1024) return (n / 1024).toFixed(0) + " KB";
  return (n / 1024 / 1024).toFixed(1) + " MB";
}
function getExt(name) {
  const m = (name || "").toLowerCase().match(/\.([a-z0-9]+)$/);
  return m ? m[1] : "";
}
function uid() { return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); }
function subGenre(g) { return g.includes("---") ? g.split("---")[1] : g; }

// ── FileCard ──────────────────────────────────────────────────────────────
function FileCard({ item, onRemove, locked }) {
  const ext  = getExt(item.file.name).toUpperCase().slice(0, 5) || "FILE";
  const name = item.file.name.replace(/\.[^.]+$/, "");

  return (
    <div className={`fc fc-${item.status}`}>
      <div className="fc-top">
        <span className="fc-badge">{ext}</span>
        <span className="fc-name" title={name}>{name}</span>
        {item.status === "done" && (
          <svg className="fc-icon-done" width="14" height="14" viewBox="0 0 24 24" fill="none">
            <path d="M5 12l5 5L20 7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        )}
        {item.status === "error" && (
          <svg className="fc-icon-err" width="14" height="14" viewBox="0 0 24 24" fill="none">
            <path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"/>
          </svg>
        )}
        {!locked && (item.status === "pending" || item.status === "error") && (
          <button className="fc-remove" aria-label="Quitar" onClick={() => onRemove(item.localId)}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none">
              <path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"/>
            </svg>
          </button>
        )}
      </div>

      {item.status === "uploading" && (
        <div className="fc-prog-wrap">
          <div className="fc-prog-bar"><div className="fc-prog-fill fc-prog-anim" /></div>
          <span className="fc-prog-label">subiendo</span>
        </div>
      )}

      {(item.status === "queued" || item.status === "converting") && (
        <div className="fc-prog-wrap">
          <div className="fc-prog-bar">
            <div className="fc-prog-fill" style={{ width: item.status === "queued" ? "4%" : `${item.progress || 4}%` }} />
          </div>
          <span className="fc-prog-label">
            {item.status === "queued" ? `#${item.position || "–"}` : `${item.progress || 0}%`}
          </span>
        </div>
      )}

      {item.status === "done" && (
        <div className="fc-meta">
          {item.bpm      && <span className="fc-pill">{item.bpm} BPM</span>}
          {item.camelot  && <span className="fc-pill">{item.camelot}</span>}
          {item.genres   && item.genres.slice(0, 2).map((g, i) => (
            <span key={i} className="fc-pill fc-pill-genre">{subGenre(g)}</span>
          ))}
          {item.instrumental != null && (
            <span className="fc-pill fc-pill-voice">
              {item.instrumental > 0.5 ? "INST" : "VOCAL"}
            </span>
          )}
          {item.moods && Object.entries(item.moods)
            .filter(([,v]) => v > 0.6).sort(([,a],[,b]) => b-a).slice(0,1)
            .map(([k]) => <span key={k} className="fc-pill fc-pill-mood">{k.toUpperCase()}</span>)
          }
        </div>
      )}

      {item.status === "error" && (
        <p className="fc-error-msg">{item.error || "Error de conversión"}</p>
      )}
    </div>
  );
}

// ── Converter ─────────────────────────────────────────────────────────────
function Converter() {
  const [items,   setItems]   = useState([]);
  const [format,  setFormat]  = useState("mp3");
  const [quality, setQuality] = useState(320);
  const [phase,   setPhase]   = useState("idle");  // idle | selected | converting | done
  const [hot,     setHot]     = useState(false);

  const inputRef = useRef(null);
  const pollRef  = useRef(null);
  const itemsRef = useRef([]);

  const currentFmt = OUTPUT_FORMATS.find(f => f.id === format) || OUTPUT_FORMATS[0];

  useEffect(() => { itemsRef.current = items; }, [items]);
  useEffect(() => () => clearInterval(pollRef.current), []);

  const doneItems  = items.filter(i => i.status === "done");
  const errorItems = items.filter(i => i.status === "error");
  const pendingItems = items.filter(i => i.status === "pending");
  const doneIds    = doneItems.filter(i => i.jobId).map(i => i.jobId);
  const isConverting = phase === "converting";

  // ── File handling ─────────────────────────────────────────────────────
  const makeItem = (f) => {
    const ext = getExt(f.name);
    const ok  = SUPPORTED.includes(ext);
    const big = f.size > MAX_BYTES;
    return {
      localId: uid(), file: f, jobId: null,
      status:  !ok ? "error" : big ? "error" : "pending",
      error:   !ok ? "Formato no compatible" : big ? `Demasiado grande (${fmtBytes(f.size)})` : null,
      progress: 0, position: 0,
      bpm: null, key: null, camelot: null, genres: null,
      danceability: null, instrumental: null, moods: null,
    };
  };

  const addFiles = useCallback((fileList) => {
    const arr = Array.from(fileList);
    setItems(prev => {
      const space = MAX_FILES - prev.length;
      if (space <= 0) return prev;
      return [...prev, ...arr.slice(0, space).map(makeItem)];
    });
    setPhase(p => p === "idle" ? "selected" : p);
  }, []);

  const removeItem = (localId) => {
    setItems(prev => {
      const next = prev.filter(i => i.localId !== localId);
      if (!next.length) setPhase("idle");
      return next;
    });
  };

  const onDrop = (e) => {
    e.preventDefault(); setHot(false);
    if (isConverting) return;
    addFiles(e.dataTransfer.files);
  };
  const onPick = (e) => { if (e.target.files) addFiles(e.target.files); e.target.value = ""; };

  const handleFormatChange = (fmtId) => {
    const f = OUTPUT_FORMATS.find(x => x.id === fmtId);
    if (!f) return;
    setFormat(fmtId);
    setQuality(f.lossless ? null : f.kbps[f.kbps.length - 1]);
  };

  // ── Convert ───────────────────────────────────────────────────────────
  const startConversion = async () => {
    const toUpload = items.filter(i => i.status === "pending");
    if (!toUpload.length) return;

    setPhase("converting");
    const toUploadIds = toUpload.map(i => i.localId);
    setItems(prev => prev.map(i => toUploadIds.includes(i.localId) ? { ...i, status: "uploading" } : i));

    const formData = new FormData();
    formData.append("format", format);
    formData.append("quality", (quality || 320).toString());
    toUpload.forEach(i => formData.append("files", i.file));

    let jobs = [];
    try {
      const r = await fetch("/api/convert/batch", { method: "POST", body: formData, credentials: "same-origin" });
      if (!r.ok) {
        const err = await r.json().catch(() => ({}));
        setItems(prev => prev.map(i =>
          i.status === "uploading" ? { ...i, status: "error", error: err.error || "Error al subir" } : i
        ));
        setPhase("done");
        return;
      }
      jobs = (await r.json()).jobs || [];
    } catch {
      setItems(prev => prev.map(i =>
        i.status === "uploading" ? { ...i, status: "error", error: "Error de red" } : i
      ));
      setPhase("done");
      return;
    }

    // Map server job IDs back to local items (same order as toUpload)
    let ji = 0;
    setItems(prev => prev.map(i => {
      if (!toUploadIds.includes(i.localId)) return i;
      const job = jobs[ji++];
      if (!job) return { ...i, status: "error", error: "Sin respuesta" };
      if (job.error) return { ...i, status: "error", error: job.error };
      return { ...i, jobId: job.jobId, status: "queued" };
    }));

    // Poll all pending jobs with a single request
    clearInterval(pollRef.current);
    pollRef.current = setInterval(async () => {
      const cur     = itemsRef.current;
      const pending = cur.filter(i => (i.status === "queued" || i.status === "converting") && i.jobId);
      if (!pending.length) {
        clearInterval(pollRef.current);
        setPhase("done");
        return;
      }
      try {
        const r = await fetch(`/api/convert/status?jobs=${pending.map(i => i.jobId).join(",")}`, { credentials: "same-origin" });
        if (!r.ok) return;
        const data = await r.json();
        setItems(prev => prev.map(i => {
          if (!i.jobId || !data[i.jobId]) return i;
          const d = data[i.jobId];
          if (d.status === "not_found") return { ...i, status: "error", error: "Job expirado" };
          return { ...i, status: d.status, progress: d.progress || 0, position: d.position || 0,
            bpm: d.bpm, key: d.key, camelot: d.camelot, genres: d.genres,
            danceability: d.danceability, instrumental: d.instrumental, moods: d.moods, error: d.error };
        }));
      } catch {}
    }, 700);
  };

  // ── ZIP download ──────────────────────────────────────────────────────
  const downloadZip = () => {
    if (!doneIds.length) return;
    const a = document.createElement("a");
    a.href = `/api/convert/batch/zip?jobs=${doneIds.join(",")}`;
    a.download = "antonic-converted.zip";
    a.rel = "noopener";
    document.body.appendChild(a);
    a.click();
    setTimeout(() => document.body.removeChild(a), 250);
  };

  const reset = () => { clearInterval(pollRef.current); setItems([]); setPhase("idle"); };

  // ── Render ────────────────────────────────────────────────────────────
  return (
    <div className="converter" id="converter">

      {/* Chrome */}
      <div className="converter-chrome">
        <div className="chrome-left">
          <span className="chrome-led">
            <i className={phase !== "idle" ? "on" : ""} />
            <i className={["selected","converting","done"].includes(phase) ? "on" : ""} />
            <i className={["converting","done"].includes(phase) ? "on" : ""} />
            <i className={phase === "done" ? "on" : ""} />
          </span>
          <span>Convertidor · Master</span>
        </div>
        <div className="chrome-right">
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: ".12em" }}>
            {phase === "idle"       && "READY"}
            {phase === "selected"   && `${pendingItems.length} ARCHIVO${pendingItems.length !== 1 ? "S" : ""} · LISTO`}
            {phase === "converting" && `CONVIRTIENDO · ${doneItems.length + errorItems.length}/${items.length}`}
            {phase === "done"       && `DONE · ${doneItems.length}/${items.length}`}
          </span>
        </div>
      </div>

      {/* Dropzone */}
      {phase === "idle" && (
        <div
          className={"dropzone" + (hot ? " hot" : "")}
          onDragOver={e => { e.preventDefault(); setHot(true); }}
          onDragEnter={e => { e.preventDefault(); setHot(true); }}
          onDragLeave={() => setHot(false)}
          onDrop={onDrop}
        >
          <div className="drop-icon">
            <svg width="34" height="34" viewBox="0 0 24 24" fill="none">
              <path d="M12 3v13M7 12l5 5 5-5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
              <path d="M4 19h16" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
            </svg>
          </div>
          <h3 className="drop-title">Arrastra tus archivos aquí</h3>
          <p className="drop-sub">Hasta <b>{MAX_FILES} archivos</b> · máx. 350 MB por archivo</p>
          <div className="drop-actions">
            <button className="btn btn-primary" onClick={() => inputRef.current?.click()} style={{ height: 44, fontSize: 14, padding: "0 18px" }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 4v16M4 12h16" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round"/></svg>
              Seleccionar archivos
            </button>
            <span className="drop-or">o suelta aquí · hasta {MAX_FILES} canciones de golpe</span>
          </div>
          <input ref={inputRef} type="file" hidden multiple onChange={onPick}
            accept=".mp3,.alac,.aac,.m4a,.mp4,.wav,.aiff,.aif,.flac,.ape,.tta,.wv,.ogg,.opus,.wma,.dsf,.dff,.caf" />
          <div className="formats-mini">
            {["mp3","wav","aiff","flac","alac","aac","ogg","opus","wma","ape","wv","dsf","dff","caf"].map(f => <span key={f}>.{f}</span>)}
          </div>
        </div>
      )}

      {/* File grid */}
      {phase !== "idle" && (
        <>
          <div className="batch-header">
            <span className="batch-count">{items.length} archivo{items.length !== 1 ? "s" : ""}</span>
            {phase === "selected" && items.length < MAX_FILES && (
              <button className="batch-add" onClick={() => inputRef.current?.click()}>+ añadir más</button>
            )}
          </div>

          {/* hidden input for adding more */}
          {phase === "selected" && (
            <input ref={inputRef} type="file" hidden multiple onChange={onPick}
              accept=".mp3,.alac,.aac,.m4a,.mp4,.wav,.aiff,.aif,.flac,.ape,.tta,.wv,.ogg,.opus,.wma,.dsf,.dff,.caf" />
          )}

          <div className="file-grid">
            {items.map(item => (
              <FileCard key={item.localId} item={item} onRemove={removeItem} locked={isConverting} />
            ))}
          </div>
        </>
      )}

      {/* Format + quality selector */}
      {(phase === "selected" || phase === "converting") && (
        <div style={{ marginTop: 26 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 12 }}>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: ".16em", textTransform: "uppercase", color: "var(--text-3)" }}>Formato de salida</span>
          </div>
          <div className="fmt-selector">
            {OUTPUT_FORMATS.map(f => (
              <button key={f.id} className={"fmt-pill" + (format === f.id ? " active" : "")}
                disabled={isConverting} onClick={() => handleFormatChange(f.id)} title={f.sub}>
                <span className="fmt-pill-label">{f.label}</span>
                <span className="fmt-pill-sub">{f.lossless ? "lossless" : f.sub}</span>
              </button>
            ))}
          </div>

          {!currentFmt.lossless && (
            <div style={{ marginTop: 20 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: ".16em", textTransform: "uppercase", color: "var(--text-3)" }}>Calidad de salida</span>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-3)", letterSpacing: ".08em" }}>{currentFmt.label} · {quality} kbps</span>
              </div>
              <div className={"quality-grid cols-" + currentFmt.kbps.length} style={{ marginTop: 12 }}>
                {currentFmt.kbps.map((kbps, i) => {
                  const ql    = QUALITY_LABELS[kbps] || { label: kbps + " kbps", sub: "" };
                  const isTop = kbps === currentFmt.kbps[currentFmt.kbps.length - 1];
                  return (
                    <button key={kbps} className={"q-card" + (quality === kbps ? " active" : "")}
                      disabled={isConverting} onClick={() => setQuality(kbps)}>
                      {isTop && <span className="q-tag">Recomendado</span>}
                      <div className="q-kbps">{kbps}<span>kbps</span></div>
                      <div className="q-label">{ql.label}</div>
                      <div className="q-bars">
                        {[0,1,2,3,4].map(n => (
                          <i key={n} style={{ height: `${(i+1) >= n+1 ? Math.min(100,(n+1)*22) : 18}%` }} />
                        ))}
                      </div>
                    </button>
                  );
                })}
              </div>
            </div>
          )}

          {currentFmt.lossless && (
            <div className="lossless-info">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/></svg>
              <div><b>{currentFmt.label} sin pérdida</b> — calidad idéntica al original.</div>
            </div>
          )}
        </div>
      )}

      {/* Convert button */}
      {phase === "selected" && (
        <div className="convert-row" style={{ marginTop: 24 }}>
          <div className="convert-info">
            <b>{pendingItems.length} archivo{pendingItems.length !== 1 ? "s" : ""}</b> → {currentFmt.label}{!currentFmt.lossless ? ` ${quality} kbps` : " lossless"}
          </div>
          <button className="btn-convert" onClick={startConversion} disabled={!pendingItems.length}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M6 4l14 8-14 8V4z" fill="currentColor"/></svg>
            Convertir {pendingItems.length} archivo{pendingItems.length !== 1 ? "s" : ""}
          </button>
        </div>
      )}

      {/* Done: ZIP + reset */}
      {phase === "done" && (
        <div style={{ marginTop: 28 }}>
          {doneItems.length > 0 && (
            <button className="btn-zip" onClick={downloadZip}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
                <path d="M12 4v12M7 11l5 5 5-5M5 20h14" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
              Descargar ZIP · {doneItems.length} archivo{doneItems.length !== 1 ? "s" : ""}
            </button>
          )}
          {errorItems.length > 0 && (
            <p style={{ textAlign: "center", color: "var(--text-3)", fontSize: 13, margin: "12px 0 0" }}>
              {errorItems.length} archivo{errorItems.length !== 1 ? "s" : ""} no {errorItems.length !== 1 ? "se pudieron convertir" : "se pudo convertir"}
            </p>
          )}
          <div style={{ display: "flex", justifyContent: "center", marginTop: 16 }}>
            <button className="btn btn-ghost" onClick={reset}>Convertir otro lote</button>
          </div>
        </div>
      )}

    </div>
  );
}

Object.assign(window, { Converter });
