/**
 * Basic Financial Calculations
 * Core mathematical functions for financial analysis
 */

/**
 * Calculate percentage change between two values
 */
export function percentageChange(oldValue: number, newValue: number): number {
  if (oldValue === 0) {
    return 0;
  }
  return ((newValue - oldValue) / oldValue) * 100;
}

/**
 * Calculate simple return
 */
export function simpleReturn(initialPrice: number, finalPrice: number): number {
  if (initialPrice === 0) {
    return 0;
  }
  return (finalPrice - initialPrice) / initialPrice;
}

/**
 * Calculate logarithmic return
 */
export function logReturn(initialPrice: number, finalPrice: number): number {
  if (initialPrice <= 0 || finalPrice <= 0) {
    return 0;
  }
  return Math.log(finalPrice / initialPrice);
}

/**
 * Calculate compound annual growth rate (CAGR)
 */
export function cagr(startValue: number, endValue: number, years: number): number {
  if (years <= 0 || startValue <= 0 || endValue <= 0) {
    return 0;
  }
  return Math.pow(endValue / startValue, 1 / years) - 1;
}

/**
 * Calculate annualized return from periodic returns
 */
export function annualizeReturn(periodicReturn: number, periodsPerYear: number): number {
  return Math.pow(1 + periodicReturn, periodsPerYear) - 1;
}

/**
 * Calculate annualized volatility from periodic returns
 */
export function annualizeVolatility(periodicVolatility: number, periodsPerYear: number): number {
  return periodicVolatility * Math.sqrt(periodsPerYear);
}

/**
 * Calculate present value
 */
export function presentValue(futureValue: number, rate: number, periods: number): number {
  return futureValue / Math.pow(1 + rate, periods);
}

/**
 * Calculate future value
 */
export function futureValue(presentValue: number, rate: number, periods: number): number {
  return presentValue * Math.pow(1 + rate, periods);
}

/**
 * Calculate net present value of cash flows
 */
export function netPresentValue(cashFlows: number[], discountRate: number): number {
  return cashFlows.reduce((npv, cashFlow, index) => {
    return npv + cashFlow / Math.pow(1 + discountRate, index);
  }, 0);
}

/**
 * Calculate internal rate of return (IRR) using Newton-Raphson method
 */
export function internalRateOfReturn(
  cashFlows: number[],
  guess: number = 0.1,
  maxIterations: number = 100
): number {
  let rate = guess;

  for (let i = 0; i < maxIterations; i++) {
    let npv = 0;
    let dnpv = 0;

    for (let j = 0; j < cashFlows.length; j++) {
      const cashFlow = cashFlows[j];
      if (cashFlow !== undefined) {
        npv += cashFlow / Math.pow(1 + rate, j);
        dnpv += (-j * cashFlow) / Math.pow(1 + rate, j + 1);
      }
    }

    if (Math.abs(npv) < 1e-10) {
      break;
    }
    if (Math.abs(dnpv) < 1e-10) {
      break;
    }

    rate = rate - npv / dnpv;
  }

  return rate;
}

/**
 * Calculate payback period
 */
export function paybackPeriod(initialInvestment: number, cashFlows: number[]): number {
  let cumulativeCashFlow = 0;

  for (let i = 0; i < cashFlows.length; i++) {
    const cashFlow = cashFlows[i];
    if (cashFlow !== undefined) {
      cumulativeCashFlow += cashFlow;
      if (cumulativeCashFlow >= initialInvestment) {
        return i + 1 - (cumulativeCashFlow - initialInvestment) / cashFlow;
      }
    }
  }

  return -1; // Never pays back
}

/**
 * Calculate compound interest
 */
export function compoundInterest(
  principal: number,
  rate: number,
  periods: number,
  compoundingFrequency: number = 1
): number {
  return principal * Math.pow(1 + rate / compoundingFrequency, compoundingFrequency * periods);
}

/**
 * Calculate effective annual rate
 */
export function effectiveAnnualRate(nominalRate: number, compoundingFrequency: number): number {
  return Math.pow(1 + nominalRate / compoundingFrequency, compoundingFrequency) - 1;
}

/**
 * Calculate bond price given yield
 */
export function bondPrice(
  faceValue: number,
  couponRate: number,
  yieldToMaturity: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2
): number {
  const couponPayment = (faceValue * couponRate) / paymentsPerYear;
  const discountRate = yieldToMaturity / paymentsPerYear;

  let price = 0;

  // Present value of coupon payments
  for (let i = 1; i <= periodsToMaturity; i++) {
    price += couponPayment / Math.pow(1 + discountRate, i);
  }

  // Present value of face value
  price += faceValue / Math.pow(1 + discountRate, periodsToMaturity);

  return price;
}

/**
 * Calculate bond yield given price (Newton-Raphson approximation)
 */
export function bondYield(
  price: number,
  faceValue: number,
  couponRate: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2,
  guess: number = 0.05
): number {
  let yield_ = guess;
  const maxIterations = 100;
  const tolerance = 1e-8;

  for (let i = 0; i < maxIterations; i++) {
    const calculatedPrice = bondPrice(
      faceValue,
      couponRate,
      yield_,
      periodsToMaturity,
      paymentsPerYear
    );
    const diff = calculatedPrice - price;

    if (Math.abs(diff) < tolerance) {
      break;
    }

    // Numerical derivative
    const delta = 0.0001;
    const priceUp = bondPrice(
      faceValue,
      couponRate,
      yield_ + delta,
      periodsToMaturity,
      paymentsPerYear
    );
    const derivative = (priceUp - calculatedPrice) / delta;

    if (Math.abs(derivative) < tolerance) {
      break;
    }

    yield_ = yield_ - diff / derivative;
  }

  return yield_;
}

/**
 * Calculate duration (Macaulay duration)
 */
export function macaulayDuration(
  faceValue: number,
  couponRate: number,
  yieldToMaturity: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2
): number {
  const couponPayment = (faceValue * couponRate) / paymentsPerYear;
  const discountRate = yieldToMaturity / paymentsPerYear;
  const bondPriceValue = bondPrice(
    faceValue,
    couponRate,
    yieldToMaturity,
    periodsToMaturity,
    paymentsPerYear
  );

  let weightedTime = 0;

  // Weighted time of coupon payments
  for (let i = 1; i <= periodsToMaturity; i++) {
    const presentValue = couponPayment / Math.pow(1 + discountRate, i);
    weightedTime += (i * presentValue) / bondPriceValue;
  }

  // Weighted time of face value
  const faceValuePV = faceValue / Math.pow(1 + discountRate, periodsToMaturity);
  weightedTime += (periodsToMaturity * faceValuePV) / bondPriceValue;

  return weightedTime / paymentsPerYear; // Convert to years
}

/**
 * Calculate modified duration
 */
export function modifiedDuration(
  faceValue: number,
  couponRate: number,
  yieldToMaturity: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2
): number {
  const macDuration = macaulayDuration(
    faceValue,
    couponRate,
    yieldToMaturity,
    periodsToMaturity,
    paymentsPerYear
  );
  return macDuration / (1 + yieldToMaturity / paymentsPerYear);
}

/**
 * Calculate bond convexity
 */
export function bondConvexity(
  faceValue: number,
  couponRate: number,
  yieldToMaturity: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2
): number {
  const couponPayment = (faceValue * couponRate) / paymentsPerYear;
  const discountRate = yieldToMaturity / paymentsPerYear;

  let convexity = 0;
  const bondPriceValue = bondPrice(
    faceValue,
    couponRate,
    yieldToMaturity,
    periodsToMaturity,
    paymentsPerYear
  );

  for (let i = 1; i <= periodsToMaturity; i++) {
    const presentValue = couponPayment / Math.pow(1 + discountRate, i);
    convexity += (i * (i + 1) * presentValue) / Math.pow(1 + discountRate, 2);
  }

  const faceValuePV = faceValue / Math.pow(1 + discountRate, periodsToMaturity);
  convexity +=
    (periodsToMaturity * (periodsToMaturity + 1) * faceValuePV) / Math.pow(1 + discountRate, 2);

  return convexity / (bondPriceValue * paymentsPerYear * paymentsPerYear);
}

/**
 * Calculate dollar duration
 */
export function dollarDuration(
  faceValue: number,
  couponRate: number,
  yieldToMaturity: number,
  periodsToMaturity: number,
  paymentsPerYear: number = 2,
  basisPointChange: number = 0.01 // 1 basis point = 0.01%
): number {
  const modifiedDur = modifiedDuration(
    faceValue,
    couponRate,
    yieldToMaturity,
    periodsToMaturity,
    paymentsPerYear
  );
  const bondPriceValue = bondPrice(
    faceValue,
    couponRate,
    yieldToMaturity,
    periodsToMaturity,
    paymentsPerYear
  );
  return modifiedDur * bondPriceValue * basisPointChange;
}

/**
 * Calculate accrued interest
 */
export function accruedInterest(
  faceValue: number,
  couponRate: number,
  daysSinceLastCoupon: number,
  daysInCouponPeriod: number
): number {
  return faceValue * couponRate * (daysSinceLastCoupon / daysInCouponPeriod);
}

/**
 * Calculate clean price
 */
export function cleanPrice(dirtyPrice: number, accruedInterestValue: number): number {
  return dirtyPrice - accruedInterestValue;
}

/**
 * Calculate dirty price
 */
export function dirtyPrice(cleanPriceValue: number, accruedInterestValue: number): number {
  return cleanPriceValue + accruedInterestValue;
}

/**
 * Calculate dividend discount model (DDM)
 */
export function dividendDiscountModel(
  currentDividend: number,
  growthRate: number,
  discountRate: number
): number {
  if (discountRate <= growthRate) {
    return NaN;
  } // Indeterminate
  return (currentDividend * (1 + growthRate)) / (discountRate - growthRate);
}

/**
 * Calculate weighted average cost of capital (WACC)
 */
export function weightedAverageCostOfCapital(
  costOfEquity: number,
  costOfDebt: number,
  equityWeight: number,
  debtWeight: number,
  taxRate: number
): number {
  return equityWeight * costOfEquity + debtWeight * costOfDebt * (1 - taxRate);
}

/**
 * Calculate capital asset pricing model (CAPM)
 */
export function capitalAssetPricingModel(
  riskFreeRate: number,
  beta: number,
  marketRiskPremium: number
): number {
  return riskFreeRate + beta * marketRiskPremium;
}

/**
 * Calculate hurdle rate
 */
export function hurdleRate(costOfCapital: number, riskPremium: number): number {
  return costOfCapital + riskPremium;
}

/**
 * Calculate degree of operating leverage (DOL)
 */
export function degreeOfOperatingLeverage(
  contributionMargin: number,
  operatingIncome: number
): number {
  return contributionMargin / operatingIncome;
}

/**
 * Calculate degree of financial leverage (DFL)
 */
export function degreeOfFinancialLeverage(ebit: number, earningsBeforeTax: number): number {
  return ebit / earningsBeforeTax;
}

/**
 * Calculate degree of total leverage (DTL)
 */
export function degreeOfTotalLeverage(dol: number, dfl: number): number {
  return dol * dfl;
}

/**
 * Calculate economic value added (EVA)
 */
export function economicValueAdded(
  netOperatingProfitAfterTax: number,
  capitalInvested: number,
  wacc: number
): number {
  return netOperatingProfitAfterTax - capitalInvested * wacc;
}
