window.FB = window.FB || {};
FB.sb = supabase.createClient(FB.config.SUPABASE_URL, FB.config.SUPABASE_ANON_KEY);
// Storage 文件名规则 = item_code 全大写 (P0004A.jpg)
// items.code 有小写尾缀 (P0004a) → 不转大写会 404 显示「暂无照片」
FB.photoUrl = (code) => FB.config.PHOTO_BASE + encodeURIComponent(String(code || "").toUpperCase()) + ".jpg";
// 「今天」一律用本地日期 — toISOString 是 UTC, 大马早上8点前会差一天 (签到页曾因此跨天不重置)
FB.todayLocal = () => new Date().toLocaleDateString("en-CA");
FB.localDate = (ts) => (ts ? new Date(ts) : new Date()).toLocaleDateString("en-CA");

async function fn(name, body) {
  const { data, error } = await FB.sb.functions.invoke(name, { body });
  if (error) return { error };
  return data;
}

FB.api = {
  // ---- auth ----
  async signIn(name, pin) {
    const u = FB.USERS.find((x) => x.name === name);
    if (!u) return { error: "no user" };
    const { data, error } = await FB.sb.auth.signInWithPassword({ email: u.email, password: pin });
    if (error) return { error };
    return { user: u, session: data.session };
  },
  signOut() { return FB.sb.auth.signOut(); },
  async currentUser() {
    const { data } = await FB.sb.auth.getSession();
    if (!data.session) return null;
    const email = data.session.user.email;
    return FB.USERS.find((x) => x.email === email) || { name: email, role: "staff", defaultLang: "vi" };
  },

  // ---- master / views ----
  async items() {
    const { data, error } = await FB.sb.from("items")
      .select("code,name_en,name_cn,name_vn,unit,carton_size,abc_class,category,zone")
      .eq("active", true).order("zone").order("code");
    return error ? [] : data;
  },
  async safetyMap() {
    // 不取 reorder_ctn: 那是「满仓要多少」的陷阱字段, 订货一律走 FB.api.reorder()
    const { data, error } = await FB.sb.from("v_safety_line")
      .select("code,safety_line,daily_usage,carton_size,supplier_name");
    if (error) return {};
    const m = {}; data.forEach((r) => (m[r.code] = r)); return m;
  },
  async stockRunway() {
    const { data, error } = await FB.sb.from("v_stock_runway")
      .select("*").in("severity", ["red", "orange", "yellow"]).order("runway_days");
    return error ? [] : data;
  },
  async reorder() {
    // 订货量 = 安全线 − 理论库存 → 按箱规进位 → 抬到 MOQ   (见 CLAUDE.md「订货量」)
    // ⚠️ 不能直接用 v_safety_line.reorder_ctn: 它没减库存, 是「满仓要多少」不是「还差多少」。
    //    2026-07-29 实测直接用会多报 —— 30 项里 15 项其实够用(纸袋现存 5600/安全线 1068,
    //    手套 125/16), 而汉堡盒现存 −7 又少报 1 箱。
    // ⚠️ 旧写法 .gt("reorder_ctn",0) 还会把 carton_size 为 null 的品整个漏掉
    //    (番茄/生菜的 reorder_ctn 是 null) → 驾驶舱从来没提示过要订它们。
    // lt + buffer = 到货天数, 驾驶舱按它排下单急迫度
    const [{ data, error }, { data: stk }, { data: rw }] = await Promise.all([
      FB.sb.from("v_safety_line")
        .select("code,name_cn,supplier_name,safety_line,daily_usage,lt,buffer,carton_size")
        .order("supplier_name"),
      FB.sb.from("v_theoretical_stock").select("item_code,theoretical_qty"),
      FB.sb.from("v_stock_runway").select("code,severity"),
    ]);
    if (error) return [];
    const onHand = {};
    (stk || []).forEach((s) => (onHand[s.item_code] = Number(s.theoretical_qty) || 0));
    // 绿灯不催单 (2026-07-29 Shanqian 定): 绿灯 = 现存撑得过「下次盘点 + LT + 缓冲」,
    // 定义上不可能因为隐藏而断货 —— 蒜粉差 1% 那种噪音就藏住了, 一掉到黄灯自动冒出来。
    // A1 在 07-23 也是这么判的 (收据纸只差 5.77 roll 可撑 38 天 → 未下)。
    // 不在 runway 里的品 (没日用量) 判不了绿灯 → 保守起见照列。
    const sev = {};
    (rw || []).forEach((r) => (sev[r.code] = r.severity));
    const gaps = (data || [])
      .map((r) => {
        const have = onHand[r.code] || 0;
        return { ...r, on_hand: have, gap: Number(r.safety_line) - have };
      })
      .filter((r) => r.gap > 0 && sev[r.code] !== "green");
    const codes = gaps.map((r) => r.code);
    if (!codes.length) return [];
    const [{ data: its }, { data: al }] = await Promise.all([
      // v_safety_line 只有 name_cn → 补 vn/en/unit; moq 用来抬量(单位 pkt)
      FB.sb.from("items").select("code,name_vn,name_en,unit,moq").in("code", codes),
      // 供应商叫法: 订货单直接发给供应商用的名字
      FB.sb.from("item_aliases").select("item_code,alias").eq("alias_type", "supplier").in("item_code", codes).order("id"),
    ]);
    const nm = {}; (its || []).forEach((i) => (nm[i.code] = i));
    // 一个品可能有多个别名(如 V0001 有 2 个) → 取 id 最小那个, 否则订单会出重复行
    const av = {}; (al || []).forEach((a) => { if (!av[a.item_code]) av[a.item_code] = a.alias; });
    return gaps.map((r) => {
      const it = nm[r.code] || {};
      const cs = Number(r.carton_size) || 0;
      const moq = Number(it.moq) || 0;
      // 有箱规 → 进位成箱; 没箱规 → 按缺口进整
      // 2026-07-30 Shanqian:「订货 kg 整数就行」— 番茄报 8 kg 不报 7.68 kg, 所有单位一律整数
      let qty = cs > 0 ? Math.ceil(r.gap / cs) : Math.ceil(r.gap);
      if (moq > 0) {
        const moqQty = cs > 0 ? Math.ceil(moq / cs) : moq;
        if (qty < moqQty) qty = moqQty;
      }
      return {
        ...r,
        name_vn: it.name_vn,
        name_en: it.name_en,
        unit: it.unit,
        alias: av[r.code] || null,
        moq: it.moq != null ? Number(it.moq) : null,
        reorder_ctn: qty,   // 沿用旧字段名; 含义: 有箱规=箱数, 无箱规=品项单位的量(Dashboard 按 carton_size 判断显示哪个单位)
      };
    }).filter((r) => Number(r.reorder_ctn) > 0);
  },
  async finishedGoods() {
    const { data, error } = await FB.sb.from("v_finished_goods").select("*").eq("active", true).order("code");
    return error ? [] : data;
  },
  async sellMargin() {
    const { data, error } = await FB.sb.from("v_sell_margin").select("*").order("margin_pct", { nullsFirst: false });
    return error ? [] : data;
  },

  // ---- 毛利「先不管」 ----
  // 表没建时返回 null → 驾驶舱隐藏忽略按钮, 不报错
  async marginSnooze() {
    const { data, error } = await FB.sb.from("margin_snooze").select("sell_code,snoozed_pct,threshold");
    if (error) return null;
    const m = {}; (data || []).forEach((r) => (m[r.sell_code] = r)); return m;
  },
  async muteMargin(sell_code, snoozed_pct, threshold, by) {
    const { error } = await FB.sb.from("margin_snooze").upsert(
      { sell_code, snoozed_pct, threshold, snoozed_by: by || null, snoozed_at: new Date().toISOString() },
      { onConflict: "sell_code" });
    return error ? { error } : { ok: true };
  },
  async unmuteMargin(sell_code) {
    const { error } = await FB.sb.from("margin_snooze").delete().eq("sell_code", sell_code);
    return error ? { error } : { ok: true };
  },
  async openExceptions() {
    const { data, error } = await FB.sb.from("exceptions")
      .select("id,severity,item_code,title,suggested_action,last_seen_at").eq("status", "open");
    if (error) return [];
    const rank = { red: 1, orange: 2, yellow: 3, green: 4 };
    return data.sort((a, b) => (rank[a.severity] || 9) - (rank[b.severity] || 9));
  },
  async theoreticalStock() {
    const { data, error } = await FB.sb.from("v_theoretical_stock")
      .select("item_code,name_cn,unit,theoretical_qty").order("item_code");
    return error ? [] : data;
  },

  // ---- sell prices (shipping) ----
  async sellPrices() {
    const { data, error } = await FB.sb.from("sell_prices")
      .select("sell_code,item_code,name_en,sell_unit,price,items(category,name_cn,name_vn)")
      .eq("active", true).order("sell_code");
    return error ? [] : data;
  },

  // ---- daily tasks ----
  async dailyTasks() {
    const { data, error } = await FB.sb.from("daily_tasks").select("*").eq("active", true).order("time_slot", { nullsFirst: false }).order("id");
    return error ? [] : data;
  },
  async taskLogs(date) {
    const { data, error } = await FB.sb.from("task_logs").select("task_id,done_by").eq("log_date", date);
    return error ? [] : data;
  },
  manageTask(payload) { return fn("manage_tasks", payload); },

  // ---- production ----
  async productionPlan() {
    const { data, error } = await FB.sb.from("production_plan").select("*");
    return error ? [] : data;
  },
  async finishedForProd() {
    // 可生产的成品: v_finished_goods 有配方 + 一批几包
    const { data, error } = await FB.sb.from("v_finished_goods")
      .select("code,name_cn,name_en,name_vn,一批几包,有配方").eq("active", true).eq("有配方", true).order("code");
    return error ? [] : data;
  },
  submitProduction(payload) { return fn("submit_production", payload); },

  // ---- SOP ----
  async sopRecipe(output_code) {
    const [{ data: bom }, { data: spec }] = await Promise.all([
      FB.sb.from("bom").select("input_code,qty,unit").eq("output_code", output_code),
      FB.sb.from("batch_spec").select("batch_yield,yield_unit").eq("output_code", output_code).maybeSingle(),
    ]);
    let names = {};
    if (bom && bom.length) {
      const codes = bom.map((b) => b.input_code);
      const { data: its } = await FB.sb.from("items").select("code,name_cn,name_en,name_vn").in("code", codes);
      (its || []).forEach((i) => (names[i.code] = i));
    }
    return { bom: bom || [], spec: spec || null, names };
  },

  // ---- write functions ----
  resolveException(payload) { return fn("resolve_exception", payload); },
  // 门店待发订单(最新一张)
  async pendingOrder(store_id) {
    const { data: o } = await FB.sb.from("orders")
      .select("id,store_id,order_date,status")
      .eq("store_id", store_id).eq("status", "待发")
      .order("order_date", { ascending: false }).order("id", { ascending: false })
      .limit(1).maybeSingle();
    if (!o) return null;
    const { data: items } = await FB.sb.from("order_items")
      .select("item_code,qty_ordered,unit").eq("order_id", o.id);
    return { order: o, items: items || [] };
  },
  closeOrder(payload) { return fn("close_order", payload); },
  amendShipment(payload) { return fn("amend_shipment", payload); },

  // ---- 收货 ----
  async pendingPOs() {
    const { data, error } = await FB.sb.from("purchase_orders")
      .select("id,supplier_name,order_date,expected_date,status,paid_by")
      .eq("status", "已下单").order("expected_date");
    if (error) return [];
    const ids = (data || []).map((p) => p.id);
    if (!ids.length) return [];
    const { data: its } = await FB.sb.from("purchase_order_items")
      .select("po_id,item_code,qty_ordered,unit,items(name_cn,name_vn,name_en)").in("po_id", ids);
    const byPo = {};
    (its || []).forEach((r) => { (byPo[r.po_id] = byPo[r.po_id] || []).push(r); });
    return (data || []).map((p) => ({ ...p, items: byPo[p.id] || [] }));
  },
  // 上传发票图 → invoices 桶
  async uploadInvoice(file, prefix) {
    const ext = (file.name || "jpg").split(".").pop().toLowerCase();
    const path = `${prefix}_${Date.now()}.${ext}`;
    const { error } = await FB.sb.storage.from("invoices").upload(path, file, { upsert: true });
    if (error) return { error };
    return { path, url: FB.sb.storage.from("invoices").getPublicUrl(path).data.publicUrl };
  },
  submitReceiving(payload) { return fn("submit_receiving", payload); },
  // 取消还没到货的采购单 (在别家买了的场景); 只有「已下单」能取消
  cancelPO(payload) { return fn("cancel_po", payload); },

  // ---- admin 审核 ----
  async pendingReviews() {
    const { data, error } = await FB.sb.from("purchase_orders")
      .select("id,supplier_name,order_date,expected_date,status,paid_by,claim_status,invoice_file,received_at,note")
      .eq("status", "已到货").order("received_at", { ascending: false });
    if (error || !data || !data.length) return [];
    const ids = data.map((p) => p.id);
    const { data: its } = await FB.sb.from("purchase_order_items")
      .select("po_id,item_code,qty_ordered,qty_received,unit,unit_price,items(name_cn,name_vn,name_en,buying_price)")
      .in("po_id", ids);
    const byPo = {};
    (its || []).forEach((r) => { (byPo[r.po_id] = byPo[r.po_id] || []).push(r); });
    return data.map((p) => {
      let meta = {};
      try { meta = p.invoice_file ? JSON.parse(p.invoice_file) : {}; } catch (e) { meta = {}; }
      const urls = (meta.files || []).map((path) => FB.sb.storage.from("invoices").getPublicUrl(path).data.publicUrl);
      return { ...p, meta, urls, items: byPo[p.id] || [] };
    });
  },
  approveReceiving(payload) { return fn("approve_receiving", payload); },
  async receivedHistory() {
    const { data, error } = await FB.sb.from("purchase_orders")
      .select("id,supplier_name,order_date,received_at,status,paid_by,claim_status,invoice_no,invoice_total,invoice_file")
      .in("status", ["已对账"]).order("received_at", { ascending: false }).limit(60);
    if (error) return [];
    return (data || []).map((p) => {
      let meta = {}; try { meta = p.invoice_file ? JSON.parse(p.invoice_file) : {}; } catch (e) {}
      return { ...p, meta, urls: (meta.files || []).map((f) => FB.sb.storage.from("invoices").getPublicUrl(f).data.publicUrl) };
    });
  },
  extractInvoice(payload) { return fn("extract_invoice", payload); },

  // ---- 考勤 ----
  async staffList() {
    const { data, error } = await FB.sb.from("staff")
      .select("id,name,name_vn,base_salary,bank_name,bank_account,std_days,ot_rate")
      .eq("active", true).order("id");
    return error ? [] : data;
  },
  async attendanceMonth(ym) {
    const { data, error } = await FB.sb.from("attendance")
      .select("id,staff_id,work_date,start_time,end_time,note")
      .gte("work_date", ym + "-01").lte("work_date", ym + "-31").order("work_date");
    return error ? [] : data;
  },
  async salaryMonth(ym) {
    const { data, error } = await FB.sb.from("v_salary_month").select("*").eq("ym", ym);
    return error ? [] : data;
  },
  submitAttendance(payload) { return fn("submit_attendance", payload); },
  clockAttendance(payload) { return fn("clock_attendance", payload); },
  async todayAttendance() {
    const d = FB.todayLocal();
    const { data, error } = await FB.sb.from("attendance")
      .select("id,staff_id,work_date,start_time,end_time").eq("work_date", d);
    return error ? [] : data;
  },
  async staffPages() {
    const { data, error } = await FB.sb.from("app_settings").select("value").eq("key", "staff_pages").maybeSingle();
    return error || !data ? ["clockin", "shipping", "stock", "task", "sop"] : data.value;
  },
  // admin 底部导航自选; 没存过 → 默认 NAV_BOTTOM.admin
  async adminNav() {
    const { data, error } = await FB.sb.from("app_settings").select("value").eq("key", "admin_nav").maybeSingle();
    const v = !error && data && data.value;
    return Array.isArray(v) && v.length ? v.slice(0, 5) : FB.NAV_BOTTOM.admin;
  },

  // ---- 生产排程建议 ----
  async productionSuggest() {
    const { data, error } = await FB.sb.from("v_production_suggest")
      .select("*").order("suggest_batches", { ascending: false });
    return error ? [] : data;
  },
  async itemOptions() {
    const { data, error } = await FB.sb.from("items")
      .select("code,name_cn,name_en,unit,supplier_name").eq("active", true).order("code");
    return error ? [] : data;
  },
  async shipCheckData() {
    const [{ data: st }, { data: it }] = await Promise.all([
      FB.sb.from("v_theoretical_stock").select("item_code,theoretical_qty"),
      FB.sb.from("items").select("code,daily_usage").eq("active", true),
    ]);
    const m = {};
    (it || []).forEach((r) => (m[r.code] = { usage: Number(r.daily_usage) || 0, stock: 0 }));
    (st || []).forEach((r) => { m[r.item_code] = m[r.item_code] || { usage: 0, stock: 0 }; m[r.item_code].stock = Number(r.theoretical_qty) || 0; });
    return m;
  },
  submitStocktake(payload) { return fn("submit_stocktake", payload); },
  // 今天交过的盘点 (改错用): 同一项取最后一笔
  async todayStocktakes() {
    const today = FB.todayLocal();
    const { data, error } = await FB.sb.from("stocktakes")
      .select("id,item_code,qty,counted_by").eq("counted_at", today).eq("store_id", "CK")
      .order("id", { ascending: false });
    if (error) return [];
    const seen = {}; const out = [];
    (data || []).forEach((r) => { if (!seen[r.item_code]) { seen[r.item_code] = 1; out.push(r); } });
    return out;
  },
  submitBaseline(payload) { return fn("submit_baseline", payload); },
  async submitShipment(payload) { return fn("submit_shipment", payload); },

  // ---- 订货页 (v20.1) ----
  createPo(payload) { return fn("create_po", payload); },
  // 影子自动审核 (v20.1, 影子跑3次): 判但不入库, 判定存单据 meta.verdict
  autoReview(payload) { return fn("auto_review", payload); },
  // 今天做什么: 今天已记的生产 (避免同一批重复记)
  async todayProduction() {
    const today = FB.todayLocal();
    const { data, error } = await FB.sb.from("production_logs")
      .select("item_code,batch_qty").eq("produced_at", today);
    if (error) return [];
    const m = {};
    (data || []).forEach((r) => { m[r.item_code] = (m[r.item_code] || 0) + Number(r.batch_qty || 0); });
    return Object.keys(m).map((k) => ({ item_code: k, batch_qty: m[k] }));
  },
  // 在途量: 已下单未到货的 PO 明细汇总 {item_code: qty}。
  // 订货页用它扣缺口 —— 不扣的话刚建完单刷新,同样的量又全冒出来重复催。
  async openPoQty() {
    const { data, error } = await FB.sb.from("purchase_order_items")
      .select("item_code,qty_ordered,purchase_orders!inner(status)")
      .eq("purchase_orders.status", "已下单");
    if (error) return {};
    const m = {};
    (data || []).forEach((r) => { m[r.item_code] = (m[r.item_code] || 0) + Number(r.qty_ordered || 0); });
    return m;
  },
  async suppliersList() {
    const { data, error } = await FB.sb.from("suppliers")
      .select("name,lead_time_days,buffer_days").order("name");
    return error ? [] : data;
  },

  // ---- shipment history ----
  async shipmentHistory() {
    const { data, error } = await FB.sb.from("stock_movements")
      .select("item_code,qty,unit,moved_at,ref_id,store_id,note")
      .eq("move_type", "shipment_out").order("moved_at", { ascending: false }).limit(300);
    return error ? [] : data;
  },
};
