// SPDX-FileCopyrightText: 2024 Aurélien Gâteau <mail@agateau.com>
//
// SPDX-License-Identifier: Apache-2.0

use anyhow;
use nu_ansi_term::Color::LightRed;
use tree_ds::prelude::TraversalStrategy;

use crate::parse::{RegexTree, Token};
use crate::print_tree;
use crate::tree_utils;

fn is_group(token: &Token) -> bool {
    matches!(token, Token::Group { .. })
}

struct LintError {
    message: String,
    start: usize,
    end: Option<usize>,
}

fn lint_extra_groups(tree: &RegexTree) -> Vec<LintError> {
    let mut errors: Vec<LintError> = Vec::<_>::new();

    let root_node_id = tree.get_root_node().unwrap().get_node_id();

    tree.traverse(&root_node_id, TraversalStrategy::PreOrder)
        .unwrap()
        .iter()
        .for_each(|node_id| {
            let node = tree.get_node_by_id(node_id).unwrap();
            let token = node.get_value().unwrap();
            if is_group(&token) {
                let children_ids = node.get_children_ids();
                if children_ids.len() == 1 {
                    let child_id = children_ids.first().unwrap();
                    let child_node = tree.get_node_by_id(child_id).unwrap();
                    let child_token = child_node.get_value().unwrap();
                    if is_group(&child_token) {
                        let (start, end) = tree_utils::get_token_span(tree, child_id);
                        errors.push(LintError {
                            message: "Extra group".into(),
                            start,
                            end: Some(end),
                        });
                    }
                }
            }
        });

    errors
}

pub fn lint(tree: &RegexTree) -> anyhow::Result<()> {
    let lint_fns = vec![lint_extra_groups];
    let mut errors: Vec<LintError> = Vec::<_>::new();
    for lint_fn in lint_fns {
        errors.extend(lint_fn(tree));
    }
    if errors.is_empty() {
        return Ok(());
    }

    for error in errors {
        print_tree::print(tree);

        let message = match error.end {
            Some(end) => {
                let inner_space = end - error.start - 2;
                format!("└{}┴─ {}", "─".repeat(inner_space), error.message)
            }
            None => format!("└ {}", error.message),
        };
        println!("{}{}\n", " ".repeat(error.start), LightRed.paint(message));
    }
    Err(anyhow::anyhow!("found some problems."))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse::parse_regex;

    #[test]
    fn find_extra_groups() {
        // GIVEN a tree with extra groups
        let tree = parse_regex("(?:(?:hello))").unwrap();

        // WHEN lint_extra_groups() is called
        let errors = lint_extra_groups(&tree);

        // THEN it reports the extra groups
        assert!(!errors.is_empty());
    }
}
