use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use crate::{SourceFile, SourceModel};

/// Severity level for a finding.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
    #[default]
    Hint,
    Warning,
    Error,
}

/// Smell category from refactoring literature.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum SmellCategory {
    #[default]
    Bloaters,
    OoAbusers,
    ChangePreventers,
    Dispensables,
    Couplers,
    Security,
}

/// Source location of a finding.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Location {
    pub path: PathBuf,
    pub start_line: usize,
    pub end_line: usize,
    pub name: Option<String>,
}

/// A single analysis finding.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Finding {
    pub smell_name: String,
    pub category: SmellCategory,
    pub severity: Severity,
    pub location: Location,
    pub message: String,
    pub suggested_refactorings: Vec<String>,
    /// The actual measured value (e.g. line count, complexity score).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actual_value: Option<f64>,
    /// The threshold that was exceeded to produce this finding.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub threshold: Option<f64>,
}

/// Analysis context passed to plugins.
pub struct AnalysisContext<'a> {
    pub file: &'a SourceFile,
    pub model: &'a SourceModel,
}

/// Core trait that all analyzers implement.
pub trait Plugin: Send + Sync {
    /// Unique identifier for this plugin.
    fn name(&self) -> &str;

    /// Plugin version (e.g. "1.0.0").
    fn version(&self) -> &str {
        env!("CARGO_PKG_VERSION")
    }

    /// Short description of what the plugin detects.
    fn description(&self) -> &str {
        ""
    }

    /// List of authors.
    fn authors(&self) -> Vec<String> {
        vec![env!("CARGO_PKG_AUTHORS").to_string()]
    }

    /// Run analysis on a single file and return findings.
    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding>;
}
