// ================= Real data layer for Fabcom Media Ops Portal =================
// Fetches live data from backend and populates window.BRANDS, window.NON_MEDIA, etc.
// App.jsx waits for window.__fabcomDataReady() before rendering dashboard screens.

(function () {
  const PLATFORMS = [
    { id: "meta",    name: "Meta",       short: "M",  color: "#1877f2" },
    { id: "google",  name: "Google Ads", short: "G",  color: "#ea4335" },
    { id: "dv360",   name: "DV360",      short: "D",  color: "#34a853" },
    { id: "tiktok",  name: "TikTok",     short: "T",  color: "#111111" },
    { id: "ttd",     name: "TTD",        short: "TT", color: "#ff6e00" },
    { id: "youtube", name: "YouTube",    short: "Y",  color: "#ff0000" },
  ];

  // Normalize arbitrary platform names from BQ (e.g. "facebook", "google_ads") to UI ids.
  function normalizePlatform(p) {
    if (!p) return "meta";
    const x = String(p).toLowerCase();
    if (x.includes("facebook") || x.includes("meta")) return "meta";
    if (x.includes("google") && !x.includes("dv")) return "google";
    if (x.includes("dv360") || x.includes("display")) return "dv360";
    if (x.includes("tiktok")) return "tiktok";
    if (x.includes("ttd") || x.includes("trade desk") || x.includes("tradedesk")) return "ttd";
    if (x.includes("youtube") || x === "yt") return "youtube";
    return x;
  }

  function brandInitials(name, code) {
    if (code && code.length <= 4) return code;
    if (!name) return (code || "??").slice(0, 3).toUpperCase();
    return name.split(/\s+/).slice(0, 2).map(s => s[0]).join("").toUpperCase();
  }

  // ---------- Format helpers ----------
  const fmt = {
    hkd: (n) => n == null ? "—" : `HK$${Math.round(n).toLocaleString()}`,
    hkdK: (n) => {
      if (n == null) return "—";
      const a = Math.abs(n);
      if (a >= 1e6) return `HK$${(n / 1e6).toFixed(2)}M`;
      if (a >= 1e3) return `HK$${(n / 1e3).toFixed(1)}k`;
      return `HK$${Math.round(n)}`;
    },
    int: (n) => n == null ? "—" : Math.round(n).toLocaleString(),
    pct: (n, digits = 1) => n == null ? "—" : `${(n * 100).toFixed(digits)}%`,
    pctDelta: (n, digits = 1) => n == null ? "—" : `${n >= 0 ? "+" : ""}${(n * 100).toFixed(digits)}%`,
    money2: (n) => n == null ? "—" : `HK$${Number(n).toFixed(2)}`,
    date: (s) => s,
    timeAgo: (hoursOrDate) => {
      if (hoursOrDate == null) return "—";
      let hours = hoursOrDate;
      if (typeof hoursOrDate === "string") {
        hours = (Date.now() - new Date(hoursOrDate).getTime()) / 3.6e6;
      }
      if (hours < 1) return `${Math.max(1, Math.round(hours * 60))}m ago`;
      if (hours < 24) return `${Math.round(hours)}h ago`;
      return `${Math.round(hours / 24)}d ago`;
    },
    mins: (m) => {
      if (m == null) return "—";
      if (m < 60) return `${m}m ago`;
      if (m < 24 * 60) return `${Math.round(m / 60)}h ago`;
      return `${Math.round(m / 1440)}d ago`;
    },
  };

  // ---------- Fetch + transform ----------
  async function api(path) {
    const res = await fetch(path, { credentials: "include", headers: { "Accept": "application/json" } });
    if (res.status === 401) throw Object.assign(new Error("unauthorized"), { code: 401 });
    if (!res.ok) throw new Error(`${path}: ${res.status}`);
    return res.json();
  }

  function buildDailySeries(series30d) {
    // Brand daily series: array of {date, cost, platforms} per day aggregated.
    // Stitch code expects brand.daily_series as number[] length 30 for sparklines.
    if (!series30d || !series30d.length) return Array(30).fill(0);
    const byDate = {};
    for (const row of series30d) byDate[row.date] = (byDate[row.date] || 0) + (row.cost || 0);
    // Last 30 days ascending
    const out = [];
    const today = new Date();
    for (let i = 29; i >= 0; i--) {
      const d = new Date(today);
      d.setDate(d.getDate() - i);
      const key = d.toISOString().slice(0, 10);
      out.push(Math.round(byDate[key] || 0));
    }
    return out;
  }

  function transformBrands(apiBrands) {
    return apiBrands.map(b => {
      const paceTone = (pace) => pace > 1.2 ? "danger" : pace < 0.7 ? "warn" : "success";
      const plat = (b.platforms || []).map(normalizePlatform);
      const mtd = b.spend_mtd_hkd || 0;
      const plan = b.plan_active_total_hkd || 0;
      const pace = plan > 0 ? mtd / plan : 0;
      const series = buildDailySeries(b.daily_series);
      const dailyBudget = series.slice(-7).length ? Math.round(series.slice(-7).reduce((s, x) => s + x, 0) / series.slice(-7).length) : 0;
      return {
        code: b.brand_code,
        name: b.brand_name || b.brand_code,
        industry: null,
        am: b.am_team_lead || null,
        tl: b.am_team_lead || null,
        accounts: [], // lazily filled on drill
        platforms: [...new Set(plat)],
        daily_budget_hkd: dailyBudget,
        mtd_spend: mtd,
        plan_mtd: plan,
        pace,
        pace_status: paceTone(pace),
        spend_7d: b.spend_7d_hkd || 0,
        spend_30d: b.spend_30d_hkd || 0,
        gp_mtd: b.gp_mtd_hkd || 0,
        non_media_mtd: b.non_media_mtd_hkd || 0,
        daily_series: series,
        alerts: 0,
        last_change: null,
        _api: b,
      };
    });
  }

  function transformNonMedia(rows) {
    return rows.map(r => ({
      id: r.id,
      brand_code: r.brand_code,
      brand_name: r.brand_name || r.brand_code,
      campaign_code: r.campaign_code,
      campaign_name: r.campaign_name,
      vendor_name: r.vendor_name,
      service_type: r.service_type,
      start_date: r.start_date,
      end_date: r.end_date,
      net_vendor_cost: r.net_vendor_cost,
      vendor_cost_with_markup: r.vendor_cost_with_markup,
      total_vendor_cost: r.total_vendor_cost,
      net_client_cost: r.net_client_cost,
      client_cost_with_ac: r.client_cost_with_ac,
      fabcom_mark_up: r.fabcom_mark_up,
      gross_profit: r.gross_profit,
      invoice_number: null,
      invoice_status: r.invoice_payment_received ? "Paid" : (r.vendor_invoice_status || "Not issued"),
      vendor_po_status: r.vendor_po_status || "Not created",
      vendor_invoice_status: r.vendor_invoice_status || "Not issued",
      workflow_state: r.workflow_state || "Draft",
      main_am_contact: r.main_am_contact,
      team_lead: r.team_lead,
    }));
  }

  function transformPacing(rows) {
    return rows.map(r => ({
      id: r.id,
      brand_code: r.brand_code,
      brand_name: r.brand_code,
      platform: normalizePlatform(r.platform),
      account: "",
      campaign: r.campaign_name,
      campaign_id: r.id,
      name: r.campaign_name,
      plan_budget: r.plan_hkd,
      spend: r.spent_hkd,
      flight_days: r.flight_days,
      days_elapsed: r.days_elapsed,
      days_remaining: r.days_remaining,
      start_date: r.start_date,
      end_date: r.end_date,
      pace: r.pace,
      forecast: r.forecast_hkd,
      variance: r.variance,
      sev: r.severity,
      tl: r.team_lead,
      am: r.main_am_contact,
      account_id: null,
      status: { id: r.severity === "danger" ? "issues" : "active", label: r.severity === "danger" ? "Over" : "Active", tone: r.severity === "danger" ? "danger" : "success" },
    }));
  }

  function transformChanges(rows) {
    return rows.map(r => ({
      id: `${r.platform}_${r.entity_id}_${r.ts}`,
      brand_code: null,
      brand_name: null,
      platform: normalizePlatform(r.platform),
      account: r.account_name || r.account_id,
      campaign: r.entity_name || r.entity_id,
      campaign_id: r.entity_id,
      actor: r.actor || "system",
      action: r.action || "update",
      hours_ago: r.ts ? (Date.now() - new Date(r.ts).getTime()) / 3.6e6 : null,
      prev: r.prev,
      next: r.next,
    }));
  }

  async function loadData() {
    const [me, brands, nonmedia, pacing, changes] = await Promise.all([
      api("/api/me"),
      api("/api/brands"),
      api("/api/nonmedia"),
      api("/api/pacing"),
      api("/api/changes").catch(() => ({ rows: [] })),
    ]);

    const transformedBrands = transformBrands(brands.brands || []);
    window.BRANDS = transformedBrands;
    window.NON_MEDIA = transformNonMedia(nonmedia.rows || []);
    window.PACING_ROWS = transformPacing(pacing.rows || []);
    window.CHANGES = transformChanges(changes.rows || []);
    window.ALL_ACCOUNTS = transformedBrands.flatMap(b => b.accounts.map(a => ({ ...a, brand_name: b.name })));
    window.ALL_CAMPAIGNS = transformedBrands.flatMap(b => b.accounts.flatMap(a => (a.campaigns || []).map(c => ({ ...c, brand_name: b.name, account_name: a.name }))));
    // Alerts: derive from pacing severity + overdue non-media
    const alerts = [];
    for (const p of window.PACING_ROWS) {
      if (p.sev === "danger") {
        alerts.push({
          id: `pacing_${p.id}`,
          type: "pacing",
          severity: "danger",
          title: p.pace > 1.3 ? "Overpacing >130% plan" : "Severe pacing deviation",
          kind: "Budget pacing",
          brand_code: p.brand_code,
          brand_name: p.brand_code,
          account_name: p.account || "",
          platform: p.platform,
          campaign_id: p.id,
          campaign_name: p.campaign || "",
          detected_at: 30,
          detail: `${p.campaign || p.id}: pace ${(p.pace * 100).toFixed(0)}%`,
        });
      }
    }
    for (const nm of window.NON_MEDIA) {
      if (nm.invoice_status === "Overdue") {
        alerts.push({
          id: `invoice_${nm.id}`,
          type: "invoice",
          severity: "danger",
          title: "Vendor invoice overdue",
          kind: "Non-media",
          brand_code: nm.brand_code,
          brand_name: nm.brand_name,
          account_name: nm.vendor_name,
          platform: "meta",
          campaign_id: nm.id,
          campaign_name: nm.campaign_name || "",
          detected_at: 60,
          detail: `${nm.vendor_name}: ${nm.campaign_name}`,
        });
      }
    }
    window.ALERTS = alerts;
    window.PLATFORMS_LIST = PLATFORMS;
    window.PEOPLE = {
      ME: me.user,
      AMS: [],
      TLS: [...new Set(window.PACING_ROWS.map(p => p.tl).filter(Boolean))],
    };
    window.ME = me.user;
    window.ME_ADMIN = me.is_admin;
    window.ME_BRANDS = me.brand_codes;

    return me;
  }

  // Lazily fetch brand detail (accounts + campaigns) when user drills in.
  async function loadBrandDetail(brandCode) {
    const payload = await api(`/api/brand/${encodeURIComponent(brandCode)}`);
    const brand = window.BRANDS.find(b => b.code === brandCode);
    if (!brand) return null;
    brand.accounts = (payload.accounts || []).map(a => {
      const platform = normalizePlatform(a.platform);
      const campaigns = (payload.campaigns || [])
        .filter(c => c.account_id === a.account_id && normalizePlatform(c.platform) === platform)
        .map(c => ({
          id: c.campaign_id,
          name: c.campaign_name,
          brand_code: brand.code,
          account_id: a.account_id,
          platform,
          objective: "—",
          status: { id: "active", label: "Active", tone: "success" },
          daily_budget: Math.round((a.spend_7d || 0) / 7) || 0,
          plan_budget: c.mtd_spend || 0,
          spend: c.mtd_spend || 0,
          mtd_spend: c.mtd_spend || 0,
          impressions: c.impressions || 0,
          clicks: c.clicks || 0,
          conv: c.leads || 0,
          ctr: c.impressions ? c.clicks / c.impressions : 0,
          cpc: c.clicks ? (c.mtd_spend || 0) / c.clicks : 0,
          cpm: c.impressions ? ((c.mtd_spend || 0) / c.impressions) * 1000 : 0,
          cpa: c.leads ? (c.mtd_spend || 0) / c.leads : 0,
          roas: 0,
          start_date: null,
          end_date: null,
          days_elapsed: 0,
          days_remaining: 0,
          flight_days: 30,
          daily_series: brand.daily_series,
          bid_strategy: "—",
          budget_type: "—",
          main_am: brand.am,
          team_lead: brand.tl,
        }));
      return {
        id: a.account_id,
        name: a.account_name,
        platform,
        brand_code: brand.code,
        balance_hkd: 0,
        spend_cap_hkd: 0,
        daily_budget_hkd: Math.round((a.spend_7d || 0) / 7),
        today_spend: a.today_spend || 0,
        yesterday_spend: a.yesterday_spend || 0,
        spend_7d: a.spend_7d || 0,
        spend_30d: a.spend_30d || 0,
        ctr: a.ctr_30d || 0,
        cpc: a.clicks_30d ? (a.spend_30d || 0) / a.clicks_30d : 0,
        cpm: a.impressions_30d ? ((a.spend_30d || 0) / a.impressions_30d) * 1000 : 0,
        conv: 0,
        cpa: 0,
        roas: 0,
        campaigns,
      };
    });
    window.ALL_ACCOUNTS = window.BRANDS.flatMap(b => b.accounts.map(x => ({ ...x, brand_name: b.name })));
    window.ALL_CAMPAIGNS = window.BRANDS.flatMap(b => b.accounts.flatMap(a => (a.campaigns || []).map(c => ({ ...c, brand_name: b.name, account_name: a.name }))));
    return brand;
  }

  // Expose globals
  Object.assign(window, {
    fmt,
    __fabcomApi: api,
    __fabcomLoadData: loadData,
    __fabcomLoadBrandDetail: loadBrandDetail,
  });
})();
