/* ============================================================
   UK PCP Calculator Pro — Finance Engine
   All figures are illustrative estimates, not financial advice.
   Exposes window.PCPEngine
   ============================================================ */
(function () {
  // APR (%) -> effective monthly rate.
  // basis 'effective' (FCA-correct): (1+APR)^(1/12)-1
  // basis 'nominal'  (aggregator-style): APR/12
  const monthlyRate = (aprPct, basis) =>
    basis === "nominal" ? (aprPct / 100) / 12 : Math.pow(1 + aprPct / 100, 1 / 12) - 1;

  // payment to amortise PV down to FV (balloon) over n months at monthly rate r
  function paymentWithBalloon(pv, fv, r, n) {
    if (n <= 0) return 0;
    if (r === 0) return (pv - fv) / n;
    const disc = Math.pow(1 + r, -n);
    return (pv - fv * disc) * r / (1 - disc);
  }

  // outstanding balance after k payments (actuarial) — used for settlement
  function balanceAfter(pv, pmt, r, k) {
    if (r === 0) return pv - pmt * k;
    return pv * Math.pow(1 + r, k) - pmt * (Math.pow(1 + r, k) - 1) / r;
  }

  const sumFees = (f = {}) =>
    (f.acceptance || 0) + (f.documentation || 0) + (f.optionToPurchase || 0);

  /* -------- Core PCP -------- */
  function calculatePCP(i) {
    const {
      cashPrice = 0, deposit = 0, partExchange = 0, dealerContribution = 0,
      apr = 0, termMonths = 48, balloon = 0, fees = {}, rateBasis = "effective",
    } = i;
    const totalDeposit = deposit + partExchange + dealerContribution;
    const amountFinanced = Math.max(0, cashPrice - totalDeposit);
    const r = monthlyRate(apr, rateBasis);
    const feeTotal = sumFees(fees);
    // acceptance + documentation financed into monthly; option-to-purchase paid with balloon
    const financedPlusFees = amountFinanced + (fees.acceptance || 0) + (fees.documentation || 0);
    const finalPayment = balloon + (fees.optionToPurchase || 0);
    const monthly = paymentWithBalloon(financedPlusFees, balloon, r, termMonths);

    const totalInstalments = monthly * termMonths;
    const totalPayable = totalDeposit + totalInstalments + finalPayment;
    const totalCharges = totalPayable - cashPrice; // interest + fees on top of car price
    const interest = totalInstalments + balloon - financedPlusFees;

    return {
      amountFinanced, totalDeposit, monthlyRate: r,
      monthly, balloon, finalPayment, feeTotal,
      totalInstalments, totalPayable, totalCharges,
      interest: Math.max(0, interest),
      costOfCredit: Math.max(0, interest) + feeTotal,
      depositPct: cashPrice ? (totalDeposit / cashPrice) * 100 : 0,
      financedPlusFees,
    };
  }

  /* -------- HP (Hire Purchase) -------- */
  function calculateHP(i) {
    const { cashPrice = 0, deposit = 0, partExchange = 0, dealerContribution = 0,
      apr = 0, termMonths = 48, fees = {}, rateBasis = "effective" } = i;
    const totalDeposit = deposit + partExchange + dealerContribution;
    const amountFinanced = Math.max(0, cashPrice - totalDeposit);
    const r = monthlyRate(apr, rateBasis);
    const financed = amountFinanced + (fees.acceptance || 0) + (fees.documentation || 0);
    const optionFee = fees.optionToPurchase || 0;
    const monthly = paymentWithBalloon(financed, 0, r, termMonths);
    const totalInstalments = monthly * termMonths;
    const totalPayable = totalDeposit + totalInstalments + optionFee;
    return {
      type: "HP", amountFinanced, totalDeposit, monthly, balloon: 0,
      finalPayment: optionFee, totalInstalments, totalPayable,
      interest: Math.max(0, totalInstalments - financed),
      costOfCredit: Math.max(0, totalPayable - cashPrice),
      ownsAtEnd: true,
    };
  }

  /* -------- Personal Loan -------- */
  function calculateLoan(i) {
    const { cashPrice = 0, deposit = 0, partExchange = 0, apr = 0, termMonths = 48, rateBasis = "effective" } = i;
    const principal = Math.max(0, cashPrice - deposit - partExchange);
    const r = monthlyRate(apr, rateBasis);
    const monthly = paymentWithBalloon(principal, 0, r, termMonths);
    const totalInstalments = monthly * termMonths;
    return {
      type: "Loan", principal, monthly, balloon: 0,
      totalInstalments, totalPayable: (deposit + partExchange) + totalInstalments,
      interest: Math.max(0, totalInstalments - principal),
      costOfCredit: Math.max(0, totalInstalments - principal),
      ownsAtEnd: true,
    };
  }

  /* -------- Cash purchase -------- */
  function calculateCash(i) {
    const { cashPrice = 0 } = i;
    return { type: "Cash", monthly: 0, totalPayable: cashPrice, interest: 0, costOfCredit: 0, ownsAtEnd: true };
  }

  /* -------- Settlement figure at a given month -------- */
  function calculateSettlement(i, atMonth) {
    const pcp = calculatePCP(i);
    const r = pcp.monthlyRate;
    const k = Math.min(Math.max(0, atMonth), i.termMonths || 48);
    const bal = balanceAfter(pcp.financedPlusFees, pcp.monthly, r, k);
    // settlement = outstanding balance (includes deferred balloon principal)
    const settlement = Math.max(pcp.balloon, bal);
    const paidToDate = pcp.totalDeposit + pcp.monthly * k;
    const remainingPayments = pcp.monthly * ((i.termMonths || 48) - k) + pcp.finalPayment;
    const interestSaved = Math.max(0, remainingPayments - settlement);
    return { atMonth: k, settlement, paidToDate, remainingPayments, interestSaved, monthly: pcp.monthly };
  }

  // settlement curve over full term
  function settlementCurve(i) {
    const n = i.termMonths || 48;
    const out = [];
    for (let m = 0; m <= n; m++) out.push({ month: m, value: calculateSettlement(i, m).settlement });
    return out;
  }

  /* -------- Equity -------- */
  function calculateEquity({ marketValue = 0, settlement = 0 }) {
    const equity = marketValue - settlement;
    return {
      equity, marketValue, settlement,
      status: equity > 50 ? "positive" : equity < -50 ? "negative" : "level",
      ltv: marketValue ? (settlement / marketValue) * 100 : 0,
    };
  }

  /* -------- Voluntary Termination (CCA 1974: 50% of total payable) -------- */
  function calculateVoluntaryTermination(i, monthsElapsed) {
    const pcp = calculatePCP(i);
    const threshold = pcp.totalPayable * 0.5;
    const paid = pcp.totalDeposit + pcp.monthly * monthsElapsed;
    const remaining = Math.max(0, threshold - paid);
    const monthsUntil = pcp.monthly > 0 ? Math.ceil(remaining / pcp.monthly) : 0;
    return {
      threshold, paid, remaining,
      eligible: paid >= threshold,
      pctOfThreshold: threshold ? Math.min(100, (paid / threshold) * 100) : 0,
      monthsUntil, totalPayable: pcp.totalPayable, monthsElapsed, monthly: pcp.monthly,
    };
  }

  /* -------- Excess mileage -------- */
  function calculateMileageCharges({ annualMileage = 10000, termMonths = 48, actualAnnual = 10000, pencePerMile = 8 }) {
    const allowance = annualMileage * (termMonths / 12);
    const projected = actualAnnual * (termMonths / 12);
    const excess = Math.max(0, projected - allowance);
    return { allowance, projected, excess, charge: (excess * pencePerMile) / 100, pencePerMile };
  }

  /* -------- Balloon optimiser: monthly for a range of GMFV % -------- */
  function balloonSweep(i, pctList) {
    return pctList.map((pct) => {
      const balloon = (i.cashPrice || 0) * (pct / 100);
      const res = calculatePCP({ ...i, balloon });
      return { pct, balloon, monthly: res.monthly, interest: res.interest, totalPayable: res.totalPayable };
    });
  }

  /* -------- Total ownership cost -------- */
  function calculateOwnership(i) {
    // monthly inputs unless annual flagged
    const {
      financeMonthly = 0,
      insuranceAnnual = 0, roadTaxAnnual = 0, servicingAnnual = 0,
      maintenanceAnnual = 0, tyresAnnual = 0,
      fuelType = "petrol", annualMileage = 10000,
      mpg = 45, pencePerLitre = 148,
      milesPerKwh = 3.8, pencePerKwh = 28,
      depreciationAnnual = 0,
    } = i;

    let energyAnnual;
    if (fuelType === "ev") {
      energyAnnual = (annualMileage / milesPerKwh) * (pencePerKwh / 100);
    } else {
      const litres = (annualMileage / mpg) * 4.54609; // gallons->litres
      energyAnnual = litres * (pencePerLitre / 100);
    }
    const runningAnnual = insuranceAnnual + roadTaxAnnual + servicingAnnual + maintenanceAnnual + tyresAnnual;
    const financeAnnual = financeMonthly * 12;
    const totalAnnual = runningAnnual + financeAnnual + energyAnnual + depreciationAnnual;
    const parts = [
      { k: "Finance", v: financeAnnual },
      { k: "Depreciation", v: depreciationAnnual },
      { k: fuelType === "ev" ? "Electricity" : "Fuel", v: energyAnnual },
      { k: "Insurance", v: insuranceAnnual },
      { k: "Servicing", v: servicingAnnual },
      { k: "Maintenance", v: maintenanceAnnual },
      { k: "Tyres", v: tyresAnnual },
      { k: "Road tax", v: roadTaxAnnual },
    ].filter(p => p.v > 0);
    return {
      energyAnnual, runningAnnual, financeAnnual, totalAnnual,
      totalMonthly: totalAnnual / 12, fiveYear: totalAnnual * 5, parts,
    };
  }

  /* -------- Dealer quote analysis (traffic-light rating) -------- */
  function calculateDealerQuoteAnalysis(quote, fairAprBand = { good: 7.9, amber: 12.9 }) {
    const pcp = calculatePCP(quote);
    let rating = "red", label = "Expensive deal";
    if (quote.apr <= fairAprBand.good) { rating = "green"; label = "Strong deal"; }
    else if (quote.apr <= fairAprBand.amber) { rating = "amber"; label = "Average deal"; }
    // markup vs a "good" APR scenario
    const benchmark = calculatePCP({ ...quote, apr: fairAprBand.good });
    const overpay = Math.max(0, pcp.costOfCredit - benchmark.costOfCredit);
    return { ...pcp, rating, label, overpayVsBenchmark: overpay, benchmarkMonthly: benchmark.monthly };
  }

  window.PCPEngine = {
    monthlyRate, calculatePCP, calculateHP, calculateLoan, calculateCash,
    calculateSettlement, settlementCurve, calculateEquity,
    calculateVoluntaryTermination, calculateMileageCharges,
    balloonSweep, calculateOwnership, calculateDealerQuoteAnalysis,
  };
})();
