use crate::{
    diag::Diagnostic,
    sexpr::{Atom, SExpr, SExprKind},
    token::Span,
};

const CURRENT_BETA_UNSUPPORTED: &str = "reserved but not supported in the current beta";

pub(crate) fn unsupported_reserved_type_diagnostic(file: &str, form: &SExpr) -> Option<Diagnostic> {
    if let Some(name) = expect_ident(form) {
        if is_generic_type_parameter_name(name) {
            return Some(unsupported_generic_type_parameter(file, form.span, name));
        }
    }

    let items = expect_list(form)?;
    let head = items.first().and_then(expect_ident)?;
    match head {
        "map" => Some(
            Diagnostic::new(
                file,
                "UnsupportedMapType",
                format!("`map` types are {}", CURRENT_BETA_UNSUPPORTED),
            )
            .with_span(form.span)
            .expected("supported concrete type")
            .found(render_type_form(form))
            .hint(
                "use current concrete arrays, vectors, option/result, structs, enums, or scalars",
            ),
        ),
        "set" => Some(
            Diagnostic::new(
                file,
                "UnsupportedSetType",
                format!("`set` types are {}", CURRENT_BETA_UNSUPPORTED),
            )
            .with_span(form.span)
            .expected("supported concrete type")
            .found(render_type_form(form))
            .hint(
                "use current concrete arrays, vectors, option/result, structs, enums, or scalars",
            ),
        ),
        "vec" if items.len() != 2 => Some(
            Diagnostic::new(
                file,
                "UnsupportedGenericTypeParameter",
                format!("generic vector syntax is {}", CURRENT_BETA_UNSUPPORTED),
            )
            .with_span(form.span)
            .expected("(vec i32), (vec i64), (vec f64), (vec bool), or (vec string)")
            .found(render_type_form(form))
            .hint("choose one current concrete vector family explicitly"),
        ),
        _ => items
            .iter()
            .find_map(|item| unsupported_reserved_type_diagnostic(file, item)),
    }
}

pub(crate) fn unsupported_generic_function(file: &str, span: Span) -> Diagnostic {
    Diagnostic::new(
        file,
        "UnsupportedGenericFunction",
        format!(
            "generic function declarations are {}",
            CURRENT_BETA_UNSUPPORTED
        ),
    )
    .with_span(span)
    .expected("(fn name ((arg ConcreteType) ...) -> ConcreteType body...)")
    .found("(type_params ...)")
    .hint("write a concrete function for each currently supported type family")
}

pub(crate) fn unsupported_generic_type_alias(file: &str, span: Span) -> Diagnostic {
    Diagnostic::new(
        file,
        "UnsupportedGenericTypeAlias",
        format!(
            "parameterized type aliases are {}",
            CURRENT_BETA_UNSUPPORTED
        ),
    )
    .with_span(span)
    .expected("(type Alias ConcreteType)")
    .found("(type_params ...)")
    .hint("aliases remain transparent names for concrete supported target types")
}

pub(crate) fn unsupported_generic_type_parameter(file: &str, span: Span, name: &str) -> Diagnostic {
    Diagnostic::new(
        file,
        "UnsupportedGenericTypeParameter",
        format!(
            "generic type parameter `{}` is {}",
            name, CURRENT_BETA_UNSUPPORTED
        ),
    )
    .with_span(span)
    .expected("concrete supported type")
    .found(name.to_string())
    .hint("use a concrete promoted type such as `i32`, `string`, or `(vec i32)`")
}

pub(crate) fn unsupported_generic_standard_library_call(
    file: &str,
    span: Span,
    name: &str,
) -> Diagnostic {
    Diagnostic::new(
        file,
        "UnsupportedGenericStandardLibraryCall",
        format!(
            "generic standard-library call `{}` is {}",
            name, CURRENT_BETA_UNSUPPORTED
        ),
    )
    .with_span(span)
    .expected("promoted concrete standard-library function")
    .found(name.to_string())
    .hint("use current concrete families such as `std.vec.i32.empty`")
}

pub(crate) fn is_unsupported_generic_standard_library_call(name: &str) -> bool {
    matches!(name, "std.vec.empty" | "std.result.map")
}

pub(crate) fn is_generic_type_parameter_name(name: &str) -> bool {
    name.len() == 1 && name.bytes().all(|byte| byte.is_ascii_uppercase())
}

fn expect_list(expr: &SExpr) -> Option<&[SExpr]> {
    match &expr.kind {
        SExprKind::List(items) => Some(items),
        _ => None,
    }
}

fn expect_ident(expr: &SExpr) -> Option<&str> {
    match &expr.kind {
        SExprKind::Atom(Atom::Ident(name)) => Some(name),
        _ => None,
    }
}

fn render_type_form(form: &SExpr) -> String {
    match &form.kind {
        SExprKind::Atom(Atom::Ident(name)) => name.clone(),
        SExprKind::Atom(Atom::Int(value)) => value.to_string(),
        SExprKind::Atom(Atom::I64(value)) => format!("{}i64", value),
        SExprKind::Atom(Atom::U32(value)) => format!("{}u32", value),
        SExprKind::Atom(Atom::U64(value)) => format!("{}u64", value),
        SExprKind::Atom(Atom::Float(value)) => value.to_string(),
        SExprKind::Atom(Atom::String(value)) => format!("{:?}", value),
        SExprKind::Atom(Atom::Arrow) => "->".to_string(),
        SExprKind::List(items) => {
            let parts = items.iter().map(render_type_form).collect::<Vec<_>>();
            format!("({})", parts.join(" "))
        }
    }
}
