//! Floats milestone post-fieldtest B1: pre-desugar walker that hard-
//! rejects `Pattern::Lit { lit: Literal::Float { .. } }`.
//!
//! Why a separate pre-desugar pass:
//! [`ailang_core::desugar::build_eq`] rewrites `Pattern::Lit` arms
//! (Int / Bool / Str / **Float**) into `Term::App { callee: ==, .. }`
//! before typecheck runs. As a consequence the in-typechecker
//! Float-pattern-reject arm in [`crate::type_check_pattern`] is
//! unreachable for any input that flows through the public check
//! entry points (`check`, `check_module`, `check_workspace`), all of
//! which call `desugar_module` first.
//!
//! This walker runs *before* desugar and short-circuits on the first
//! `Pattern::Lit { lit: Literal::Float }` it finds, returning the
//! bare [`CheckError::FloatPatternNotAllowed`]. Callers that build a
//! diagnostic (the multi-diagnose entry points) wrap the bare error
//! in [`CheckError::Def`] themselves so the diagnostic carries the
//! offending def's name. The in-typechecker arm at
//! [`crate::type_check_pattern`] remains as defence-in-depth for
//! direct `synth` callers (the `reject_float_pattern_in_match` unit
//! test in `crate::builtins` exercises that path).

use ailang_core::ast::*;

use crate::CheckError;

/// Walks every `Term::Match` arm in `m` and returns
/// `Err(CheckError::FloatPatternNotAllowed)` on the first
/// `Pattern::Lit { lit: Literal::Float }` encountered. Defs are
/// visited in declaration order; the walk bails on the first hit.
///
/// The returned error is bare (not wrapped in [`CheckError::Def`]).
/// The single-error entry point [`crate::check`] propagates it as-is
/// so callers asserting `matches!(err, CheckError::FloatPatternNotAllowed)`
/// stay clean. Multi-diagnose entry points should call
/// [`reject_float_patterns_in_def`] per def to attach the def name.
pub(crate) fn reject_float_patterns_in_module(m: &Module) -> Result<(), CheckError> {
    for def in &m.defs {
        reject_float_patterns_in_def(def)?;
    }
    Ok(())
}

/// Per-def variant of [`reject_float_patterns_in_module`]. Returns
/// the bare [`CheckError::FloatPatternNotAllowed`] on first hit;
/// callers wrap with [`CheckError::Def`] themselves to keep the
/// def-name attribution on the diagnostic.
pub(crate) fn reject_float_patterns_in_def(def: &Def) -> Result<(), CheckError> {
    match def {
        Def::Fn(f) => walk_term(&f.body),
        Def::Const(c) => walk_term(&c.value),
        Def::Type(_) => Ok(()),
        Def::Class(c) => {
            for method in &c.methods {
                if let Some(default) = &method.default {
                    walk_term(default)?;
                }
            }
            Ok(())
        }
        Def::Instance(i) => {
            for method in &i.methods {
                walk_term(&method.body)?;
            }
            Ok(())
        }
    }
}

fn walk_term(t: &Term) -> Result<(), CheckError> {
    match t {
        Term::Lit { .. } | Term::Var { .. } => Ok(()),
        Term::App { callee, args, .. } => {
            walk_term(callee)?;
            for a in args {
                walk_term(a)?;
            }
            Ok(())
        }
        Term::Let { value, body, .. } => {
            walk_term(value)?;
            walk_term(body)
        }
        Term::LetRec { body, in_term, .. } => {
            walk_term(body)?;
            walk_term(in_term)
        }
        Term::If { cond, then, else_ } => {
            walk_term(cond)?;
            walk_term(then)?;
            walk_term(else_)
        }
        Term::Do { args, .. } => {
            for a in args {
                walk_term(a)?;
            }
            Ok(())
        }
        Term::Ctor { args, .. } => {
            for a in args {
                walk_term(a)?;
            }
            Ok(())
        }
        Term::Match { scrutinee, arms } => {
            walk_term(scrutinee)?;
            for arm in arms {
                walk_pattern(&arm.pat)?;
                walk_term(&arm.body)?;
            }
            Ok(())
        }
        Term::Lam { body, .. } => walk_term(body),
        Term::Seq { lhs, rhs } => {
            walk_term(lhs)?;
            walk_term(rhs)
        }
        Term::Clone { value } => walk_term(value),
        Term::ReuseAs { source, body } => {
            walk_term(source)?;
            walk_term(body)
        }
    }
}

fn walk_pattern(p: &Pattern) -> Result<(), CheckError> {
    match p {
        Pattern::Wild | Pattern::Var { .. } => Ok(()),
        Pattern::Lit {
            lit: Literal::Float { .. },
        } => Err(CheckError::FloatPatternNotAllowed),
        Pattern::Lit { .. } => Ok(()),
        Pattern::Ctor { fields, .. } => {
            for sub in fields {
                walk_pattern(sub)?;
            }
            Ok(())
        }
    }
}
