use boltffi_ffi_rules::callable::CallableForm;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ReturnType {
    #[default]
    Void,
    Value(Type),
    Fallible {
        ok: Type,
        err: Type,
    },
}

impl ReturnType {
    pub fn value(ty: Type) -> Self {
        if ty.is_void() {
            Self::Void
        } else {
            Self::Value(ty)
        }
    }

    pub fn fallible(ok: Type, err: Type) -> Self {
        Self::Fallible { ok, err }
    }

    pub fn from_output(ty: Type) -> Self {
        match ty.result_types() {
            Some((ok, err)) => Self::Fallible {
                ok: ok.clone(),
                err: err.clone(),
            },
            None => Self::value(ty),
        }
    }

    pub fn is_void(&self) -> bool {
        matches!(self, Self::Void)
    }

    pub fn is_fallible(&self) -> bool {
        matches!(self, Self::Fallible { .. })
    }

    pub fn throws(&self) -> bool {
        self.is_fallible()
    }

    pub fn ok_type(&self) -> Option<&Type> {
        match self {
            Self::Void => None,
            Self::Value(ty) => Some(ty),
            Self::Fallible { ok, .. } => Some(ok),
        }
    }

    pub fn err_type(&self) -> Option<&Type> {
        match self {
            Self::Fallible { err, .. } => Some(err),
            _ => None,
        }
    }

    pub fn has_return_value(&self) -> bool {
        match self {
            Self::Void => false,
            Self::Value(ty) => !ty.is_void(),
            Self::Fallible { ok, .. } => !ok.is_void(),
        }
    }

    pub fn value_type(&self) -> Option<&Type> {
        match self {
            Self::Value(ty) => Some(ty),
            _ => None,
        }
    }

    pub fn as_result_types(&self) -> Option<(&Type, &Type)> {
        match self {
            Self::Fallible { ok, err } => Some((ok, err)),
            _ => None,
        }
    }
}

pub type Primitive = boltffi_ffi_rules::primitive::Primitive;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClosureSignature {
    pub params: Vec<Type>,
    pub returns: Box<Type>,
}

impl ClosureSignature {
    pub fn new(params: Vec<Type>, returns: Type) -> Self {
        Self {
            params,
            returns: Box::new(returns),
        }
    }

    pub fn void_return(params: Vec<Type>) -> Self {
        Self::new(params, Type::Void)
    }

    pub fn single_param(param: Type) -> Self {
        Self::void_return(vec![param])
    }

    pub fn is_void_return(&self) -> bool {
        self.returns.is_void()
    }

    pub fn signature_id(&self) -> String {
        let params_id = self
            .params
            .iter()
            .map(|p| p.type_id())
            .collect::<Vec<_>>()
            .join("_");
        let ret_id = self.returns.type_id();
        if self.is_void_return() {
            if params_id.is_empty() {
                "Void".to_string()
            } else {
                params_id
            }
        } else if params_id.is_empty() {
            format!("To{}", ret_id)
        } else {
            format!("{}To{}", params_id, ret_id)
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BuiltinId {
    Duration,
    SystemTime,
    Uuid,
    Url,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinSpec {
    pub id: BuiltinId,
    pub rust_paths: &'static [&'static str],
}

const BUILTIN_SPECS: &[BuiltinSpec] = &[
    BuiltinSpec {
        id: BuiltinId::Duration,
        rust_paths: &["std::time::Duration", "core::time::Duration"],
    },
    BuiltinSpec {
        id: BuiltinId::SystemTime,
        rust_paths: &["std::time::SystemTime"],
    },
    BuiltinSpec {
        id: BuiltinId::Uuid,
        rust_paths: &["uuid::Uuid"],
    },
    BuiltinSpec {
        id: BuiltinId::Url,
        rust_paths: &["url::Url"],
    },
];

impl BuiltinId {
    pub fn from_rust_path(path: &str) -> Option<Self> {
        let trimmed = path.trim();
        BUILTIN_SPECS
            .iter()
            .find_map(|spec| spec.rust_paths.contains(&trimmed).then_some(spec.id))
    }

    pub fn type_id(self) -> &'static str {
        match self {
            Self::Duration => "Duration",
            Self::SystemTime => "SystemTime",
            Self::Uuid => "Uuid",
            Self::Url => "Url",
        }
    }

    pub fn fixed_wire_size(self) -> Option<usize> {
        match self {
            Self::Duration => Some(12),
            Self::SystemTime => Some(12),
            Self::Uuid => Some(16),
            Self::Url => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type {
    Primitive(Primitive),
    String,
    Bytes,
    Builtin(BuiltinId),
    Slice(Box<Type>),
    MutSlice(Box<Type>),
    Vec(Box<Type>),
    Option(Box<Type>),
    Result { ok: Box<Type>, err: Box<Type> },
    Closure(ClosureSignature),
    Custom { name: String, repr: Box<Type> },
    Object(String),
    Record(String),
    Enum(String),
    BoxedTrait(String),
    Void,
}

impl Type {
    pub fn is_void(&self) -> bool {
        matches!(self, Self::Void)
    }

    pub fn is_primitive(&self) -> bool {
        matches!(self, Self::Primitive(_))
    }

    pub fn is_optional(&self) -> bool {
        matches!(self, Self::Option(_))
    }

    pub fn is_result(&self) -> bool {
        matches!(self, Self::Result { .. })
    }

    pub fn inner_type(&self) -> Option<&Type> {
        match self {
            Self::Vec(inner) | Self::Option(inner) => Some(inner),
            _ => None,
        }
    }

    pub fn result_types(&self) -> Option<(&Type, &Type)> {
        match self {
            Self::Result { ok, err } => Some((ok, err)),
            _ => None,
        }
    }

    pub fn named_type(&self) -> Option<&str> {
        match self {
            Self::Custom { name, .. }
            | Self::Object(name)
            | Self::Record(name)
            | Self::Enum(name) => Some(name),
            _ => None,
        }
    }

    pub fn vec(element: Type) -> Self {
        Self::Vec(Box::new(element))
    }

    pub fn option(inner: Type) -> Self {
        Self::Option(Box::new(inner))
    }

    pub fn result(ok: Type, err: Type) -> Self {
        Self::Result {
            ok: Box::new(ok),
            err: Box::new(err),
        }
    }

    pub fn is_string(&self) -> bool {
        matches!(self, Self::String)
    }

    pub fn is_record(&self) -> bool {
        matches!(self, Self::Record(_))
    }

    pub fn is_enum(&self) -> bool {
        matches!(self, Self::Enum(_))
    }

    pub fn is_vec(&self) -> bool {
        matches!(self, Self::Vec(_))
    }

    pub fn primitive(&self) -> Option<Primitive> {
        match self {
            Self::Primitive(p) => Some(*p),
            _ => None,
        }
    }

    pub fn builtin_id(&self) -> Option<BuiltinId> {
        match self {
            Self::Builtin(id) => Some(*id),
            _ => None,
        }
    }

    pub fn record_name(&self) -> Option<&str> {
        match self {
            Self::Record(name) => Some(name),
            _ => None,
        }
    }

    pub fn enum_name(&self) -> Option<&str> {
        match self {
            Self::Enum(name) => Some(name),
            _ => None,
        }
    }

    pub fn vec_inner(&self) -> Option<&Type> {
        match self {
            Self::Vec(inner) => Some(inner),
            _ => None,
        }
    }

    pub fn type_id(&self) -> String {
        match self {
            Self::Void => "Void".into(),
            Self::Primitive(p) => p.type_id().into(),
            Self::String => "String".into(),
            Self::Bytes => "Bytes".into(),
            Self::Builtin(id) => id.type_id().into(),
            Self::Vec(inner) => format!("Vec{}", inner.type_id()),
            Self::Option(inner) => format!("Opt{}", inner.type_id()),
            Self::Slice(inner) => format!("Slice{}", inner.type_id()),
            Self::MutSlice(inner) => format!("MutSlice{}", inner.type_id()),
            Self::Result { ok, err } => format!("Result{}Err{}", ok.type_id(), err.type_id()),
            Self::Custom { name, .. } => heck::AsUpperCamelCase(name).to_string(),
            Self::Record(name) => heck::AsUpperCamelCase(name).to_string(),
            Self::Enum(name) => heck::AsUpperCamelCase(name).to_string(),
            Self::Object(name) => heck::AsUpperCamelCase(name).to_string(),
            Self::BoxedTrait(name) => heck::AsUpperCamelCase(name).to_string(),
            Self::Closure(sig) => format!("Fn{}", sig.signature_id()),
        }
    }
}

use super::layout::{CLayout, Layout};

impl CLayout for Type {
    fn c_layout(&self) -> Layout {
        match self {
            Self::Primitive(primitive) => primitive.c_layout(),
            Self::String | Self::Bytes | Self::Vec(_) | Self::Slice(_) | Self::MutSlice(_) => {
                Layout::new(24, 8)
            }
            Self::Object(_) | Self::BoxedTrait(_) | Self::Closure(_) => Layout::new(8, 8),
            Self::Builtin(_) | Self::Record(_) | Self::Enum(_) | Self::Custom { .. } => {
                Layout::new(8, 8)
            }
            Self::Option(inner) => {
                let inner_layout = inner.c_layout();
                Layout::new(
                    inner_layout.size.as_usize() + inner_layout.alignment.as_usize(),
                    inner_layout.alignment.as_usize(),
                )
            }
            Self::Result { ok, .. } => ok.c_layout(),
            Self::Void => Layout::new(0, 1),
        }
    }
}

impl CLayout for Primitive {
    fn c_layout(&self) -> Layout {
        match self {
            Self::Bool | Self::I8 | Self::U8 => Layout::new(1, 1),
            Self::I16 | Self::U16 => Layout::new(2, 2),
            Self::I32 | Self::U32 | Self::F32 => Layout::new(4, 4),
            Self::I64 | Self::U64 | Self::F64 | Self::USize | Self::ISize => Layout::new(8, 8),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Receiver {
    None,
    Ref,
    RefMut,
}

impl Receiver {
    pub fn callable_form(self) -> CallableForm {
        match self {
            Self::None => CallableForm::StaticMethod,
            Self::Ref | Self::RefMut => CallableForm::InstanceMethod,
        }
    }

    pub fn is_static(self) -> bool {
        self.callable_form() == CallableForm::StaticMethod
    }

    pub fn is_mutable(self) -> bool {
        matches!(self, Self::RefMut)
    }

    pub fn takes_self(self) -> bool {
        !self.is_static()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Deprecation {
    pub message: Option<String>,
    pub since: Option<String>,
}

impl Deprecation {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: Some(message.into()),
            since: None,
        }
    }

    pub fn with_since(mut self, version: impl Into<String>) -> Self {
        self.since = Some(version.into());
        self
    }
}
