pub mod decl;
pub mod expr;
pub mod statement;
pub mod types;

use crate::{
    Rule,
    ast::*,
    ast_ensure, ast_expr,
    error::{AstError, AstResult, IntoErr, collect_recovered, collect_recovered_map},
    parser::{consume_rule, listen_rule},
};

impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Identifier {
    type Error = AstError<'a, Self>;

    fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
        ast_ensure!(pair, Rule::identifier => {
            Ok(Identifier(pair.as_str().to_string()))
        })
    }
}

impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Path {
    type Error = AstError<'a, Self>;

    fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
        match pair.as_rule() {
            Rule::static_path => Ok(Path(collect_recovered(pair.into_inner()).get()?)),
            _ => AstError::bug_unimplemented(pair),
        }
    }
}

impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Literal {
    type Error = AstError<'a, Self>;

    fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
        let rule = pair.as_rule();
        let mut inner = pair.clone().into_inner();

        Ok(match rule {
            Rule::primary => Self::try_from(inner.next().unwrap())?,
            Rule::literal => Self::try_from(inner.next().unwrap())?,
            Rule::integer => Literal::Int(pair.as_str().parse::<i64>().unwrap()),
            Rule::float => Literal::Float(pair.as_str().parse::<f64>().unwrap()),
            Rule::boolean => Literal::Bool(pair.as_str().parse::<bool>().unwrap()),
            Rule::string_lit => Literal::String(inner.as_str().to_string()),
            Rule::tuple => Literal::Tuple(collect_recovered(inner).get()?),
            _ => return AstError::bug_unimplemented(pair),
        })
    }
}

impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Pattern {
    type Error = AstError<'a, Self>;

    fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
        let rule = pair.as_rule();
        let mut inner = pair.clone().into_inner();

        match rule {
            Rule::tuple_pattern => ast_expr!(Pattern::Tuple(collect_recovered_map(inner, |v| {
                Self::try_from(v).map(Box::new)
            }))),

            Rule::named_tuple_pattern => ast_expr!(Pattern::NamedTuple(
                Path::try_from(inner.next().unwrap()),
                collect_recovered_map(inner, |v| Self::try_from(v).map(Box::new)),
            )),

            Rule::struct_pattern => ast_expr!(Pattern::Struct(
                Path::try_from(inner.next().unwrap()),
                collect_recovered_map(inner, |v| {
                    if v.as_rule() == Rule::etc_pattern {
                        return Ok(None);
                    }

                    let mut inner = dbg!(v).into_inner();
                    Some(ast_expr!((
                        Identifier::try_from(inner.next().unwrap()),
                        inner
                            .next()
                            .map(|v| Self::try_from(v).map(Box::new).get_map(Box::new))
                            .transpose()
                    )))
                    .transpose()
                }),
            )),

            Rule::literal => ast_expr!(Pattern::Literal(pair.try_into())),

            Rule::path_pattern => ast_expr!(Pattern::Path(
                Ok(listen_rule(&mut inner, Rule::mutable)) as AstResult<'_, bool>,
                inner.next().unwrap().try_into()
            )),

            Rule::etc_pattern => Ok(Pattern::Etc),

            _ => AstError::bug_unimplemented(pair),
        }
    }
}

impl<'a> TryFrom<&mut pest::iterators::Pairs<'a, Rule>> for Visibility {
    type Error = AstError<'a, Self>;

    fn try_from(pairs: &mut pest::iterators::Pairs<'a, Rule>) -> Result<Self, Self::Error> {
        Ok(consume_rule(pairs, Rule::visibility)
            .map(|pair| -> Result<Visibility, AstError<'a, Self>> {
                if let Some(path) = pair.into_inner().next() {
                    ast_expr!(Visibility::PublicTarget(Path::try_from(path)))
                } else {
                    Ok(Visibility::Public)
                }
            })
            .transpose()?
            .unwrap_or_else(|| Visibility::Private))
    }
}
