use ruin_app::prelude::*;

type EvalResult = std::result::Result<f64, String>;

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CalcMode {
    #[default]
    Basic,
    Scientific,
}

#[derive(Clone, Debug)]
pub struct HistoryEntry {
    pub expression: String,
    pub result: String,
}

// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------

#[derive(Clone)]
pub struct CalcState {
    /// The expression currently shown in the input display.
    pub expression: Signal<String>,
    /// The result of the last evaluation (or error message). Empty until first `=`.
    pub result: Signal<String>,
    /// Whether the last evaluation produced an error.
    pub is_error: Signal<bool>,
    /// Past evaluations, newest first.
    pub history: Signal<Vec<HistoryEntry>>,
    pub mode: Signal<CalcMode>,
    /// When true, the next digit/operator input clears the expression first.
    pub fresh: Signal<bool>,
    /// Current scroll offset for the history scroll box.
    pub history_scroll: Signal<f32>,
}

impl CalcState {
    pub fn new() -> Self {
        Self {
            expression: use_signal(String::new),
            result: use_signal(String::new),
            is_error: use_signal(|| false),
            history: use_signal(Vec::new),
            mode: use_signal(|| CalcMode::Basic),
            fresh: use_signal(|| false),
            history_scroll: use_signal(|| 10_000.0_f32),
        }
    }

    /// Append `text` to the expression.  If `fresh` is set (just evaluated),
    /// clear the expression first so digits don't append to the old result.
    pub fn input(&self, text: &str) {
        if self.fresh.get() {
            // Starting fresh after a result: if it's an operator continue
            // from the result, otherwise start a new expression.
            let is_operator = matches!(text, "+" | "-" | "×" | "÷" | "^" | "%");
            if !is_operator {
                let _ = self.expression.set(String::new());
            }
            let _ = self.fresh.set(false);
        }
        self.expression.update(|e| e.push_str(text));
    }

    /// Delete the last character (or close-paren group) from the expression.
    pub fn backspace(&self) {
        if self.fresh.get() {
            let _ = self.fresh.set(false);
            let _ = self.expression.set(String::new());
            return;
        }
        self.expression.update(|e| {
            // Pop a full UTF-8 character
            let new_len = e.char_indices().next_back().map(|(i, _)| i).unwrap_or(0);
            e.truncate(new_len);
        });
    }

    /// Clear the expression, result, and fresh flag.
    pub fn clear(&self) {
        let _ = self.expression.set(String::new());
        let _ = self.result.set(String::new());
        let _ = self.is_error.set(false);
        let _ = self.fresh.set(false);
    }

    /// Evaluate the current expression, update result, push to history.
    pub fn evaluate(&self) {
        let expr = self.expression.with(|e| e.clone());
        if expr.is_empty() {
            return;
        }
        match evaluate(&expr) {
            Ok(value) => {
                let result_str = format_result(value);
                let _ = self.expression.set(result_str.clone());
                let _ = self.result.set(String::new());
                let _ = self.is_error.set(false);
                self.history.update(|h| {
                    h.insert(
                        0,
                        HistoryEntry {
                            expression: expr.clone(),
                            result: result_str,
                        },
                    );
                    h.truncate(50);
                });
            }
            Err(msg) => {
                let _ = self.result.set(msg);
                let _ = self.is_error.set(true);
            }
        }
        let _ = self.fresh.set(true);
        let _ = self.history_scroll.set(10_000.0);
    }

    /// Set the expression to a previously computed result (e.g., when clicking a history entry).
    pub fn use_history_result(&self, result: String) {
        let _ = self.expression.set(result);
        let _ = self.result.set(String::new());
        let _ = self.is_error.set(false);
        let _ = self.fresh.set(false);
    }

    pub fn toggle_mode(&self) {
        self.mode.update(|m| {
            *m = match *m {
                CalcMode::Basic => CalcMode::Scientific,
                CalcMode::Scientific => CalcMode::Basic,
            };
        });
    }

    /// Insert the right parenthesis character based on current balance.
    pub fn input_paren(&self) {
        let open = self
            .expression
            .with(|e| e.chars().filter(|&c| c == '(').count());
        let close = self
            .expression
            .with(|e| e.chars().filter(|&c| c == ')').count());
        self.input(if open > close { ")" } else { "(" });
    }
}

// ---------------------------------------------------------------------------
// Evaluation
// ---------------------------------------------------------------------------

/// Evaluate `raw_expr`, preprocessing display symbols to meval-compatible syntax.
pub fn evaluate(raw_expr: &str) -> EvalResult {
    let expr = raw_expr
        .replace('×', "*")
        .replace('÷', "/")
        .replace('π', "pi")
        // √( is inserted by the scientific button; meval knows sqrt
        .replace("√", "sqrt");

    let mut ctx = meval::Context::new();
    // Add factorial — only integers 0..=20 to avoid overflow
    ctx.func("fact", |x: f64| {
        if x < 0.0 || x.fract() != 0.0 || x > 20.0 {
            return f64::NAN;
        }
        (1..=(x as u64)).product::<u64>() as f64
    });

    meval::eval_str_with_context(&expr, ctx).map_err(|e| e.to_string())
}

/// Format an f64 result: use integer display when possible, otherwise up to 10 sig figs.
fn format_result(value: f64) -> String {
    if value.is_nan() {
        return "Error".to_string();
    }
    if value.is_infinite() {
        return if value > 0.0 {
            "∞".to_string()
        } else {
            "-∞".to_string()
        };
    }
    if value.fract() == 0.0 && value.abs() < 1e15 {
        return format!("{}", value as i64);
    }
    // Up to 10 significant figures, strip trailing zeros
    let s = format!("{:.10}", value);
    let s = s.trim_end_matches('0').trim_end_matches('.');
    s.to_string()
}
