#![allow(warnings, clippy::all, unexpected_cfgs)]
mod colored_print;
mod file_name_printer;
pub(crate) mod interactive_print;
mod json_print;

use crate::ast_grep::lang::Lang;
use ast_grep_config::{Fixer, RuleConfig};
use ast_grep_core::{Matcher, NodeMatch as SgNodeMatch, tree_sitter::StrDoc};

use anyhow::Result;
use clap::ValueEnum;

use std::borrow::Cow;
use std::path::Path;

pub use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::termcolor::ColorChoice;
use colored_print::PrintStyles;
pub use colored_print::{ColoredPrinter, Heading, ReportStyle};
pub use file_name_printer::FileNamePrinter;
pub use interactive_print::InteractivePrinter;
pub use json_print::{JSONPrinter, JsonStyle};

type NodeMatch<'a> = SgNodeMatch<'a, StrDoc<Lang>>;

/// A trait to process nodeMatches to diff/match output
/// it must be Send + 'static to be shared in worker thread
pub trait PrintProcessor<Output>: Send + Sync + 'static {
    fn print_rule(
        &self,
        matches: Vec<NodeMatch>,
        file: SimpleFile<Cow<str>, &str>,
        rule: &RuleConfig<Lang>,
    ) -> Result<Output>;
    fn print_matches(&self, matches: Vec<NodeMatch>, path: &Path) -> Result<Output>;
    fn print_diffs(&self, diffs: Vec<Diff>, path: &Path) -> Result<Output>;
    fn print_rule_diffs(
        &self,
        diffs: Vec<(Diff, &RuleConfig<Lang>)>,
        path: &Path,
    ) -> Result<Output>;
}

pub trait Printer {
    // processed item must be sent to printer thread
    type Processed: Send + 'static;
    type Processor: PrintProcessor<Self::Processed>;

    fn get_processor(&self) -> Self::Processor;
    /// Runs processed output from processor. This runs multiple times.
    fn process(&mut self, processed: Self::Processed) -> Result<()>;

    /// Run before all printing. One CLI will run this exactly once.
    #[inline]
    fn before_print(&mut self) -> Result<()> {
        Ok(())
    }
    /// Run after all printing. One CLI will run this exactly once.
    #[inline]
    fn after_print(&mut self) -> Result<()> {
        Ok(())
    }
}

#[derive(Clone)]
pub struct AdditionalFix {
    pub replacement: String,
    pub range: std::ops::Range<usize>,
    pub title: Option<String>,
}

#[derive(Clone)]
pub struct Diff<'n> {
    /// the matched node
    pub node_match: NodeMatch<'n>,
    /// string content for the replacement
    pub replacement: String,
    pub range: std::ops::Range<usize>,
    pub title: Option<String>,
    pub additional_fixes: Option<Box<[AdditionalFix]>>,
}

impl<'n> Diff<'n> {
    pub fn generate(node_match: NodeMatch<'n>, matcher: &impl Matcher, rewrite: &Fixer) -> Self {
        let edit = node_match.make_edit(matcher, rewrite);
        let replacement = String::from_utf8(edit.inserted_text).unwrap();
        Self {
            node_match,
            replacement,
            range: edit.position..edit.position + edit.deleted_length,
            additional_fixes: None,
            title: rewrite.title().map(|t| t.to_string()),
        }
    }

    pub fn multiple(
        node_match: NodeMatch<'n>,
        matcher: &impl Matcher,
        fixers: &[Fixer],
    ) -> Option<Self> {
        let fixer = fixers.first()?;
        let mut ret = Self::generate(node_match.clone(), matcher, fixer);
        // no additional fixes
        if fixers.len() == 1 {
            return Some(ret);
        }
        let additional = fixers
            .iter()
            .skip(1)
            .map(|f| {
                let edit = node_match.make_edit(matcher, f);
                AdditionalFix {
                    replacement: String::from_utf8(edit.inserted_text).unwrap(),
                    range: edit.position..edit.position + edit.deleted_length,
                    title: f.title().map(|t| t.to_string()),
                }
            })
            .collect::<Vec<_>>()
            .into_boxed_slice();
        ret.additional_fixes = Some(additional);
        Some(ret)
    }

    pub fn into_list(mut self) -> Vec<Self> {
        let node_match = self.node_match.clone();
        let additional_fixes = self.additional_fixes.take();
        let mut ret = vec![self];
        ret.extend(additional_fixes.into_iter().flatten().map(|f| Self {
            node_match: node_match.clone(),
            replacement: f.replacement,
            range: f.range,
            additional_fixes: None,
            title: f.title,
        }));
        ret
    }

    /// Returns the root doc source code
    /// N.B. this can be different from node.text() because
    /// tree-sitter's root Node may not start at the begining
    pub fn get_root_text(&self) -> &'n str {
        self.node_match.root().get_text()
    }
}

#[derive(ValueEnum, Clone, Copy)]
pub enum ColorArg {
    /// Try to use colors, but don't force the issue. If the output is piped to another program,
    /// or the console isn't available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined,
    /// for example, then don't use colors.
    Auto,
    /// Try very hard to emit colors. This includes emitting ANSI colors
    /// on Windows if the console API is unavailable (not implemented yet).
    Always,
    /// Ansi is like Always, except it never tries to use anything other
    /// than emitting ANSI color codes.
    Ansi,
    /// Never emit colors.
    Never,
}

impl ColorArg {
    pub fn should_use_color(self) -> bool {
        use colored_print::should_use_color;
        should_use_color(&self.into())
    }
}

impl From<ColorArg> for ColorChoice {
    fn from(arg: ColorArg) -> ColorChoice {
        use ColorArg::*;
        use std::io::IsTerminal;
        match arg {
            Auto => {
                if std::io::stdout().is_terminal() {
                    ColorChoice::Auto
                } else {
                    ColorChoice::Never
                }
            }
            Always => ColorChoice::Always,
            Ansi => ColorChoice::AlwaysAnsi,
            Never => ColorChoice::Never,
        }
    }
}
