use core::ops;
use arithmetic_parser::{grammars::Grammar, Destructure, Spanned, SpannedExpr, SpannedLvalue};
use crate::{
    alloc::Vec,
    ast::TypeAst,
    error::{Error, ErrorContext, ErrorKind, ErrorPathFragment},
    PrimitiveType,
};
#[derive(Debug)]
pub struct OpErrors<'a, Prim: PrimitiveType> {
    errors: Goat<'a, Vec<ErrorPrecursor<Prim>>>,
    current_path: Vec<ErrorPathFragment>,
}
impl<Prim: PrimitiveType> OpErrors<'_, Prim> {
    pub fn push(&mut self, kind: ErrorKind<Prim>) {
        self.errors.push(ErrorPrecursor {
            kind,
            path: self.current_path.clone(),
        });
    }
    pub fn check(&mut self, check: impl FnOnce(OpErrors<'_, Prim>)) -> bool {
        let error_count = self.errors.len();
        check(self.by_ref());
        self.errors.len() == error_count
    }
    pub fn by_ref(&mut self) -> OpErrors<'_, Prim> {
        OpErrors {
            errors: Goat::Borrowed(&mut *self.errors),
            current_path: self.current_path.clone(),
        }
    }
    pub fn join_path(&mut self, path: impl Into<ErrorPathFragment>) -> OpErrors<'_, Prim> {
        let mut current_path = self.current_path.clone();
        current_path.push(path.into());
        OpErrors {
            errors: Goat::Borrowed(&mut *self.errors),
            current_path,
        }
    }
    pub(crate) fn push_path_fragment(&mut self, path: impl Into<ErrorPathFragment>) {
        self.current_path.push(path.into());
    }
    pub(crate) fn pop_path_fragment(&mut self) {
        self.current_path.pop().expect("Location is empty");
    }
    #[cfg(test)]
    pub(crate) fn into_vec(self) -> Vec<ErrorKind<Prim>> {
        let Goat::Owned(errors) = self.errors else {
            panic!("Attempt to call `into_vec` for borrowed errors");
        };
        errors.into_iter().map(|err| err.kind).collect()
    }
}
impl<Prim: PrimitiveType> OpErrors<'static, Prim> {
    pub(crate) fn new() -> Self {
        Self {
            errors: Goat::Owned(Vec::new()),
            current_path: Vec::new(),
        }
    }
    pub(crate) fn contextualize<T: Grammar>(
        self,
        span: &SpannedExpr<'_, T>,
        context: impl Into<ErrorContext<Prim>>,
    ) -> Vec<Error<Prim>> {
        let context = context.into();
        self.do_contextualize(|item| item.into_expr_error(context.clone(), span))
    }
    fn do_contextualize(
        self,
        map_fn: impl Fn(ErrorPrecursor<Prim>) -> Error<Prim>,
    ) -> Vec<Error<Prim>> {
        let Goat::Owned(errors) = self.errors else {
            unreachable!()
        };
        errors.into_iter().map(map_fn).collect()
    }
    pub(crate) fn contextualize_assignment<'a>(
        self,
        span: &SpannedLvalue<'a, TypeAst<'a>>,
        context: &ErrorContext<Prim>,
    ) -> Vec<Error<Prim>> {
        if self.errors.is_empty() {
            Vec::new()
        } else {
            self.do_contextualize(|item| item.into_assignment_error(context.clone(), span))
        }
    }
    pub(crate) fn contextualize_destructure<'a>(
        self,
        span: &Spanned<'a, Destructure<'a, TypeAst<'a>>>,
        create_context: impl FnOnce() -> ErrorContext<Prim>,
    ) -> Vec<Error<Prim>> {
        if self.errors.is_empty() {
            Vec::new()
        } else {
            let context = create_context();
            self.do_contextualize(|item| item.into_destructure_error(context.clone(), span))
        }
    }
}
#[derive(Debug)]
enum Goat<'a, T> {
    Owned(T),
    Borrowed(&'a mut T),
}
impl<T> ops::Deref for Goat<'_, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        match self {
            Self::Owned(value) => value,
            Self::Borrowed(mut_ref) => mut_ref,
        }
    }
}
impl<T> ops::DerefMut for Goat<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            Self::Owned(value) => value,
            Self::Borrowed(mut_ref) => mut_ref,
        }
    }
}
#[derive(Debug)]
struct ErrorPrecursor<Prim: PrimitiveType> {
    kind: ErrorKind<Prim>,
    path: Vec<ErrorPathFragment>,
}
impl<Prim: PrimitiveType> ErrorPrecursor<Prim> {
    fn into_expr_error<T: Grammar>(
        self,
        context: ErrorContext<Prim>,
        root_expr: &SpannedExpr<'_, T>,
    ) -> Error<Prim> {
        Error {
            inner: ErrorPathFragment::walk_expr(&self.path, root_expr)
                .copy_with_extra(self.kind)
                .into(),
            root_location: root_expr.with_no_extra().into(),
            context,
            path: self.path,
        }
    }
    fn into_assignment_error<'a>(
        self,
        context: ErrorContext<Prim>,
        root_lvalue: &SpannedLvalue<'a, TypeAst<'a>>,
    ) -> Error<Prim> {
        Error {
            inner: ErrorPathFragment::walk_lvalue(&self.path, root_lvalue)
                .copy_with_extra(self.kind)
                .into(),
            root_location: root_lvalue.with_no_extra().into(),
            context,
            path: self.path,
        }
    }
    fn into_destructure_error<'a>(
        self,
        context: ErrorContext<Prim>,
        root_destructure: &Spanned<'a, Destructure<'a, TypeAst<'a>>>,
    ) -> Error<Prim> {
        Error {
            inner: ErrorPathFragment::walk_destructure(&self.path, root_destructure)
                .copy_with_extra(self.kind)
                .into(),
            root_location: root_destructure.with_no_extra().into(),
            context,
            path: self.path,
        }
    }
}