// ===========================================================
// CollectionImport — bulk-import a collection from a CSV (phase 1).
//
// A source (CSV now; "Connect Discogs" lands in phase 2) emits normalised
// rows into the shared pipeline (window.BBR_import): parse -> column-map ->
// dedupe (skip-if-exists) -> cover resolve -> PREVIEW -> confirm -> batch
// write through the existing store.addCollectionItemsV2Batch path. Nothing
// is ever written without an explicit confirm.
//
// Full-screen overlay; reuses the av2 modal chrome + the iOS scroll-lock
// pattern from MyCollectionAdd_v2. Import-specific styling is the ci-*
// classes (collection.css). Loaded via <script type="text/babel">.
// ===========================================================
const { useState: useCiState, useMemo: useCiMemo, useEffect: useCiEffect } = React;

// Friendly labels for the mapping dropdown, in a sensible order.
const CI_TARGET_LABELS = [
  ["artist", "Artist"],
  ["title", "Title / Album"],
  ["year", "Year"],
  ["format", "Format"],
  ["label", "Label"],
  ["catalogueNo", "Catalogue no."],
  ["mediaCondition", "Media condition"],
  ["sleeveCondition", "Sleeve condition"],
  ["genre", "Genre"],
  ["notes", "Notes"],
  ["sourceId", "Discogs release id"],
  ["sourceCoverUrl", "Cover image URL"]
];

function ciGradeDisplay(code) { return code ? code.replace("_PLUS", "+") : "—"; }

// Trim an "e.g." sample for an <option> label. A native <select> sizes itself
// to its widest option, so a long sample would balloon the control past the
// modal width on mobile (horizontal scroll). Keep option text short.
function ciClip(s, n) { s = String(s || ""); n = n || 20; return s.length > n ? s.slice(0, n - 1) + "…" : s; }

// One preview row's cover: canon/source URL, else a calm placeholder.
function CiThumb({ p }) {
  const [bad, setBad] = useCiState(false);
  if (p.coverUrl && !bad) {
    return <img className="ci-thumb" src={p.coverUrl} alt="" loading="lazy"
      onError={() => setBad(true)} />;
  }
  return (
    <div className="ci-thumb ci-thumb-ph" aria-hidden="true">
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
        <circle cx="12" cy="12" r="9" /><circle cx="12" cy="12" r="2.4" />
      </svg>
    </div>
  );
}

function CiStatusBadge({ status }) {
  if (status === "new") return <span className="ci-badge new">New</span>;
  if (status === "duplicate") return <span className="ci-badge dup">Already in collection</span>;
  return <span className="ci-badge attn" title="Missing an artist or title — won't be imported">Needs attention</span>;
}

function CollectionImport({ albums, onClose, autoDiscogs, discogsError }) {
  const store = window.BBR_store;
  const [step, setStep] = useCiState("source"); // source | mapping | preview | writing | done
  const [source, setSource] = useCiState("csv"); // csv | discogs — drives copy
  const [connecting, setConnecting] = useCiState(false); // Discogs handshake/pull in flight
  const [fileName, setFileName] = useCiState("");
  const [headers, setHeaders] = useCiState([]);
  const [records, setRecords] = useCiState([]);
  const [mapping, setMapping] = useCiState({}); // header -> target | null
  const [preview, setPreview] = useCiState(null); // { rows, totals }
  const [busy, setBusy] = useCiState(false);
  const [err, setErr] = useCiState("");
  const [progress, setProgress] = useCiState({ done: 0, total: 0 });
  const [result, setResult] = useCiState(null); // { inserted, failed, duplicates }
  const [showGrid, setShowGrid] = useCiState(false); // reveal the full column-mapping grid

  // Back button (mobile/iOS) closes the importer instead of leaving the page.
  window.BBR_useModalBack(onClose);

  // Lock the page behind the overlay (iOS-safe), same as the add flow.
  useCiEffect(() => {
    const scrollY = window.scrollY || window.pageYOffset || 0;
    const b = document.body;
    const prev = {
      position: b.style.position, top: b.style.top, left: b.style.left,
      right: b.style.right, width: b.style.width, overflow: b.style.overflow
    };
    b.style.position = "fixed";
    b.style.top = "-" + scrollY + "px";
    b.style.left = "0"; b.style.right = "0"; b.style.width = "100%"; b.style.overflow = "hidden";
    return () => {
      b.style.position = prev.position; b.style.top = prev.top;
      b.style.left = prev.left; b.style.right = prev.right;
      b.style.width = prev.width; b.style.overflow = prev.overflow;
      window.scrollTo(0, scrollY);
    };
  }, []);

  // First non-empty value of a header, for the mapping preview.
  const sampleFor = useCiMemo(() => {
    const out = {};
    (headers || []).forEach(h => {
      for (let i = 0; i < records.length && i < 30; i++) {
        const v = records[i][h];
        if (v != null && String(v).trim() !== "") { out[h] = String(v).trim(); break; }
      }
    });
    return out;
  }, [headers, records]);

  const artistMapped = Object.keys(mapping).some(h => mapping[h] === "artist");
  const titleMapped = Object.keys(mapping).some(h => mapping[h] === "title");
  const canPreview = artistMapped && titleMapped && records.length > 0;

  // The human-facing "here's what we found" playback of the auto-detection.
  const playback = useCiMemo(
    () => (headers.length ? window.BBR_import.describeMapping(headers, mapping, records) : null),
    [headers, mapping, records]
  );
  // Headers available to assign to a still-missing required field (unmapped,
  // plus whatever is currently on that field).
  function headersForRequired(target) {
    return headers.filter(h => !mapping[h] || mapping[h] === target);
  }

  function onFile(e) {
    setErr("");
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (!window.Papa) { setErr("The CSV reader didn't load. Refresh and try again."); return; }
    setBusy(true);
    setFileName(file.name);
    // Parse raw (header:false) so we can de-duplicate column names ourselves —
    // PapaParse's header mode would silently merge same-named columns and lose
    // a column's data. We take the first non-empty row as the header.
    window.Papa.parse(file, {
      header: false,
      skipEmptyLines: "greedy",
      complete: (res) => {
        setBusy(false);
        const grid = (res.data || []).filter(r => Array.isArray(r) && r.some(c => c != null && String(c).trim() !== ""));
        if (grid.length < 2) { setErr("That file looks empty, or has no header row and at least one record."); return; }
        const flds = window.BBR_import.uniqueHeaders(grid[0]);
        const rows = window.BBR_import.rowsToRecords(grid.slice(1), flds);
        if (!flds.length || !rows.length) { setErr("That file looks empty, or has no header row and at least one record."); return; }
        setHeaders(flds);
        setRecords(rows);
        setMapping(window.BBR_import.autoMap(flds, rows).mapping);
        setShowGrid(false);
        setStep("mapping");
      },
      error: () => { setBusy(false); setErr("Couldn't read that file. Is it a .csv?"); }
    });
    // allow re-picking the same file later
    e.target.value = "";
  }

  async function buildPreview() {
    setErr(""); setBusy(true);
    try {
      const rows = window.BBR_import.csvToRows(records, mapping);
      const pv = await window.BBR_import.buildPreview(rows, store.collectionV2 || [], albums || []);
      setPreview(pv);
      setStep("preview");
    } catch (e) {
      setErr("Something went wrong building the preview. " + ((e && e.message) || ""));
    } finally { setBusy(false); }
  }

  // Discogs: pull the collection (server-side, OAuth) and feed the SAME pipeline
  // as CSV. If the user hasn't connected yet, kick off the OAuth handshake.
  async function connectDiscogs() {
    setErr(""); setConnecting(true); setSource("discogs");
    try {
      const sess = await window.BBR_supabase.auth.getSession();
      const token = sess && sess.data && sess.data.session && sess.data.session.access_token;
      if (!token) { setErr("Please sign in again to connect Discogs."); setConnecting(false); return; }
      const auth = { Authorization: "Bearer " + token };
      const r = await fetch("/api/discogs-price?action=fetch", { method: "POST", headers: auth });
      const j = await r.json().catch(() => ({}));
      if (j && j.needsAuth) {
        // not connected (or token revoked) -> start the authorise handshake
        const ir = await fetch("/api/discogs-price?action=initiate", { method: "POST", headers: auth });
        const ij = await ir.json().catch(() => ({}));
        if (ij && ij.authorizeUrl) { window.location.href = ij.authorizeUrl; return; } // navigating away
        setErr((ij && ij.error) || "Couldn't start the Discogs connection."); setConnecting(false); return;
      }
      if (!r.ok || !j || !Array.isArray(j.rows)) {
        setErr((j && j.error) || "Couldn't read your Discogs collection."); setConnecting(false); return;
      }
      const pv = await window.BBR_import.buildPreview(j.rows, store.collectionV2 || [], albums || []);
      setPreview(pv);
      setStep("preview");
      setConnecting(false);
    } catch (e) {
      setErr("Something went wrong connecting to Discogs. " + ((e && e.message) || ""));
      setConnecting(false);
    }
  }

  // Returning from the Discogs authorise screen: ?discogs=connected auto-pulls;
  // ?discogs=error surfaces a friendly retry on the source step.
  useCiEffect(() => {
    if (autoDiscogs) { connectDiscogs(); }
    else if (discogsError) { setErr("Discogs couldn't connect that time. Give it another go."); }
  }, []);

  async function doImport() {
    if (!preview) return;
    const newRows = preview.rows.filter(p => p.status === "new");
    if (!newRows.length) return;
    const items = newRows.map(p => window.BBR_import.toCollectionItem(p));
    setStep("writing");
    setProgress({ done: 0, total: items.length });
    const res = await store.addCollectionItemsV2Batch(items, (pr) => setProgress({ done: pr.done, total: pr.total }));
    setResult({ inserted: res.inserted, failed: res.failed || [], duplicates: preview.totals.duplicate });
    setStep("done");
  }

  function setMap(header, target) {
    setMapping(prev => {
      const next = Object.assign({}, prev);
      // a single-valued target maps from at most one header: clear any other
      // header currently pointing at it.
      if (target) Object.keys(next).forEach(h => { if (next[h] === target) next[h] = null; });
      next[header] = target || null;
      return next;
    });
  }

  const totals = preview && preview.totals;
  const previewRows = preview ? preview.rows : [];
  const DISPLAY_CAP = 400;

  return (
    <div className="av2-backdrop" onClick={onClose}>
      <div className="av2-modal ci-modal" onClick={(e) => e.stopPropagation()}>
        <div className="av2-head">
          <span className="av2-head-title">Import your collection</span>
          <button className="av2-x" onClick={onClose}>×</button>
        </div>

        <div className="av2-body ci-body">
          {err && <div className="ci-err">{err}</div>}

          {/* STEP: source picker */}
          {step === "source" && (
            <div className="ci-pane">
              <p className="ci-lede">Bring your whole shelf across in one go. Upload a spreadsheet, or an export from Discogs or CLZ.</p>

              <button type="button" className="ci-source ci-source-active" onClick={connectDiscogs} disabled={busy || connecting}>
                <div className="ci-source-ico">
                  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><circle cx="12" cy="12" r="9"/><path d="M8 12l2.5 2.5L16 9"/></svg>
                </div>
                <div className="ci-source-txt">
                  <span className="ci-source-h">{connecting ? "Connecting to Discogs…" : "Connect Discogs"}</span>
                  <small>One click — authorise on Discogs and we'll pull your collection in</small>
                </div>
              </button>

              <label className={"ci-source ci-source-active" + ((busy || connecting) ? " ci-source-disabled" : "")}>
                <input type="file" accept=".csv,text/csv" onChange={onFile} disabled={busy || connecting} hidden />
                <div className="ci-source-ico">
                  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M4 4h11l5 5v11a0 0 0 0 1 0 0H4z"/><path d="M15 4v5h5"/><path d="M8 13h8M8 17h5"/></svg>
                </div>
                <div className="ci-source-txt">
                  <span className="ci-source-h">{busy ? "Reading your file…" : "Upload a CSV"}</span>
                  <small>Your own spreadsheet, or a Discogs / CLZ export</small>
                </div>
              </label>

              <div className="ci-hints">
                <div className="ci-hint ci-hint-reassure">
                  Any CSV works — whatever the column names or order. We'll read your file and show you exactly what we found before anything is saved. The only must-haves are a column for the <b>artist</b> and one for the <b>album</b>.
                </div>
                <div className="ci-hint"><b>From Discogs:</b> easiest is "Connect Discogs" above. Or export your collection to CSV and upload it.</div>
                <div className="ci-hint"><b>From CLZ:</b> Export to CSV from the app or clz.com, then upload it here.</div>
                <div className="ci-hint"><b>Your own spreadsheet:</b> one row per record, with a header row on top.</div>
              </div>
            </div>
          )}

          {/* STEP: column mapping — a confident "here's what we found" playback,
              with the full grid one tap away for corrections. */}
          {step === "mapping" && playback && (
            <div className="ci-pane">
              <p className="ci-lede">
                We read <b>{records.length}</b> {records.length === 1 ? "record" : "records"} from <b>{fileName}</b> and worked out your columns. Have a quick look:
              </p>

              {/* what we matched */}
              <div className="ci-found">
                {playback.matched.map(m => (
                  <div className="ci-found-row" key={m.target}>
                    <span className="ci-found-tick" aria-hidden="true">
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M5 12.5l4.5 4.5L19 7"/></svg>
                    </span>
                    <span className="ci-found-field">{m.label}</span>
                    <span className="ci-found-from">from <b>{m.header}</b></span>
                    {m.sample && <span className="ci-found-eg">e.g. {m.sample}</span>}
                  </div>
                ))}
              </div>

              {/* required field still missing -> ask plainly */}
              {playback.missingRequired.length > 0 && (
                <div className="ci-ask">
                  <div className="ci-ask-h">We couldn't tell which {playback.missingRequired.length === 1 ? "column is your" : "columns are your"} {playback.missingRequired.map(t => window.BBR_import.TARGET_LABEL[t]).join(" and ")}. Pick {playback.missingRequired.length === 1 ? "it" : "them"}:</div>
                  {playback.missingRequired.map(t => (
                    <div className="ci-ask-row" key={t}>
                      <span className="ci-ask-label">{window.BBR_import.TARGET_LABEL[t]}</span>
                      {/* Uncontrolled (defaultValue, not value): a controlled select
                          pinned at "" with a disabled placeholder never commits the
                          pick on iOS Safari. Picking a header fires onChange -> setMap,
                          and the row unmounts as the field becomes matched. */}
                      <select className="ci-ask-sel" defaultValue="" onChange={(e) => { if (e.target.value) setMap(e.target.value, t); }}>
                        <option value="" disabled>Choose a column…</option>
                        {headersForRequired(t).map(h => (
                          <option key={h} value={h}>{h}{sampleFor[h] ? "  (e.g. " + ciClip(sampleFor[h]) + ")" : ""}</option>
                        ))}
                      </select>
                    </div>
                  ))}
                </div>
              )}

              {/* skipped columns */}
              {playback.skipped.length > 0 && (
                <div className="ci-skip">We'll skip {playback.skipped.map((h, i) => <span key={h}><b>{h}</b>{i < playback.skipped.length - 1 ? ", " : ""}</span>)}.</div>
              )}

              {/* escape hatch: the full grid */}
              <button className="ci-link" onClick={() => setShowGrid(g => !g)}>
                {showGrid ? "Hide column matching" : "Change what we matched"}
              </button>

              {showGrid && (
                <div className="ci-maptable">
                  {headers.map(h => (
                    <div className="ci-maprow" key={h}>
                      <div className="ci-mapcol">
                        <div className="ci-mapname">{h}</div>
                        {sampleFor[h] && <div className="ci-mapsample">e.g. {sampleFor[h]}</div>}
                      </div>
                      <div className="ci-maparrow">→</div>
                      <div className="ci-mapsel">
                        <select value={mapping[h] || ""} onChange={(e) => setMap(h, e.target.value)}>
                          <option value="">— Don't import —</option>
                          {CI_TARGET_LABELS.map(([val, lbl]) => (
                            <option key={val} value={val}>{lbl}</option>
                          ))}
                        </select>
                      </div>
                    </div>
                  ))}
                </div>
              )}

              <div className="ci-actions">
                <button className="ci-btn ghost" onClick={() => { setStep("source"); setErr(""); }}>Back</button>
                <button className="ci-btn solid" disabled={!canPreview || busy} onClick={buildPreview}>
                  {busy ? "Preparing…" : canPreview ? "Looks right" : "Pick artist & title first"}
                </button>
              </div>
            </div>
          )}

          {/* STEP: preview */}
          {step === "preview" && totals && (
            <div className="ci-pane">
              <div className="ci-summary">
                <span className="ci-sum new"><b>{totals.new}</b> to import</span>
                {totals.duplicate > 0 && <span className="ci-sum dup"><b>{totals.duplicate}</b> already in your collection, skipped</span>}
                {totals.attention > 0 && <span className="ci-sum attn"><b>{totals.attention}</b> need attention</span>}
              </div>
              <p className="ci-fineprint">
                New records show <b>Valuation loading</b> while we fetch live market prices — this can take up to 48 hours.
              </p>

              <div className="ci-previewwrap">
                <table className="ci-preview">
                  <thead>
                    <tr><th></th><th>Record</th><th>Year</th><th>Cond.</th><th>Status</th></tr>
                  </thead>
                  <tbody>
                    {previewRows.slice(0, DISPLAY_CAP).map(p => (
                      <tr key={p.index} className={"ci-prow " + p.status}>
                        <td><CiThumb p={p} /></td>
                        <td>
                          <div className="ci-prow-title">{p.row.title || <em>(no title)</em>}</div>
                          <div className="ci-prow-artist">
                            {p.row.artist || <em>(no artist)</em>}
                            {p.matched && <span className="ci-canon" title="Matched to an album in the Hundred">· in the Hundred</span>}
                          </div>
                        </td>
                        <td>{p.row.year || "—"}</td>
                        <td>{ciGradeDisplay(p.row.mediaCondition)}{p.row.sleeveCondition ? <small style={{ opacity: .6 }}>/{ciGradeDisplay(p.row.sleeveCondition)}</small> : ""}</td>
                        <td><CiStatusBadge status={p.status} /></td>
                      </tr>
                    ))}
                  </tbody>
                </table>
                {previewRows.length > DISPLAY_CAP && (
                  <div className="ci-note">Showing the first {DISPLAY_CAP} of {previewRows.length} rows. All eligible rows will be imported.</div>
                )}
              </div>

              <div className="ci-actions">
                <button className="ci-btn ghost" onClick={() => setStep("mapping")}>Back</button>
                <button className="ci-btn solid" disabled={totals.new === 0} onClick={doImport}>
                  {totals.new === 0 ? "Nothing new to import" : "Import " + totals.new + " " + (totals.new === 1 ? "record" : "records")}
                </button>
              </div>
            </div>
          )}

          {/* STEP: writing */}
          {step === "writing" && (
            <div className="ci-pane ci-center">
              <h3 className="ci-bigh">Adding your records…</h3>
              <div className="ci-progress"><div className="ci-progress-bar" style={{ width: (progress.total ? Math.round(progress.done / progress.total * 100) : 0) + "%" }} /></div>
              <div className="ci-progress-num">{Math.min(progress.done, progress.total)} of {progress.total}</div>
            </div>
          )}

          {/* STEP: done */}
          {step === "done" && result && (
            <div className="ci-pane ci-center">
              <div className="ci-done-ico">
                <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><circle cx="12" cy="12" r="10"/><path d="M7.5 12.5l3 3 6-6.5"/></svg>
              </div>
              <h3 className="ci-bigh">{result.inserted} {result.inserted === 1 ? "record" : "records"} added</h3>
              <p className="ci-done-sub">
                {result.duplicates > 0 && <span>{result.duplicates} were already in your collection. </span>}
                {result.failed.length > 0 && <span>{result.failed.length} couldn't be added. </span>}
                Values populate over the next 48 hours.
              </p>
              {result.failed.length > 0 && (
                <div className="ci-fail">
                  <div className="ci-fail-h">Couldn't add:</div>
                  <ul>
                    {result.failed.slice(0, 6).map((f, i) => (
                      <li key={i}>{(f.item && (f.item.artist + " — " + f.item.title)) || "a row"}</li>
                    ))}
                    {result.failed.length > 6 && <li>and {result.failed.length - 6} more</li>}
                  </ul>
                </div>
              )}
              <div className="ci-actions ci-actions-center">
                <button className="ci-btn solid" onClick={onClose}>Done</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { CollectionImport });
