/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::APIConverter;
use crate::attributes::ArgumentAttributes;
use crate::attributes::{ConstructorAttributes, FunctionAttributes, MethodAttributes};
use crate::converters::convert_docstring;
use crate::literal::convert_default_value;
use crate::InterfaceCollector;
use anyhow::{bail, Result};

use uniffi_meta::{
    ConstructorMetadata, FieldMetadata, FnMetadata, FnParamMetadata, MethodMetadata,
    TraitMethodMetadata, Type,
};

impl APIConverter<FieldMetadata> for weedle::argument::Argument<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FieldMetadata> {
        match self {
            weedle::argument::Argument::Single(t) => t.convert(ci),
            weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"),
        }
    }
}

impl APIConverter<FieldMetadata> for weedle::argument::SingleArgument<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FieldMetadata> {
        let type_ = ci.resolve_type_expression(&self.type_)?;
        if let Type::Object { .. } = type_ {
            bail!("Objects cannot currently be used in enum variant data");
        }
        if self.default.is_some() {
            bail!("enum interface variant fields must not have default values");
        }
        if self.attributes.is_some() {
            bail!("enum interface variant fields must not have attributes");
        }
        // TODO: maybe we should use our own `Field` type here with just name and type,
        // rather than appropriating record::Field..?
        Ok(FieldMetadata {
            name: self.identifier.0.to_string(),
            ty: type_,
            default: None,
            docstring: None,
        })
    }
}

impl APIConverter<FnParamMetadata> for weedle::argument::Argument<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnParamMetadata> {
        match self {
            weedle::argument::Argument::Single(t) => t.convert(ci),
            weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"),
        }
    }
}

impl APIConverter<FnParamMetadata> for weedle::argument::SingleArgument<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnParamMetadata> {
        let type_ = ci.resolve_type_expression(&self.type_)?;
        let default = match self.default {
            None => None,
            Some(v) => Some(convert_default_value(&v.value, &type_)?),
        };
        let by_ref = ArgumentAttributes::try_from(self.attributes.as_ref())?.by_ref();
        Ok(FnParamMetadata {
            name: self.identifier.0.to_string(),
            ty: type_,
            by_ref,
            optional: self.optional.is_some(),
            default,
        })
    }
}

impl APIConverter<FnMetadata> for weedle::namespace::NamespaceMember<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnMetadata> {
        match self {
            weedle::namespace::NamespaceMember::Operation(f) => f.convert(ci),
            _ => bail!("no support for namespace member type {:?} yet", self),
        }
    }
}

impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<FnMetadata> {
        let return_type = ci.resolve_return_type_expression(&self.return_type)?;
        let name = match self.identifier {
            None => bail!("anonymous functions are not supported {:?}", self),
            Some(id) => id.0.to_string(),
        };
        let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?;
        let is_async = attrs.is_async();
        let throws = match attrs.get_throws_err() {
            None => None,
            Some(name) => match ci.get_type(name) {
                Some(t) => Some(t),
                None => bail!("unknown type for error: {name}"),
            },
        };
        Ok(FnMetadata {
            module_path: ci.module_path(),
            name,
            is_async,
            return_type,
            inputs: self.args.body.list.convert(ci)?,
            throws,
            docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
            checksum: None,
        })
    }
}

impl APIConverter<ConstructorMetadata> for weedle::interface::ConstructorInterfaceMember<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<ConstructorMetadata> {
        let attributes = match &self.attributes {
            Some(attr) => ConstructorAttributes::try_from(attr)?,
            None => Default::default(),
        };
        let throws = attributes
            .get_throws_err()
            .map(|name| ci.get_type(name).expect("invalid throws type"));
        Ok(ConstructorMetadata {
            module_path: ci.module_path(),
            name: String::from(attributes.get_name().unwrap_or("new")),
            // We don't know the name of the containing `Object` at this point, fill it in later.
            self_name: Default::default(),
            is_async: attributes.is_async(),
            // Also fill in checksum_fn_name later, since it depends on object_name
            inputs: self.args.body.list.convert(ci)?,
            throws,
            checksum: None,
            docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
        })
    }
}

impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMember<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<MethodMetadata> {
        if self.special.is_some() {
            bail!("special operations not supported");
        }
        if self.modifier.is_some() {
            bail!("method modifiers are not supported")
        }
        let return_type = ci.resolve_return_type_expression(&self.return_type)?;
        let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
        let is_async = attributes.is_async();

        let throws = match attributes.get_throws_err() {
            Some(name) => match ci.get_type(name) {
                Some(t) => Some(t),
                None => bail!("unknown type for error: {name}"),
            },
            None => None,
        };

        let takes_self_by_arc = attributes.get_self_by_arc();
        Ok(MethodMetadata {
            module_path: ci.module_path(),
            name: match self.identifier {
                None => bail!("anonymous methods are not supported {:?}", self),
                Some(id) => {
                    let name = id.0.to_string();
                    if name == "new" {
                        bail!("the method name \"new\" is reserved for the default constructor");
                    }
                    name
                }
            },
            // We don't know the name of the containing `Object` at this point, fill it in later.
            self_name: Default::default(),
            is_async,
            inputs: self.args.body.list.convert(ci)?,
            return_type,
            throws,
            takes_self_by_arc,
            checksum: None,
            docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
        })
    }
}

impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterfaceMember<'_> {
    fn convert(&self, ci: &mut InterfaceCollector) -> Result<TraitMethodMetadata> {
        if self.special.is_some() {
            bail!("special operations not supported");
        }
        if self.modifier.is_some() {
            bail!("method modifiers are not supported")
        }
        let return_type = ci.resolve_return_type_expression(&self.return_type)?;
        let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
        let is_async = attributes.is_async();

        let throws = match attributes.get_throws_err() {
            Some(name) => match ci.get_type(name) {
                Some(t) => Some(t),
                None => bail!("unknown type for error: {name}"),
            },
            None => None,
        };

        let takes_self_by_arc = attributes.get_self_by_arc();
        Ok(TraitMethodMetadata {
            module_path: ci.module_path(),
            trait_name: Default::default(), // we'll fill these in later.
            index: Default::default(),
            name: match self.identifier {
                None => bail!("anonymous methods are not supported {:?}", self),
                Some(id) => {
                    let name = id.0.to_string();
                    if name == "new" {
                        bail!("the method name \"new\" is reserved for the default constructor");
                    }
                    name
                }
            },
            is_async,
            inputs: self.args.body.list.convert(ci)?,
            return_type,
            throws,
            takes_self_by_arc,
            checksum: None,
            docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
        })
    }
}
