/* ============================================================
   Supabase integration — client, auth, data access, upload
   Browser-safe: uses public URL + anon key (window.CDE_ENV).
   Falls back to "not ready" so the prototype still runs on mock.
   ============================================================ */

const CDE_READY = !!(window.CDE_ENV && window.supabase && window.CDE_ENV.SUPABASE_URL);
const sb = CDE_READY
  ? window.supabase.createClient(window.CDE_ENV.SUPABASE_URL, window.CDE_ENV.SUPABASE_ANON_KEY)
  : null;

/* ---------- helpers ---------- */
function humanSize(b) {
  if (b == null) return "—";
  const mb = b / 1048576;
  if (mb >= 1) return (mb >= 10 ? Math.round(mb) : mb.toFixed(1)) + " MB";
  return Math.max(1, Math.round(b / 1024)) + " KB";
}
// parse "07/06 09:42" or "07/06 · 09:42" → sortable number (newest = largest)
function dateKey(s) {
  const m = (s || "").match(/(\d{2})\/(\d{2})\D+(\d{2}):(\d{2})/);
  if (!m) return 0;
  return (+m[2]) * 1e6 + (+m[1]) * 1e4 + (+m[3]) * 100 + (+m[4]);
}
// client-side CSV download (Excel-friendly, UTF-8 BOM)
function downloadCSV(filename, headers, rows) {
  const esc = (s) => '"' + String(s == null ? "" : s).replace(/"/g, '""') + '"';
  const lines = [headers.map(esc).join(",")].concat(rows.map(r => r.map(esc).join(",")));
  const blob = new Blob(["﻿" + lines.join("\r\n")], { type: "text/csv;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename; document.body.appendChild(a); a.click();
  a.remove(); URL.revokeObjectURL(url);
}

/* ---------- auth ---------- */
const cdeAuth = {
  ready: CDE_READY,
  async signIn(email, password) {
    const { data, error } = await sb.auth.signInWithPassword({ email, password });
    if (error) throw error;
    return data;
  },
  async signOut() { if (sb) await sb.auth.signOut(); },
  async session() { if (!sb) return null; const { data } = await sb.auth.getSession(); return data.session; },
};

function useCdeAuth() {
  const [state, setState] = React.useState({ loading: CDE_READY, user: null, profile: null, ready: CDE_READY });
  React.useEffect(() => {
    if (!CDE_READY) return;
    let sub;
    const load = async (session) => {
      if (!session) { setState({ loading: false, user: null, profile: null, ready: true }); return; }
      let profile = null;
      try {
        const { data } = await sb.from("profiles").select("*").eq("id", session.user.id).maybeSingle();
        profile = data;
      } catch {}
      setState({ loading: false, user: session.user, profile, ready: true });
    };
    (async () => {
      const { data } = await sb.auth.getSession();
      await load(data.session);
      sub = sb.auth.onAuthStateChange((_e, s) => load(s)).data.subscription;
    })();
    return () => { try { sub && sub.unsubscribe(); } catch {} };
  }, []);
  return state;
}

/* ---------- data access (maps DB rows → prototype shapes) ---------- */
const cdeData = {
  async folders() {
    const { data, error } = await sb.from("folders").select("*").order("sort");
    if (error) throw error;
    return data.map(f => ({ id: f.key, parent: f.parent_key, name: f.name,
      disc: f.disc, tag: f.tag, state: f.state, root: !f.parent_key }));
  },
  async containers() {
    const [{ data: cs, error: e1 }, { data: vs, error: e2 }] = await Promise.all([
      sb.from("containers").select("*"),
      sb.from("versions").select("*"),
    ]);
    if (e1) throw e1; if (e2) throw e2;
    const byC = {};
    for (const v of vs) (byC[v.container_id] = byC[v.container_id] || []).push(v);
    for (const k in byC) byC[k].sort((a, b) => dateKey(b.date_label) - dateKey(a.date_label));
    return cs
      .map(c => ({
        id: c.id, folder: c.folder_key, name: c.name, type: c.type, disc: c.disc,
        status: c.status, size: humanSize(c.size_bytes), date: c.date_label, by: c.author,
        rev: c.rev, desc: c.descr,
        versions: (byC[c.id] || []).map(v => ({ rev: v.rev, date: v.date_label, by: v.author, status: v.status, note: v.note, r2_key: v.r2_key })),
        _key: dateKey(c.date_label),
      }))
      .sort((a, b) => b._key - a._key);
  },
  async issues() {
    const [{ data: is, error: e1 }, { data: cm, error: e2 }] = await Promise.all([
      sb.from("issues").select("*").order("code", { ascending: false }),
      sb.from("issue_comments").select("*").order("created_at"),
    ]);
    if (e1) throw e1; if (e2) throw e2;
    const byI = {};
    for (const c of cm) (byI[c.issue_id] = byI[c.issue_id] || []).push({ who: c.author, t: c.body, time: c.time_label });
    return is.map(i => ({ _id: i.id, id: i.code, title: i.title, disc: i.disc, who: i.assignee, due: i.due,
      pri: i.priority, status: i.status, guid: i.guid, sheet: i.sheet, desc: i.descr,
      comments: byI[i.id] || [] }));
  },
  async transmittals() {
    const [{ data: ts, error: e1 }, { data: items, error: e2 }] = await Promise.all([
      sb.from("transmittals").select("*").order("code", { ascending: false }),
      sb.from("transmittal_items").select("*"),
    ]);
    if (e1) throw e1; if (e2) throw e2;
    const byT = {};
    for (const it of items) (byT[it.transmittal_id] = byT[it.transmittal_id] || []).push({ name: it.container_name, rev: it.rev });
    return ts.map(t => ({ _id: t.id, id: t.code, title: t.title, to: t.to_party, from: t.from_user, disc: t.disc,
      date: t.date_label, status: t.status, docs: byT[t.id] || [] }));
  },
  async audit(limit = 50) {
    const { data, error } = await sb.from("audit_events").select("*").order("created_at", { ascending: false }).limit(limit);
    if (error) throw error;
    return data;
  },
  async logAudit(ev) { try { await sb.from("audit_events").insert(ev); } catch (e) { console.warn("audit", e.message); } },

  async projectId() {
    const { data } = await sb.from("projects").select("id").eq("code", "CP-02").maybeSingle();
    return data ? data.id : null;
  },

  // FR-4 — gated status transition (+ auto suitability + audit)
  async setContainerStatus(id, status, actor) {
    const suit = (window.STATUS && window.STATUS[status] && window.STATUS[status].suit) || null;
    const { data, error } = await sb.from("containers")
      .update({ status, suitability: suit, updated_at: new Date().toISOString() })
      .eq("id", id).select().maybeSingle();
    if (error) throw error;
    await this.logAudit({ project_id: data.project_id, actor: actor || "demo", action: "status:" + status, target_type: "container", target_id: data.name });
    return data;
  },

  // FR-6 — issues writes
  async createIssue(p) {
    const pid = await this.projectId();
    const code = "ISS-" + String(Date.now()).slice(-4);
    const { data, error } = await sb.from("issues").insert({
      project_id: pid, code, title: p.title, disc: p.disc, assignee: p.assignee,
      due: p.due, priority: p.priority, status: "open", sheet: p.sheet, descr: p.descr }).select().maybeSingle();
    if (error) throw error;
    await this.logAudit({ project_id: pid, actor: "demo", action: "issue_open", target_type: "issue", target_id: code });
    return data;
  },
  async addComment(issueId, author, body) {
    const { error } = await sb.from("issue_comments").insert({ issue_id: issueId, author, body, time_label: "vừa xong" });
    if (error) throw error;
  },
  async setIssueStatus(issueId, status) {
    const { data, error } = await sb.from("issues").update({ status }).eq("id", issueId).select().maybeSingle();
    if (error) throw error;
    await this.logAudit({ project_id: data.project_id, actor: "demo", action: "issue:" + status, target_type: "issue", target_id: data.code });
    return data;
  },

  // FR-5 — transmittal create
  async createTransmittal(p, items) {
    const pid = await this.projectId();
    const code = "TR-" + String(Date.now()).slice(-4);
    const now = new Date();
    const dl = String(now.getDate()).padStart(2, "0") + "/" + String(now.getMonth() + 1).padStart(2, "0") + " · " +
      String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0");
    const { data: tr, error } = await sb.from("transmittals").insert({
      project_id: pid, code, title: p.title, to_party: p.to, from_user: p.from, disc: p.disc, status: "sent", date_label: dl }).select().maybeSingle();
    if (error) throw error;
    if (items && items.length) {
      const ins = await sb.from("transmittal_items").insert(items.map(it => ({ transmittal_id: tr.id, container_name: it.name, rev: it.rev })));
      if (ins.error) throw ins.error;
    }
    await this.logAudit({ project_id: pid, actor: "demo", action: "transmittal_send", target_type: "transmittal", target_id: code });
    return tr;
  },

  // folder create (editor+admin per RLS 0003)
  async createFolder({ name, parentKey, disc, tag }) {
    const pid = await this.projectId();
    const key = "f" + Date.now().toString(36) + Math.floor(Math.random() * 1e4).toString(36);
    const { data, error } = await sb.from("folders").insert({
      project_id: pid, key, parent_key: parentKey || null, name,
      disc: disc || null, tag: tag || null, sort: 999,
    }).select().maybeSingle();
    if (error) throw error;
    await this.logAudit({ project_id: pid, actor: "demo", action: "folder_create", target_type: "folder", target_id: name });
    return data;
  },

  // document delete (editor+admin per RLS 0003; versions cascade via FK)
  async deleteContainer(id, name) {
    const { error } = await sb.from("containers").delete().eq("id", id);
    if (error) throw error;
    const pid = await this.projectId();
    await this.logAudit({ project_id: pid, actor: "demo", action: "delete", target_type: "container", target_id: name || id });
  },

  // FR-1 — R2 access via server proxy (same-origin, no CORS needed)
  objectUrl(key) { return "/api/r2/object?key=" + encodeURIComponent(key); },
  downloadUrl(key) { return "/api/r2/object?key=" + encodeURIComponent(key) + "&download=1"; },
  async presignGet(key) { return this.objectUrl(key); },

  // FR-10 — clash coordination (results imported from external engine)
  async clashGroups() {
    const [{ data: g }, { data: c }] = await Promise.all([
      sb.from("clash_groups").select("*").order("created_at", { ascending: false }),
      sb.from("clashes").select("group_id,status"),
    ]);
    const cnt = {}, act = {};
    (c || []).forEach(x => { cnt[x.group_id] = (cnt[x.group_id] || 0) + 1; if (x.status === "active") act[x.group_id] = (act[x.group_id] || 0) + 1; });
    return (g || []).map(x => ({ ...x, total: cnt[x.id] || 0, active: act[x.id] || 0 }));
  },
  async clashes() {
    const [{ data: cs, error }, { data: gs }] = await Promise.all([
      sb.from("clashes").select("*").order("code"),
      sb.from("clash_groups").select("*"),
    ]);
    if (error) throw error;
    const gmap = {}; (gs || []).forEach(g => gmap[g.id] = g);
    return (cs || []).map(c => { const g = gmap[c.group_id] || {}; return { ...c, group_name: g.name, disc_a: g.disc_a, disc_b: g.disc_b, source: g.source }; });
  },
  async setClashStatus(id, status) {
    const { data, error } = await sb.from("clashes").update({ status }).eq("id", id).select().maybeSingle();
    if (error) throw error;
    await this.logAudit({ project_id: data.project_id, actor: "demo", action: "clash:" + status, target_type: "clash", target_id: data.code });
    return data;
  },
  async clashToIssue(clash) {
    const issue = await this.createIssue({
      title: "Va chạm " + clash.code + ": " + (clash.element_a || "") + " ↔ " + (clash.element_b || ""),
      disc: clash.disc_a || "str", assignee: "", due: "", priority: clash.distance_mm >= 50 ? "Cao" : "TB",
      sheet: clash.location || "", descr: clash.descr || "" });
    await sb.from("clashes").update({ issue_id: issue.id, status: "reviewing" }).eq("id", clash.id);
    await this.logAudit({ project_id: clash.project_id, actor: "demo", action: "clash_to_issue", target_type: "clash", target_id: clash.code });
    return issue;
  },

  // FR-18 — dashboard aggregates
  async dashboard() {
    const [c, i, t] = await Promise.all([
      sb.from("containers").select("disc,status"),
      sb.from("issues").select("status"),
      sb.from("transmittals").select("id"),
    ]);
    return { containers: c.data || [], issues: i.data || [], transmittals: (t.data || []).length };
  },
};

/* ---------- upload (R2 presign → PUT → DB insert) ---------- */
async function cdeUpload(file, meta) {
  // meta: { name, type, disc, folderKey, projectId }
  const rev = "P01";
  const ct = file.type || "application/octet-stream";
  const key = `containers/${meta.name}/${rev}/${file.name}`;
  // 1) ask the server for a short-lived presigned PUT url (the "ticket")
  const ticket = await fetch("/api/r2/presign-put", {
    method: "POST", headers: { "content-type": "application/json" },
    body: JSON.stringify({ key, contentType: ct }),
  });
  if (!ticket.ok) throw new Error("Không lấy được vé upload (" + ticket.status + ")");
  const { url } = await ticket.json();
  // 2) browser uploads the bytes straight to R2 (no proxy, no size limit)
  const put = await fetch(url, { method: "PUT", headers: { "content-type": ct }, body: file });
  if (!put.ok) throw new Error("Upload R2 thất bại (" + put.status + ")");

  const { data: proj } = await sb.from("projects").select("id").eq("code", "CP-02").maybeSingle();
  const today = new Date();
  const dl = String(today.getDate()).padStart(2, "0") + "/" + String(today.getMonth() + 1).padStart(2, "0") + " · " +
    String(today.getHours()).padStart(2, "0") + ":" + String(today.getMinutes()).padStart(2, "0");
  const { data: c, error } = await sb.from("containers").insert({
    project_id: proj?.id, folder_key: meta.folderKey, name: meta.name, type: meta.type, disc: meta.disc,
    status: "wip", rev, suitability: "S0", size_bytes: file.size, descr: "Tệp vừa nạp", date_label: dl,
  }).select().maybeSingle();
  if (error) throw error;
  await sb.from("versions").insert({ container_id: c.id, rev, status: "wip", note: "Khởi tạo", r2_key: key,
    size_bytes: file.size, date_label: dl.replace(" · ", " ") });
  await cdeData.logAudit({ project_id: proj?.id, actor: "demo", action: "upload", target_type: "container", target_id: meta.name, meta: { rev } });
  return c;
}

/* ---------- login modal ---------- */
function LoginModal({ onClose }) {
  const [email, setEmail] = React.useState("demo@bimup.test");
  const [pw, setPw] = React.useState("bimup-cde-demo");
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState("");
  const submit = async () => {
    setBusy(true); setErr("");
    try { await cdeAuth.signIn(email, pw); onClose && onClose(); }
    catch (e) { setErr(e.message || "Đăng nhập thất bại"); }
    finally { setBusy(false); }
  };
  const f = { width: "100%", height: 42, padding: "0 12px", borderRadius: 10, border: "1px solid var(--line)",
    background: "var(--surface)", color: "var(--ink)", font: "inherit", fontSize: 14, fontWeight: 600, outline: "none", marginTop: 6 };
  const lb = { display: "block", fontSize: 12, fontWeight: 700, color: "var(--ink-3)", marginTop: 14 };
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 60, background: "rgba(8,19,23,.55)", backdropFilter: "blur(3px)", display: "grid", placeItems: "center" }}>
      <div onClick={(e) => e.stopPropagation()} className="card" style={{ width: 400, padding: 26, boxShadow: "var(--shadow-lg)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
          <span style={{ width: 34, height: 34, borderRadius: 9, background: "var(--accent)", color: "#fff", display: "grid", placeItems: "center" }}><Icon.sync size={18} /></span>
          <div style={{ fontWeight: 800, fontSize: 17 }}>Đăng nhập BIMUP CDE</div>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 500 }}>Kết nối Supabase · dữ liệu thật</div>
        <label style={lb}>Email</label>
        <input style={f} value={email} onChange={(e) => setEmail(e.target.value)} type="email" />
        <label style={lb}>Mật khẩu</label>
        <input style={f} value={pw} onChange={(e) => setPw(e.target.value)} type="password"
          onKeyDown={(e) => e.key === "Enter" && submit()} />
        {err && <div style={{ marginTop: 12, fontSize: 12.5, color: "var(--danger)", fontWeight: 600 }}>{err}</div>}
        <button className="btn primary" disabled={busy} onClick={submit}
          style={{ width: "100%", justifyContent: "center", marginTop: 18, height: 44, opacity: busy ? .6 : 1 }}>
          {busy ? "Đang đăng nhập…" : "Đăng nhập"}
        </button>
        <div style={{ marginTop: 12, fontSize: 11.5, color: "var(--ink-4)", textAlign: "center" }}>
          Demo: demo@bimup.test / bimup-cde-demo
        </div>
      </div>
    </div>
  );
}

/* shared auth context so children read one subscription */
const CdeAuthCtx = React.createContext({ ready: CDE_READY, loading: CDE_READY, user: null, profile: null });
const useAuthCtx = () => React.useContext(CdeAuthCtx);

Object.assign(window, { sb, CDE_READY, cdeAuth, cdeData, cdeUpload, useCdeAuth, LoginModal, CdeAuthCtx, useAuthCtx, downloadCSV });
