use nu_protocol::{Span, Type, ast::PipelineElement};

use crate::{
    Fix, Replacement,
    config::LintLevel,
    context::LintContext,
    format_conversions::{ConversionSpec, check_all_pipelines},
    rule::{DetectFix, Rule},
    violation::Detection,
};

struct StructuredDataToCsvTool;

impl DetectFix for StructuredDataToCsvTool {
    type FixInput<'a> = Span;

    fn id(&self) -> &'static str {
        "structured_data_to_csv_tool"
    }

    fn short_description(&self) -> &'static str {
        "Table piped to CSV tool without `to csv`"
    }

    fn source_link(&self) -> Option<&'static str> {
        Some("https://www.nushell.sh/book/pipelines.html#external-commands")
    }

    fn level(&self) -> LintLevel {
        LintLevel::Warning
    }

    fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
        let spec = ConversionSpec {
            matches_command: &|cmd| matches!(cmd, "csvlook" | "csvstat" | "csvcut" | "csvgrep"),
            matches_type: &|ty| matches!(ty, Type::Table(_)),
        };

        check_all_pipelines(context, &spec, create_violation)
    }

    fn fix(&self, context: &LintContext, data_span: &Self::FixInput<'_>) -> Option<Fix> {
        let original_text = context.span_text(*data_span);
        let new_text = format!("{original_text} | to csv");

        Some(Fix {
            explanation: "Add 'to csv' to convert to CSV before piping to CSV tool".into(),
            replacements: vec![Replacement::new(*data_span, new_text)],
        })
    }
}

fn create_violation(
    ty: &Type,
    cmd_name: &str,
    left: &PipelineElement,
    right: &PipelineElement,
) -> (Detection, Span) {
    let type_name = ty.to_string();
    let message =
        format!("Piping {type_name} into CSV tool '{cmd_name}' requires 'to csv' conversion");

    let detection = Detection::from_global_span(message, right.expr.span)
        .with_extra_label(format!("{type_name} output"), left.expr.span)
        .with_extra_label("CSV tool", right.expr.span);

    (detection, left.expr.span)
}

pub static RULE: &dyn Rule = &StructuredDataToCsvTool;

#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod generated_fix;
#[cfg(test)]
mod ignore_good;
