use serde::de::DeserializeOwned;

/// Trait implemented by `#[derive(Extract)]` to provide repair schema.
///
/// Types implementing this trait can be used with [`extract`](crate::extract)
/// for fuzzy-repair-assisted deserialization.
pub trait Extract: DeserializeOwned {
    /// Return the repair schema for this type.
    fn repair_schema() -> RepairSchema;
}

/// Repair schema generated by `derive(Extract)`.
///
/// Describes the structure of a type so that fuzzy repair can fix
/// field name and enum variant typos before deserialization.
#[derive(Debug, Clone)]
pub struct RepairSchema {
    /// The kind of schema (struct with fields, or tagged enum).
    pub kind: SchemaKind,
}

/// The structural kind of a repair schema.
#[derive(Debug, Clone)]
pub enum SchemaKind {
    /// A struct with known field names.
    Struct(StructSchema),
    /// A tagged enum (discriminated union).
    TaggedEnum(TaggedEnumDef),
}

/// Schema for a struct: its field names and per-field settings.
#[derive(Debug, Clone)]
pub struct StructSchema {
    /// Valid field names (as they appear in JSON).
    pub fields: &'static [FieldDef],
}

/// Schema for a tagged enum.
#[derive(Debug, Clone)]
pub struct TaggedEnumDef {
    /// The discriminator field name (e.g., "type").
    pub tag_field: &'static str,
    /// Valid variant names.
    pub variants: &'static [&'static str],
}

/// Per-field configuration for repair.
#[derive(Clone)]
pub struct FieldDef {
    /// The JSON field name (respects `#[serde(rename = "...")]`).
    pub name: &'static str,
    /// Whether repair is enabled for this field.
    pub repair: bool,
    /// Custom similarity threshold (overrides struct-level).
    pub similarity: Option<f64>,
    /// Additional accepted names for this field.
    pub aliases: &'static [&'static str],
    /// Child schema for recursive repair. Returns `Some(schema)` if the
    /// field's type implements `Extract`, `None` otherwise.
    pub child_schema: Option<fn() -> RepairSchema>,
}

impl std::fmt::Debug for FieldDef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FieldDef")
            .field("name", &self.name)
            .field("repair", &self.repair)
            .field("similarity", &self.similarity)
            .field("aliases", &self.aliases)
            .field("has_child_schema", &self.child_schema.is_some())
            .finish()
    }
}

impl RepairSchema {
    /// Collect all valid field names for use with fuzzy-parser's ObjectSchema.
    pub fn field_names(&self) -> Vec<&'static str> {
        match &self.kind {
            SchemaKind::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
            SchemaKind::TaggedEnum(_) => Vec::new(),
        }
    }

    /// For tagged enums, return the tag field and valid variants.
    pub fn tag_info(&self) -> Option<(&'static str, &'static [&'static str])> {
        match &self.kind {
            SchemaKind::TaggedEnum(e) => Some((e.tag_field, e.variants)),
            SchemaKind::Struct(_) => None,
        }
    }
}
