// Transaction Advisory — shared logic + UI helpers (window.TA)
// ─────────────────────────────────────────────────────────────────────────────
// Loaded before the six pages-* Transaction Advisory files. Everything sell-side
// derives from REAL GL data (window.PerduraData / data-live). Where the GL cannot
// honestly source a figure (interest/tax/D&A are lumped into opex; recurring-
// revenue % has no feed) we return null and the UI shows "—" / a manual input —
// never a fabricated number. See memory: pl-categories-dynamic, dashboard-data-wiring.
const { useState: useTA_useState, useMemo: useTA_useMemo, useEffect: useTA_useEffect, useRef: useTA_useRef } = React;

(function () {
  // ── Formatting (delegates to PerduraFormat; safe fallback) ──────────────────
  function money(v, opts) {
    if (v === null || v === undefined || (typeof v === "number" && !isFinite(v))) return "—";
    if (window.PerduraFormat && window.PerduraFormat.money) return window.PerduraFormat.money(v, opts);
    const o = opts || {}; const abs = Math.abs(v);
    if (o.compact && abs >= 1000) {
      const units = [[1e9, "B"], [1e6, "M"], [1e3, "K"]];
      for (const [d, s] of units) { if (abs >= d) return (v < 0 ? "-" : "") + "$" + (abs / d).toFixed(abs / d >= 100 ? 0 : 1) + s; }
    }
    return (v < 0 ? "-$" : "$") + Math.round(abs).toLocaleString();
  }
  function pct(v, decimals) {
    if (v === null || v === undefined || !isFinite(v)) return "—";
    return (v * 100).toFixed(decimals === undefined ? 1 : decimals) + "%";
  }
  function num(v, d) {
    if (v === null || v === undefined || !isFinite(v)) return "—";
    return v.toLocaleString(undefined, { maximumFractionDigits: d === undefined ? 0 : d });
  }

  function sumTail(arr, n) {
    if (!Array.isArray(arr)) return 0;
    return arr.slice(Math.max(0, arr.length - n)).reduce((a, b) => a + (Number(b) || 0), 0);
  }

  // Chunk a monthly series into trailing-12 annual buckets (oldest → newest; last = LTM).
  function annualSeries(monthly, years) {
    if (!Array.isArray(monthly) || !monthly.length) return [];
    const out = []; let end = monthly.length;
    while (end > 0 && out.length < years) {
      const start = Math.max(0, end - 12);
      const seg = monthly.slice(start, end);
      out.unshift({ sum: seg.reduce((a, b) => a + (Number(b) || 0), 0), months: seg.length });
      end = start;
    }
    return out; // [{sum, months}], last item is the trailing-12 LTM
  }

  // ── Core derivation from the live data prop ─────────────────────────────────
  function derive(data, companyProfile) {
    const pl = data && data.pl ? data.pl : null;
    const hist = data && data.plHistory && Array.isArray(data.plHistory.revenue) ? data.plHistory : pl;
    const connected = !!(data && data.isLive && pl && Array.isArray(pl.revenue) && pl.revenue.some(v => Number(v)));
    if (!connected) return { connected: false };

    const ltmRevenue = sumTail(pl.revenue, 12);
    const ltmCogs = sumTail(pl.cogs, 12);
    const ltmGrossProfit = sumTail(pl.gp, 12);
    const ltmOpex = sumTail(pl.opex, 12);
    const ltmEbitda = sumTail(pl.ebitda, 12);
    const ltmNetIncome = (typeof data.net_income_ltm === "number")
      ? data.net_income_ltm
      : (ltmGrossProfit - ltmOpex);

    const revAnnual = annualSeries(hist.revenue, 3);
    const ebitdaAnnual = annualSeries(hist.ebitda, 3);

    // YoY revenue growth from the two most recent full-ish annual buckets.
    let revenueGrowthPct = null;
    if (revAnnual.length >= 2) {
      const last = revAnnual[revAnnual.length - 1];
      const prev = revAnnual[revAnnual.length - 2];
      if (prev.months >= 6 && prev.sum > 0) revenueGrowthPct = (last.sum - prev.sum) / prev.sum;
    }

    // Years of clean data from the GL date range, else fall back to month count.
    let yearsOfData = null;
    const dr = data.dataRange;
    if (dr && dr.startDate && dr.endDate) {
      const s = new Date(dr.startDate), e = new Date(dr.endDate);
      if (!isNaN(s) && !isNaN(e)) yearsOfData = Math.max(0, (e - s) / (365.25 * 864e5));
    }
    if (yearsOfData === null && Array.isArray(hist.revenue)) {
      yearsOfData = hist.revenue.filter(v => v !== null && v !== undefined).length / 12;
    }

    // Customer concentration — honest from AR subledger HHI (top_share) or null.
    let topCustomerPct = null, topCustomerName = null, concentrationAvailable = false;
    const an = data.analytics;
    if (an && an.customerHHI && an.customerHHI.available && typeof an.customerHHI.top_share === "number") {
      topCustomerPct = an.customerHHI.top_share;
      concentrationAvailable = true;
      const rows = (an.topCustomers && an.topCustomers.rows) || (an.customers && an.customers.rows) || [];
      if (rows.length) topCustomerName = rows[0].name || rows[0].customer || null;
    }

    // GL mapping coverage — fraction of txns whose account code is mapped.
    let mappingCoverage = null;
    if (Array.isArray(data.txns) && data.txns.length && data.mappingsByCode) {
      let mapped = 0;
      for (const t of data.txns) {
        const code = t.account_code || t.code || t.accountCode;
        if (code != null && Object.prototype.hasOwnProperty.call(data.mappingsByCode, String(code))) mapped++;
      }
      mappingCoverage = mapped / data.txns.length;
    }

    // recurring-revenue % has no feed today → honest null.
    const recurringRevenuePct = null;

    return {
      connected: true,
      ltmRevenue, ltmCogs, ltmGrossProfit, ltmOpex, ltmEbitda, ltmNetIncome,
      ebitdaMargin: ltmRevenue > 0 ? ltmEbitda / ltmRevenue : null,
      revenueGrowthPct, revenueGrowthYoY: revenueGrowthPct,
      yearsOfData, topCustomerPct, topCustomerName, concentrationAvailable,
      mappingCoverage, recurringRevenuePct,
      revAnnual, ebitdaAnnual,
      monthlyRevenue: pl.revenue.slice(-12), monthlyEbitda: pl.ebitda.slice(-12),
      companyName: (companyProfile && (companyProfile.name || companyProfile.company_name)) || "Your Company",
      industry: (companyProfile && companyProfile.industry) || null,
      businessType: (companyProfile && companyProfile.business_type) || null,
    };
  }

  // ── Industry comparable multiple ranges (indicative — publicly-cited SMB bands) ─
  const INDUSTRY_COMPS = {
    "Healthcare Services":   { rev: [1.5, 3.5], ebitda: [5, 9] },
    "SaaS":                  { rev: [4, 12],    ebitda: [10, 25] },
    "Distribution":          { rev: [0.5, 1.5], ebitda: [4, 7] },
    "Professional Services": { rev: [1, 2.5],   ebitda: [5, 10] },
  };
  function matchComp(m) {
    const s = ((m && (m.industry || m.businessType)) || "").toLowerCase();
    if (/health|medical|clinic|dental|care|pharma/.test(s)) return "Healthcare Services";
    if (/saas|software|subscription|tech|app|platform/.test(s)) return "SaaS";
    if (/distrib|wholesale|logistic|supply|trading/.test(s)) return "Distribution";
    return "Professional Services";
  }

  // ── localStorage: EBITDA add-backs + DCF assumptions, keyed per company ──────
  const DEFAULT_ADDBACKS = [
    { id: "owner_comp", description: "Owner compensation above market", amount: 0, category: "Owner", rationale: "Add back owner salary in excess of a market-rate replacement.", locked: false },
    { id: "legal", description: "One-time legal / professional fees", amount: 0, category: "One-time", rationale: "Non-recurring transaction, litigation or advisory costs.", locked: false },
    { id: "nonrecurring", description: "Non-recurring expenses", amount: 0, category: "One-time", rationale: "Expenses that will not repeat under new ownership.", locked: false },
    { id: "related_party", description: "Related-party transactions", amount: 0, category: "Related-party", rationale: "Above/below-market dealings with related entities normalised.", locked: false },
  ];
  function addbackKey(cid) { return "perdura_addbacks_" + (cid || "default"); }
  function loadAddbacks(cid) {
    try { const raw = localStorage.getItem(addbackKey(cid)); if (raw) return JSON.parse(raw); } catch (e) { /* ignore */ }
    return DEFAULT_ADDBACKS.map(a => ({ ...a }));
  }
  function saveAddbacks(cid, arr) { try { localStorage.setItem(addbackKey(cid), JSON.stringify(arr)); } catch (e) { /* ignore */ } }

  const DEFAULT_DCF = {
    growth13: 0.15, growth45: 0.10, ebitdaMargin: 0.22, daPct: 0.03,
    capexPct: 0.02, wcPct: 0.02, taxRate: 0.25, terminalGrowth: 0.03, wacc: 0.12,
  };
  function dcfKey(cid) { return "perdura_dcf_" + (cid || "default"); }
  function loadDcf(cid) {
    try { const raw = localStorage.getItem(dcfKey(cid)); if (raw) return { ...DEFAULT_DCF, ...JSON.parse(raw) }; } catch (e) { /* ignore */ }
    return { ...DEFAULT_DCF };
  }
  function saveDcf(cid, obj) { try { localStorage.setItem(dcfKey(cid), JSON.stringify(obj)); } catch (e) { /* ignore */ } }

  // ── GL add-back signal scan — flag opex accounts that look like add-backs ───
  const ADBACK_SIGNALS = [
    { re: /owner.*salary|officer.*comp|owner.*comp/i, label: "Owner / officer compensation" },
    { re: /legal.*settle|settlement|litigation/i, label: "Legal settlement" },
    { re: /depreciat|amortiz/i, label: "Depreciation & amortization" },
    { re: /one.?time|non.?recurring/i, label: "One-time / non-recurring" },
    { re: /personal|owner.*expense/i, label: "Personal / owner expense" },
    { re: /related.?party/i, label: "Related-party" },
  ];
  function flagAddbackSignals(data) {
    const out = [];
    const seen = new Set();
    const consider = (name, amount) => {
      if (!name) return;
      for (const sig of ADBACK_SIGNALS) {
        if (sig.re.test(name)) {
          const key = sig.label + "|" + name;
          if (!seen.has(key)) { seen.add(key); out.push({ account: name, signal: sig.label, amount: amount || null }); }
          break;
        }
      }
    };
    if (data && data.mappingsByCode) {
      Object.values(data.mappingsByCode).forEach(m => {
        if (typeof m === "string") consider(m);
        else if (m && (m.account_name || m.name)) consider(m.account_name || m.name);
      });
    }
    if (Array.isArray(data && data.txns)) {
      data.txns.forEach(t => consider(t.account_name || t.description, t.amount));
    }
    return out.slice(0, 12);
  }

  const DISCLAIMER = "This is an indicative estimate for management planning only. It is not a formal valuation, fairness opinion, or offer. Consult your M&A advisor, accountant, and legal counsel before acting.";

  // ── Shared UI components ────────────────────────────────────────────────────
  function Page({ title, subtitle, badge, children, actions }) {
    return (
      <div style={{ padding: "24px 28px 64px", maxWidth: 1180, margin: "0 auto" }}>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, marginBottom: 4 }}>
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <h1 style={{ fontSize: 22, fontWeight: 700, color: "var(--text)", margin: 0, letterSpacing: "-0.02em" }}>{title}</h1>
              {badge && <span style={{ fontSize: 10.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: "#0d9488", background: "rgba(13,148,136,0.12)", padding: "3px 8px", borderRadius: 5 }}>{badge}</span>}
            </div>
            {subtitle && <div style={{ fontSize: 13, color: "var(--text-3)", marginTop: 4 }}>{subtitle}</div>}
          </div>
          {actions && <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>{actions}</div>}
        </div>
        <div style={{ marginTop: 20 }}>{children}</div>
        <Disclaimer />
      </div>
    );
  }

  function Card({ title, children, style, right }) {
    return (
      <div style={{ background: "var(--bg-card)", border: "1px solid var(--border)", borderRadius: 12, padding: "18px 20px", ...(style || {}) }}>
        {(title || right) && (
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
            {title && <div style={{ fontSize: 13, fontWeight: 700, color: "var(--text)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{title}</div>}
            {right}
          </div>
        )}
        {children}
      </div>
    );
  }

  function Disclaimer() {
    return (
      <div style={{ marginTop: 28, paddingTop: 14, borderTop: "1px solid var(--border)", fontSize: 11.5, color: "var(--text-3)", lineHeight: 1.5, display: "flex", gap: 8 }}>
        <span style={{ flexShrink: 0 }}>ⓘ</span><span>{DISCLAIMER}</span>
      </div>
    );
  }

  function NotConnected({ what }) {
    return (
      <div style={{ padding: "48px 28px", maxWidth: 620, margin: "40px auto", textAlign: "center" }}>
        <div style={{ fontSize: 40, marginBottom: 12 }}>🔌</div>
        <h2 style={{ fontSize: 18, fontWeight: 700, color: "var(--text)", margin: "0 0 8px" }}>Connect your accounting data</h2>
        <p style={{ fontSize: 13.5, color: "var(--text-3)", lineHeight: 1.6, margin: 0 }}>
          {what || "This page builds entirely from your live general ledger."} Connect QuickBooks, Xero, or import a workbook to see real numbers here — nothing is estimated or fabricated.
        </p>
      </div>
    );
  }

  // Semicircle gauge, 0–100.
  function Gauge({ value, size = 200, label }) {
    const v = Math.max(0, Math.min(100, value || 0));
    const r = size / 2 - 16, cx = size / 2, cy = size / 2;
    const a0 = Math.PI, a1 = Math.PI * (1 - v / 100);
    const x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0) * -1;
    const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1) * -1;
    const xe = cx + r * Math.cos(0), ye = cy;
    const color = v >= 75 ? "#16a34a" : v >= 50 ? "#d97706" : "#dc2626";
    const large = (a0 - a1) > Math.PI ? 1 : 0;
    return (
      <svg width={size} height={size / 2 + 26} viewBox={`0 0 ${size} ${size / 2 + 26}`}>
        <path d={`M ${x0} ${y0} A ${r} ${r} 0 1 1 ${xe} ${ye}`} fill="none" stroke="var(--border)" strokeWidth="14" strokeLinecap="round" />
        <path d={`M ${x0} ${y0} A ${r} ${r} 0 ${large} 1 ${x1} ${y1}`} fill="none" stroke={color} strokeWidth="14" strokeLinecap="round" />
        <text x={cx} y={cy - 2} textAnchor="middle" fontSize={size * 0.24} fontWeight="800" fill="var(--text)">{Math.round(v)}</text>
        <text x={cx} y={cy + 18} textAnchor="middle" fontSize="12" fill="var(--text-3)">{label || "/ 100"}</text>
      </svg>
    );
  }

  // Low / mid / high horizontal range bar.
  function RangeBar({ low, mid, high, format }) {
    const f = format || (v => money(v, { compact: true }));
    if (!(high > low)) return <div style={{ fontSize: 13, color: "var(--text-3)" }}>—</div>;
    const midPct = ((mid - low) / (high - low)) * 100;
    return (
      <div style={{ padding: "8px 0" }}>
        <div style={{ position: "relative", height: 12, borderRadius: 999, background: "linear-gradient(90deg,#93c5fd,#2563eb,#1e3a8a)" }}>
          <div style={{ position: "absolute", top: -5, left: `calc(${Math.max(0, Math.min(100, midPct))}% - 2px)`, width: 4, height: 22, background: "var(--text)", borderRadius: 2 }} />
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", marginTop: 8, fontSize: 12.5 }}>
          <span style={{ color: "var(--text-3)" }}>{f(low)}</span>
          <span style={{ fontWeight: 800, color: "var(--text)" }}>{f(mid)}</span>
          <span style={{ color: "var(--text-3)" }}>{f(high)}</span>
        </div>
      </div>
    );
  }

  function Slider({ value, min, max, step, onChange, format }) {
    return (
      <div>
        <input type="range" min={min} max={max} step={step || 0.1} value={value}
          onChange={e => onChange(parseFloat(e.target.value))}
          style={{ width: "100%", accentColor: "var(--accent,#0F2044)" }} />
        <div style={{ fontSize: 12, color: "var(--text-3)", display: "flex", justifyContent: "space-between", marginTop: 2 }}>
          <span>{format ? format(min) : min}</span><span>{format ? format(max) : max}</span>
        </div>
      </div>
    );
  }

  function tealBtn(extra) {
    return { padding: "9px 16px", borderRadius: 8, border: "none", background: "#0d9488", color: "#fff", fontSize: 13, fontWeight: 700, cursor: "pointer", ...(extra || {}) };
  }
  function ghostBtn(extra) {
    return { padding: "9px 16px", borderRadius: 8, border: "1px solid var(--border-strong,var(--border))", background: "var(--bg-card)", color: "var(--text)", fontSize: 13, fontWeight: 600, cursor: "pointer", ...(extra || {}) };
  }

  window.TA = {
    money, pct, num, sumTail, annualSeries, derive,
    INDUSTRY_COMPS, matchComp,
    DEFAULT_ADDBACKS, loadAddbacks, saveAddbacks,
    DEFAULT_DCF, loadDcf, saveDcf,
    ADBACK_SIGNALS, flagAddbackSignals, DISCLAIMER,
    Page, Card, Disclaimer, NotConnected, Gauge, RangeBar, Slider, tealBtn, ghostBtn,
  };
})();
