/* === synthetic-data.jsx ============================================
   F2 — Synthetic Data Engine (Prioridade #1 · Demo Interativa Real).

   Gera um dataset sintético plausível e INTERNAMENTE CONSISTENTE a
   partir do perfil do prospect (cosmetic + data + vars do state).
   100% client-side, sem backend. Determinístico: a mesma demo (slug/
   empresa) sempre gera os mesmos dados — reabrir o link é idempotente.

   Consistência garantida por construção:
     Σ spend fornecedor = Σ spend categoria = Σ spend filial
       = Σ spend mês = totalSpend
     Σ saving (todas as dimensões) = roiValor (quando o anchor existe)

   Exposto em window.SyntheticData. NÃO é persistido no state nem no
   hash — é regenerado sob demanda e cacheado em memória pelo perfil.
   ===================================================================== */
(function () {

    /* ---------- PRNG determinístico (mulberry32 + FNV-1a) ---------- */
    function hashString(str) {
        let h = 2166136261 >>> 0;
        const s = String(str || '');
        for (let i = 0; i < s.length; i++) {
            h ^= s.charCodeAt(i);
            h = Math.imul(h, 16777619);
        }
        return h >>> 0;
    }
    function mulberry32(a) {
        return function () {
            a |= 0; a = (a + 0x6D2B79F5) | 0;
            let t = Math.imul(a ^ (a >>> 15), 1 | a);
            t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
            return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
        };
    }
    const pick = (rng, arr) => arr[Math.floor(rng() * arr.length)];
    const randInt = (rng, lo, hi) => lo + Math.floor(rng() * (hi - lo + 1));

    /* ---------- Parse de valores BRL abreviados ----------
       Aceita "417,1 Mi", "5,54Mi", "R$ 2,4 M", "1.840", "12 bilhões". */
    function parseBRLNumber(str) {
        if (typeof str === 'number') return str;
        if (!str) return null;
        const s = String(str).trim().toLowerCase().replace(/r\$/g, '').trim();
        /* captura número + sufixo de escala (colado ou separado): "5,54M",
           "100 mi", "417,1 Mi", "12 mil", "2,4 bilhões", "1.840". A ordem da
           alternância importa: milhão antes de mil antes de mi/m. */
        const m = s.match(/(-?[\d.]+(?:,\d+)?)\s*(bilh\w*|bi|milh\w*|mil|mm|mi|m|b|k)?/);
        if (!m) return null;
        const num = parseFloat(m[1].replace(/\./g, '').replace(',', '.'));
        if (isNaN(num)) return null;
        const unit = m[2] || '';
        let mult = 1;
        if (/^(bilh|bi|b)/.test(unit)) mult = 1e9;
        else if (/^(milh|mm|mi|m)$/.test(unit) || /^milh/.test(unit)) mult = 1e6;
        else if (/^(mil|k)$/.test(unit)) mult = 1e3;
        return num * mult;
    }

    /* ---------- Formatação compacta pt-BR ---------- */
    function fmtBRL(n, prefix) {
        const p = prefix === false ? '' : 'R$ ';
        const abs = Math.abs(n);
        /* arredonda e remove só os zeros à direita da parte decimal (nunca os inteiros) */
        const fix = (v) => {
            let str = v.toFixed(v >= 100 ? 0 : v >= 10 ? 1 : 2);
            if (str.indexOf('.') >= 0) str = str.replace(/0+$/, '').replace(/\.$/, '');
            return str.replace('.', ',');
        };
        if (abs >= 1e9) return p + fix(n / 1e9) + ' Bi';
        if (abs >= 1e6) return p + fix(n / 1e6) + ' Mi';
        if (abs >= 1e3) return p + fix(n / 1e3) + ' mil';
        return p + Math.round(n).toLocaleString('pt-BR');
    }
    const fmtInt = (n) => Math.round(n).toLocaleString('pt-BR');
    const fmtPct = (n) => (n * 100).toFixed(1).replace('.', ',') + '%';

    /* ---------- CNPJ sintético com dígitos verificadores válidos ---------- */
    function cnpj(rng) {
        const base = [];
        for (let i = 0; i < 8; i++) base.push(randInt(rng, 0, 9));
        base.push(0, 0, 0, 1);                              /* filial 0001 */
        const calc = (nums, weights) => {
            const sum = nums.reduce((s, d, i) => s + d * weights[i], 0);
            const r = sum % 11;
            return r < 2 ? 0 : 11 - r;
        };
        const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
        const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
        const d1 = calc(base, w1);
        const d2 = calc([...base, d1], w2);
        const all = [...base, d1, d2].join('');
        return `${all.slice(0, 2)}.${all.slice(2, 5)}.${all.slice(5, 8)}/${all.slice(8, 12)}-${all.slice(12)}`;
    }

    /* ---------- Pools de nomes plausíveis por setor ---------- */
    const NAME_POOLS = {
        alimentos: {
            sup: ['Bunge', 'Cargill', 'Klabin Embalagens', 'Trombini', 'Bemis Latam', 'Tetra Pak', 'Suzano Papel', 'Ipiranga Logística', 'Friozem Armazéns', 'BRF Insumos', 'Yara Fertilizantes', 'Seara Alimentos'],
            cat: ['Embalagens', 'Matéria-prima — grãos', 'Insumos químicos', 'Logística & frete', 'Manutenção industrial', 'EPI & segurança', 'Energia & utilidades', 'Serviços terceirizados', 'TI & telecom', 'Marketing & trade', 'Frota & combustível', 'Limpeza & higienização'],
        },
        manufatura: {
            sup: ['Wieland do Brasil', 'Saint-Gobain', 'Tigre S.A.', 'Aperam Inox', 'Mahle Metal Leve', 'Gerdau Aços', 'Votorantim Metais', 'WEG Equipamentos', 'Bosch Rexroth', 'SKF Rolamentos', 'Atlas Copco', 'Tupy Fundições'],
            cat: ['Metais & ligas', 'Componentes usinados', 'Manutenção (MRO)', 'Logística & frete', 'Energia & utilidades', 'Ferramentaria', 'EPI & segurança', 'Serviços de engenharia', 'TI & automação', 'Embalagens', 'Frota & combustível', 'Químicos industriais'],
        },
        varejo: {
            sup: ['Procter & Gamble', 'Unilever Brasil', 'Nestlé', 'Ambev', 'JBS', 'Coca-Cola FEMSA', 'Mondelez', 'Bunge Alimentos', 'Reckitt', 'Colgate-Palmolive', 'Pepsico', 'BRF'],
            cat: ['Mercadoria para revenda', 'Logística & distribuição', 'Embalagens', 'Marketing & trade', 'Manutenção de lojas', 'TI & e-commerce', 'Serviços terceirizados', 'Energia & utilidades', 'EPI & uniformes', 'Frota & combustível', 'Limpeza & facilities', 'Material de escritório'],
        },
        saude: {
            sup: ['Johnson & Johnson Medical', 'Medtronic', 'Cremer', 'Baxter', 'Fresenius', 'B. Braun', 'GE Healthcare', 'Philips Medical', 'Cristália', 'Eurofarma Hospitalar', 'BD Becton Dickinson', 'Hospitalar Distribuição'],
            cat: ['OPME (órteses & próteses)', 'Medicamentos', 'Material médico-hospitalar', 'Equipamentos médicos', 'Serviços terceirizados', 'Manutenção predial & clínica', 'TI & sistemas', 'Limpeza & higienização', 'Lavanderia', 'Alimentação & nutrição', 'Energia & utilidades', 'EPI & descartáveis'],
        },
        default: {
            sup: ['Fornecedor Alfa', 'Fornecedor Beta', 'Fornecedor Gama', 'Fornecedor Delta', 'Fornecedor Épsilon', 'Fornecedor Zeta', 'Fornecedor Eta', 'Fornecedor Teta', 'Fornecedor Iota', 'Fornecedor Capa', 'Fornecedor Lambda', 'Fornecedor Sigma'],
            cat: ['Materiais diretos', 'Materiais indiretos', 'Serviços', 'Logística & frete', 'Manutenção (MRO)', 'TI & telecom', 'Energia & utilidades', 'Marketing', 'EPI & segurança', 'Frota & combustível', 'Facilities', 'Material de escritório'],
        },
    };
    function sectorKey(industry) {
        const s = String(industry || '').toLowerCase();
        if (/aliment|bebida|food|agro/.test(s)) return 'alimentos';
        if (/saúde|saude|hospital|operadora|clínic|clinic|health/.test(s)) return 'saude';
        if (/varejo|retail|consumo|atacad|distribu/.test(s)) return 'varejo';
        if (/manufat|indúst|indust|metal|químic|quimic|automotiv|bens/.test(s)) return 'manufatura';
        return 'default';
    }

    /* sufixos para alongar pools sem repetir nomes */
    const SUFFIXES = ['Brasil', 'Indústria', 'Comércio', 'Latam', 'do Sul', 'Nordeste', 'Distribuição', 'Logística', 'Group', 'S.A.', 'Ltda', 'Serviços', 'Engenharia', 'Suprimentos'];

    /* ---------- Perfil derivado do state ---------- */
    function profileFromState(state) {
        const cosmetic = state.cosmetic || {};
        const data = state.data || {};
        const vars = state.vars || {};
        const company = (vars.empresa && String(vars.empresa).trim()) || cosmetic.company || 'Prospect';
        const sector = sectorKey(cosmetic.industry || vars.setor);
        const currency = data.currency || 'BRL';
        const totalSpend = parseBRLNumber(vars.spend_total)
            || parseBRLNumber(vars.spend_p2p_enderecavel)
            || defaultSpendBySector(sector);
        const roiValor = parseBRLNumber(vars.roi_valor) || null;
        const seedKey = `${company}|${sector}|${cosmetic.industry || ''}`;
        return {
            company, sector, currency, totalSpend, roiValor, seedKey,
            sitesSeed: (data.sites && data.sites.length ? data.sites : ['Matriz']).slice(0, 8),
            suppliersSeed: data.suppliers || [],
            categoriesSeed: data.skuCategories || [],
        };
    }
    function defaultSpendBySector(sector) {
        return { saude: 320e6, varejo: 540e6, manufatura: 260e6, alimentos: 410e6, default: 200e6 }[sector] || 200e6;
    }

    /* ---------- Distribuição Pareto (curva ABC) ---------- */
    function paretoWeights(rng, n) {
        /* peso ~ 1/rank^1.1 com ruído → concentração ~80/20 */
        const w = [];
        for (let i = 0; i < n; i++) {
            w.push((1 / Math.pow(i + 1, 1.08)) * (0.75 + rng() * 0.5));
        }
        const sum = w.reduce((s, x) => s + x, 0);
        return w.map(x => x / sum);
    }
    /* aloca `total` em n parcelas inteiras que somam EXATAMENTE total */
    function allocateExact(weights, total) {
        const raw = weights.map(w => w * total);
        const floored = raw.map(Math.floor);
        let rem = Math.round(total - floored.reduce((s, x) => s + x, 0));
        /* distribui o resto pelas maiores frações */
        const order = raw.map((v, i) => ({ i, frac: v - Math.floor(v) }))
            .sort((a, b) => b.frac - a.frac);
        for (let k = 0; k < order.length && rem > 0; k++) { floored[order[k].i]++; rem--; }
        return floored;
    }

    /* ---------- Geração principal ---------- */
    function generateDataset(profile) {
        const rng = mulberry32(hashString(profile.seedKey));
        const pool = NAME_POOLS[profile.sector] || NAME_POOLS.default;
        const total = profile.totalSpend;

        /* --- Categorias (~12, curva ABC) --- */
        const catNames = uniqueNames(pool.cat, profile.categoriesSeed, 12, rng);
        const catW = paretoWeights(rng, catNames.length);
        const catSpend = allocateExact(catW, total);
        const categories = catNames.map((name, i) => {
            const share = catSpend[i] / total;
            const klass = share > 0.12 ? 'A' : share > 0.045 ? 'B' : 'C';
            return {
                id: 'cat' + i, name, spend: catSpend[i], share, class: klass,
                skuCount: randInt(rng, klass === 'A' ? 80 : 20, klass === 'A' ? 600 : 180),
            };
        });

        /* --- Filiais --- */
        const siteNames = profile.sitesSeed.length ? profile.sitesSeed : ['Matriz'];
        const siteW = paretoWeights(rng, siteNames.length);
        const siteSpend = allocateExact(siteW, total);
        const sites = siteNames.map((name, i) => ({ id: 'site' + i, name, spend: siteSpend[i], share: siteSpend[i] / total }));

        /* --- Fornecedores (~120, Pareto; cada um numa categoria) --- */
        const N_SUP = 120;
        const supNames = uniqueNames(pool.sup, profile.suppliersSeed, N_SUP, rng);
        const supW = paretoWeights(rng, N_SUP);
        const supSpend = allocateExact(supW, total);
        /* atribui categoria ponderada pelo spend da categoria → Σ supplier por categoria ≈ spend da categoria */
        const suppliers = supNames.map((name, i) => {
            const catIdx = weightedIndex(rng, catSpend);
            const lt = randInt(rng, 3, 45);
            const otif = Math.min(0.99, 0.62 + rng() * 0.37);
            const risk = +(rng() * 100).toFixed(0);
            return {
                id: 'sup' + i, name, cnpj: cnpj(rng),
                categoryId: 'cat' + catIdx, category: categories[catIdx].name,
                spend: supSpend[i], share: supSpend[i] / total,
                leadTime: lt, otif, riskScore: risk,
                strategic: i < 8,
            };
        });

        /* --- Meses (12, com sazonalidade por setor) --- */
        const months = buildMonths(rng, profile.sector, total);

        /* --- Pedidos de Compra (amostra navegável) --- */
        const savingRateByClass = { A: 0.045, B: 0.075, C: 0.11 };
        let pos = [];
        let poSeq = 4500000000;
        suppliers.forEach((sup) => {
            const nPo = Math.max(1, Math.round((sup.spend / total) * 2400) + randInt(rng, 0, 2));
            const w = paretoWeights(rng, nPo);
            const vals = allocateExact(w, sup.spend);
            for (let k = 0; k < nPo; k++) {
                if (pos.length > 2600) break;
                const cat = categories.find(c => c.id === sup.categoryId);
                const site = sites[weightedIndex(rng, siteSpend)];
                const month = months[weightedIndex(rng, months.map(m => m.spend))];
                const lt = Math.max(1, Math.round(sup.leadTime + (rng() - 0.5) * 10));
                const maverick = rng() < 0.10;
                const split = rng() < 0.05;
                const status = pick(rng, ['Pago', 'Pago', 'Pago', 'Recebido', 'Em aprovação', 'Cotação']);
                pos.push({
                    id: String(poSeq++),
                    supplierId: sup.id, supplier: sup.name,
                    categoryId: sup.categoryId, category: sup.category,
                    siteId: site.id, site: site.name,
                    ym: month.ym, value: vals[k],
                    leadTime: lt, status, maverick, split,
                    saving: Math.round(vals[k] * savingRateByClass[cat ? cat.class : 'C']),
                });
            }
        });

        /* --- Saving total + calibração ao anchor roi_valor --- */
        let savingTotal = pos.reduce((s, p) => s + p.saving, 0);
        if (profile.roiValor && savingTotal > 0) {
            const scale = profile.roiValor / savingTotal;
            pos.forEach(p => { p.saving = Math.round(p.saving * scale); });
            savingTotal = pos.reduce((s, p) => s + p.saving, 0);
        }

        /* --- KPIs derivados (consistentes com os registros) --- */
        const leadTimes = pos.map(p => p.leadTime).sort((a, b) => a - b);
        const pctl = (arr, q) => arr.length ? arr[Math.min(arr.length - 1, Math.floor(q * arr.length))] : 0;
        const otifMean = suppliers.reduce((s, x) => s + x.otif * x.spend, 0) / total;   /* ponderado por spend */
        const maverickShare = pos.filter(p => p.maverick).reduce((s, p) => s + p.value, 0) / total;

        /* alavancas de RoAI somando savingTotal (≈ roi_valor quando existe) */
        const leverDefs = [
            { id: 'cauda', label: 'Consolidação de cauda longa', w: 0.34 },
            { id: 'negoc', label: 'Renegociação categorias A', w: 0.28 },
            { id: 'maverick', label: 'Redução de maverick buying', w: 0.20 },
            { id: 'prazo', label: 'Ganho de prazo de pagamento', w: 0.18 },
        ];
        const leverVals = allocateExact(leverDefs.map(l => l.w), savingTotal);
        const roiLevers = leverDefs.map((l, i) => ({ id: l.id, label: l.label, value: leverVals[i] }));

        return {
            meta: {
                company: profile.company, sector: profile.sector, currency: profile.currency,
                totalSpend: total, supplierCount: suppliers.length, categoryCount: categories.length,
                siteCount: sites.length, poCount: pos.length,
                savingTotal, roiAnchor: profile.roiValor, seedKey: profile.seedKey,
                generatedLabel: 'dados simulados',
            },
            categories, suppliers, sites, months, pos,
            kpis: {
                totalSpend: total,
                savingTotal,
                leadTimeMean: leadTimes.reduce((s, x) => s + x, 0) / (leadTimes.length || 1),
                leadTimeP50: pctl(leadTimes, 0.5),
                leadTimeP90: pctl(leadTimes, 0.9),
                otif: otifMean,
                maverickShare,
                roiLevers,
            },
        };
    }

    /* ---------- helpers de nomes / pesos ---------- */
    function uniqueNames(basePool, seedNames, target, rng) {
        const out = [];
        const seen = new Set();
        const push = (n) => { const k = n.toLowerCase(); if (n && !seen.has(k)) { seen.add(k); out.push(n); } };
        (seedNames || []).forEach(push);
        basePool.forEach(push);
        let si = 0;
        while (out.length < target) {
            const base = basePool[out.length % basePool.length];
            const suf = SUFFIXES[si % SUFFIXES.length]; si++;
            push(`${base} ${suf}`);
            if (si > target * 4) break;
        }
        return out.slice(0, target);
    }
    function weightedIndex(rng, weights) {
        const sum = weights.reduce((s, x) => s + x, 0);
        let r = rng() * sum;
        for (let i = 0; i < weights.length; i++) { r -= weights[i]; if (r <= 0) return i; }
        return weights.length - 1;
    }
    function buildMonths(rng, sector, total) {
        const labels = [];
        const now = { y: 2026, m: 4 };                       /* abr/2026, alinhado ao app */
        for (let i = 11; i >= 0; i--) {
            let m = now.m - i, y = now.y;
            while (m <= 0) { m += 12; y--; }
            labels.push(`${y}-${String(m).padStart(2, '0')}`);
        }
        /* sazonalidade leve por setor */
        const seasonal = labels.map((ym) => {
            const mm = parseInt(ym.slice(5), 10);
            let base = 1;
            if (sector === 'varejo' && (mm === 11 || mm === 12)) base = 1.6;      /* black friday/natal */
            if (sector === 'alimentos' && (mm >= 3 && mm <= 6)) base = 1.35;        /* safra */
            if (mm === 12 || mm === 3 || mm === 6 || mm === 9) base *= 1.12;        /* fim de trimestre */
            return base * (0.85 + rng() * 0.3);
        });
        const wsum = seasonal.reduce((s, x) => s + x, 0);
        const spend = allocateExact(seasonal.map(x => x / wsum), total);
        return labels.map((ym, i) => ({ ym, spend: spend[i], poCount: 0 }));
    }

    /* ---------- Cache por perfil (em memória) ---------- */
    const cache = {};
    function getDataset(state) {
        const p = profileFromState(state);
        const key = `${p.seedKey}|${Math.round(p.totalSpend)}|${p.roiValor || 0}`;
        if (cache[key]) return cache[key];
        const ds = generateDataset(p);
        cache[key] = ds;
        return ds;
    }

    window.SyntheticData = {
        getDataset, generateDataset, profileFromState,
        fmtBRL, fmtInt, fmtPct, parseBRLNumber, hashString, sectorKey,
    };
})();
