/**
 * POS tax, totals, payment reconciliation helpers (Phase 2).
 * Used by pos.html and optionally analytics/dashboard readers.
 */

/** @typedef {{ enabled: boolean, defaultMode: 'inclusive'|'exclusive', defaultRate: number }} PosTaxConfig */
/** @typedef {{ legalName?: string, binVat?: string, footerText?: string, returnPolicyText?: string }} PosReceiptConfig */

const TOL = 0.02;

/**
 * @param {unknown} v
 * @returns {PosTaxConfig}
 */
export function mergePosTaxConfig(siteSettings) {
    const s = siteSettings && typeof siteSettings === 'object' ? siteSettings : {};
    const t = s.posTax && typeof s.posTax === 'object' ? s.posTax : {};
    const enabled = t.enabled === true;
    const dm = String(t.defaultMode || 'exclusive').toLowerCase() === 'inclusive' ? 'inclusive' : 'exclusive';
    const dr = Math.min(100, Math.max(0, Number(t.defaultRate) || 0));
    return { enabled, defaultMode: dm, defaultRate: dr };
}

/**
 * @param {unknown} siteSettings
 * @returns {PosReceiptConfig}
 */
export function mergePosReceiptConfig(siteSettings) {
    const s = siteSettings && typeof siteSettings === 'object' ? siteSettings : {};
    const r = s.posReceipt && typeof s.posReceipt === 'object' ? s.posReceipt : {};
    return {
        legalName: r.legalName != null ? String(r.legalName).trim() : '',
        binVat: r.binVat != null ? String(r.binVat).trim() : '',
        footerText: r.footerText != null ? String(r.footerText).trim() : '',
        returnPolicyText: r.returnPolicyText != null ? String(r.returnPolicyText).trim() : ''
    };
}

/**
 * @param {unknown} product
 * @param {PosTaxConfig} globalTax
 * @returns {{ mode: 'inclusive'|'exclusive', rate: number, exempt: boolean }}
 */
export function resolveLineTaxRule(product, globalTax) {
    const p = product && typeof product === 'object' ? product : {};
    if (p.taxExempt === true) return { mode: globalTax.defaultMode, rate: 0, exempt: true };
    const rateOverride = p.taxRateOverride;
    const rate = rateOverride != null && !Number.isNaN(Number(rateOverride))
        ? Math.min(100, Math.max(0, Number(rateOverride)))
        : globalTax.defaultRate;
    const o = p.taxModeOverride != null ? String(p.taxModeOverride).trim().toLowerCase() : '';
    const mode = o === 'inclusive' || o === 'exclusive' ? o : globalTax.defaultMode;
    return { mode, rate, exempt: false };
}

export function roundMoney2(n) {
    const x = Number(n);
    if (Number.isNaN(x)) return 0;
    return Math.round(x * 100) / 100;
}

/**
 * Split tax from inclusive gross (after discount allocation).
 * @param {number} grossInclTax
 * @param {number} ratePercent
 */
export function splitInclusiveTax(grossInclTax, ratePercent) {
    const g = Math.max(0, roundMoney2(grossInclTax));
    const r = Math.max(0, ratePercent);
    if (r <= 0) return { net: g, tax: 0, taxable: g };
    const net = roundMoney2(g / (1 + r / 100));
    const tax = roundMoney2(g - net);
    return { net, tax, taxable: net };
}

/**
 * Tax on exclusive taxable base.
 */
export function taxOnExclusiveBase(base, ratePercent) {
    const b = Math.max(0, roundMoney2(base));
    const r = Math.max(0, ratePercent);
    if (r <= 0) return { net: b, tax: 0, taxable: b };
    const tax = roundMoney2(b * (r / 100));
    return { net: b, tax, taxable: b };
}

/**
 * Cart lines may include optional per-line discounts (applied before cart-level discount):
 * `lineDiscountFixed` (money), `lineDiscountPercent` (0–100), capped so the line net is not below zero.
 *
 * @param {{ cart: object[], freshProducts: Record<string, object>, discountFixed: number, discountPercent: number, taxConfig: PosTaxConfig }}
 */
export function computePosCheckoutAmounts({ cart, freshProducts, discountFixed, discountPercent, taxConfig }) {
    const lines = [];
    /** Sum of shelf-extension (price×qty) before any discounts */
    let lineSubtotal = 0;
    /** Total line-level (per-SKU) discounts */
    let lineLevelDiscountSum = 0;
    for (const l of cart || []) {
        const p = freshProducts[l.id];
        const unit = Number(l.price) || 0;
        const qty = Math.max(0, Number(l.quantity) || 0);
        const grossRaw = roundMoney2(unit * qty);
        const ldFixed = Math.max(0, Number(l.lineDiscountFixed) || 0);
        const ldPct = Math.min(100, Math.max(0, Number(l.lineDiscountPercent) || 0));
        const fromLinePct = roundMoney2(grossRaw * (ldPct / 100));
        const lineLevelDisc = Math.min(grossRaw, roundMoney2(ldFixed + fromLinePct));
        const gross = roundMoney2(grossRaw - lineLevelDisc);
        lineSubtotal = roundMoney2(lineSubtotal + grossRaw);
        lineLevelDiscountSum = roundMoney2(lineLevelDiscountSum + lineLevelDisc);
        const rule = resolveLineTaxRule(p, taxConfig);
        lines.push({ cartLine: l, product: p, grossRaw, lineLevelDisc, gross, qty, unit, rule });
    }

    const allocBase = roundMoney2(lines.reduce((s, li) => s + li.gross, 0));

    const fixed = Math.max(0, discountFixed);
    const pct = Math.min(100, Math.max(0, discountPercent));
    const fromPct = roundMoney2(allocBase * (pct / 100));
    const orderDiscountTotal = Math.min(allocBase, roundMoney2(fixed + fromPct));
    const discountTotal = roundMoney2(lineLevelDiscountSum + orderDiscountTotal);

    const alloc = [];
    let sumAlloc = 0;
    for (let i = 0; i < lines.length; i++) {
        const li = lines[i];
        const share = allocBase > 0 ? roundMoney2((orderDiscountTotal * li.gross) / allocBase) : 0;
        alloc.push(share);
        sumAlloc = roundMoney2(sumAlloc + share);
    }
    const drift = roundMoney2(orderDiscountTotal - sumAlloc);
    if (alloc.length && Math.abs(drift) >= 0.001) {
        alloc[alloc.length - 1] = roundMoney2(alloc[alloc.length - 1] + drift);
    }

    const taxLinesAgg = {};
    let taxTotal = 0;
    let netBeforeTaxTotal = 0;
    let taxableBaseTotal = 0;
    let payableTotal = 0;
    const lineSnapshots = [];

    for (let i = 0; i < lines.length; i++) {
        const li = lines[i];
        const discShare = alloc[i] || 0;
        const afterDisc = Math.max(0, roundMoney2(li.gross - discShare));
        let tax = 0;
        let net = afterDisc;
        let taxable = afterDisc;
        const modeUsed = li.rule.mode;
        let rateUsed = li.rule.rate;
        let linePayable = afterDisc;

        if (!taxConfig.enabled || li.rule.exempt || rateUsed <= 0) {
            tax = 0;
            net = afterDisc;
            taxable = afterDisc;
            rateUsed = 0;
            linePayable = afterDisc;
        } else if (li.rule.mode === 'inclusive') {
            const sp = splitInclusiveTax(afterDisc, rateUsed);
            tax = sp.tax;
            net = sp.net;
            taxable = sp.taxable;
            linePayable = afterDisc;
        } else {
            const sp = taxOnExclusiveBase(afterDisc, rateUsed);
            tax = sp.tax;
            net = sp.net;
            taxable = sp.taxable;
            linePayable = roundMoney2(net + tax);
        }

        payableTotal = roundMoney2(payableTotal + linePayable);
        netBeforeTaxTotal = roundMoney2(netBeforeTaxTotal + net);
        taxableBaseTotal = roundMoney2(taxableBaseTotal + taxable);
        taxTotal = roundMoney2(taxTotal + tax);

        const aggKey = `${modeUsed}|${rateUsed}`;
        if (!taxLinesAgg[aggKey]) taxLinesAgg[aggKey] = { mode: modeUsed, rate: rateUsed, taxable: 0, tax: 0 };
        taxLinesAgg[aggKey].taxable = roundMoney2(taxLinesAgg[aggKey].taxable + taxable);
        taxLinesAgg[aggKey].tax = roundMoney2(taxLinesAgg[aggKey].tax + tax);

        lineSnapshots.push({
            productId: li.cartLine.id,
            variantId: li.cartLine.variantId || null,
            name: li.cartLine.name,
            qty: li.qty,
            unit: li.unit,
            lineGross: li.grossRaw,
            lineLevelDiscount: li.lineLevelDisc,
            discountShare: discShare,
            afterDiscount: afterDisc,
            taxMode: modeUsed,
            taxRate: rateUsed,
            taxable,
            tax,
            net,
            linePayable
        });
    }

    const taxLines = Object.values(taxLinesAgg)
        .filter((x) => x.tax > 0 || x.rate > 0)
        .map((x) => ({
            mode: x.mode,
            rate: x.rate,
            taxable: roundMoney2(x.taxable),
            tax: roundMoney2(x.tax)
        }));

    return {
        lineSubtotal: roundMoney2(lineSubtotal),
        discountTotal: roundMoney2(discountTotal),
        taxableBaseTotal: roundMoney2(taxableBaseTotal),
        taxTotal: roundMoney2(taxTotal),
        netBeforeTaxTotal: roundMoney2(netBeforeTaxTotal),
        payableTotal: roundMoney2(payableTotal),
        taxLines,
        lineSnapshots,
        taxModeSnapshot: taxConfig.enabled ? taxConfig.defaultMode : 'none'
    };
}

export function readOrderAmountSnapshot(order) {
    const a = order?.amount && typeof order.amount === 'object' ? order.amount : {};
    const totalBill = Number(order?.totalBill ?? a.total ?? 0) || 0;
    return {
        total: totalBill,
        subtotal: Number(order?.subtotal ?? a.subtotal ?? totalBill) || 0,
        discount: Number(a.discount ?? 0) || 0,
        tax: Number(a.tax ?? 0) || 0,
        netBeforeTax: Number(a.netBeforeTax ?? 0) || 0,
        taxableBase: Number(a.taxableBase ?? 0) || 0
    };
}

/**
 * Order value for refund ratio (line totals).
 */
export function orderItemsGrossValue(order) {
    const items = Array.isArray(order?.items) ? order.items : Object.values(order?.items || {});
    return items.reduce((s, it) => s + (Number(it.price) || 0) * (Number(it.quantity) || 0), 0);
}

export function orderRefundedMoneyValue(order) {
    const items = Array.isArray(order?.items) ? order.items : Object.values(order?.items || {});
    return items.reduce((s, it) => s + (Number(it.price) || 0) * (Number(it.refundedQty) || 0), 0);
}

/**
 * Allocate total refund amount across methods by original payment breakdown ratio.
 * @param {number} totalRefund
 * @param {{ method: string, amount: number }[]} breakdown
 */
export function allocateRefundByBreakdown(totalRefund, breakdown) {
    const tr = Math.max(0, roundMoney2(totalRefund));
    const b = Array.isArray(breakdown) ? breakdown : [];
    const norm = b.map((x) => ({ method: String(x.method || 'Cash'), amount: Math.max(0, roundMoney2(Number(x.amount) || 0)) }));
    const sum = roundMoney2(norm.reduce((s, x) => s + x.amount, 0));
    if (tr <= 0 || sum <= 0) {
        return { cash: 0, card: 0, mobile: 0, breakdown: [] };
    }
    const breakdownOut = [];
    let allocated = 0;
    for (let i = 0; i < norm.length; i++) {
        const row = norm[i];
        const share = i === norm.length - 1 ? roundMoney2(tr - allocated) : roundMoney2((tr * row.amount) / sum);
        allocated = roundMoney2(allocated + share);
        if (share > 0) breakdownOut.push({ method: row.method, amount: share });
    }
    const cash = roundMoney2(breakdownOut.filter((x) => x.method === 'Cash').reduce((s, x) => s + x.amount, 0));
    const card = roundMoney2(breakdownOut.filter((x) => x.method === 'Card').reduce((s, x) => s + x.amount, 0));
    const mobile = roundMoney2(breakdownOut.filter((x) => x.method === 'Mobile banking').reduce((s, x) => s + x.amount, 0));
    return { cash, card, mobile, breakdown: breakdownOut };
}

/**
 * Expected tender totals for POS orders in a time window.
 * @param {Record<string, object>} ordersMap
 * @param {number} startMs
 * @param {number} endMs
 * @param {(o: object) => boolean} isPos
 * @param {(o: object) => number|null} createdMs
 */
export function computeExpectedTenderByMethod(ordersMap, startMs, endMs, isPos, createdMs) {
    let cash = 0;
    let card = 0;
    let mobile = 0;
    for (const o of Object.values(ordersMap || {})) {
        if (!isPos(o)) continue;
        const st = String(o.status || '');
        if (st === 'Canceled') continue;
        const ms = createdMs(o);
        if (ms == null || ms < startMs || ms >= endMs) continue;
        const total = Number(o.totalBill) || 0;
        const refundedVal = orderRefundedMoneyValue(o);
        const grossVal = orderItemsGrossValue(o) || total;
        const scale = grossVal > 0 ? Math.max(0, Math.min(1, (grossVal - refundedVal) / grossVal)) : st === 'Refunded' ? 0 : 1;
        const effTotal = roundMoney2(total * scale);
        const br = Array.isArray(o.payment?.breakdown) ? o.payment.breakdown : [];
        const sumBr = roundMoney2(br.reduce((s, x) => s + (Number(x.amount) || 0), 0));
        if (sumBr > 0) {
            for (const x of br) {
                const a = roundMoney2((Number(x.amount) || 0) * (effTotal / sumBr));
                const m = String(x.method || '');
                if (m === 'Cash') cash = roundMoney2(cash + a);
                else if (m === 'Card') card = roundMoney2(card + a);
                else if (m.includes('Mobile') || m === 'Mobile banking') mobile = roundMoney2(mobile + a);
            }
        } else {
            const method = String(o.payment?.method || '');
            if (method === 'Split' || (br && br.length > 1)) {
                /* skip unknown */
            } else if (method === 'Card') card = roundMoney2(card + effTotal);
            else if (method.includes('Mobile')) mobile = roundMoney2(mobile + effTotal);
            else cash = roundMoney2(cash + effTotal);
        }
    }
    return { cash, card, mobile, total: roundMoney2(cash + card + mobile) };
}

export { TOL };
