#![coverage(off)]

use std::{error::Error as StdError, fmt, rc::Rc};

use crate::vm::value::{ConsCell, Keyword, Value};

#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
    pub input: Value,
    pub error: ParseErrorKind,
}

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ParseErrorKind {
    #[error("Not an expression")]
    NotAnExpression,
    #[error("Expected {0} {1}")]
    Expected(ExpectedWhat, ExpectedWhere),
    #[error("Extraneous expression: {0}")]
    ExtraneousExpression(Value),
    #[error("Extraneous expression(s): {0}")]
    ExtraneousExpressions(Rc<ConsCell>),
}

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ExpectedWhat {
    #[error("an expression")]
    Expression,
    #[error("an argument list")]
    ArgumentList,
    #[error("a condition")]
    Condition,
    #[error("a condition list")]
    ConditionList,
    #[error("at least one argument")]
    AtLeastOneArgument,
    #[error("exactly one argument")]
    ExactlyOneArgument,
    #[error("an argument, &optional or &rest keywords")]
    ArgumentSpec,
    #[error("a proper list")]
    ProperList,
    #[error("an identifier")]
    Identifier,
    #[error("at least one binding pair")]
    AtLeastOneBinding,
}

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ExpectedWhere {
    #[error("after a condition")]
    AfterCondition,
    #[error("after the \"{0}\" keyword")]
    AfterKeyword(Keyword),
    #[error("after the argument list in a {0}")]
    AfterArgumentList(Keyword),
    #[error("in the argument list")]
    InArgumentList,
    #[error("in the call expression")]
    InCallExpression,
    #[error("in the condition list")]
    InConditionList,
    #[error("in the condition arm")]
    InConditionArm,
    #[error("in the function definition")]
    InFunctionDefinition,
    #[error("in assignment")]
    InAssignment,
}

pub(super) trait CollectErrors<E> {
    fn collect_errors(&self, errors: &mut Vec<E>) -> bool;
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.error, f)
    }
}

impl StdError for ParseError {
    fn description(&self) -> &str {
        "Parse error"
    }
}

impl ParseErrorKind {
    pub fn extraneous(value: &Value) -> Self {
        match value {
            Value::Nil => unreachable!(),
            Value::Cons(cons) => Self::ExtraneousExpressions(cons.clone()),
            _ => Self::ExtraneousExpression(value.clone()),
        }
    }

    pub fn collect_extraneous<I: IntoIterator<Item = Value>>(iter: I) -> Self
    where
        I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
    {
        Self::extraneous(&Value::list_or_nil(iter))
    }

    pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>(
        iter: I,
    ) -> Result<Self, E>
    where
        I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
    {
        Ok(Self::extraneous(&Value::try_list_or_nil(iter)?))
    }
}
