#![allow(warnings, clippy::all, unexpected_cfgs)]
// copied from termcolor
use super::{ColorChoice, DiffStyles};
use anyhow::Result;
use nu_ansi_term::{Color, Style};

use std::borrow::Cow;
use std::env;
use std::io::Write;
use std::path::Path;

// warn[rule-id]: rule message here.
// |------------|------------------|
//    header            message
#[derive(Default, Clone)]
pub struct RuleStyle {
    // header style
    pub error: Style,
    pub warning: Style,
    pub info: Style,
    pub hint: Style,
    // message style
    pub message: Style,
    pub note: Style,
}

impl RuleStyle {
    fn colored() -> Self {
        Self {
            error: Color::Red.bold(),
            warning: Color::Yellow.bold(),
            info: Style::new().bold(),
            hint: Style::new().dimmed().bold(),
            note: Style::new().italic(),
            message: Style::new().bold(),
        }
    }
}

// TODO: use termcolor instead
#[derive(Default, Clone)]
pub struct PrintStyles {
    // print match color
    pub file_path: Style,
    pub matched: Style,
    pub rule: RuleStyle,
    pub diff: DiffStyles,
}

impl PrintStyles {
    fn colored() -> Self {
        Self {
            file_path: Color::Cyan.italic(),
            matched: Color::Red.bold(),
            diff: DiffStyles::colored(),
            rule: RuleStyle::colored(),
        }
    }
    fn no_color() -> Self {
        Self::default()
    }

    pub fn push_matched_to_ret(&self, ret: &mut String, matched: &str) -> Result<()> {
        use std::fmt::Write;
        // TODO: use intersperse
        let mut lines = matched.lines();
        if let Some(line) = lines.next() {
            write!(ret, "{}", self.matched.paint(line))?;
        } else {
            return Ok(());
        }
        for line in lines {
            ret.push('\n');
            write!(ret, "{}", self.matched.paint(line))?;
        }
        Ok(())
    }

    pub fn print_prelude(&self, path: &Path, writer: &mut impl Write) -> Result<()> {
        let file_path = adjust_dir_separator(path);
        writeln!(writer, "{}", self.file_path.paint(file_path))?;
        Ok(())
    }

    pub fn print_highlight<W: Write>(
        &self,
        ret: &str,
        start_line: usize,
        writer: &mut W,
    ) -> Result<usize> {
        let max_line_num = ret.lines().count() + start_line;
        // compute width for line number. log10(num) = the digit count of num - 1
        let width = max_line_num.checked_ilog10().unwrap_or(0) as usize + 1;
        for (offset, line) in ret.lines().enumerate() {
            // note the width modifier must be applied before coloring the line_num
            let line_num = format!("{:<width$}", start_line + offset);
            // otherwise the color ascii code will be counted in the width
            let ln_text = self.diff.line_num.paint(line_num);
            writeln!(writer, "{ln_text}│{line}")?;
        }
        Ok(width)
    }
}

#[cfg(not(target_os = "windows"))]
fn adjust_dir_separator(p: &Path) -> Cow<'_, str> {
    p.to_string_lossy()
}

// change \ to / on windows
#[cfg(target_os = "windows")]
fn adjust_dir_separator(p: &Path) -> String {
    const VERBATIM_PREFIX: &str = r#"\\?\"#;
    let p = p.display().to_string();
    if p.starts_with(VERBATIM_PREFIX) {
        p[VERBATIM_PREFIX.len()..].to_string()
    } else {
        p
    }
}

impl From<ColorChoice> for PrintStyles {
    fn from(color: ColorChoice) -> Self {
        if should_use_color(&color) {
            Self::colored()
        } else {
            Self::no_color()
        }
    }
}

/// Returns true if we should attempt to write colored output.
pub fn should_use_color(color: &ColorChoice) -> bool {
    use std::io::IsTerminal;
    match *color {
        // TODO: we should check if ansi is supported on windows console
        ColorChoice::Always => true,
        ColorChoice::AlwaysAnsi => true,
        ColorChoice::Never => false,
        // NOTE tty check is added
        ColorChoice::Auto => std::io::stdout().is_terminal() && env_allows_color(),
    }
}

fn env_allows_color() -> bool {
    match env::var_os("TERM") {
        // On Windows, if TERM isn't set, then we should not automatically
        // assume that colors aren't allowed. This is unlike Unix environments
        None => {
            if !cfg!(windows) {
                return false;
            }
        }
        Some(k) => {
            if k == "dumb" {
                return false;
            }
        }
    }
    // If TERM != dumb, then the only way we don't allow colors at this
    // point is if NO_COLOR is set.
    if env::var_os("NO_COLOR").is_some() {
        return false;
    }
    true
}
