use std::convert::TryFrom;
use std::fmt::Debug;
use lazy_static::lazy_static;
use regex::Regex;
use crate::model::*;
pub trait DocReference: Debug + Clone {}
pub fn doc<D: Into<DocString<Unvalidated>>>(brief: D) -> Doc<Unvalidated> {
    Doc {
        brief: brief.into(),
        details: Vec::new(),
    }
}
pub fn text(text: &str) -> DocString<Validated> {
    DocString {
        elements: vec![DocStringElement::Text(text.to_string())],
    }
}
pub fn brief(text: &str) -> Doc<Validated> {
    Doc {
        brief: DocString {
            elements: vec![DocStringElement::Text(text.to_string())],
        },
        details: Vec::new(),
    }
}
#[derive(Debug, Clone)]
pub struct Doc<T>
where
    T: DocReference,
{
    pub(crate) brief: DocString<T>,
    pub(crate) details: Vec<DocParagraph<T>>,
}
impl Doc<Validated> {
    #[must_use]
    pub fn warning(mut self, warning: &str) -> Self {
        self.details.push(DocParagraph::Warning(text(warning)));
        self
    }
}
impl Doc<Unvalidated> {
    pub(crate) fn validate(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
    ) -> BindResult<Doc<Validated>> {
        self.validate_with_args(symbol_name, lib, None)
    }
    pub(crate) fn validate_with_args(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
        args: Option<&[Name]>,
    ) -> BindResult<Doc<Validated>> {
        let details: BindResult<Vec<DocParagraph<Validated>>> = self
            .details
            .iter()
            .map(|x| x.validate_with_args(symbol_name, lib, args))
            .collect();
        Ok(Doc {
            brief: self.brief.validate_with_args(symbol_name, lib, args)?,
            details: details?,
        })
    }
    #[must_use]
    pub fn warning<D: Into<DocString<Unvalidated>>>(mut self, warning: D) -> Self {
        self.details.push(DocParagraph::Warning(warning.into()));
        self
    }
    #[must_use]
    pub fn details<D: Into<DocString<Unvalidated>>>(mut self, details: D) -> Self {
        self.details.push(DocParagraph::Details(details.into()));
        self
    }
}
impl<T: AsRef<str>> From<T> for Doc<Unvalidated> {
    fn from(from: T) -> Self {
        doc(from)
    }
}
pub(crate) struct OptionalDoc {
    parent_name: Name,
    inner: Option<Doc<Unvalidated>>,
}
impl OptionalDoc {
    pub(crate) fn new(parent_name: Name) -> Self {
        Self {
            parent_name,
            inner: None,
        }
    }
    pub(crate) fn set(&mut self, doc: Doc<Unvalidated>) -> BindResult<()> {
        match self.inner {
            None => {
                self.inner = Some(doc);
                Ok(())
            }
            Some(_) => Err(BindingErrorVariant::DocAlreadyDefined {
                symbol_name: self.parent_name.clone(),
            }
            .into()),
        }
    }
    pub(crate) fn extract(self) -> BindResult<Doc<Unvalidated>> {
        match self.inner {
            Some(doc) => Ok(doc),
            None => Err(BindingErrorVariant::DocNotDefined {
                symbol_name: self.parent_name.clone(),
            }
            .into()),
        }
    }
}
#[derive(Debug, Clone)]
pub(crate) enum DocParagraph<T>
where
    T: DocReference,
{
    Details(DocString<T>),
    Warning(DocString<T>),
}
impl DocParagraph<Unvalidated> {
    fn validate_with_args(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
        args: Option<&[Name]>,
    ) -> BindResult<DocParagraph<Validated>> {
        Ok(match self {
            DocParagraph::Details(x) => {
                DocParagraph::Details(x.validate_with_args(symbol_name, lib, args)?)
            }
            DocParagraph::Warning(x) => {
                DocParagraph::Warning(x.validate_with_args(symbol_name, lib, args)?)
            }
        })
    }
}
#[derive(Debug, Clone)]
pub struct DocString<T>
where
    T: DocReference,
{
    elements: Vec<DocStringElement<T>>,
}
impl DocString<Unvalidated> {
    pub(crate) fn validate(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
    ) -> BindResult<DocString<Validated>> {
        self.validate_with_args(symbol_name, lib, None)
    }
    pub(crate) fn validate_with_args(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
        args: Option<&[Name]>,
    ) -> BindResult<DocString<Validated>> {
        let elements: BindResult<Vec<DocStringElement<Validated>>> = self
            .elements
            .iter()
            .map(|x| x.validate(symbol_name, lib, args))
            .collect();
        Ok(DocString {
            elements: elements?,
        })
    }
}
impl<T> DocString<T>
where
    T: DocReference,
{
    pub(crate) fn new() -> Self {
        Self {
            elements: Vec::new(),
        }
    }
    pub(crate) fn push(&mut self, element: DocStringElement<T>) {
        self.elements.push(element);
    }
    pub(crate) fn elements(&self) -> impl Iterator<Item = &DocStringElement<T>> {
        self.elements.iter()
    }
}
impl<T> Default for DocString<T>
where
    T: DocReference,
{
    fn default() -> Self {
        DocString::new()
    }
}
impl<U: AsRef<str>> From<U> for DocString<Unvalidated> {
    fn from(from: U) -> DocString<Unvalidated> {
        let mut from = from.as_ref();
        let mut result = DocString::new();
        while let Some(start_idx) = from.find('{') {
            let (before_str, current_str) = from.split_at(start_idx);
            if let Some(end_idx) = current_str.find('}') {
                let (element_str, current_str) = current_str.split_at(end_idx + 1);
                let element = DocStringElement::try_from(element_str)
                    .expect("Invalid docstring: ill-formatted docstring element");
                if !before_str.is_empty() {
                    result.push(DocStringElement::Text(before_str.to_owned()));
                }
                result.push(element);
                from = current_str;
            } else {
                panic!("Invalid docstring: no end bracket");
            }
        }
        if !from.is_empty() {
            result.push(DocStringElement::Text(from.to_owned()));
        }
        result
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum DocStringElement<T>
where
    T: DocReference,
{
    Text(String),
    Null,
    Iterator,
    Reference(T),
}
impl DocStringElement<Unvalidated> {
    pub(crate) fn validate(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
        args: Option<&[Name]>,
    ) -> BindResult<DocStringElement<Validated>> {
        Ok(match self {
            DocStringElement::Text(x) => DocStringElement::Text(x.clone()),
            DocStringElement::Null => DocStringElement::Null,
            DocStringElement::Iterator => DocStringElement::Iterator,
            DocStringElement::Reference(x) => {
                DocStringElement::Reference(x.validate(symbol_name, lib, args)?)
            }
        })
    }
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Unvalidated {
    Argument(String),
    Class(String),
    ClassMethod(String, String),
    ClassConstructor(String),
    ClassDestructor(String),
    Struct(String),
    StructField(String, String),
    Enum(String),
    EnumVariant(String, String),
    Interface(String),
    InterfaceMethod(String, String),
}
impl DocReference for Unvalidated {}
impl Unvalidated {
    pub(crate) fn validate(
        &self,
        symbol_name: &Name,
        lib: &LibraryFields,
        args: Option<&[Name]>,
    ) -> BindResult<Validated> {
        match self {
            Self::Argument(name) => {
                let args = match args {
                    Some(args) => args,
                    None => {
                        return Err(BindingErrorVariant::DocInvalidArgumentContext {
                            symbol_name: symbol_name.to_string(),
                            ref_name: name.to_string(),
                        }
                        .into())
                    }
                };
                match args.iter().find(|arg| arg.as_ref() == name) {
                    Some(arg) => Ok(Validated::Argument(arg.clone())),
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: name.to_string(),
                    }
                    .into()),
                }
            }
            Self::Class(name) => match lib.find_class_declaration(name) {
                None => Err(BindingErrorVariant::DocInvalidReference {
                    symbol_name: symbol_name.to_string(),
                    ref_name: name.to_string(),
                }
                .into()),
                Some(x) => Ok(Validated::Class(x.clone())),
            },
            Self::ClassMethod(class_name, method_name) => {
                match lib
                    .find_class(class_name)
                    .and_then(|class| class.find_method(method_name).map(|func| (class, func)))
                {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{class_name}.{method_name}()"),
                    }
                    .into()),
                    Some((class, (name, function))) => {
                        Ok(Validated::ClassMethod(class.clone(), name, function))
                    }
                }
            }
            Self::ClassConstructor(class_name) => {
                match lib
                    .find_class(class_name)
                    .and_then(|x| x.constructor.clone().map(|d| (x.clone(), d)))
                {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{class_name}.[constructor]",),
                    }
                    .into()),
                    Some((class, constructor)) => {
                        Ok(Validated::ClassConstructor(class, constructor))
                    }
                }
            }
            Self::ClassDestructor(class_name) => {
                match lib
                    .find_class(class_name)
                    .and_then(|x| x.destructor.clone().map(|d| (x, d)))
                {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{class_name}.[destructor]"),
                    }
                    .into()),
                    Some((class, destructor)) => {
                        Ok(Validated::ClassDestructor(class.clone(), destructor))
                    }
                }
            }
            Self::Struct(struct_name) => match lib.find_struct(struct_name) {
                None => Err(BindingErrorVariant::DocInvalidReference {
                    symbol_name: symbol_name.to_string(),
                    ref_name: struct_name.to_string(),
                }
                .into()),
                Some(x) => Ok(Validated::Struct(x.clone())),
            },
            Self::StructField(struct_name, field_name) => {
                match lib
                    .find_struct(struct_name)
                    .and_then(|st| st.find_field_name(field_name).map(|n| (st, n)))
                {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{struct_name}.{field_name}"),
                    }
                    .into()),
                    Some((st, name)) => Ok(Validated::StructField(st.clone(), name)),
                }
            }
            Self::Enum(enum_name) => match lib.find_enum(enum_name) {
                None => Err(BindingErrorVariant::DocInvalidReference {
                    symbol_name: symbol_name.to_string(),
                    ref_name: enum_name.to_string(),
                }
                .into()),
                Some(x) => Ok(Validated::Enum(x.clone())),
            },
            Self::EnumVariant(enum_name, variant_name) => {
                match lib.find_enum(enum_name).and_then(|e| {
                    e.find_variant_by_name(variant_name)
                        .map(|v| (e, v.name.clone()))
                }) {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{enum_name}.{variant_name}"),
                    }
                    .into()),
                    Some((e, v)) => Ok(Validated::EnumVariant(e.clone(), v)),
                }
            }
            Self::Interface(interface_name) => match lib.find_interface(interface_name) {
                None => Err(BindingErrorVariant::DocInvalidReference {
                    symbol_name: symbol_name.to_string(),
                    ref_name: interface_name.to_string(),
                }
                .into()),
                Some(x) => Ok(Validated::Interface(x.clone())),
            },
            Self::InterfaceMethod(interface_name, method_name) => {
                match lib
                    .find_interface(interface_name)
                    .and_then(|i| i.find_callback(method_name).map(|m| (i, m)))
                {
                    None => Err(BindingErrorVariant::DocInvalidReference {
                        symbol_name: symbol_name.to_string(),
                        ref_name: format!("{interface_name}.{method_name}()"),
                    }
                    .into()),
                    Some((i, m)) => Ok(Validated::InterfaceMethod(i.clone(), m.name.clone())),
                }
            }
        }
    }
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Validated {
    Argument(Name),
    Class(ClassDeclarationHandle),
    ClassMethod(
        Handle<Class<Unvalidated>>,
        Name,
        Handle<Function<Unvalidated>>,
    ),
    ClassConstructor(Handle<Class<Unvalidated>>, ClassConstructor<Unvalidated>),
    ClassDestructor(Handle<Class<Unvalidated>>, ClassDestructor<Unvalidated>),
    Struct(StructType<Unvalidated>),
    StructField(StructType<Unvalidated>, Name),
    Enum(Handle<Enum<Unvalidated>>),
    EnumVariant(Handle<Enum<Unvalidated>>, Name),
    Interface(Handle<Interface<Unvalidated>>),
    InterfaceMethod(Handle<Interface<Unvalidated>>, Name),
}
impl DocReference for Validated {}
impl TryFrom<&str> for DocStringElement<Unvalidated> {
    type Error = BindingError;
    fn try_from(from: &str) -> Result<DocStringElement<Unvalidated>, BindingError> {
        lazy_static! {
            static ref RE_PARAM: Regex = Regex::new(r"\{param:([[:word:]]+)\}").unwrap();
            static ref RE_CLASS: Regex = Regex::new(r"\{class:([[:word:]]+)\}").unwrap();
            static ref RE_CLASS_METHOD: Regex =
                Regex::new(r"\{class:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
            static ref RE_CLASS_CONSTRUCTOR: Regex =
                Regex::new(r"\{class:([[:word:]]+)\.\[constructor\]\}").unwrap();
            static ref RE_CLASS_DESTRUCTOR: Regex =
                Regex::new(r"\{class:([[:word:]]+)\.\[destructor\]\}").unwrap();
            static ref RE_STRUCT: Regex = Regex::new(r"\{struct:([[:word:]]+)\}").unwrap();
            static ref RE_STRUCT_ELEMENT: Regex =
                Regex::new(r"\{struct:([[:word:]]+)\.([[:word:]]+)\}").unwrap();
            static ref RE_STRUCT_METHOD: Regex =
                Regex::new(r"\{struct:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
            static ref RE_ENUM: Regex = Regex::new(r"\{enum:([[:word:]]+)\}").unwrap();
            static ref RE_ENUM_VARIANT: Regex =
                Regex::new(r"\{enum:([[:word:]]+)\.([[:word:]]+)\}").unwrap();
            static ref RE_INTERFACE: Regex = Regex::new(r"\{interface:([[:word:]]+)\}").unwrap();
            static ref RE_INTERFACE_METHOD: Regex =
                Regex::new(r"\{interface:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
        }
        fn try_get_regex(from: &str) -> Option<Unvalidated> {
            if let Some(capture) = RE_PARAM.captures(from) {
                return Some(Unvalidated::Argument(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_CLASS.captures(from) {
                return Some(Unvalidated::Class(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_CLASS_METHOD.captures(from) {
                return Some(Unvalidated::ClassMethod(
                    capture.get(1).unwrap().as_str().to_owned(),
                    capture.get(2).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_CLASS_CONSTRUCTOR.captures(from) {
                return Some(Unvalidated::ClassConstructor(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_CLASS_DESTRUCTOR.captures(from) {
                return Some(Unvalidated::ClassDestructor(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_STRUCT.captures(from) {
                return Some(Unvalidated::Struct(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_STRUCT_ELEMENT.captures(from) {
                return Some(Unvalidated::StructField(
                    capture.get(1).unwrap().as_str().to_owned(),
                    capture.get(2).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_ENUM.captures(from) {
                return Some(Unvalidated::Enum(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_ENUM_VARIANT.captures(from) {
                return Some(Unvalidated::EnumVariant(
                    capture.get(1).unwrap().as_str().to_owned(),
                    capture.get(2).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_INTERFACE.captures(from) {
                return Some(Unvalidated::Interface(
                    capture.get(1).unwrap().as_str().to_owned(),
                ));
            }
            if let Some(capture) = RE_INTERFACE_METHOD.captures(from) {
                return Some(Unvalidated::InterfaceMethod(
                    capture.get(1).unwrap().as_str().to_owned(),
                    capture.get(2).unwrap().as_str().to_owned(),
                ));
            }
            None
        }
        if let Some(x) = try_get_regex(from) {
            return Ok(DocStringElement::Reference(x));
        }
        if from == "{null}" {
            return Ok(DocStringElement::Null);
        }
        if from == "{iterator}" {
            return Ok(DocStringElement::Iterator);
        }
        Err(BindingErrorVariant::InvalidDocString.into())
    }
}
#[cfg(test)]
mod tests {
    use std::convert::TryInto;
    use super::*;
    #[test]
    fn parse_param_reference() {
        let doc: DocString<Unvalidated> = "This is a {param:foo} test.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::Argument("foo".to_owned())),
                DocStringElement::Text(" test.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_class_reference() {
        let doc: DocString<Unvalidated> = "This is a {class:MyClass} test.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::Class("MyClass".to_owned())),
                DocStringElement::Text(" test.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_class_reference_at_the_end() {
        let doc: DocString<Unvalidated> = "This is a test {class:MyClass2}".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a test ".to_owned()),
                DocStringElement::Reference(Unvalidated::Class("MyClass2".to_owned())),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_class_method() {
        let doc: DocString<Unvalidated> = "This is a {class:MyClass.do_something()} method."
            .try_into()
            .unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::ClassMethod(
                    "MyClass".to_owned(),
                    "do_something".to_owned()
                )),
                DocStringElement::Text(" method.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_struct() {
        let doc: DocString<Unvalidated> = "This is a {struct:MyStruct} struct.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::Struct("MyStruct".to_owned())),
                DocStringElement::Text(" struct.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_struct_element() {
        let doc: DocString<Unvalidated> = "This is a {struct:MyStruct.foo} struct element."
            .try_into()
            .unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::StructField(
                    "MyStruct".to_owned(),
                    "foo".to_owned()
                )),
                DocStringElement::Text(" struct element.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_enum() {
        let doc: DocString<Unvalidated> = "This is a {enum:MyEnum} enum.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::Enum("MyEnum".to_owned())),
                DocStringElement::Text(" enum.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_enum_element() {
        let doc: DocString<Unvalidated> = "This is a {enum:MyEnum.foo} enum variant."
            .try_into()
            .unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::EnumVariant(
                    "MyEnum".to_owned(),
                    "foo".to_owned()
                )),
                DocStringElement::Text(" enum variant.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_interface() {
        let doc: DocString<Unvalidated> = "This is a {interface:Interface} interface."
            .try_into()
            .unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::Interface("Interface".to_owned())),
                DocStringElement::Text(" interface.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_interface_method() {
        let doc: DocString<Unvalidated> = "This is a {interface:Interface.foo()} interface method."
            .try_into()
            .unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Reference(Unvalidated::InterfaceMethod(
                    "Interface".to_owned(),
                    "foo".to_owned()
                )),
                DocStringElement::Text(" interface method.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_null() {
        let doc: DocString<Unvalidated> = "This is a {null} null.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Null,
                DocStringElement::Text(" null.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_iterator() {
        let doc: DocString<Unvalidated> = "This is a {iterator} iterator.".try_into().unwrap();
        assert_eq!(
            [
                DocStringElement::Text("This is a ".to_owned()),
                DocStringElement::Iterator,
                DocStringElement::Text(" iterator.".to_owned()),
            ]
            .as_ref(),
            doc.elements.as_slice()
        );
    }
    #[test]
    fn parse_from_owned_string() {
        doc(format!("{{null}} this is a {}", "test"));
    }
}