use serde::{Deserialize, Serialize};
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};

use crate::{
    pattern::NamePattern,
    rule::{visitor_rule, LintRule, LintRuleContext},
    ConfigError,
};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnitNoUnknownConfig {
    ignore_units: Option<Vec<String>>,
}

pub fn unit_no_unknown(
    ctx: LintRuleContext<UnitNoUnknownConfig>,
) -> Result<Box<dyn LintRule>, ConfigError> {
    let ignored_units = ctx
        .config()
        .ignore_units
        .clone()
        .unwrap_or_default()
        .into_iter()
        .map(NamePattern::try_from)
        .collect::<Result<_, _>>()?;
    Ok(visitor_rule(
        ctx.reaction(),
        UnitNoUnknown { ctx, ignored_units },
    ))
}

#[derive(Debug, Default)]
struct UnitNoUnknown {
    ctx: LintRuleContext<UnitNoUnknownConfig>,
    ignored_units: Vec<NamePattern>,
}

impl Visit for UnitNoUnknown {
    fn visit_unknown_dimension(&mut self, unknown_dimension: &UnknownDimension) {
        let unit = &unknown_dimension.unit.value;

        if self.ignored_units.iter().all(|item| !item.is_match(unit)) {
            let message = format!("Unexpected unknown unit \"{unit}\".");
            self.ctx.report(&unknown_dimension.unit, message);
        }

        unknown_dimension.visit_children_with(self);
    }

    fn visit_component_value(&mut self, component_value: &ComponentValue) {
        if let Some(token_and_span) = component_value.as_preserved_token() {
            if let Some(unit) = match &token_and_span.token {
                Token::Dimension {
                    dimension: dimension_token,
                } => Some(dimension_token.unit.as_ref()),
                _ => None,
            } {
                if self.ignored_units.iter().all(|item| !item.is_match(unit)) {
                    let message = format!("Unexpected unknown unit \"{unit}\".");
                    self.ctx.report(token_and_span, message);
                }
            }
        }

        component_value.visit_children_with(self);
    }
}
