use scip::types::symbol_information::Kind;

use crate::core::schema::{QualifiedName, SchemaModel, TypeKind};

/// SCIP metadata derived from a SQL entity's qualified name and schema model.
pub struct ScipMeta {
    /// The SCIP descriptor suffix string (e.g. `"public/items."`, `"public/fn()."`)
    pub descriptor: String,
    /// Display-friendly unqualified name.
    pub display_name: String,
    /// SCIP symbol kind.
    pub kind: Kind,
}

/// `"schema/name"` or `"schema/table#col"` → `"pgscip . . . <descriptor>"`.
#[must_use]
pub fn to_scip_symbol(symbol: &str, model: &SchemaModel) -> String {
    let meta = build_descriptor(symbol, model);
    format!("pgscip . . . {}", meta.descriptor)
}

pub(crate) fn build_descriptor(symbol: &str, model: &SchemaModel) -> ScipMeta {
    if let Some((table_part, col)) = symbol.split_once('#') {
        return ScipMeta {
            descriptor: format!("{table_part}#{col}."),
            display_name: col.to_owned(),
            kind: Kind::Field,
        };
    }
    let qn = QualifiedName::from_symbol_str(symbol);
    let display_name = qn.name.clone();
    if model.functions.contains_key(&qn) {
        return ScipMeta {
            descriptor: format!("{symbol}()."),
            display_name,
            kind: Kind::Function,
        };
    }
    if let Some(type_def) = model.types.get(&qn) {
        let kind = match type_def.kind {
            TypeKind::Enum => Kind::Enum,
            TypeKind::Composite => Kind::Struct,
        };
        return ScipMeta {
            descriptor: format!("{symbol}#"),
            display_name,
            kind,
        };
    }
    if model.views.contains_key(&qn) {
        return ScipMeta {
            descriptor: format!("{symbol}."),
            display_name,
            kind: Kind::Class,
        };
    }
    if model.triggers.contains_key(&qn) {
        return ScipMeta {
            descriptor: format!("{symbol}."),
            display_name,
            kind: Kind::Event,
        };
    }
    if model.policies.contains_key(&qn) {
        return ScipMeta {
            descriptor: format!("{symbol}."),
            display_name,
            kind: Kind::Attribute,
        };
    }
    if model.indexes.contains_key(&qn) {
        return ScipMeta {
            descriptor: format!("{symbol}."),
            display_name,
            kind: Kind::Property,
        };
    }
    ScipMeta {
        descriptor: format!("{symbol}."),
        display_name,
        kind: Kind::Struct,
    }
}

#[cfg(test)]
mod tests {
    use indexmap::IndexMap;
    use text_size::TextRange;

    use super::*;
    use crate::core::schema::{FunctionDef, SchemaModel, TableDef, TypeDef, TypeKind, ViewDef};

    fn make_model() -> SchemaModel {
        let mut model = SchemaModel::default();
        let qn = QualifiedName::new(Some("public"), "items");
        model.tables.insert(
            qn,
            TableDef {
                columns: IndexMap::new(),
                range: TextRange::default(),
                doc: None,
            },
        );
        let fn_qn = QualifiedName::new(Some("public"), "find_item");
        model.functions.insert(
            fn_qn,
            FunctionDef {
                range: TextRange::default(),
                doc: None,
                return_type: None,
                param_types: Vec::new(),
                params: Vec::new(),
            },
        );
        let view_qn = QualifiedName::new(Some("public"), "items_view");
        model.views.insert(
            view_qn,
            ViewDef {
                range: TextRange::default(),
                doc: None,
                source_tables: Vec::new(),
            },
        );
        let enum_qn = QualifiedName::new(Some("public"), "item_status");
        model.types.insert(
            enum_qn,
            TypeDef {
                kind: TypeKind::Enum,
                range: TextRange::default(),
                doc: None,
                fields: IndexMap::new(),
                labels: Vec::new(),
            },
        );
        let comp_qn = QualifiedName::new(Some("public"), "point_2d");
        model.types.insert(
            comp_qn,
            TypeDef {
                kind: TypeKind::Composite,
                range: TextRange::default(),
                doc: None,
                fields: IndexMap::new(),
                labels: Vec::new(),
            },
        );
        model
    }

    #[test]
    fn scip_meta_table_descriptor() {
        let model = make_model();
        let meta = build_descriptor("public/items", &model);
        assert_eq!(meta.descriptor, "public/items.");
        assert_eq!(meta.display_name, "items");
    }

    #[test]
    fn scip_meta_function_descriptor() {
        let model = make_model();
        let meta = build_descriptor("public/find_item", &model);
        assert_eq!(meta.descriptor, "public/find_item().");
        assert_eq!(meta.display_name, "find_item");
    }

    #[test]
    fn scip_meta_column_descriptor() {
        let model = make_model();
        let meta = build_descriptor("public/items#id", &model);
        assert_eq!(meta.descriptor, "public/items#id.");
        assert_eq!(meta.display_name, "id");
    }

    #[test]
    fn to_scip_symbol_table() {
        let model = make_model();
        let result = to_scip_symbol("public/items", &model);
        assert_eq!(result, "pgscip . . . public/items.");
    }

    #[test]
    fn to_scip_symbol_column() {
        let model = make_model();
        let result = to_scip_symbol("public/items#id", &model);
        assert_eq!(result, "pgscip . . . public/items#id.");
    }

    #[test]
    fn to_scip_symbol_function() {
        let model = make_model();
        let result = to_scip_symbol("public/find_item", &model);
        assert_eq!(result, "pgscip . . . public/find_item().");
    }

    #[test]
    fn scip_meta_table_kind_is_struct() {
        let model = make_model();
        let meta = build_descriptor("public/items", &model);
        assert_eq!(meta.kind, Kind::Struct, "table must have Kind::Struct");
    }

    #[test]
    fn scip_meta_column_kind_is_field() {
        let model = make_model();
        let meta = build_descriptor("public/items#id", &model);
        assert_eq!(meta.kind, Kind::Field, "column must have Kind::Field");
    }

    #[test]
    fn scip_meta_function_kind_is_function() {
        let model = make_model();
        let meta = build_descriptor("public/find_item", &model);
        assert_eq!(
            meta.kind,
            Kind::Function,
            "function must have Kind::Function"
        );
    }

    #[test]
    fn scip_meta_view_kind_is_class() {
        let model = make_model();
        let meta = build_descriptor("public/items_view", &model);
        assert_eq!(meta.kind, Kind::Class, "view must have Kind::Class");
    }

    #[test]
    fn scip_meta_enum_type_kind_is_enum() {
        let model = make_model();
        let meta = build_descriptor("public/item_status", &model);
        assert_eq!(meta.kind, Kind::Enum, "enum type must have Kind::Enum");
    }

    #[test]
    fn scip_meta_composite_type_kind_is_struct() {
        let model = make_model();
        let meta = build_descriptor("public/point_2d", &model);
        assert_eq!(
            meta.kind,
            Kind::Struct,
            "composite type must have Kind::Struct"
        );
    }
}
