use crate::names::go_name;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PreludeType {
    Option,
    Result,
    Partial,
    Range,
    RangeInclusive,
    RangeFrom,
    RangeTo,
    RangeToInclusive,
    PanicValue,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct VariantInfo {
    pub(crate) name: &'static str,
}

impl PreludeType {
    pub(crate) fn from_name(name: &str) -> Option<Self> {
        match name {
            "Option" => Some(Self::Option),
            "Result" => Some(Self::Result),
            "Partial" => Some(Self::Partial),
            "Range" => Some(Self::Range),
            "RangeInclusive" => Some(Self::RangeInclusive),
            "RangeFrom" => Some(Self::RangeFrom),
            "RangeTo" => Some(Self::RangeTo),
            "RangeToInclusive" => Some(Self::RangeToInclusive),
            "PanicValue" => Some(Self::PanicValue),
            _ => None,
        }
    }

    pub(crate) fn emit_type(&self, type_args: &[String]) -> String {
        let name = self.go_name();
        let pkg = go_name::GO_STDLIB_PKG;
        if type_args.is_empty() {
            format!("{pkg}.{}", name)
        } else {
            format!("{pkg}.{}[{}]", name, type_args.join(", "))
        }
    }

    pub(crate) fn go_name(&self) -> &'static str {
        match self {
            Self::Option => "Option",
            Self::Result => "Result",
            Self::Partial => "Partial",
            Self::Range => "Range",
            Self::RangeInclusive => "RangeInclusive",
            Self::RangeFrom => "RangeFrom",
            Self::RangeTo => "RangeTo",
            Self::RangeToInclusive => "RangeToInclusive",
            Self::PanicValue => "PanicValue",
        }
    }

    pub(crate) fn variants(&self) -> Option<&'static [VariantInfo]> {
        match self {
            Self::Option => Some(&[VariantInfo { name: "Some" }, VariantInfo { name: "None" }]),
            Self::Result => Some(&[VariantInfo { name: "Ok" }, VariantInfo { name: "Err" }]),
            Self::Partial => Some(&[
                VariantInfo { name: "Ok" },
                VariantInfo { name: "Err" },
                VariantInfo { name: "Both" },
            ]),
            Self::Range
            | Self::RangeInclusive
            | Self::RangeFrom
            | Self::RangeTo
            | Self::RangeToInclusive
            | Self::PanicValue => None,
        }
    }

    pub(crate) fn make_function_name(&self, variant: &str) -> String {
        format!("prelude.Make{}{}", self.go_name(), variant)
    }

    pub(crate) fn enum_types() -> &'static [PreludeType] {
        &[Self::Option, Self::Result, Self::Partial]
    }

    pub(crate) fn make_function_entries(&self) -> impl Iterator<Item = (String, String)> + '_ {
        self.variants().into_iter().flatten().map(move |v| {
            let constructor = format!("{}.{}", self.go_name(), v.name);
            let make_fn = self.make_function_name(v.name);
            (constructor, make_fn)
        })
    }
}
