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

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

    fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result<Self, Self::Error> {
        ast_ensure!(pair, Rule::block => {
            ast_expr!(Block(collect_recovered(pair.into_inner())))
        })
    }
}

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

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

        ast_ensure!(pair, Rule::statement_branch => {
            ast_expr!(StatementBranch {
                condition: inner.next().unwrap().try_into(),
                body: inner.next().unwrap().try_into().map(Box::new),
            })
        })
    }
}

impl<'a> TryFrom<pest::iterators::Pair<'a, Rule>> for Statement {
    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::statement => Statement::try_from(inner.next().unwrap()),

            Rule::expr_stmt => {
                ast_expr!(Statement::Expression(inner.next().unwrap().try_into()))
            }

            Rule::block => ast_expr!(Statement::Block(pair.try_into())),

            Rule::var_decl_statement => ast_expr!(Statement::VarDecl(pair.try_into())),

            Rule::return_stmt => {
                ast_expr!(Statement::Return(
                    inner.next().map(Expression::try_from).transpose()
                ))
            }

            Rule::break_stmt => Ok(Statement::Break),

            Rule::continue_stmt => Ok(Statement::Continue),

            Rule::if_stmt => {
                ast_expr!(Statement::If {
                    initial: inner.next().unwrap().try_into(),
                    else_if: collect_recovered(inner.next().unwrap().into_inner()),
                    else_branch: inner
                        .next()
                        .map(Statement::try_from)
                        .transpose()
                        .map(|v| v.map(Box::new))
                        .get_map(|v| { Some(Box::new(v)) }),
                })
            }

            Rule::while_stmt => ast_expr!(Statement::While(inner.next().unwrap().try_into())),

            Rule::c_for_stmt => ast_expr!(Statement::CStyleFor {
                init: inner.next().unwrap().try_into().map(Box::new),
                condition: inner.next().unwrap().try_into(),
                update: inner.next().unwrap().try_into().map(Box::new),
                body: inner.next().unwrap().try_into().map(Box::new),
            }),

            Rule::for_stmt => ast_expr!(Statement::For {
                mutable: Ok(listen_rule(&mut inner, Rule::mutable)) as AstResult<'_, bool>,
                pattern: inner.next().unwrap().try_into(),
                iterator: inner.next().unwrap().try_into(),
                body: inner.next().unwrap().try_into().map(Box::new),
            }),

            Rule::assign_statement => ast_expr!(Statement::Assign {
                target: inner.next().unwrap().try_into(),
                compound: Ok(inner.next().unwrap().as_str().trim().to_string())
                    as AstResult<'_, String>,
                value: inner.next().unwrap().try_into(),
            }),
            Rule::match_stmt => ast_expr!(Statement::Match(
                inner.next().unwrap().try_into(),
                inner
                    .map(|match_itms| {
                        let mut match_inner = match_itms.into_inner();
                        Ok((
                            Pattern::try_from(match_inner.next().unwrap()).get()?,
                            Block::try_from(match_inner.next().unwrap()).get()?,
                        ))
                    })
                    .collect::<AstResult<'a, Vec<_>>>(),
            )),

            Rule::increment_statement => {
                ast_expr!(Statement::Increment(inner.next().unwrap().try_into()))
            }

            Rule::decrement_statement => {
                ast_expr!(Statement::Decrement(inner.next().unwrap().try_into()))
            }

            Rule::unexpected_statement => {
                return Err(AstError {
                    span: pair.as_span(),
                    error_code: ErrorCode::InvalidStatement,
                    error_message: "Invalid Statement".to_string(),
                    recovered: None,
                });
            }

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