//! Utilities for manually traversing a Python AST.
use crate::{self as ast, ExceptHandler, Stmt, Suite};

/// Given a [`Stmt`] and its parent, return the [`Suite`] that contains the [`Stmt`].
pub fn suite<'a>(stmt: &'a Stmt, parent: &'a Stmt) -> Option<&'a Suite> {
    // TODO: refactor this to work without a parent, ie when `stmt` is at the top level
    match parent {
        Stmt::FunctionDef(ast::StmtFunctionDef { body, .. }) => Some(body),
        Stmt::ClassDef(ast::StmtClassDef { body, .. }) => Some(body),
        Stmt::For(ast::StmtFor { body, orelse, .. }) => {
            if body.contains(stmt) {
                Some(body)
            } else if orelse.contains(stmt) {
                Some(orelse)
            } else {
                None
            }
        }
        Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
            if body.contains(stmt) {
                Some(body)
            } else if orelse.contains(stmt) {
                Some(orelse)
            } else {
                None
            }
        }
        Stmt::If(ast::StmtIf {
            body,
            elif_else_clauses,
            ..
        }) => {
            if body.contains(stmt) {
                Some(body)
            } else {
                elif_else_clauses
                    .iter()
                    .map(|elif_else_clause| &elif_else_clause.body)
                    .find(|body| body.contains(stmt))
            }
        }
        Stmt::With(ast::StmtWith { body, .. }) => Some(body),
        Stmt::Match(ast::StmtMatch { cases, .. }) => cases
            .iter()
            .map(|case| &case.body)
            .find(|body| body.contains(stmt)),
        Stmt::Try(ast::StmtTry {
            body,
            handlers,
            orelse,
            finalbody,
            ..
        }) => {
            if body.contains(stmt) {
                Some(body)
            } else if orelse.contains(stmt) {
                Some(orelse)
            } else if finalbody.contains(stmt) {
                Some(finalbody)
            } else {
                handlers
                    .iter()
                    .filter_map(ExceptHandler::as_except_handler)
                    .map(|handler| &handler.body)
                    .find(|body| body.contains(stmt))
            }
        }
        _ => None,
    }
}

/// Given a [`Stmt`] and its containing [`Suite`], return the next [`Stmt`] in the [`Suite`].
pub fn next_sibling<'a>(stmt: &'a Stmt, suite: &'a Suite) -> Option<&'a Stmt> {
    let mut iter = suite.iter();
    while let Some(sibling) = iter.next() {
        if sibling == stmt {
            return iter.next();
        }
    }
    None
}

/// Given a [`Stmt`] and its containing [`Suite`], return the previous [`Stmt`] in the [`Suite`].
pub fn prev_sibling<'a>(stmt: &'a Stmt, suite: &'a Suite) -> Option<&'a Stmt> {
    let mut prev = None;
    for sibling in suite {
        if sibling == stmt {
            return prev;
        }
        prev = Some(sibling);
    }
    None
}
