use boltffi_ffi_rules::callable::{CallableForm, ExecutionKind};
use boltffi_ffi_rules::classification::{self, FieldPrimitive, PassableCategory};

use crate::ir::abi::CallId;
use crate::ir::ids::{
    CallbackId, ClassId, ConverterPath, CustomTypeId, EnumId, FieldName, FunctionId, MethodId,
    ParamName, QualifiedName, RecordId, StreamId, VariantName,
};
use crate::ir::types::{PrimitiveType, TypeExpr};

#[derive(Debug, Clone)]
pub struct DeprecationInfo {
    pub message: Option<String>,
    pub since: Option<String>,
}

#[derive(Debug, Clone)]
pub struct RecordDef {
    pub id: RecordId,
    pub is_repr_c: bool,
    pub is_error: bool,
    pub fields: Vec<FieldDef>,
    pub constructors: Vec<ConstructorDef>,
    pub methods: Vec<MethodDef>,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

impl RecordDef {
    pub fn has_methods(&self) -> bool {
        !self.constructors.is_empty() || !self.methods.is_empty()
    }

    pub fn constructor_calls(&self) -> impl Iterator<Item = (CallId, &ConstructorDef)> {
        self.constructors.iter().enumerate().map(|(i, ctor)| {
            (
                CallId::RecordConstructor {
                    record_id: self.id.clone(),
                    index: i,
                },
                ctor,
            )
        })
    }

    pub fn method_calls(&self) -> impl Iterator<Item = (CallId, &MethodDef)> {
        self.methods.iter().map(|m| {
            (
                CallId::RecordMethod {
                    record_id: self.id.clone(),
                    method_id: m.id.clone(),
                },
                m,
            )
        })
    }

    pub fn is_blittable(&self) -> bool {
        let field_primitives: Vec<FieldPrimitive> = self
            .fields
            .iter()
            .filter_map(|f| match &f.type_expr {
                TypeExpr::Primitive(p) => Some(p.to_field_primitive()),
                _ => None,
            })
            .collect();
        let all_primitive = field_primitives.len() == self.fields.len();
        let classify_fields = if all_primitive {
            &field_primitives[..]
        } else {
            &[]
        };
        matches!(
            classification::classify_struct(self.is_repr_c, classify_fields),
            PassableCategory::Blittable,
        )
    }
}

#[derive(Debug, Clone)]
pub struct FieldDef {
    pub name: FieldName,
    pub type_expr: TypeExpr,
    pub doc: Option<String>,
    pub default: Option<DefaultValue>,
}

#[derive(Debug, Clone)]
pub enum DefaultValue {
    Bool(bool),
    Integer(i64),
    Float(f64),
    String(String),
    EnumVariant {
        enum_name: String,
        variant_name: String,
    },
    Null,
}

#[derive(Debug, Clone)]
pub struct EnumDef {
    pub id: EnumId,
    pub repr: EnumRepr,
    pub is_error: bool,
    pub constructors: Vec<ConstructorDef>,
    pub methods: Vec<MethodDef>,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

impl EnumDef {
    pub fn has_methods(&self) -> bool {
        !self.constructors.is_empty() || !self.methods.is_empty()
    }

    pub fn constructor_calls(&self) -> impl Iterator<Item = (CallId, &ConstructorDef)> {
        self.constructors.iter().enumerate().map(|(i, ctor)| {
            (
                CallId::EnumConstructor {
                    enum_id: self.id.clone(),
                    index: i,
                },
                ctor,
            )
        })
    }

    pub fn method_calls(&self) -> impl Iterator<Item = (CallId, &MethodDef)> {
        self.methods.iter().map(|m| {
            (
                CallId::EnumMethod {
                    enum_id: self.id.clone(),
                    method_id: m.id.clone(),
                },
                m,
            )
        })
    }

    pub fn variant_docs(&self) -> Vec<Option<String>> {
        match &self.repr {
            EnumRepr::CStyle { variants, .. } => variants.iter().map(|v| v.doc.clone()).collect(),
            EnumRepr::Data { variants, .. } => variants.iter().map(|v| v.doc.clone()).collect(),
        }
    }
}

#[derive(Debug, Clone)]
pub enum EnumRepr {
    CStyle {
        tag_type: PrimitiveType,
        variants: Vec<CStyleVariant>,
    },
    Data {
        tag_type: PrimitiveType,
        variants: Vec<DataVariant>,
    },
}

#[derive(Debug, Clone)]
pub struct CStyleVariant {
    pub name: VariantName,
    pub discriminant: i128,
    pub doc: Option<String>,
}

#[derive(Debug, Clone)]
pub struct DataVariant {
    pub name: VariantName,
    pub discriminant: i128,
    pub payload: VariantPayload,
    pub doc: Option<String>,
}

#[derive(Debug, Clone)]
pub enum VariantPayload {
    Unit,
    Tuple(Vec<TypeExpr>),
    Struct(Vec<FieldDef>),
}

#[derive(Debug, Clone)]
pub struct FunctionDef {
    pub id: FunctionId,
    pub params: Vec<ParamDef>,
    pub returns: ReturnDef,
    pub execution_kind: ExecutionKind,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

impl FunctionDef {
    pub fn is_async(&self) -> bool {
        self.execution_kind == ExecutionKind::Async
    }

    pub fn callable_form(&self) -> CallableForm {
        CallableForm::Function
    }

    pub fn execution_kind(&self) -> ExecutionKind {
        self.execution_kind
    }
}

#[derive(Debug, Clone)]
pub struct ParamDef {
    pub name: ParamName,
    pub type_expr: TypeExpr,
    pub passing: ParamPassing,
    pub doc: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamPassing {
    Value,
    Ref,
    RefMut,
    ImplTrait,
    BoxedDyn,
}

#[derive(Debug, Clone)]
pub enum ReturnDef {
    Void,
    Value(TypeExpr),
    Result { ok: TypeExpr, err: TypeExpr },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamMode {
    Async,
    Batch,
    Callback,
}

#[derive(Debug, Clone)]
pub struct StreamDef {
    pub id: StreamId,
    pub item_type: TypeExpr,
    pub mode: StreamMode,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

#[derive(Debug, Clone)]
pub struct ClassDef {
    pub id: ClassId,
    pub constructors: Vec<ConstructorDef>,
    pub methods: Vec<MethodDef>,
    pub streams: Vec<StreamDef>,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

#[derive(Debug, Clone)]
pub enum ConstructorDef {
    Default {
        params: Vec<ParamDef>,
        is_fallible: bool,
        is_optional: bool,
        doc: Option<String>,
        deprecated: Option<DeprecationInfo>,
    },
    NamedFactory {
        name: MethodId,
        is_fallible: bool,
        is_optional: bool,
        doc: Option<String>,
        deprecated: Option<DeprecationInfo>,
    },
    NamedInit {
        name: MethodId,
        first_param: ParamDef,
        rest_params: Vec<ParamDef>,
        is_fallible: bool,
        is_optional: bool,
        doc: Option<String>,
        deprecated: Option<DeprecationInfo>,
    },
}

impl ConstructorDef {
    pub fn params(&self) -> Vec<&ParamDef> {
        match self {
            Self::Default { params, .. } => params.iter().collect(),
            Self::NamedFactory { .. } => vec![],
            Self::NamedInit {
                first_param,
                rest_params,
                ..
            } => std::iter::once(first_param).chain(rest_params).collect(),
        }
    }

    pub fn is_fallible(&self) -> bool {
        match self {
            Self::Default { is_fallible, .. }
            | Self::NamedFactory { is_fallible, .. }
            | Self::NamedInit { is_fallible, .. } => *is_fallible,
        }
    }

    pub fn is_optional(&self) -> bool {
        match self {
            Self::Default { is_optional, .. }
            | Self::NamedFactory { is_optional, .. }
            | Self::NamedInit { is_optional, .. } => *is_optional,
        }
    }

    pub fn name(&self) -> Option<&MethodId> {
        match self {
            Self::Default { .. } => None,
            Self::NamedFactory { name, .. } | Self::NamedInit { name, .. } => Some(name),
        }
    }

    pub fn doc(&self) -> Option<&str> {
        match self {
            Self::Default { doc, .. }
            | Self::NamedFactory { doc, .. }
            | Self::NamedInit { doc, .. } => doc.as_deref(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct MethodDef {
    pub id: MethodId,
    pub receiver: Receiver,
    pub params: Vec<ParamDef>,
    pub returns: ReturnDef,
    pub execution_kind: ExecutionKind,
    pub doc: Option<String>,
    pub deprecated: Option<DeprecationInfo>,
}

impl MethodDef {
    pub fn is_async(&self) -> bool {
        self.execution_kind == ExecutionKind::Async
    }

    pub fn callable_form(&self) -> CallableForm {
        self.receiver.callable_form()
    }

    pub fn execution_kind(&self) -> ExecutionKind {
        self.execution_kind
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Receiver {
    Static,
    RefSelf,
    RefMutSelf,
    OwnedSelf,
}

impl Receiver {
    pub fn callable_form(self) -> CallableForm {
        match self {
            Self::Static => CallableForm::StaticMethod,
            Self::RefSelf | Self::RefMutSelf | Self::OwnedSelf => CallableForm::InstanceMethod,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallbackKind {
    Trait,
    Closure,
}

#[derive(Debug, Clone)]
pub struct CallbackTraitDef {
    pub id: CallbackId,
    pub methods: Vec<CallbackMethodDef>,
    pub kind: CallbackKind,
    pub doc: Option<String>,
}

#[derive(Debug, Clone)]
pub struct CallbackMethodDef {
    pub id: MethodId,
    pub params: Vec<ParamDef>,
    pub returns: ReturnDef,
    pub execution_kind: ExecutionKind,
    pub doc: Option<String>,
}

impl CallbackMethodDef {
    pub fn is_async(&self) -> bool {
        self.execution_kind == ExecutionKind::Async
    }

    pub fn execution_kind(&self) -> ExecutionKind {
        self.execution_kind
    }
}

#[derive(Debug, Clone)]
pub struct CustomTypeDef {
    pub id: CustomTypeId,
    pub rust_type: QualifiedName,
    pub repr: TypeExpr,
    pub converters: ConverterPath,
    pub doc: Option<String>,
}
