use pg_query::NodeEnum;

use crate::core::schema::QualifiedName;

pub(crate) fn type_name_to_string(tn: &pg_query::protobuf::TypeName) -> String {
    let parts: Vec<String> = tn
        .names
        .iter()
        .filter_map(|n| n.node.as_ref())
        .filter_map(|n| {
            if let NodeEnum::String(s) = n {
                if s.sval == "pg_catalog" {
                    None
                } else {
                    Some(s.sval.clone())
                }
            } else {
                None
            }
        })
        .collect();
    let base = parts.join(".");
    if tn.array_bounds.is_empty() {
        base
    } else {
        format!("{base}[]")
    }
}

// libpg_query stores these without a pg_catalog prefix — treat as builtins, emit no QualifiedName
const PSEUDO_TYPES: &[&str] = &[
    "void",
    "trigger",
    "record",
    "anyelement",
    "anyarray",
    "anynonarray",
    "anyenum",
    "anyrange",
    "anycompatible",
    "anycompatiblearray",
    "anycompatiblenonarray",
    "anycompatiblerange",
    "cstring",
    "internal",
    "language_handler",
    "fdw_handler",
    "index_am_handler",
    "tsm_handler",
    "table_am_handler",
    "event_trigger",
    "pg_ddl_command",
    "unknown",
];

// None for pg_catalog builtins and PSEUDO_TYPES; Some for schema-qualified user types
pub(crate) fn type_name_to_qn(tn: &pg_query::protobuf::TypeName) -> Option<QualifiedName> {
    let parts: Vec<&str> = tn
        .names
        .iter()
        .filter_map(|n| n.node.as_ref())
        .filter_map(|n| {
            if let NodeEnum::String(s) = n {
                Some(s.sval.as_str())
            } else {
                None
            }
        })
        .collect();
    match parts.as_slice() {
        ["pg_catalog", _] => None,
        [name] if PSEUDO_TYPES.contains(name) => None,
        [schema, name] => Some(QualifiedName::new(Some(schema), name)),
        [name] => Some(QualifiedName::new(None, name)),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use pg_query::protobuf::TypeName;

    use super::*;

    #[test]
    fn type_name_strips_pg_catalog_prefix() {
        let tn = TypeName {
            names: vec![
                pg_query::protobuf::Node {
                    node: Some(NodeEnum::String(pg_query::protobuf::String {
                        sval: "pg_catalog".into(),
                    })),
                },
                pg_query::protobuf::Node {
                    node: Some(NodeEnum::String(pg_query::protobuf::String {
                        sval: "int8".into(),
                    })),
                },
            ],
            ..Default::default()
        };
        assert_eq!(type_name_to_string(&tn), "int8");
    }

    #[test]
    fn type_name_to_qn_returns_none_for_all_pseudo_types() {
        for &pseudo in super::PSEUDO_TYPES {
            let tn = TypeName {
                names: vec![pg_query::protobuf::Node {
                    node: Some(NodeEnum::String(pg_query::protobuf::String {
                        sval: pseudo.to_owned(),
                    })),
                }],
                ..Default::default()
            };
            assert!(
                type_name_to_qn(&tn).is_none(),
                "pseudo-type `{pseudo}` must produce None from type_name_to_qn"
            );
        }
    }
}
