use std::collections::HashMap;

use serde_json::Value;

use crate::scanner::body::BodyFinding;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tool {
    Claude,
    Codex,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Loss {
    Lossless,
    Lossy,
    Dropped,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Kind {
    Skill,
    Plugin,
    Subagent,
    Hooks,
    Mcp,
    Memory,
    Settings,
}

impl Kind {
    /// Returns the mappings domain key used in `--only` filter comparisons and
    /// as the handler key in `mappings/*.yaml` (e.g. `"skills"`, `"mcp"`).
    pub fn domain_name(&self) -> &'static str {
        match self {
            Kind::Skill => "skills",
            Kind::Plugin => "plugins",
            Kind::Subagent => "subagents",
            Kind::Hooks => "hooks",
            Kind::Mcp => "mcp",
            Kind::Memory => "memory",
            Kind::Settings => "settings-config",
        }
    }
}

/// Normalized conversion unit corresponding to one mappings entry.
#[derive(Debug, Clone)]
pub struct IRField {
    /// Entry id from mappings (e.g. "mcp.timeout")
    pub id: String,
    /// Normalized value after lift (serde_json::Value)
    pub value: Value,
    /// Loss level of the conversion
    pub loss: Loss,
    /// List of transform names applied (for reporting)
    pub transforms_applied: Vec<String>,
    /// Degrade information, if a downgrade occurred
    pub degrade: Option<DegradeInfo>,
    /// Warning message arising from warn:true
    pub warning: Option<String>,
    /// Reason information when the field is dropped
    pub dropped: Option<DroppedInfo>,
}

/// Information attached when a field is relocated to a different scope via the degrade engine.
#[derive(Debug, Clone)]
pub struct DegradeInfo {
    /// Kind of degrade destination (e.g. "session", "subagent")
    pub to: String,
    /// Degrade destination target (e.g. ".codex/agents/deploy.toml")
    pub target: String,
}

#[derive(Debug, Clone)]
pub struct DroppedInfo {
    pub reason: String,
}

/// Parsed result of a skill/command/prompt body.
#[derive(Debug, Clone)]
pub struct BodySegment {
    /// Original body text (unmodified)
    pub raw: String,
    /// Variables, syntax patterns, and dynamic injections detected by the scanner
    pub findings: Vec<BodyFinding>,
}

/// Intermediate representation node that uniformly represents any domain (skill/mcp/hooks/plugin).
#[derive(Debug, Clone)]
pub struct IRNode {
    pub kind: Kind,
    pub source_tool: Tool,
    pub source_path: String,
    pub fields: HashMap<String, IRField>,
    pub body: Option<BodySegment>,
    /// Child nodes for skills/hooks/mcp contained within a plugin
    pub children: Vec<IRNode>,
    pub side_artifacts: Vec<SideArtifact>,
    pub diagnostics: Vec<Diagnostic>,
    /// Original, pre-transform frontmatter values for --keep-claude-frontmatter.
    /// Set only for nodes whose source has parseable frontmatter (e.g. SKILL.md).
    pub raw_frontmatter: Option<serde_json::Map<String, Value>>,
}

/// Additional file generated by a degrade or conversion. Path is relative to the output root.
#[derive(Debug, Clone)]
pub struct SideArtifact {
    pub path: String,
    pub content: String,
    /// Supplementary description for the report
    pub note: String,
}

/// Diagnostic entry for warnings, dropped fields, and degrades that occur during conversion.
#[derive(Debug, Clone)]
pub struct Diagnostic {
    pub level: DiagLevel,
    /// Entry id of the related mappings entry
    pub id: Option<String>,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiagLevel {
    Info,
    Warn,
    /// A warning produced by the body scanner, routed to `body_warnings` in the report.
    BodyWarn,
    Drop,
}

pub fn new_node(kind: Kind, source_tool: Tool, source_path: &str) -> IRNode {
    IRNode {
        kind,
        source_tool,
        source_path: source_path.to_string(),
        fields: HashMap::new(),
        body: None,
        children: Vec::new(),
        side_artifacts: Vec::new(),
        diagnostics: Vec::new(),
        raw_frontmatter: None,
    }
}
