use std::convert::{TryFrom, TryInto};

use full_moon::{
    ast::{self, Ast},
    node::Node,
    tokenizer::{self, Position, TokenReference},
};

mod extract_static_token;
mod loop_tracker;
pub mod name_paths;
mod purge_trivia;
pub mod scopes;
mod side_effects;
mod strip_parentheses;
pub mod visit_nodes;

pub use extract_static_token::extract_static_token;
pub use purge_trivia::purge_trivia;
pub use side_effects::HasSideEffects;
pub use strip_parentheses::strip_parentheses;

pub fn is_type_function(name: &str, roblox: bool) -> bool {
    name == "type" || (name == "typeof" && roblox)
}

pub fn range<N: Node, P: TryFrom<usize>>(node: N) -> (P, P)
where
    <P as TryFrom<usize>>::Error: std::fmt::Debug,
{
    let (start, end) = node.range().unwrap();
    (
        start
            .bytes()
            .try_into()
            .expect("range start_position couldn't convert"),
        end.bytes()
            .try_into()
            .expect("range end_position couldn't convert"),
    )
}

pub fn first_code(ast: &Ast) -> Option<(Position, Position)> {
    match ast.nodes().stmts().next() {
        Some(first_stmt) => first_stmt.range(),
        None => ast.nodes().last_stmt().and_then(Node::range),
    }
}

pub fn is_vararg(expression: &ast::Expression) -> bool {
    if_chain::if_chain! {
        if let ast::Expression::Symbol(token) = expression;
        if let tokenizer::TokenType::Symbol {
            symbol: tokenizer::Symbol::Ellipsis,
        } = token.token().token_type();

        then {
            true
        } else {
            false
        }
    }
}

pub fn expression_to_ident(expression: &ast::Expression) -> Option<&TokenReference> {
    if let ast::Expression::Var(ast::Var::Name(name)) = expression {
        return Some(name);
    }

    None
}

#[cfg(test)]
mod test {
    use super::*;
    use full_moon::parse;

    #[test]
    fn test_first_code() {
        let code = r"-- hello world
        -- line two
        lineThree()
        -- line four";

        let first_code = first_code(&parse(code).unwrap()).expect("first_code returned None");

        assert_eq!(first_code.0.line(), 3);
    }
}
