const { useState, useMemo, useEffect, useRef, Fragment } = React;

// Order from narrowest to widest. `data-bp-and-below="tablet"` means
// "viewport is tablet-sized or narrower" — i.e. matches when current bp is
// tablet, drawer, mobile, or narrow. Mirrors `@media (max-width: <tablet>)`.
const BP_ORDER = ["narrow", "mobile", "drawer", "tablet", "desktop"];

function _computeBp() {
  // Landscape phones (wide-but-short, touch) route to the mobile card feed
  // rather than the cramped desktop matrix. Same threshold the deck-viewer
  // uses for its own landscape mode. Keep IDENTICAL to the inline bp script
  // in index.html so first paint agrees.
  if (window.matchMedia && window.matchMedia("(orientation: landscape) and (max-height: 600px) and (pointer: coarse)").matches) return "mobile";
  const w = window.innerWidth;
  if (w <= window.BP.narrow) return "narrow";
  if (w <= window.BP.mobile) return "mobile";
  if (w <= window.BP.drawer) return "drawer";
  if (w <= window.BP.tablet) return "tablet";
  return "desktop";
}

function useBreakpoint() {
  const [bp, setBp] = useState(_computeBp);
  useEffect(() => {
    const idx = BP_ORDER.indexOf(bp);
    document.documentElement.dataset.bp = bp;
    // Include current bucket and every wider bucket. At narrow, all five
    // tokens are present; at desktop, only "desktop". This makes
    // [data-bp-and-below~="tablet"] match at tablet and narrower viewports.
    document.documentElement.dataset.bpAndBelow = BP_ORDER.slice(idx).join(" ");
  }, [bp]);
  useEffect(() => {
    let frame = 0;
    const recompute = () => {
      if (frame) return;
      frame = requestAnimationFrame(() => {
        frame = 0;
        setBp(_computeBp());
      });
    };
    window.addEventListener("resize", recompute);
    // resize usually fires on rotate, but orientationchange is the reliable
    // signal — and the landscape-phone MQ flips bp independently of width.
    window.addEventListener("orientationchange", recompute);
    const mq = window.matchMedia
      && window.matchMedia("(orientation: landscape) and (max-height: 600px) and (pointer: coarse)");
    if (mq) {
      if (mq.addEventListener) mq.addEventListener("change", recompute);
      else if (mq.addListener) mq.addListener(recompute); // Safari < 14 fallback
    }
    return () => {
      window.removeEventListener("resize", recompute);
      window.removeEventListener("orientationchange", recompute);
      if (mq) {
        if (mq.removeEventListener) mq.removeEventListener("change", recompute);
        else if (mq.removeListener) mq.removeListener(recompute);
      }
      if (frame) cancelAnimationFrame(frame);
    };
  }, []);
  return bp;
}

window.useBreakpoint = useBreakpoint;

// ───────────────────── helpers ─────────────────────
window.formatRange = function(product) {
  const r = product.targetReturn;
  if (r.irr) return { lo: r.irr[0], hi: r.irr[1], unit: "%", kind: "irr" };
  if (r.yield) return { lo: r.yield[0], hi: r.yield[1], unit: "%", kind: "yield" };
  if (r.multiple) return { lo: r.multiple[0], hi: r.multiple[1], unit: "x", kind: "multiple" };
  return null;
};

window.formatRangeStr = function(product) {
  const r = window.formatRange(product);
  if (!r) return "—";
  const fmt = (v) => (r.kind === "multiple" ? v.toFixed(2) : `${v}`);
  // A single-value target is stored as [n, n]; print it once.
  if (r.lo === r.hi) return `${fmt(r.lo)}${r.unit}`;
  return `${fmt(r.lo)}–${fmt(r.hi)}${r.unit}`;
};

window.returnKindLabel = function(product, t) {
  const r = product.targetReturn;
  if (r.irr) return t.targetIrr;
  if (r.yield) return t.targetYield;
  if (r.multiple) return t.targetMultiple;
  return "";
};

// Round to nearest $1k — calculator inputs step in $5k+ multiples and we don't
// want headline projections like "$256,123" implying false precision.
window.formatCurrency = function(amount) {
  const rounded = Math.round(amount / 1000) * 1000;
  return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(rounded);
};

// Prefer full number ($50,000); abbreviate ($50K / $2M) only when caller requests.
// Round FIRST, then pick the unit: picking the unit off the raw value made
// anything just under a threshold render in the smaller unit with a nonsensical
// magnitude (999999 → "$1000K"). We never emit a magnitude of 1000 in a unit
// that has a larger unit available.
window.formatCurrencyShort = function(amount) {
  const n = Number(amount);
  if (!Number.isFinite(n)) return "$0";
  const sign = n < 0 ? "-" : "";
  const abs = Math.abs(n);

  const units = Math.round(abs);
  if (units < 1000) return `${sign}$${units}`;

  const thousands = Math.round(abs / 1000);
  if (thousands >= 1000) {
    // Millions, one decimal; drop a trailing ".0" ($1M, not $1.0M).
    const m = Math.round(abs / 100_000) / 10;
    return `${sign}$${m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)}M`;
  }
  return `${sign}$${thousands}K`;
};

// V3: each product carries a displayGroupId (e.g. "DG-DEBT", "DG-EQUITY"...).
// TIER_ORDER / TIER_LABELS are also generated from 03-DisplayGroups, so the
// matrix renders one row per display group with the sheet's Col B name.
window.productTier = function(product) {
  return product.displayGroupId || (window.TIER_ORDER && window.TIER_ORDER[0]);
};

window.buildColumns = function(products, profile) {
  const projectOrder = [];
  const map = new Map();
  for (const p of products) {
    if (!map.has(p.project)) {
      projectOrder.push(p.project);
      map.set(p.project, {
        name: p.project, city: p.projectCity, kind: p.projectKind,
        kindLabel: p.projectKindLabel,
        statusLabel: p.projectStatusLabel, status: p.projectStatus, image: p.projectImage,
        // Project-level "reminder" facts shown in the tower head and detail
        // slideout — independent of which offering is being viewed.
        totalCostUsd: p.projectTotalCostUsd || 0,
        unitsPlanned: p.projectUnitsPlanned || 0,
        programSummary: p.projectProgramSummary || "",
        developmentTimelineYears: p.projectDevelopmentTimelineYears || 0,
        projectOverview: p.projectOverview,
        projectCardBlurb: p.projectCardBlurb,
        projectAboutShort: p.projectAboutShort,
        percentFundedOverride: typeof p.percentFundedOverride === "number" ? p.percentFundedOverride : null,
        fundraising: p.fundraising || null,
        cells: new Map(), products: []
      });
    }
    const col = map.get(p.project);
    const tier = window.productTier(p);
    const el = window.checkEligibility(p, profile);
    const candidate = { product: p, eligible: el.eligible, strategicOnly: el.strategicOnly, strategicActive: el.strategicActive, reasons: el.reasons, unanswered: el.unanswered };
    // Multiple offerings can map to the same (project, tier) — e.g. residency
    // wrapper alternatives (MEZZ-US vs EQ-MX). Keep whichever the profile
    // actually qualifies for; otherwise let later offerings overwrite.
    const existing = col.cells.get(tier);
    const existingPicks = existing && (existing.eligible || existing.strategicActive);
    const candidatePicks = candidate.eligible || candidate.strategicActive;
    if (!existing || (!existingPicks && candidatePicks)) {
      col.cells.set(tier, candidate);
    }
    col.products.push(p);
  }
  return projectOrder.map(n => map.get(n));
};

window.countProfile = function(profile) {
  return Object.values(profile || {}).filter(function (v) {
    return v != null && !(Array.isArray(v) && v.length === 0);
  }).length;
};

// ── Investment-type (capital-stack tier) soft filter ─────────────────
// Synthesized client-side from TIER_ORDER + TIER_LABELS (both bundle-
// provided) so the feature needs no SSOT/data.js change. Soft filter:
// it only emphasizes rows, never excludes offerings.
window.TYPE_TIER_MAP = {
  "equity": "DG-EQUITY",
  "long-term-hold": "DG-LONG-TERM-HOLD",
  "buy-a-home": "DG-OWN-LAND",
  "debt": "DG-DEBT",
};
// No pre-selection. mutedTypeTiers() treats an empty selection as the baseline
// (returns null, nothing muted), so the matrix opens with every tier at equal
// weight and no chip lit. Defaulting to "equity" pre-highlighted the one row a
// US-residency visitor is not eligible for.
window.TYPE_INTEREST_DEFAULT = [];

window.buildTypeInterestFilter = function () {
  var order = window.TIER_ORDER || [];
  var labels = window.TIER_LABELS || {};
  var slugByTier = {};
  Object.keys(window.TYPE_TIER_MAP).forEach(function (slug) {
    slugByTier[window.TYPE_TIER_MAP[slug]] = slug;
  });
  var options = order.map(function (tid) {
    var slug = slugByTier[tid];
    if (!slug) return null;
    var L = labels[tid] || {};
    return { value: slug, label: { en: L.en || slug, es: L.es || L.en || slug } };
  }).filter(Boolean);
  return {
    key: "typeInterest",
    group: "preferences",
    kind: "pills",
    multi: true,
    default: (window.TYPE_INTEREST_DEFAULT || []).slice(),
    label: { en: "Investment type", es: "Tipo de inversión" },
    why: {
      en: "Which structures interest you? This emphasizes those rows; it never hides any offering.",
      es: "¿Qué estructuras te interesan? Resalta esas filas; nunca oculta ninguna oferta.",
    },
    options: options,
  };
};

// Insert the synthesized filter into window.FILTERS once, if absent —
// directly below "Investment horizon" (the `horizon` filter). Falls back to
// appending at the end if `horizon` isn't present.
(function () {
  if (window.FILTERS && Array.isArray(window.FILTERS)
      && !window.FILTERS.some(function (f) { return f.key === "typeInterest"; })) {
    var hi = window.FILTERS.findIndex(function (f) { return f.key === "horizon"; });
    var at = hi >= 0 ? hi + 1 : window.FILTERS.length;
    window.FILTERS.splice(at, 0, window.buildTypeInterestFilter());
  }
})();

// Returns the Set of tier ids to MUTE, or null when nothing should be muted
// (untouched default with all-but-equity is muting; explicit [] = baseline;
// all-selected = nothing muted).
window.mutedTypeTiers = function (profile) {
  var sel = (profile && profile.typeInterest != null)
    ? profile.typeInterest
    : (window.TYPE_INTEREST_DEFAULT || []);
  if (!Array.isArray(sel) || sel.length === 0) return null;
  var active = {};
  sel.forEach(function (s) { var tid = window.TYPE_TIER_MAP[s]; if (tid) active[tid] = true; });
  var order = window.TIER_ORDER || [];
  var muted = order.filter(function (tk) { return !active[tk]; });
  if (muted.length === 0 || muted.length === order.length) return null;
  return new Set(muted);
};
