//! Shared calculation logic — zero I/O or GUI dependencies.
//!
//! Every calculator is a pure function: numbers in, numbers out.
//! Formatting helpers produce the same human-readable strings
//! used by both the GUI and the daemon.

// ══════════════════════════════════════════════════════════════════════
//  AI model pricing data
// ══════════════════════════════════════════════════════════════════════

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AiModel {
    Gpt4o,
    Gpt4,
    Gpt35Turbo,
    Claude35Sonnet,
    Claude3Opus,
    Claude3Haiku,
}

pub const ALL_MODELS: [AiModel; 6] = [
    AiModel::Gpt4o,
    AiModel::Gpt4,
    AiModel::Gpt35Turbo,
    AiModel::Claude35Sonnet,
    AiModel::Claude3Opus,
    AiModel::Claude3Haiku,
];

impl AiModel {
    pub fn label(&self) -> &'static str {
        match self {
            Self::Gpt4o          => "GPT-4o",
            Self::Gpt4           => "GPT-4",
            Self::Gpt35Turbo     => "GPT-3.5 Turbo",
            Self::Claude35Sonnet => "Claude 3.5 Sonnet",
            Self::Claude3Opus    => "Claude 3 Opus",
            Self::Claude3Haiku   => "Claude 3 Haiku",
        }
    }

    pub fn input_price(&self) -> f64 {
        match self {
            Self::Gpt4o          => 2.50,
            Self::Gpt4           => 30.00,
            Self::Gpt35Turbo     => 0.50,
            Self::Claude35Sonnet => 3.00,
            Self::Claude3Opus    => 15.00,
            Self::Claude3Haiku   => 0.25,
        }
    }

    pub fn output_price(&self) -> f64 {
        match self {
            Self::Gpt4o          => 10.00,
            Self::Gpt4           => 60.00,
            Self::Gpt35Turbo     => 1.50,
            Self::Claude35Sonnet => 15.00,
            Self::Claude3Opus    => 75.00,
            Self::Claude3Haiku   => 1.25,
        }
    }
}

pub fn next_model(m: AiModel) -> AiModel {
    let i = ALL_MODELS.iter().position(|&x| x == m).unwrap_or(0);
    ALL_MODELS[(i + 1) % ALL_MODELS.len()]
}

pub fn prev_model(m: AiModel) -> AiModel {
    let i = ALL_MODELS.iter().position(|&x| x == m).unwrap_or(0);
    ALL_MODELS[(i + ALL_MODELS.len() - 1) % ALL_MODELS.len()]
}

/// Parse a model name (case-insensitive) to an AiModel variant.
pub fn parse_model(name: &str) -> Option<AiModel> {
    let n = name.to_lowercase();
    ALL_MODELS.iter().find(|m| m.label().to_lowercase() == n).copied()
}

// ══════════════════════════════════════════════════════════════════════
//  Calculation engines
// ══════════════════════════════════════════════════════════════════════

/// Ohm's Law / Watts Law pair-matching solver.
/// Given any 2 non-zero values of (p, i, r, v), computes all 4.
/// Returns (power, current, resistance, voltage).
pub fn ohm_calculate(p: f64, i: f64, r: f64, v: f64) -> (f64, f64, f64, f64) {
    if i != 0.0 && r != 0.0 { return (i * i * r, i, r, i * r); }
    if i != 0.0 && v != 0.0 { return (i * v, i, v / i, v); }
    if i != 0.0 && p != 0.0 { let v = p / i; return (p, i, v / i, v); }
    if r != 0.0 && v != 0.0 { let i = v / r; return (i * v, i, r, v); }
    if v != 0.0 && p != 0.0 { let i = p / v; return (p, i, v * v / p, v); }
    if p != 0.0 && r != 0.0 {
        let i = (p / r).max(0.0).sqrt();
        let v = (p * r).max(0.0).sqrt();
        return (p, i, r, v);
    }
    (p, i, r, v)
}

/// Returns (profit, margin_pct, markup_pct).
pub fn margin_calculate(cost: f64, sell: f64) -> (f64, f64, f64) {
    let profit = sell - cost;
    let margin = if sell != 0.0 { (profit / sell) * 100.0 } else { 0.0 };
    let markup = if cost != 0.0 { (profit / cost) * 100.0 } else { 0.0 };
    (profit, margin, markup)
}

/// Returns (profit, roi_pct).
pub fn roi_calculate(investment: f64, revenue: f64) -> (f64, f64) {
    let profit = revenue - investment;
    let roi = if investment != 0.0 { (profit / investment) * 100.0 } else { 0.0 };
    (profit, roi)
}

/// Returns (input_cost_usd, output_cost_usd, total_cost_usd).
pub fn token_cost_calculate(
    model: AiModel, input_tokens: f64, output_tokens: f64,
) -> (f64, f64, f64) {
    let in_cost  = (input_tokens  / 1_000_000.0) * model.input_price();
    let out_cost = (output_tokens / 1_000_000.0) * model.output_price();
    (in_cost, out_cost, in_cost + out_cost)
}

/// Returns (daily_kwh, monthly_kwh, monthly_cost_usd, yearly_cost_usd).
pub fn electricity_calculate(
    watts: f64, hours: f64, days: f64, rate: f64,
) -> (f64, f64, f64, f64) {
    let daily_kwh   = (watts * hours) / 1000.0;
    let monthly_kwh = daily_kwh * days;
    let monthly_cost = monthly_kwh * rate;
    let yearly_cost  = monthly_cost * 12.0;
    (daily_kwh, monthly_kwh, monthly_cost, yearly_cost)
}

/// Returns (units_to_break_even, revenue_at_break_even).
pub fn break_even_calculate(fixed: f64, price: f64, variable: f64) -> (f64, f64) {
    let contribution = price - variable;
    let units = if contribution != 0.0 { fixed / contribution } else { 0.0 };
    let revenue = units * price;
    (units, revenue)
}

// ══════════════════════════════════════════════════════════════════════
//  Formatting utilities (5-decimal precision)
// ══════════════════════════════════════════════════════════════════════

pub fn fmt_eng(val: f64, unit: &str) -> String {
    if val.fract() == 0.0 && val.abs() < 1e12 {
        format!("{} {}", val as i64, unit)
    } else {
        format!("{} {}", trim_f(val, 5), unit)
    }
}

pub fn fmt_num(val: f64) -> String {
    if val.fract() == 0.0 && val.abs() < 1e12 {
        format!("{}", val as i64)
    } else {
        trim_f(val, 5)
    }
}

pub fn fmt_pct(val: f64) -> String {
    format!("{} %", trim_f(val, 5))
}

pub fn fmt_dollar(val: f64) -> String {
    format!("${:.2}", val)
}

fn trim_f(val: f64, precision: usize) -> String {
    let s = format!("{:.precision$}", val, precision = precision);
    let t = s.trim_end_matches('0').trim_end_matches('.');
    if t.is_empty() || t == "-0" { "0".into() } else { t.into() }
}