use crate::{
    compile::{
        function::FunctionSignature,
        syntax::{
            CollectErrors, FunctionBody,
            error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
            maybe_docstring,
        },
    },
    vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value},
};

#[derive(Debug, PartialEq)]
pub struct DefmacroExpression {
    pub name: IdentifierValue,
    pub docstring: Option<StringValue>,
    pub signature: FunctionSignature,
    pub body: FunctionBody,
}

impl DefmacroExpression {
    pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
        let Value::Cons(value) = value else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::InFunctionDefinition,
                ),
            });
        };
        let ConsCell(identifier, cdr) = value.as_ref();

        let Value::Identifier(identifier) = identifier else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::Identifier,
                    ExpectedWhere::AfterKeyword(Keyword::Defun),
                ),
            });
        };

        let Value::Cons(cdr) = cdr else {
            return Err(ParseError {
                input: input.clone(),
                error: ParseErrorKind::Expected(
                    ExpectedWhat::ProperList,
                    ExpectedWhere::InFunctionDefinition,
                ),
            });
        };
        let ConsCell(car, cdr) = cdr.as_ref();

        let signature = FunctionSignature::parse(car, input)?;
        let (cdr, docstring) = maybe_docstring(cdr);
        let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
        Ok(Self {
            name: identifier.clone(),
            docstring,
            signature,
            body,
        })
    }
}

impl CollectErrors<ParseError> for DefmacroExpression {
    fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
        self.body.collect_errors(errors)
    }
}
