// ui/report_export_modal.jsx — the report export dialog (document control, revision
// history, sign-off) for the component detail screen.
// ---------------------------------------------------------------------------
// Report export dialog.
//
// Collects every field the report's Revision History sheet shows and hands them
// to VCExport.report() as opts.doc.
//
// That sheet is a TABLE, not a single row: reportHtml() renders opts.doc.revisions
// in order (up to 12) and pads what is left with blank ruled rows. So this dialog
// edits a LIST — one card per revision — and a controlled copy can carry the
// document's real history (V1.0 -> V1.1 -> V2.0) instead of only the revision
// being issued today. Oldest first, because that is the order the sheet prints.
//
// Dates: an earlier revision carries the date it was actually issued, which is
// history and so is typed. The LAST card is the revision THIS download issues,
// so its date is not a field — reportHtml() stamps it when the report is built,
// and the dialog shows it read-only so the caller can see what will be printed.
// Submit freezes that stamp into the stored row, so the next export sees a dated
// historical revision rather than re-stamping it with a later date.
//
// `list` is the findings to report on. Omit it and the full ledger catalog is
// fetched instead. `reportOpts` is merged into the export call, so a caller can
// pass target/statusOf/filename etc.
//
// Values are remembered between exports through window.VCReportDoc (localStorage),
// so this is PER-BROWSER state: there is no writable settings endpoint on the
// backend (ConfigController is GET-only).
// ---------------------------------------------------------------------------
const REPORT_LOGO_MAX = 2 * 1024 * 1024; // 2 MB — it is inlined as a data URI
// Matches the .slice(0, 12) reportHtml() applies to doc.revisions. Offering more
// cards than the sheet can print would silently drop what was typed.
const REPORT_MAX_REVISIONS = 12;
// The Owner Details block — the lab's own identity, defaulted in LAB_DOC
// (report/report_doc.js) and editable in the dialog's Owner details section.
const REPORT_OWNER_KEYS = ["ownerName", "ownerRegion", "ownerEmail", "ownerAddress"];

function reportTodayIso() {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, "0");
  return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}
// dd.mm.yyyy — the form the report's footer strip and its Report Details sheet
// print dates in, so what the dialog seeds is what the sheet would print.
function reportTodayDot() {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, "0");
  return pad(d.getDate()) + "." + pad(d.getMonth() + 1) + "." + d.getFullYear();
}

// Stable React keys for the revision cards. Index keys would re-point a card's
// inputs at a different revision the moment one in the middle is removed.
let reportRevUid = 0;

// One Revision History row. Seeded with today's date so a card that later stops
// being the current revision already carries the date it was issued on.
function blankRevision(date) {
  return { uid: ++reportRevUid, status: "Draft", date: date, version: "V1.0",
    change: "", author: "", reviewer: "", approver: "" };
}

// Storage written before this dialog edited a list holds the single revision as
// flat top-level fields; storage written since holds doc.revisions. Read both, so
// upgrading does not throw away the letterhead an admin already saved.
function savedRevisions(saved, date) {
  const rows = (Array.isArray(saved.revisions) ? saved.revisions : [])
    .slice(0, REPORT_MAX_REVISIONS)
    .map((r) => ({
      uid: ++reportRevUid,
      status: r.status || "Draft",
      date: r.date || date,
      version: r.version || "V1.0",
      change: r.change || "",
      author: r.author || "",
      reviewer: r.reviewer || "",
      approver: r.approver || "",
    }));
  if (rows.length) return rows;
  return [Object.assign(blankRevision(date), {
    status: saved.status || "Draft",
    version: saved.revisionVersion || "V1.0",
    change: saved.changeDescription || "",
    author: saved.author || "",
    reviewer: saved.reviewer || "",
    approver: saved.approver || "",
  })];
}

function ReportExportModal({ onClose, list, reportOpts }) {
  const store = window.VCReportDoc;
  const saved = (store && store.read()) || {};
  const docDefaults = (store && store.DEFAULTS) || { revisionNo: "00", revisionDate: "--" };
  // What the report prints for the owner when nobody has changed it.
  const lab = (store && store.LAB_DOC) || {};
  // A saved key wins even when it is empty: emptying a field is a choice, and
  // re-seeding the default would show a value the report is not going to print.
  const ownerSeed = (k) => (Object.prototype.hasOwnProperty.call(saved, k) ? String(saved[k] || "") : String(lab[k] || ""));
  const dateStr = reportTodayIso();
  const dotStr = reportTodayDot();
  const [revs, setRevs] = useState(() => savedRevisions(saved, dateStr));
  const [f, setF] = useState({
    // The company the report is ISSUED TO. Seeded from the last export, falling
    // back to the signed-in organisation so the common case needs no typing — but
    // editable, because an admin exporting on behalf of a customer is not
    // necessarily exporting for their own tenant.
    customerName: saved.customerName || (window.VCExport.signedInOrg && window.VCExport.signedInOrg()) || "",
    customerLogo: saved.customerLogo || "",
    // Testing Date and Issue Date of the Report Details sheet. Free text in
    // dd.mm.yyyy, seeded with today and remembered, because a lab issues a report
    // for testing that happened on a stated day — not necessarily the day the file
    // is downloaded — and the issue date is the one the lab puts on it.
    testingDate: saved.testingDate || dotStr,
    reportIssueDate: saved.reportIssueDate || dotStr,
    // The Tester Details block of the same sheet. Separate from the per-revision
    // author / reviewer / approver cards below, because they answer a different
    // question: those name who signed off THIS REVISION OF THE DOCUMENT, these name
    // who ran the test. The same people on a small engagement, different people as
    // soon as the lab has more than one. Blank is legitimate — the sheet prints
    // N/A — so they seed from the last export only.
    testedBy: saved.testedBy || "",
    reportPreparedBy: saved.reportPreparedBy || "",
    // The Owner Details block of the same sheet: pre-filled with the lab's own
    // details, so the common case is to leave it alone.
    ownerName: ownerSeed("ownerName"),
    ownerRegion: ownerSeed("ownerRegion"),
    ownerEmail: ownerSeed("ownerEmail"),
    ownerAddress: ownerSeed("ownerAddress"),
    // The Customer Details block of the same sheet. Seeded from the last export
    // ONLY — never from Company above or the signed-in organisation, which is what
    // once printed the lab as its own customer. All four blank leaves the block out.
    customerDetailsName: saved.customerDetailsName || "",
    customerRegion: saved.customerRegion || "",
    customerEmail: saved.customerEmail || "",
    customerAddress: saved.customerAddress || "",
    // The Revision No / Revision Date half of the footer's document-control strip.
    // Free text, not a number and a date picker: the lab's form control starts them
    // at "00" and "--" — no revision issued yet — and neither an empty box nor a
    // date input can say that. Seeded from the defaults the report itself falls
    // back to, so the dialog shows exactly what the footer would print.
    revisionNo: saved.revisionNo || docDefaults.revisionNo,
    revisionDate: saved.revisionDate || docDefaults.revisionDate,
  });
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");
  const set = (k, v) => { setF((p) => ({ ...p, [k]: v })); setErr(""); };
  const ownerIsDefault = REPORT_OWNER_KEYS.every((k) => String(f[k] || "").trim() === String(lab[k] || ""));
  const resetOwner = () => {
    setF((p) => REPORT_OWNER_KEYS.reduce((o, k) => ({ ...o, [k]: String(lab[k] || "") }), p));
    setErr("");
  };
  const setRev = (i, k, v) => {
    setRevs((p) => p.map((r, n) => (n === i ? Object.assign({}, r, { [k]: v }) : r)));
    setErr("");
  };
  const addRev = () => {
    setRevs((p) => (p.length >= REPORT_MAX_REVISIONS ? p : p.concat([blankRevision(dateStr)])));
    setErr("");
  };
  // Never below one row. The sheet's whole point is to name the revision being
  // issued, and an empty log would print as a card of blank ruled lines.
  const dropRev = (i) => {
    setRevs((p) => (p.length > 1 ? p.filter((_, n) => n !== i) : p));
    setErr("");
  };
  const statuses = (store && store.STATUSES) || ["Draft", "In-Review", "Approved"];

  // The logo is inlined into the report as a data URI, because the export renders
  // in an isolated iframe that cannot resolve the app's relative asset paths.
  function pickLogo(e) {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (!/^image\//.test(file.type)) { setErr("That file is not an image."); return; }
    if (file.size > REPORT_LOGO_MAX) { setErr("Logo must be under 2 MB."); return; }
    const fr = new FileReader();
    fr.onload = () => set("customerLogo", String(fr.result));
    fr.onerror = () => setErr("Could not read that file.");
    fr.readAsDataURL(file);
  }

  function submit(e) {
    e.preventDefault();
    setBusy(true); setErr("");
    // The last card is the revision this download issues, so it takes today's
    // stamp; every earlier card keeps the date it was issued on.
    const lastIx = revs.length - 1;
    const revisions = revs.map((r, i) => ({
      status: r.status,
      date: i === lastIx ? dateStr : (String(r.date || "").trim() || dateStr),
      version: r.version.trim() || "V1.0",
      change: r.change.trim(),
      author: r.author.trim(),
      reviewer: r.reviewer.trim(),
      approver: r.approver.trim(),
    }));
    const cur = revisions[lastIx];
    const doc = {
      // Flat mirrors of the CURRENT revision. reportHtml() only falls back to
      // these when doc.revisions is empty — which it never is here — but LAB_DOC
      // merges saved settings into every export, so they are kept in step rather
      // than left pointing at whatever the previous version of this dialog wrote.
      status: cur.status,
      revisionVersion: cur.version,
      changeDescription: cur.change,
      author: cur.author,
      reviewer: cur.reviewer,
      approver: cur.approver,
      customerName: f.customerName.trim(),
      customerLogo: f.customerLogo || "",
      // Emptied fields are stored empty; the report then dates the sheet with the
      // day it is built.
      testingDate: f.testingDate.trim(),
      reportIssueDate: f.reportIssueDate.trim(),
      testedBy: String(f.testedBy || "").trim(),
      reportPreparedBy: String(f.reportPreparedBy || "").trim(),
      customerDetailsName: String(f.customerDetailsName || "").trim(),
      customerRegion: String(f.customerRegion || "").trim(),
      customerEmail: String(f.customerEmail || "").trim(),
      customerAddress: String(f.customerAddress || "").trim(),
      // Printed in every sheet's footer strip. Emptied fields fall back to the
      // defaults rather than through: "" would override LAB_DOC and print a footer
      // cell reading "Revision No:" with nothing after it.
      revisionNo: f.revisionNo.trim() || docDefaults.revisionNo,
      revisionDate: f.revisionDate.trim() || docDefaults.revisionDate,
      // Every row of the Revision History table, oldest first.
      revisions: revisions,
      // The ONLY place this is set. Reaching here means a person filled in the
      // status, version, change description and the three sign-off names above, so
      // this copy is entitled to the Revision History card and to those names on
      // the Report Details sheet. Every other export path leaves it false and
      // prints as an uncontrolled copy.
      controlled: true,
    };
    // Owner values that still equal LAB_DOC's are left OUT, not stored: a stored
    // copy of a default outranks LAB_DOC in the report's merge, so a later change
    // to the lab's address in code would never reach this browser — the trap
    // SUPERSEDED_REV in report_doc.js has to clean up after for the revision pair.
    // Anything changed, including a field emptied on purpose, is stored.
    REPORT_OWNER_KEYS.forEach((k) => {
      const v = String(f[k] || "").trim();
      if (v !== String(lab[k] || "")) doc[k] = v;
    });
    if (store) store.write(doc); // remember for the next export
    // A caller that already has its findings (the component screen) passes them in.
    // Otherwise fall back to the full ledger catalog.
    const rows = list ? Promise.resolve(list) : window.VC.ensureCatalog();
    rows
      .then((r) => window.VCExport.report(
        r, Object.assign({ target: "All monitored programs" }, reportOpts || {}, { doc })))
      .then(() => { setBusy(false); onClose(); })
      .catch((ex) => {
        setBusy(false);
        setErr((ex && ex.message) || "Could not generate the report.");
      });
  }

  // Escape closes, the way AssigneeModal above already does it. Both it and the scrim
  // now refuse while an export is in flight: clicking the scrim mid-download used to
  // unmount this while VCExport.report() was still building the PDF, so the progress
  // state vanished, the .then() ran against a dead component, and the file arrived with
  // no dialog left to explain it.
  useEffect(() => {
    function key(e) { if (e.key === "Escape" && !busy) onClose(); }
    document.addEventListener("keydown", key);
    return () => document.removeEventListener("keydown", key);
  }, [busy, onClose]);
  // Focus goes to the dialog shell, not the first field: this is a remembered form and
  // the common path is "check it, then Download", not "retype the company name".
  // tabIndex -1 makes it a focus target without adding a tab stop, so the next Tab enters
  // the form in order instead of resuming behind the scrim on the button that opened it.
  const dlgRef = useRef(null);
  useEffect(() => { if (dlgRef.current) dlgRef.current.focus(); }, []);

  return (
    <div className="usm-scrim" onClick={() => !busy && onClose()}>
      <div className="usm" ref={dlgRef} tabIndex={-1} role="dialog" aria-modal="true"
        aria-label="Report version control" onClick={(e) => e.stopPropagation()}>
        <div className="usm-head">
          <div>
            <span className="eyebrow">Export</span>
            <h3>Version Control</h3>
          </div>
          {/* Deliberately NOT disabled while busy, unlike the scrim and Escape above.
              When the popup is blocked, VCExport falls back to the hidden-iframe path
              (reportPdfInFrame in report/report.js), which resolves on afterprint or a 60s timeout — so busy can
              genuinely last a minute, and a dialog with no exit at all is worse than a
              cancelled export. The scrim and Escape block the ACCIDENTAL dismissal; this
              stays as the one deliberate one. */}
          <button className="iconbtn" type="button" onClick={onClose} title="Close">
            <Icon name="close" size={16} />
          </button>
        </div>

        <form onSubmit={submit}>
          <div className="usm-body">
            <div className="rdm-sec">
              <div className="rdm-sec-head"><h4>Document</h4></div>
              <div className="admin-form">
                <label className="field admin-span2">
                  <span className="field-lab">Company</span>
                  <div className="field-wrap">
                    <input value={f.customerName} onChange={(e) => set("customerName", e.target.value)}
                      placeholder="Company the report is issued to" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Testing Date</span>
                  <div className="field-wrap">
                    <input value={f.testingDate} onChange={(e) => set("testingDate", e.target.value)}
                      placeholder={dotStr} spellCheck="false" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Issue Date</span>
                  <div className="field-wrap">
                    <input value={f.reportIssueDate} onChange={(e) => set("reportIssueDate", e.target.value)}
                      placeholder={dotStr} spellCheck="false" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Tested by</span>
                  <div className="field-wrap">
                    <input value={f.testedBy} onChange={(e) => set("testedBy", e.target.value)}
                      placeholder="Engineer who ran the test" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Prepared by</span>
                  <div className="field-wrap">
                    <input value={f.reportPreparedBy} onChange={(e) => set("reportPreparedBy", e.target.value)}
                      placeholder="Who prepared the report" />
                  </div>
                  <span className="field-hint">Tester Details block. Leave <b>Tested by</b> blank to reuse the revision author; blank prints N/A. Not the footer's Quality&nbsp;Manager / Technical&nbsp;Manager roles.</span>
                </label>
                <label className="field">
                  <span className="field-lab">Revision No</span>
                  <div className="field-wrap">
                    <input value={f.revisionNo} onChange={(e) => set("revisionNo", e.target.value)}
                      placeholder={docDefaults.revisionNo} />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Revision Date</span>
                  <div className="field-wrap">
                    <input value={f.revisionDate} onChange={(e) => set("revisionDate", e.target.value)}
                      placeholder={docDefaults.revisionDate} />
                  </div>
                </label>
                <label className="field admin-span2">
                  <span className="field-lab">Company logo</span>
                  <div className="field-wrap">
                    <input type="file" accept="image/*" onChange={pickLogo} />
                  </div>
                </label>
                {f.customerLogo ? (
                  <div className="field admin-span2">
                    <span className="field-lab">Logo preview</span>
                    <div className="rdm-logo">
                      <img src={f.customerLogo} alt="Company logo" />
                      <button className="btn btn-ghost btn-sm" type="button" onClick={() => set("customerLogo", "")}>
                        <Icon name="trash" size={14} />Remove
                      </button>
                    </div>
                  </div>
                ) : null}
              </div>
            </div>

            <div className="rdm-sec">
              <div className="rdm-sec-head">
                <h4>Owner details</h4>
                <div className="rdm-sec-act">
                  <button className="btn btn-ghost btn-sm" type="button" onClick={resetOwner}
                    disabled={ownerIsDefault} title="Restore the lab's own details">
                    Reset
                  </button>
                </div>
              </div>
              <div className="admin-form">
                <label className="field">
                  <span className="field-lab">Name</span>
                  <div className="field-wrap">
                    <input value={f.ownerName} onChange={(e) => set("ownerName", e.target.value)}
                      placeholder="Owner name" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Office/Region</span>
                  <div className="field-wrap">
                    <input value={f.ownerRegion} onChange={(e) => set("ownerRegion", e.target.value)}
                      placeholder="City, country" />
                  </div>
                </label>
                <label className="field admin-span2">
                  <span className="field-lab">Email</span>
                  <div className="field-wrap">
                    <input value={f.ownerEmail} onChange={(e) => set("ownerEmail", e.target.value)}
                      placeholder="contact@owner.com" inputMode="email" spellCheck="false" />
                  </div>
                </label>
                <label className="field admin-span2">
                  <span className="field-lab">Address/Location</span>
                  <div className="field-wrap">
                    <input value={f.ownerAddress} onChange={(e) => set("ownerAddress", e.target.value)}
                      placeholder="Street, city, postcode, country" />
                  </div>
                  <span className="field-hint">Owner Details block of the report, pre-filled with the lab's own details. Leave all four blank to leave the block out; a blank row in a filled block prints N/A. <b>Reset</b> restores the defaults.</span>
                </label>
              </div>
            </div>

            <div className="rdm-sec">
              <div className="rdm-sec-head"><h4>Customer details</h4></div>
              <div className="admin-form">
                <label className="field">
                  <span className="field-lab">Name</span>
                  <div className="field-wrap">
                    <input value={f.customerDetailsName} onChange={(e) => set("customerDetailsName", e.target.value)}
                      placeholder="Customer name" />
                  </div>
                </label>
                <label className="field">
                  <span className="field-lab">Office/Region</span>
                  <div className="field-wrap">
                    <input value={f.customerRegion} onChange={(e) => set("customerRegion", e.target.value)}
                      placeholder="e.g. Stuttgart, Germany" />
                  </div>
                </label>
                <label className="field admin-span2">
                  <span className="field-lab">Email</span>
                  <div className="field-wrap">
                    <input value={f.customerEmail} onChange={(e) => set("customerEmail", e.target.value)}
                      placeholder="contact@customer.com" inputMode="email" spellCheck="false" />
                  </div>
                </label>
                <label className="field admin-span2">
                  <span className="field-lab">Address/Location</span>
                  <div className="field-wrap">
                    <input value={f.customerAddress} onChange={(e) => set("customerAddress", e.target.value)}
                      placeholder="Street, city, postcode, country" />
                  </div>
                  <span className="field-hint">Customer Details block of the report. Leave all four blank to leave the block out; a blank row in a filled block prints N/A. Never filled in from <b>Company</b> or your own organisation.</span>
                </label>
              </div>
            </div>

            <div className="rdm-sec">
              <div className="rdm-sec-head">
                <h4>Revision history</h4>
                <div className="rdm-sec-act">
                  <span className="rdm-sec-note">{revs.length} of {REPORT_MAX_REVISIONS} rows</span>
                  <button className="btn btn-ghost btn-sm" type="button" onClick={addRev}
                    disabled={revs.length >= REPORT_MAX_REVISIONS}>
                    <Icon name="plus" size={14} />Add revision
                  </button>
                </div>
              </div>
              {revs.map((r, i) => {
                const current = i === revs.length - 1;
                return (
                  <div className={"rev-card" + (current ? " is-current" : "")} key={r.uid}>
                    <div className="rev-card-head">
                      <span className="rev-card-no">
                        Revision {i + 1}
                        {current ? <em className="rev-card-cur">issued by this download</em> : null}
                      </span>
                      {revs.length > 1 ? (
                        <button className="btn btn-ghost btn-sm" type="button" onClick={() => dropRev(i)}
                          title="Remove this revision">
                          <Icon name="trash" size={14} />
                        </button>
                      ) : null}
                    </div>
                    <div className="rev-grid">
                      <label className="field">
                        <span className="field-lab">Status</span>
                        <div className="field-wrap">
                          <select value={r.status} onChange={(e) => setRev(i, "status", e.target.value)}>
                            {statuses.map((s) => <option key={s} value={s}>{s}</option>)}
                          </select>
                        </div>
                      </label>
                      <label className="field">
                        <span className="field-lab">Date</span>
                        <div className="field-wrap">
                          {current
                            ? <input value={dateStr} readOnly tabIndex={-1} />
                            : <input type="date" value={r.date}
                                onChange={(e) => setRev(i, "date", e.target.value)} />}
                        </div>
                      </label>
                      <label className="field">
                        <span className="field-lab">Version</span>
                        <div className="field-wrap">
                          <input value={r.version} onChange={(e) => setRev(i, "version", e.target.value)}
                            placeholder="V1.0" spellCheck="false" />
                        </div>
                      </label>
                      <label className="field rev-span">
                        <span className="field-lab">Change description</span>
                        <div className="field-wrap">
                          <input value={r.change} onChange={(e) => setRev(i, "change", e.target.value)}
                            placeholder="e.g. Initial Draft" />
                        </div>
                      </label>
                      <label className="field">
                        <span className="field-lab">Author</span>
                        <div className="field-wrap">
                          <input value={r.author} onChange={(e) => setRev(i, "author", e.target.value)}
                            placeholder="Name" />
                        </div>
                      </label>
                      <label className="field">
                        <span className="field-lab">Reviewer</span>
                        <div className="field-wrap">
                          <input value={r.reviewer} onChange={(e) => setRev(i, "reviewer", e.target.value)}
                            placeholder="Name" />
                        </div>
                      </label>
                      <label className="field">
                        <span className="field-lab">Approver</span>
                        <div className="field-wrap">
                          <input value={r.approver} onChange={(e) => setRev(i, "approver", e.target.value)}
                            placeholder="Name" />
                        </div>
                      </label>
                    </div>
                  </div>
                );
              })}
            </div>
            <AdminNote err={err} ok="" />
          </div>
          <div className="usm-foot">
            <span className="usm-foot-note">The newest revision is dated at download. Details are saved in this browser.</span>
            {/* The spacer every other .usm-foot in the product has (sbom/update_sbom.jsx,
                DeleteConfirm). Without it the note's length decides where the primary
                action sits, which is only accidentally right at today's copy length. */}
            <span className="wiz-foot-sp" />
            <button className="btn btn-ghost" type="button" disabled={busy} onClick={onClose}>Cancel</button>
            <button className="btn btn-accent" type="submit" disabled={busy}>
              {busy ? <span className="btn-spin" /> : <><Icon name="download" size={15} />Download</>}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}
