/* Attachments.jsx — the Documents panel (ledger-attachments-v1, A1).
   One component, mounted on any record: <window.LedgerAttachments entityType="matter" entityId={id} />.
   Files are stored in the Ledger Attachments Shared Drives and open in Google Drive.

   Upload flow, per file:
     start (function opens a Drive session) → bytes PUT straight to Google, with a progress
     bar → meanwhile page 1 is rendered in the browser and the AI proposes type, title,
     date, source, case number and court (D6) → the person checks the form and saves.
   Nothing becomes a document until Save; Cancel sends the half-made Drive file to the drive's trash.
   Video and audio are refused (Iconik). Over 100 MB asks first; over 2 GB is refused (D5). */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const { Button, Select, Input } = DS;
  const A = () => window.LedgerAPI.att;
  const Ic = ({ n }) => <span className="ic-wrap" key={n}><i data-lucide={n} /></span>;
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);

  const SOURCES = [
    { value: "", label: "— source —" },
    { value: "court", label: "Court" },
    { value: "agency", label: "Agency" },
    { value: "law_enforcement", label: "Law enforcement" },
    { value: "freelancer", label: "Freelancer" },
    { value: "internal", label: "Law & Crime (internal)" },
    { value: "other", label: "Other" },
  ];
  const FIELDS = ["doc_type_key", "title", "document_date", "source", "case_number", "court"];
  const EMPTY = { doc_type_key: "", title: "", document_date: "", source: "", case_number: "", court: "", description: "" };

  function fmtBytes(n) {
    if (!n && n !== 0) return "";
    if (n < 1024 * 1024) return `${Math.max(1, Math.round(n / 1024))} KB`;
    if (n < 1024 ** 3) return `${(n / 1024 / 1024).toFixed(n < 10 * 1024 * 1024 ? 1 : 0)} MB`;
    return `${(n / 1024 ** 3).toFixed(2)} GB`;
  }
  const fmtDate = (d) => (d ? window.LedgerTime.fmtDate(d) : "");
  const stripExt = (n) => (n || "").replace(/\.[a-z0-9]{2,5}$/i, "");
  let seq = 0;
  // Case number and court only mean something on court-side records.
  const CASE_FIELDS = new Set(["matter", "asset_request"]);
  // Restricted records (freelancer PII; courthouses, Content Ops only per 0151 D12) live in the
  // restricted drive, viewable by this group.
  const RESTRICTED = new Set(["freelancer", "booking", "courthouse"]);
  /** Is the AI proposer on for this record type? (ledger_settings attachments.ai_entity_types) */
  const aiOn = (entityType) => A().limits().then((l) => (l.ai_entity_types || []).includes(entityType)).catch(() => false);

  // ---- metadata form (shared by new uploads and edits) ------------------------
  function MetaForm({ value, onChange, types, suggested, caseFields = true }) {
    const set = (k) => (e) => onChange({ ...value, [k]: e.target.value });
    const sug = (k) => !!(suggested && suggested.has(k));
    // DS labels are strings; an AI-filled field says so in its label and gets a tinted border.
    const lab = (text, k) => (sug(k) ? `${text} · AI suggested` : text);
    const cls = (k, extra) => ["att-f", extra, sug(k) && "att-f--ai"].filter(Boolean).join(" ");
    const typeOpts = [{ value: "", label: "— document type —" }].concat(types.map((t) => ({ value: t.key, label: t.label })));
    return (
      <div className="att-form">
        <div className={cls("doc_type_key", "att-f--type")}><Select label={lab("Document type *", "doc_type_key")} value={value.doc_type_key} onChange={set("doc_type_key")} options={typeOpts} /></div>
        <div className={cls("title", "att-f--title")}><Input label={lab("Title", "title")} value={value.title} onChange={set("title")} placeholder="e.g. Order denying motion to suppress" /></div>
        <div className={cls("document_date")}><Input type="date" label={lab("Document date", "document_date")} value={value.document_date} onChange={set("document_date")} /></div>
        <div className={cls("source")}><Select label={lab("Source", "source")} value={value.source} onChange={set("source")} options={SOURCES} /></div>
        {caseFields && <div className={cls("case_number")}><Input label={lab("Case number", "case_number")} value={value.case_number} onChange={set("case_number")} /></div>}
        {caseFields && <div className={cls("court")}><Input label={lab("Court", "court")} value={value.court} onChange={set("court")} /></div>}
        <div className="att-f att-f--full"><Input label="Note (optional)" value={value.description} onChange={set("description")} /></div>
      </div>
    );
  }

  // ---- one in-flight upload ------------------------------------------------
  function UploadCard({ u, types, caseFields, onPatch, onSave, onCancel }) {
    const uploaded = u.progress >= 1 && u.phase !== "uploading";
    const pct = Math.round((u.progress || 0) * 100);
    const status = u.error ? <span className="att-err">{u.error}</span>
      : u.phase === "starting" ? "Preparing…"
      : u.phase === "uploading" ? `Uploading ${pct}%`
      : u.phase === "saving" ? "Saving…"
      : "Uploaded";
    const aiNote = u.ai === "working" ? "Reading page 1…"
      : u.ai === "done" ? (u.suggested && u.suggested.size ? "AI filled the fields marked AI. Check them before saving." : "AI couldn't read this file. Fill in the type.")
      : u.ai === "off" ? null : u.ai === "failed" ? "AI suggestion unavailable. Fill in the type." : null;
    return (
      <div className={"att-up" + (u.error ? " att-up--err" : "")}>
        <div className="att-up__head">
          <Ic n="file-text" />
          <b className="att-up__name" title={u.file.name}>{u.file.name}</b>
          <span className="att-up__size">{fmtBytes(u.file.size)}</span>
          <span className="att-up__status">{status}</span>
        </div>
        <div className="att-bar"><span style={{ width: `${pct}%` }} /></div>
        {aiNote && <div className="att-up__ai"><Ic n="sparkles" /> {aiNote}</div>}
        {u.attachmentId && (
          <MetaForm value={u.form} types={types} suggested={u.suggested} caseFields={caseFields}
            onChange={(form) => onPatch(u.key, { form, suggested: new Set([...(u.suggested || [])].filter((k) => form[k] === u.form[k])) })} />
        )}
        <div className="att-up__actions">
          <Button variant="primary" icon="check" onClick={() => onSave(u)}
            disabled={!uploaded || !u.form.doc_type_key || u.phase === "saving" || !!u.fatal}>
            {u.phase === "saving" ? "Saving…" : "Save document"}
          </Button>
          <button type="button" className="link-btn" onClick={() => onCancel(u)}>Cancel</button>
          {!uploaded && !u.error && u.attachmentId && <span className="att-hint">You can fill this in while it uploads.</span>}
        </div>
      </div>
    );
  }

  // ---- the panel -----------------------------------------------------------
  function Attachments({ entityType, entityId, onCount }) {
    const [rows, setRows] = React.useState(null);
    const [types, setTypes] = React.useState([]);
    const [ups, setUps] = React.useState([]);
    const [err, setErr] = React.useState(null);
    const [note, setNote] = React.useState(null);
    const [drag, setDrag] = React.useState(false);
    const [linkOpen, setLinkOpen] = React.useState(false);
    const [link, setLink] = React.useState("");
    const [linkBusy, setLinkBusy] = React.useState(false);
    const [editing, setEditing] = React.useState(null);   // { id, form }
    const [bulkType, setBulkType] = React.useState("");
    const fileRef = React.useRef(null);
    const caseFields = CASE_FIELDS.has(entityType);
    const restricted = RESTRICTED.has(entityType);
    const upsRef = React.useRef([]);
    upsRef.current = ups;

    const load = React.useCallback(() => {
      return A().list(entityType, entityId)
        .then((r) => { setRows(r); onCount && onCount(r.length); })
        .catch((e) => { setRows([]); setErr(e.message || String(e)); });
    }, [entityType, entityId]);
    React.useEffect(() => { setRows(null); setUps([]); load(); }, [load]);
    React.useEffect(() => { A().docTypes(entityType).then(setTypes).catch(() => setTypes([])); }, [entityType]);
    React.useEffect(() => { icons(); });
    // Leaving the page abandons unfinished uploads (they are never shown without Save).
    React.useEffect(() => () => upsRef.current.forEach((u) => { u.xfer && u.xfer.abort && u.xfer.abort(); if (u.attachmentId) A().cancel(u.attachmentId).catch(() => {}); }), []);

    const patch = (key, p) => setUps((list) => list.map((u) => (u.key === key ? { ...u, ...(typeof p === "function" ? p(u) : p) } : u)));
    const drop = (key) => setUps((list) => list.filter((u) => u.key !== key));

    async function addFiles(fileList) {
      setErr(null); setNote(null);
      const files = Array.from(fileList || []);
      if (!files.length) return;
      let lim;
      try { lim = await A().limits(); } catch (e) { setErr(e.message || String(e)); return; }
      const refused = [];
      for (const file of files) {
        const type = (file.type || "").toLowerCase();
        if ((lim.blocked_mime_prefixes || []).some((p) => type.startsWith(p))) { refused.push(`${file.name}: video and audio go to Iconik, not Documents.`); continue; }
        if (file.size > lim.max_bytes) { refused.push(`${file.name}: over the ${fmtBytes(lim.max_bytes)} limit.`); continue; }
        if (!file.size) { refused.push(`${file.name}: the file is empty.`); continue; }
        if (file.size > lim.confirm_bytes &&
            !window.confirm(`${file.name} is ${fmtBytes(file.size)}. That looks like an evidence bundle. Upload it?`)) continue;
        startOne(file);
      }
      if (refused.length) setErr(refused.join(" "));
    }

    async function startOne(file) {
      const key = ++seq;
      const form = { ...EMPTY, title: stripExt(file.name), doc_type_key: bulkType || "" };
      setUps((list) => list.concat([{ key, file, form, progress: 0, phase: "starting", ai: null, suggested: new Set() }]));
      let s;
      try { s = await A().start(entityType, entityId, file); }
      catch (e) { patch(key, { error: e.message || String(e), fatal: true }); return; }
      patch(key, { attachmentId: s.attachment_id, phase: "uploading" });

      const xfer = A().uploadBytes(s.upload_url, file, (f) => patch(key, { progress: Math.min(f, 0.999) }));
      patch(key, { xfer });
      xfer.then(() => patch(key, { progress: 1, phase: "uploaded" }))
        .catch((e) => { if (e.code !== "aborted") patch(key, { error: e.message || String(e), fatal: true }); });

      // AI proposal in parallel with the upload (D6). Fills only fields the person hasn't touched.
      // Off for restricted records: their page 1 is never rendered or sent anywhere.
      if (!(await aiOn(entityType))) { patch(key, { ai: "off" }); return; }
      patch(key, { ai: "working" });
      try {
        const pv = await A().preview(file);
        const r = await A().propose(s.attachment_id, pv);
        if (r.skipped || !r.proposal) { patch(key, { ai: "off" }); return; }
        patch(key, (u) => {
          const form = { ...u.form }; const sug = new Set(u.suggested);
          FIELDS.forEach((k) => {
            const v = r.proposal[k];
            if (!v) return;
            const untouched = k === "title" ? u.form.title === stripExt(file.name) : !u.form[k];
            if (untouched) { form[k] = v; sug.add(k); }
          });
          return { form, suggested: sug, ai: "done" };
        });
      } catch (_) { patch(key, { ai: "failed" }); }
    }

    async function save(u) {
      patch(u.key, { phase: "saving", error: null });
      try {
        const r = await A().complete(u.attachmentId, u.form);
        drop(u.key);
        if (r.duplicates && r.duplicates.length) {
          setNote(`Saved. The same file was already attached elsewhere (${r.duplicates.map((d) => d.title || d.file_name).join(", ")}).`);
        }
        load();
      } catch (e) { patch(u.key, { phase: "uploaded", error: e.message || String(e) }); }
    }

    async function cancel(u) {
      u.xfer && u.xfer.abort && u.xfer.abort();
      drop(u.key);
      if (u.attachmentId) A().cancel(u.attachmentId).catch(() => {});
    }

    async function copyLink() {
      setLinkBusy(true); setErr(null);
      try {
        const r = await A().copy(entityType, entityId, link.trim());
        const key = ++seq;
        const file = { name: r.file_name, size: r.size, type: r.mime_type };
        setUps((list) => list.concat([{
          key, file, attachmentId: r.attachment_id, progress: 1, phase: "uploaded",
          form: { ...EMPTY, title: stripExt(r.file_name), doc_type_key: bulkType || "" }, ai: null, suggested: new Set(),
        }]));
        setLink(""); setLinkOpen(false);
        if (!(await aiOn(entityType))) { patch(key, { ai: "off" }); return; }
        patch(key, { ai: "working" });
        // A copied file isn't in the browser, so the model works from its name and the record.
        A().propose(r.attachment_id, {}).then((p) => {
          if (!p.proposal) { patch(key, { ai: "off" }); return; }
          patch(key, (u) => {
            const form = { ...u.form }; const sug = new Set();
            FIELDS.forEach((k) => { if (p.proposal[k] && (!u.form[k] || (k === "title" && u.form.title === stripExt(r.file_name)))) { form[k] = p.proposal[k]; sug.add(k); } });
            return { form, suggested: sug, ai: "done" };
          });
        }).catch(() => patch(key, { ai: "failed" }));
      } catch (e) { setErr(e.message || String(e)); }
      finally { setLinkBusy(false); }
    }

    function applyBulk(k) {
      setBulkType(k);
      if (k) setUps((list) => list.map((u) => (u.form.doc_type_key ? u : { ...u, form: { ...u.form, doc_type_key: k } })));
    }

    async function saveEdit() {
      try { await A().update(editing.id, editing.form); setEditing(null); load(); }
      catch (e) { setErr(e.message || String(e)); }
    }
    async function removeRow(r) {
      const reason = window.prompt(`Remove "${r.title || r.file_name}" from Ledger? It is kept in Drive's _Removed folder. Reason (optional):`, "");
      if (reason === null) return;
      try { await A().remove(r.id, reason); load(); }
      catch (e) { setErr(e.message || String(e)); }
    }

    const typeOpts = [{ value: "", label: "— set type for all —" }].concat(types.map((t) => ({ value: t.key, label: t.label })));

    return (
      <div className={"att" + (drag ? " att--drag" : "")}
        onDragOver={(e) => { if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes("Files")) { e.preventDefault(); setDrag(true); } }}
        onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setDrag(false); }}
        onDrop={(e) => { e.preventDefault(); setDrag(false); addFiles(e.dataTransfer.files); }}>
        <div className="att-head">
          <div className="att-head__l"><Ic n="paperclip" /> <b>Documents</b>{rows && <span className="att-count">{rows.length}</span>}</div>
          <div className="att-head__r">
            <Button variant="secondary" icon="link" onClick={() => setLinkOpen((v) => !v)}>Copy from Drive link</Button>
            <Button variant="primary" icon="upload" onClick={() => fileRef.current && fileRef.current.click()}>Upload</Button>
            <input ref={fileRef} type="file" multiple hidden onChange={(e) => { addFiles(e.target.files); e.target.value = ""; }} />
          </div>
        </div>

        {linkOpen && (
          <div className="att-link">
            <input className="lc-input" value={link} onChange={(e) => setLink(e.target.value)} placeholder="Paste a Google Drive file link" />
            <Button variant="primary" icon="copy" onClick={copyLink} disabled={linkBusy || !link.trim()}>{linkBusy ? "Copying…" : "Copy in"}</Button>
            <span className="att-hint">Ledger keeps its own copy, so the document stays if the original is moved or its owner leaves.</span>
          </div>
        )}
        {err && <div className="login__err att-msg">{err}</div>}
        {note && <div className="ledger-ok att-msg">{note}</div>}

        {ups.length > 1 && (
          <div className="att-bulk"><Select label="Several files: document type for all" value={bulkType} onChange={(e) => applyBulk(e.target.value)} options={typeOpts} /></div>
        )}
        {ups.map((u) => <UploadCard key={u.key} u={u} types={types} caseFields={caseFields} onPatch={patch} onSave={save} onCancel={cancel} />)}

        {rows === null ? <div className="att-empty">Loading…</div>
          : rows.length === 0 && ups.length === 0 ? (
            <div className="att-empty att-drop" onClick={() => fileRef.current && fileRef.current.click()}>
              <Ic n="upload" /> Drop files here or click Upload. PDFs, images and Office files up to 2 GB. Video and audio go to Iconik.
            </div>
          ) : rows.length > 0 && (
            <table className="att-table">
              <thead><tr><th>Type</th><th>Title</th><th>Date</th><th>Added</th><th>Size</th><th /></tr></thead>
              <tbody>
                {rows.map((r) => (editing && editing.id === r.id ? (
                  <tr key={r.id} className="att-editrow"><td colSpan={6}>
                    <MetaForm value={editing.form} types={types} caseFields={caseFields} onChange={(form) => setEditing({ ...editing, form })} />
                    <div className="att-up__actions">
                      <Button variant="primary" icon="check" onClick={saveEdit} disabled={!editing.form.doc_type_key}>Save</Button>
                      <button type="button" className="link-btn" onClick={() => setEditing(null)}>Cancel</button>
                    </div>
                  </td></tr>
                ) : (
                  <tr key={r.id}>
                    <td><span className="att-type">{r.doc_type_label || r.doc_type_key}</span></td>
                    <td>
                      <a href={r.drive_url} target="_blank" rel="noopener noreferrer" className="att-title">{r.title || r.file_name} <Ic n="external-link" /></a>
                      {(r.case_number || r.court) && <div className="att-sub">{[r.case_number, r.court].filter(Boolean).join(" · ")}</div>}
                      {r.description && <div className="att-sub">{r.description}</div>}
                    </td>
                    <td className="att-nowrap">{fmtDate(r.document_date)}</td>
                    <td className="att-nowrap"><span className="att-sub">{r.uploaded_by_name || r.uploaded_by_email}<br />{fmtDate(r.uploaded_at)}</span></td>
                    <td className="att-nowrap att-sub">{fmtBytes(r.size_bytes)}</td>
                    <td className="att-acts">
                      <button type="button" className="att-iconbtn" title="Edit details" onClick={() => setEditing({ id: r.id, form: {
                        doc_type_key: r.doc_type_key || "", title: r.title || "", document_date: r.document_date || "", source: r.source || "",
                        case_number: r.case_number || "", court: r.court || "", description: r.description || "" } })}><Ic n="pencil" /></button>
                      <button type="button" className="att-iconbtn" title="Remove from Ledger" onClick={() => removeRow(r)}><Ic n="trash-2" /></button>
                    </td>
                  </tr>
                )))}
              </tbody>
            </table>
          )}
        {restricted && <div className="att-foot"><Ic n="lock" /> Restricted: only Content Operations can see these. They are kept in a separate restricted drive.</div>}
        {rows && rows.length > 0 && <div className="att-foot">Documents open in Google Drive. If Drive asks you to request access, ask IT to add you to {restricted ? "ledger-content-ops@lawnewz.com" : "ledger-users@lawnewz.com"}.</div>}
      </div>
    );
  }

  window.LedgerAttachments = Attachments;
})();
