/* Live-API boot orchestrator.
   - On mount, pulls /companies and /contacts in parallel from the live
     backend and merges them into the in-memory stores already populated
     by the static seeds in api-companies.js / api-contacts.js. Static
     data stays as a fallback so the demo never looks empty on a network
     blip; live records win on id collision.
   - Polls /health every 30s. The result is exposed on
     window.__apiHealth and broadcast via a `businessteam:health` event
     so chrome elements (e.g. sidebar status dot) can subscribe.
   - Bumps a global data-version counter on every successful merge.
     Components can subscribe via `useApiDataVersion()` so they re-render
     once live data lands.

   No new mutation of the in-memory stores; we re-use the same
   adaptApiCompany / adaptApiContact / mergeApi* functions already
   shipped in data.jsx. */

(function (global) {
  /* ─── event bus ─── */
  const dataListeners = new Set();
  function emitDataChanged() {
    global.__apiDataVersion = (global.__apiDataVersion || 0) + 1;
    dataListeners.forEach(fn => { try { fn(global.__apiDataVersion); } catch (_) {} });
  }
  global.__apiDataVersion = global.__apiDataVersion || 0;
  global.__subscribeApiData = (fn) => { dataListeners.add(fn); return () => dataListeners.delete(fn); };

  /* ─── live data refresh ───
     Two-phase strategy:
       (1) the first page (limit=100) fetched eagerly so the UI has data
           to render immediately;
       (2) `backfillRemainingPages()` continues paginating in the
           background, merging each new page and bumping the data
           version so screens grow as records arrive. Order matters
           because the list endpoints clamp to limit=100. */
  const PAGE_SIZE = 100;
  const TEST_FIXTURE_NAMES = new Set([
    "Graph Filter", "Ignored Graph Filter", "Matching Graph Filter",
  ]);
  /* Drop the obvious backend-regression fixtures so the contacts inbox
     doesn't lead with rows like "Graph Filter". */
  function cleanContactsPage(page) {
    return page.filter(c => {
      const fullName = `${c.firstName || ""} ${c.lastName || ""}`.trim();
      if (TEST_FIXTURE_NAMES.has(fullName)) return false;
      if (/^(graph|matching|ignored)\b/i.test(c.firstName || "") &&
          /filter|graph/i.test(c.lastName || "")) return false;
      return true;
    });
  }

  /* Fields the UI actually needs from the company list endpoint. The
     server defaults to a slim projection (id/name/linkedin_url/updatedAt
     only), so we explicitly request the rich fields used by the
     Accounts table + Account 360 hero. */
  const COMPANY_LIST_FIELDS = [
    "company_id", "company_name", "logo_url", "domain", "website",
    "industry", "industries", "employee_count", "employee_range",
    "follower_count", "linkedin_url", "year_founded",
    "hq_city", "hq_country", "hq_full_address",
    "gtm_priority", "tagline", "description", "specialties",
    "executive_english_target_score", "executive_english_target_tier",
    "executive_english_sales_reason", "executive_english_score_breakdown",
    "operatingCountries", "operatingCountryCount",
    "totalLocationsCount", "officialLanguages",
    "highestLanguageEnglishCountries", "highestLanguageSpanishCountries",
    "outsideSpainLocationCount", "spainLocationCount",
    "locations",
    "updatedAt",
  ].join(",");

  /* Same idea for contacts — the slim default omits the profile picture,
     current position, role enrichment, location, etc. */
  const CONTACT_LIST_FIELDS = [
    "linkedinContactId", "publicIdentifier", "linkedinUrl",
    "firstName", "lastName", "headline", "about",
    "profilePicture", "coverPicture",
    "location", "connectionsCount", "followerCount",
    "verified", "premium", "openToWork", "hiring", "influencer", "composeOptionType",
    "currentPosition", "experience", "education",
    "skills", "certifications", "languages",
    "emails", "companyWebsites",
    "registeredAt", "updatedAt",
  ].join(",");

  async function refreshCompaniesFromLive(limit = PAGE_SIZE) {
    try {
      /* Use envelope mode so we can read meta.total (filter-aware DB
         total) from the same response — zero extra round-trip. */
      const envelope = await global.ApiClient.apiFetch(
        "/companies" + global.ApiClient.buildQuery({ limit, offset: 0, fields: COMPANY_LIST_FIELDS }),
        { envelope: true }
      );
      const data = envelope && envelope.data;
      const total = envelope && envelope.meta && typeof envelope.meta.total === "number" ? envelope.meta.total : null;
      if (!Array.isArray(data)) return { ok: false, reason: "bad-shape" };
      global.API_COMPANIES_RAW = { data };
      const merged = global.mergeApiCompanies ? global.mergeApiCompanies() : 0;
      return { ok: true, fetched: data.length, merged, lastPageSize: data.length, total };
    } catch (err) {
      console.warn("[BusinessTeam] live companies fetch failed:", err && err.message);
      return { ok: false, error: err };
    }
  }

  async function refreshContactsFromLive(limit = PAGE_SIZE) {
    try {
      const envelope = await global.ApiClient.apiFetch(
        "/contacts" + global.ApiClient.buildQuery({ limit, offset: 0, fields: CONTACT_LIST_FIELDS }),
        { envelope: true }
      );
      const data = envelope && envelope.data;
      const total = envelope && envelope.meta && typeof envelope.meta.total === "number" ? envelope.meta.total : null;
      if (!Array.isArray(data)) return { ok: false, reason: "bad-shape" };
      const cleaned = cleanContactsPage(data);
      global.API_CONTACTS_RAW = { data: cleaned };
      const merged = global.mergeApiContacts ? global.mergeApiContacts() : 0;
      return { ok: true, fetched: data.length, kept: cleaned.length, merged, lastPageSize: data.length, total };
    } catch (err) {
      console.warn("[BusinessTeam] live contacts fetch failed:", err && err.message);
      return { ok: false, error: err };
    }
  }

  /* ─── total-count discovery ───
     PLACEHOLDER: today the list endpoints return `{ data: [...] }` with
     no count, and probing offsets via binary search is too fragile to
     ship (the live API 500s on certain `fields=` projections, and the
     parallel probe cost is high). Disabled until the backend ships
     either a `meta.total` field on list responses or a dedicated
     `/companies/count` + `/contacts/count` endpoint.

     The consumer hooks (`useApiTotals`, the sidebar label, the
     pagination pill `displayTotal`, the dashboard KPI) all stay wired —
     they just see `null` until this function returns real counts.
     Re-enable by replacing the body with the actual call. */
  async function discoverTotalsInBackground() {
    /* no-op — backend lacks the necessary endpoint today */
    return null;
  }

  /* ─── on-demand pagination ───
     Track how many pages we've loaded per domain. Each call to
     loadMoreXFromLive() pulls the next page and appends to the
     in-memory store (so the UI grows progressively as the user scrolls).
     Concurrent calls are deduped — if a load is already in flight, the
     second caller awaits the same promise. */
  const loadingPages = { companies: null, contacts: null };
  const nextOffset   = { companies: PAGE_SIZE, contacts: PAGE_SIZE };
  const exhausted    = { companies: false, contacts: false };

  function _resetPaginationCursors() {
    nextOffset.companies = PAGE_SIZE; nextOffset.contacts = PAGE_SIZE;
    exhausted.companies = false; exhausted.contacts = false;
  }

  async function loadMoreCompaniesFromLive() {
    if (exhausted.companies) return { ok: true, exhausted: true, appended: 0 };
    if (loadingPages.companies) return loadingPages.companies;
    const offset = nextOffset.companies;
    const p = (async () => {
      try {
        const envelope = await global.ApiClient.apiFetch(
          "/companies" + global.ApiClient.buildQuery({ limit: PAGE_SIZE, offset, fields: COMPANY_LIST_FIELDS }),
          { envelope: true }
        );
        const data = envelope && envelope.data;
        if (!Array.isArray(data) || data.length === 0) {
          exhausted.companies = true;
          return { ok: true, exhausted: true, appended: 0 };
        }
        const appended = global.appendApiCompanies ? global.appendApiCompanies(data) : 0;
        /* Mirror RAW so debugging shows the full backing dataset. */
        global.API_COMPANIES_RAW = { data: (global.API_COMPANIES_RAW?.data || []).concat(data) };
        nextOffset.companies = offset + data.length;
        if (data.length < PAGE_SIZE) exhausted.companies = true;
        /* Update meta if shipped (so the pill total stays accurate even
           if the DB grows between requests). */
        if (envelope.meta && typeof envelope.meta.total === "number") {
          global.__apiTotalCount = { ...(global.__apiTotalCount || {}), companies: envelope.meta.total };
        }
        global.__apiLiveStats = global.__apiLiveStats || { companies: 0, contacts: 0 };
        global.__apiLiveStats.companies = (global.API_COMPANIES_RAW?.data || []).length;
        global.__apiLiveStats.syncedAt = new Date().toISOString();
        emitDataChanged();
        return { ok: true, appended };
      } catch (err) {
        console.warn(`[BusinessTeam] loadMore companies failed at offset ${offset}:`, err && err.message);
        return { ok: false, error: err };
      } finally {
        loadingPages.companies = null;
      }
    })();
    loadingPages.companies = p;
    return p;
  }

  async function loadMoreContactsFromLive() {
    if (exhausted.contacts) return { ok: true, exhausted: true, appended: 0 };
    if (loadingPages.contacts) return loadingPages.contacts;
    const offset = nextOffset.contacts;
    const p = (async () => {
      try {
        const envelope = await global.ApiClient.apiFetch(
          "/contacts" + global.ApiClient.buildQuery({ limit: PAGE_SIZE, offset, fields: CONTACT_LIST_FIELDS }),
          { envelope: true }
        );
        const data = envelope && envelope.data;
        if (!Array.isArray(data) || data.length === 0) {
          exhausted.contacts = true;
          return { ok: true, exhausted: true, appended: 0 };
        }
        const cleaned = cleanContactsPage(data);
        const appended = global.appendApiContacts ? global.appendApiContacts(cleaned) : 0;
        global.API_CONTACTS_RAW = { data: (global.API_CONTACTS_RAW?.data || []).concat(cleaned) };
        nextOffset.contacts = offset + data.length;
        if (data.length < PAGE_SIZE) exhausted.contacts = true;
        if (envelope.meta && typeof envelope.meta.total === "number") {
          global.__apiTotalCount = { ...(global.__apiTotalCount || {}), contacts: envelope.meta.total };
        }
        global.__apiLiveStats = global.__apiLiveStats || { companies: 0, contacts: 0 };
        global.__apiLiveStats.contacts = (global.API_CONTACTS_RAW?.data || []).length;
        global.__apiLiveStats.syncedAt = new Date().toISOString();
        emitDataChanged();
        return { ok: true, appended };
      } catch (err) {
        console.warn(`[BusinessTeam] loadMore contacts failed at offset ${offset}:`, err && err.message);
        return { ok: false, error: err };
      } finally {
        loadingPages.contacts = null;
      }
    })();
    loadingPages.contacts = p;
    return p;
  }

  /* Ensure at least N records of `domain` are loaded. Fires successive
     page requests until the target is met or the corpus is exhausted.
     Used by jump-to-page in the pagination pill so jumping to page 50
     just-in-time fetches enough records to honor the jump. */
  async function ensureCompaniesLoaded(targetCount) {
    while (!exhausted.companies && (global.COMPANIES?.length || 0) < targetCount) {
      const r = await loadMoreCompaniesFromLive();
      if (!r || !r.ok) break;
    }
  }
  async function ensureContactsLoaded(targetCount) {
    while (!exhausted.contacts && (global.CONTACTS?.length || 0) < targetCount) {
      const r = await loadMoreContactsFromLive();
      if (!r || !r.ok) break;
    }
  }

  async function refreshAllFromLive() {
    const [c1, c2] = await Promise.all([
      refreshCompaniesFromLive(),
      refreshContactsFromLive(),
    ]);
    /* Track per-domain live counts so the chrome can surface "synced N
       companies / M contacts" without re-scanning the array. */
    global.__apiLiveStats = {
      companies: c1 && c1.ok ? c1.merged : 0,
      contacts:  c2 && c2.ok ? c2.merged : 0,
      syncedAt:  new Date().toISOString(),
    };
    /* Capture the filter-aware DB totals shipped in meta.total on each
       list response. Powers the pagination pill + dashboard KPI. */
    global.__apiTotalCount = {
      companies: c1 && typeof c1.total === "number" ? c1.total : null,
      contacts:  c2 && typeof c2.total === "number" ? c2.total : null,
      discoveredAt: new Date().toISOString(),
    };
    /* Single re-render covering both first-page refreshes. */
    if ((c1 && c1.ok) || (c2 && c2.ok)) emitDataChanged();

    /* Kick off background total-count discovery so the pagination pill /
       dashboard KPIs can show the true DB total even though we only
       loaded one page. We do NOT eagerly fetch every page — that's a
       lot of network for an information-only requirement. */
    discoverTotalsInBackground();

    /* Greedy prefetch: pull two extra pages per domain in parallel
       immediately after the first page lands. With ~700ms per page,
       this means by the time the user scrolls past row ~30, pages 2
       and 3 (records 100–299) are already in memory. The boot helpers
       dedupe concurrent calls so this is safe alongside the in-page
       sentinel that fires its own load-mores. */
    if (typeof c1?.lastPageSize === "number" && c1.lastPageSize === PAGE_SIZE) {
      loadMoreCompaniesFromLive();
      loadMoreCompaniesFromLive();
    }
    if (typeof c2?.lastPageSize === "number" && c2.lastPageSize === PAGE_SIZE) {
      loadMoreContactsFromLive();
      loadMoreContactsFromLive();
    }
    return { companies: c1, contacts: c2 };
  }

  /* ─── single-record fetchers (used by detail pages on direct nav) ─── */
  async function fetchCompanyById(companyId) {
    try {
      const live = await global.ApiClient.getCompany(companyId);
      if (live && live.company_id) {
        /* Merge this one record into the store. */
        const adapted = global.adaptApiCompany(live);
        const arr = global.COMPANIES;
        const idx = arr.findIndex(c => c.id === adapted.id);
        if (idx >= 0) arr[idx] = adapted; else arr.unshift(adapted);
        emitDataChanged();
        return adapted;
      }
      return null;
    } catch (err) {
      console.warn("[BusinessTeam] live company fetch failed for", companyId, err && err.message);
      return null;
    }
  }

  async function fetchContactById(contactId) {
    try {
      const live = await global.ApiClient.getContact(contactId);
      if (live && (live.linkedinContactId || live.id)) {
        if (global.ensureCompanyForApiContact) global.ensureCompanyForApiContact(live);
        const adapted = global.adaptApiContact(live);
        const arr = global.CONTACTS;
        const idx = arr.findIndex(c => c.id === adapted.id);
        if (idx >= 0) arr[idx] = adapted; else arr.unshift(adapted);
        emitDataChanged();
        return adapted;
      }
      return null;
    } catch (err) {
      console.warn("[BusinessTeam] live contact fetch failed for", contactId, err && err.message);
      return null;
    }
  }

  /* ─── health polling ─── */
  /* States: "unknown" | "ok" | "error". `ok` is reported when /health
     returns 200 and data.ok === true. Anything else maps to `error` so
     the user gets an unambiguous indicator. */
  global.__apiHealth = { state: "unknown", checkedAt: null, runtime: null, error: null };
  const healthListeners = new Set();
  global.__subscribeApiHealth = (fn) => { healthListeners.add(fn); return () => healthListeners.delete(fn); };
  function setHealth(next) {
    global.__apiHealth = { ...global.__apiHealth, ...next, checkedAt: new Date().toISOString() };
    healthListeners.forEach(fn => { try { fn(global.__apiHealth); } catch (_) {} });
    /* Also a DOM event so non-React consumers can subscribe. */
    try { window.dispatchEvent(new CustomEvent("businessteam:health", { detail: global.__apiHealth })); } catch (_) {}
  }
  async function pingHealth() {
    try {
      const data = await global.ApiClient.getHealth();
      if (data && data.ok) {
        setHealth({ state: "ok", runtime: data.runtime || null, error: null });
      } else {
        setHealth({ state: "error", error: "unexpected payload" });
      }
    } catch (err) {
      setHealth({ state: "error", error: (err && err.message) || "unknown" });
    }
  }

  let pollHandle = null;
  function startHealthPolling(intervalMs = 30000) {
    if (pollHandle) return;
    pingHealth();
    pollHandle = setInterval(pingHealth, intervalMs);
  }
  function stopHealthPolling() {
    if (pollHandle) { clearInterval(pollHandle); pollHandle = null; }
  }

  /* ─── React hooks ─── */
  /* useApiDataVersion → integer, bumps whenever live data merges. */
  function useApiDataVersion() {
    const [v, setV] = React.useState(global.__apiDataVersion || 0);
    React.useEffect(() => global.__subscribeApiData(setV), []);
    return v;
  }
  /* useApiHealth → { state, checkedAt, runtime, error } */
  function useApiHealth() {
    const [h, setH] = React.useState(global.__apiHealth);
    React.useEffect(() => global.__subscribeApiHealth(setH), []);
    return h;
  }
  /* useApiLiveStats → { companies, contacts, syncedAt } | null
     In-memory counts of records that landed from live fetches. */
  function useApiLiveStats() {
    useApiDataVersion();
    return global.__apiLiveStats || null;
  }
  /* useApiTotals → { companies, contacts, discoveredAt } | null
     Total record counts from the live DB (discovered via binary-search
     probe, not by loading all pages). */
  function useApiTotals() {
    useApiDataVersion();
    return global.__apiTotalCount || null;
  }
  /* useApiBoot → called once by <App>. Kicks off the parallel refresh
     plus health polling. Returns the current data version so the App
     can use it as a re-render key. */
  function useApiBoot() {
    const v = useApiDataVersion();
    React.useEffect(() => {
      let cancelled = false;
      startHealthPolling();
      refreshAllFromLive().then(r => {
        if (!cancelled) {
          console.info(
            "[BusinessTeam] live boot:",
            "companies", r.companies && r.companies.ok ? `+${r.companies.merged}` : (r.companies && r.companies.error ? "err" : "skip"),
            "contacts", r.contacts && r.contacts.ok ? `+${r.contacts.merged}` : (r.contacts && r.contacts.error ? "err" : "skip"),
          );
        }
      });
      return () => { cancelled = true; stopHealthPolling(); };
    }, []);
    return v;
  }

  global.BusinessTeamBoot = {
    refreshCompaniesFromLive,
    refreshContactsFromLive,
    refreshAllFromLive,
    discoverTotalsInBackground,
    loadMoreCompaniesFromLive,
    loadMoreContactsFromLive,
    ensureCompaniesLoaded,
    ensureContactsLoaded,
    fetchCompanyById,
    fetchContactById,
    startHealthPolling,
    stopHealthPolling,
    pingHealth,
    useApiBoot,
    useApiHealth,
    useApiDataVersion,
    useApiLiveStats,
    useApiTotals,
  };
  /* Convenience top-level hooks (matches the rest of the codebase's
     window.useXxx pattern). */
  global.useApiBoot = useApiBoot;
  global.useApiHealth = useApiHealth;
  global.useApiDataVersion = useApiDataVersion;
  global.useApiLiveStats = useApiLiveStats;
  global.useApiTotals = useApiTotals;
})(window);
