//! Money helpers.
//!
//! Prices are stored throughout the app as integer **minor units** (cents).
//! This module is the single place that converts between that storage form and
//! the human-facing decimal strings shown in forms and templates.

use loco_rs::prelude::*;

/// Parse a price typed in major units ("12", "12.5", "12.34") into integer
/// minor units (cents). Rejects negatives and more than two decimals.
pub fn parse_price_to_cents(value: &str) -> Result<i64> {
    let value = value.trim().replace(',', ".");
    let invalid = || Error::BadRequest("invalid price".to_string());
    let (whole, frac) = match value.split_once('.') {
        Some((w, f)) => (w, f),
        None => (value.as_str(), ""),
    };
    if frac.len() > 2 || !whole.chars().all(|c| c.is_ascii_digit()) || whole.is_empty() {
        return Err(invalid());
    }
    if !frac.chars().all(|c| c.is_ascii_digit()) {
        return Err(invalid());
    }
    let whole: i64 = whole.parse().map_err(|_| invalid())?;
    let cents: i64 = match frac.len() {
        0 => 0,
        1 => frac.parse::<i64>().map_err(|_| invalid())? * 10,
        _ => frac.parse().map_err(|_| invalid())?,
    };
    Ok(whole * 100 + cents)
}

/// Render minor units as a human price string, e.g. `1234` -> `"12.34"`.
pub fn format_price(cents: i64) -> String {
    format!("{}.{:02}", cents / 100, (cents % 100).abs())
}

/// Parse a percentage typed as "20", "20.5" or "20,5" into an `f64`. Returns
/// `None` for anything non-numeric or non-finite.
#[must_use]
pub fn parse_percent(value: &str) -> Option<f64> {
    let parsed: f64 = value.trim().replace(',', ".").parse().ok()?;
    parsed.is_finite().then_some(parsed)
}

/// Convert a percentage to basis points (5% -> 500), rounded to the nearest bp.
#[must_use]
pub fn percent_to_bp(percent: f64) -> i32 {
    (percent * 100.0).round() as i32
}

/// Render basis points as a human percentage string, e.g. `550` -> `"5.5"`,
/// `500` -> `"5"`.
#[must_use]
pub fn format_bp(bp: i32) -> String {
    let whole = bp / 100;
    let frac = (bp % 100).abs();
    if frac == 0 {
        whole.to_string()
    } else if frac % 10 == 0 {
        format!("{whole}.{}", frac / 10)
    } else {
        format!("{whole}.{frac:02}")
    }
}

/// Apply a basis-point discount to a price in minor units, rounding the discount
/// amount to the nearest cent. Never returns more than `cents`.
#[must_use]
pub fn apply_discount_bp(cents: i64, percent_bp: i32) -> i64 {
    let bp = percent_bp.max(0) as i128;
    let discount = (cents as i128 * bp + 5000) / 10000; // round half up
    (cents - discount as i64).max(0)
}
