// Live data wiring for Supermetrics.
//
// Pattern: each KPI/chart calls useSupermetrics(query) with a query object.
// If the client has a corresponding `supermetrics` mapping (Google Ads
// customer id, Meta account id, etc.), the hook hits /api/supermetrics and
// returns the live value. If the mapping isn't there, it returns
// { ready: false } so the caller can fall back to its mock value.
//
// To wire another KPI: pass the right `query` here and read `data` /
// `loading` / `error` / `ready` from the hook. No more, no less.

const { useState: smState, useEffect: smEffect, useMemo: smMemo } = React;

function useSupermetrics(query, deps = []) {
  const [state, setState] = smState({ loading: true, data: null, error: null, ready: !!query });

  smEffect(() => {
    if (!query) {
      setState({ loading: false, data: null, error: null, ready: false });
      return;
    }
    let cancelled = false;
    setState((s) => ({ ...s, loading: true, error: null }));
    fetch('/api/supermetrics', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(query),
    })
      .then(async (res) => {
        const body = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(body.error || `HTTP ${res.status}`);
        return body;
      })
      .then((data) => {
        if (cancelled) return;
        setState({ loading: false, data, error: null, ready: true });
      })
      .catch((e) => {
        if (cancelled) return;
        setState({ loading: false, data: null, error: e.message || String(e), ready: true });
      });
    return () => { cancelled = true; };
  // eslint-disable-next-line
  }, deps);

  return state;
}
window.useSupermetrics = useSupermetrics;

// Helper: sum a numeric column from a Supermetrics JSON response.
//
// Returns:
//   - null  if the payload is unusable (no payload, or response was an error)
//   - 0     if the query succeeded but returned no rows (genuinely empty —
//           e.g. no spend in the period). The caller's truthiness check
//           treats this as a live answer of "0", not a fallback to mock.
//   - sum   if the query returned rows
//
// Supermetrics returns rows under payload.data[]. The first row is the
// header (string labels); we skip non-numeric cells automatically.
function sumColumn(payload, columnIndex) {
  if (!payload || payload.error) return null;
  const isSuccess = payload?.meta?.status_code === 'SUCCESS' || !!payload?.data;
  const rows = payload?.data?.data || payload?.data || [];
  if (!Array.isArray(rows)) return isSuccess ? 0 : null;
  let total = 0;
  let counted = 0;
  for (const row of rows) {
    const cell = Array.isArray(row) ? row[columnIndex] : row?.[columnIndex];
    const n = typeof cell === 'number' ? cell : parseFloat(cell);
    if (!Number.isFinite(n)) continue;
    total += n;
    counted += 1;
  }
  // Successful query + zero numeric rows = real "0" answer.
  if (counted === 0) return isSuccess ? 0 : null;
  return total;
}
window.sumSupermetricsColumn = sumColumn;

// Small visual pill that shows whether a KPI is currently live or mock.
function LivePill({ live, loading, error }) {
  let label = 'Mock';
  let bg = 'rgba(26,25,25,0.06)';
  let fg = 'var(--ink-stone)';
  if (loading) { label = 'Loading'; }
  else if (error) { label = 'Live (error)'; bg = 'rgba(220, 38, 38, 0.10)'; fg = '#b91c1c'; }
  else if (live) { label = 'Live'; bg = 'rgba(16, 185, 129, 0.14)'; fg = '#047857'; }
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 4,
      fontSize: 9.5, letterSpacing: '0.08em', textTransform: 'uppercase',
      padding: '2px 6px', borderRadius: 999, background: bg, color: fg,
      fontWeight: 600, marginLeft: 6,
    }}>
      {label}
    </span>
  );
}
window.LivePill = LivePill;

// ── Per-source hooks for RCG live data ───────────────────
//
// These hooks read the entity's `supermetrics` config (only RCG has one
// currently) and only fire a query when all the required IDs are present.
// When the config is missing, they return { ready: false } so the calling
// component can fall back to its mock value cleanly.
//
// ds_id values are Supermetrics' canonical codes (visible in the Query
// Manager's generated query JSON). If a connector ever rejects a query for
// "unknown ds_id", override the constant here.

// Canonical Supermetrics ds_id codes per docs.supermetrics.com/apidocs/data-sources.
const DS_GA4 = 'GAWA';            // Google Analytics 4
const DS_LINKEDIN_ADS = 'LIA';    // LinkedIn Ads
const DS_LINKEDIN_PAGE = 'LIP';   // LinkedIn Company Pages
const DS_ACTIVE_CAMPAIGN = 'ACT'; // ActiveCampaign

// Build a Supermetrics query. The current v2 query API authenticates the
// team via the Bearer token on the server side, so we no longer need
// ds_user_id on the client.
//
// ds_accounts is optional — sources like ActiveCampaign auto-resolve to
// the single connected account when it's omitted. Sources that require it
// (GAWA, LIA, LIP) will be rejected upstream with a clear error if absent.
function buildSmQuery({ ds_id, ds_accounts, fields, filter, dateRange = 'last_30_days', maxRows = 5000 }) {
  if (!ds_id || !fields) return null;
  const q = {
    ds_id,
    fields,
    max_rows: maxRows,
  };
  // dateRange can be a Supermetrics preset string (e.g. 'last_30_days') OR
  // an object with explicit start/end ISO dates. Custom ranges from
  // ChartControls flow in as { start: '2026-04-15', end: '2026-05-15' }.
  if (dateRange && typeof dateRange === 'object' && dateRange.start && dateRange.end) {
    q.date_range_type = 'custom';
    q.start_date = dateRange.start;
    q.end_date = dateRange.end;
  } else {
    q.date_range_type = dateRange;
  }
  if (ds_accounts) q.ds_accounts = ds_accounts;
  if (filter) q.filter = filter;
  return q;
}

// Map the dashboard's date-range UI ids ('7D', '30D', '90D', '12M', 'YTD')
// or custom-range objects into the value buildSmQuery expects.
function toSmRange(input) {
  if (!input) return 'last_30_days';
  if (typeof input === 'object' && input.start && input.end) return input;
  const map = {
    '7D':  'last_7_days',
    '30D': 'last_30_days',
    '90D': 'last_90_days',
    '12M': 'last_12_months',
    'YTD': 'this_year_so_far',
  };
  return map[input] || 'last_30_days';
}
window.toSmRange = toSmRange;

// GA4 — single-field aggregate (Sessions, Conversions, Users, etc.).
// Automatically applies the entity's hostnameFilter (e.g. for RCG-self,
// scope to *.rottmancreative.com instead of summing all 16 managed properties).
// Pass `dateRange` (UI-form '7D'/'30D'/'90D'/'12M' or a {start,end} object)
// to override the default 30-day window.
function useGa4Total(client, field, opts = {}) {
  const cfg = client?.supermetrics;
  const filter = cfg?.ga4?.hostnameFilter || null;
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const query = client?.isSelf ? buildSmQuery({
    ds_id: DS_GA4,
    ds_accounts: cfg?.ga4?.propertyId,
    fields: field,
    filter,
    dateRange,
  }) : null;
  const state = useSupermetrics(query, [client?.id, field, cfg?.ga4?.propertyId, filter, JSON.stringify(dateRange)]);
  return { ...state, value: sumColumn(state.data, 0) };
}
window.useGa4Total = useGa4Total;

// GA4 — table-style rows (dimensions + metrics). Returns raw rows including
// the header row. Each row is [dim, metric1, metric2, ...]. Use the
// returned `rows` directly to render a <tbody>.
function useGa4Rows(client, dimensions, metrics, opts = {}) {
  const cfg = client?.supermetrics;
  const filter = opts.filter || cfg?.ga4?.hostnameFilter || null;
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const fields = [...(dimensions || []), ...(metrics || [])].join(',');
  const query = (client?.isSelf && fields) ? buildSmQuery({
    ds_id: DS_GA4,
    ds_accounts: cfg?.ga4?.propertyId,
    fields,
    filter,
    dateRange,
    maxRows: opts.limit || 25,
  }) : null;
  const state = useSupermetrics(query, [client?.id, fields, cfg?.ga4?.propertyId, filter, opts.limit, JSON.stringify(dateRange)]);
  // First row is the header (string labels); rest are the data rows.
  const allRows = state.data?.data || state.data || [];
  const headers = Array.isArray(allRows) && allRows.length > 0 && Array.isArray(allRows[0]) && typeof allRows[0][0] === 'string'
    ? allRows[0] : null;
  const rows = Array.isArray(allRows) && headers ? allRows.slice(1) : [];
  return { ...state, headers, rows };
}
window.useGa4Rows = useGa4Rows;

// LinkedIn Ads — single-field aggregate (Cost, Impressions, Clicks, Conversions).
function useLinkedinAdsTotal(client, field, opts = {}) {
  const cfg = client?.supermetrics;
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const query = client?.isSelf ? buildSmQuery({
    ds_id: DS_LINKEDIN_ADS,
    ds_accounts: cfg?.linkedinAds?.accountId,
    fields: field,
    dateRange,
  }) : null;
  const state = useSupermetrics(query, [client?.id, field, cfg?.linkedinAds?.accountId, JSON.stringify(dateRange)]);
  return { ...state, value: sumColumn(state.data, 0) };
}
window.useLinkedinAdsTotal = useLinkedinAdsTotal;

// ActiveCampaign — single-field aggregate. ACT auto-resolves to the single
// connected account when ds_accounts is omitted, so we don't require one.
// Field names use the DWH-style double-underscore convention, e.g.
// "campaigns__sends", "campaigns__total_opens", "campaigns__unsubscribes".
function useActiveCampaignTotal(client, field, opts = {}) {
  const cfg = client?.supermetrics;
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const query = client?.isSelf ? buildSmQuery({
    ds_id: DS_ACTIVE_CAMPAIGN,
    ds_accounts: cfg?.activeCampaign?.accountId,
    fields: field,
    dateRange,
  }) : null;
  const state = useSupermetrics(query, [client?.id, field, cfg?.activeCampaign?.accountId, JSON.stringify(dateRange)]);
  return { ...state, value: sumColumn(state.data, 0) };
}
window.useActiveCampaignTotal = useActiveCampaignTotal;

// ActiveCampaign — multi-field in a single round trip. Returns an object
// keyed by field name with each summed value. Same auto-resolve behavior
// for accounts as the single-field hook.
function useActiveCampaignTotals(client, fields, opts = {}) {
  const cfg = client?.supermetrics;
  const fieldsCsv = (fields || []).join(',');
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const query = (client?.isSelf && fieldsCsv) ? buildSmQuery({
    ds_id: DS_ACTIVE_CAMPAIGN,
    ds_accounts: cfg?.activeCampaign?.accountId,
    fields: fieldsCsv,
    dateRange,
  }) : null;
  const state = useSupermetrics(query, [client?.id, fieldsCsv, cfg?.activeCampaign?.accountId, JSON.stringify(dateRange)]);
  const values = {};
  (fields || []).forEach((f, i) => { values[f] = sumColumn(state.data, i); });
  return { ...state, values };
}
window.useActiveCampaignTotals = useActiveCampaignTotals;

// LinkedIn Ads — multi-field in one round trip. Same convention as the AC
// version but uses LIA ds_id.
function useLinkedinAdsTotals(client, fields, opts = {}) {
  const cfg = client?.supermetrics;
  const fieldsCsv = (fields || []).join(',');
  const dateRange = opts.dateRange ? toSmRange(opts.dateRange) : 'last_30_days';
  const query = (client?.isSelf && fieldsCsv) ? buildSmQuery({
    ds_id: DS_LINKEDIN_ADS,
    ds_accounts: cfg?.linkedinAds?.accountId,
    fields: fieldsCsv,
    dateRange,
  }) : null;
  const state = useSupermetrics(query, [client?.id, fieldsCsv, cfg?.linkedinAds?.accountId, JSON.stringify(dateRange)]);
  const values = {};
  (fields || []).forEach((f, i) => { values[f] = sumColumn(state.data, i); });
  return { ...state, values };
}
window.useLinkedinAdsTotals = useLinkedinAdsTotals;
