Documentation
use serde::{Deserialize, Serialize};

use super::{
    Components, ExternalDocs, Info, OpenApiVersion, Paths, SecurityRequirement, Server, Tag,
    builder, extensions::Extensions, path::PathsMap, set_value,
};

builder! {
    OpenApiBuilder;

    #[non_exhaustive]
    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct OpenApi {
        pub openapi: OpenApiVersion,
        pub info: Info,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub servers: Option<Vec<Server>>,
        pub paths: Paths,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub components: Option<Components>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub security: Option<Vec<SecurityRequirement>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub tags: Option<Vec<Tag>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub external_docs: Option<ExternalDocs>,
        #[serde(rename = "$schema", default, skip_serializing_if = "String::is_empty")]
        pub schema: String,
        #[serde(skip_serializing_if = "Option::is_none", flatten)]
        pub extensions: Option<Extensions>,
    }
}

impl OpenApi {
    pub fn new<P: Into<Paths>>(info: Info, paths: P) -> Self {
        Self {
            info,
            paths: paths.into(),
            ..Default::default()
        }
    }

    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string(self)
    }

    pub fn to_pretty_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(self)
    }

    pub fn merge_from(mut self, other: OpenApi) -> OpenApi {
        self.merge(other);
        self
    }

    pub fn merge(&mut self, mut other: OpenApi) {
        merge_servers(&mut self.servers, &mut other.servers);
        if !other.paths.paths.is_empty() {
            self.paths.merge(other.paths);
        }
        merge_components(&mut self.components, &mut other.components);
        merge_vec(&mut self.security, &mut other.security);
        merge_vec(&mut self.tags, &mut other.tags);
    }

    pub fn nest<P: Into<String>, O: Into<OpenApi>>(self, path: P, other: O) -> Self {
        self.nest_with_path_composer(path, other, |base, path| format!("{base}{path}"))
    }

    pub fn nest_with_path_composer<
        P: Into<String>,
        O: Into<OpenApi>,
        F: Fn(&str, &str) -> String,
    >(
        mut self,
        path: P,
        other: O,
        composer: F,
    ) -> Self {
        let path: String = path.into();
        let mut other_api: OpenApi = other.into();
        let nested_paths = other_api
            .paths
            .paths
            .into_iter()
            .map(|(item_path, item)| (composer(&path, &item_path), item))
            .collect::<PathsMap<_, _>>();
        self.paths.paths.extend(nested_paths);
        other_api.paths.paths = PathsMap::new();
        self.merge_from(other_api)
    }
}

impl OpenApiBuilder {
    pub fn info<I: Into<Info>>(mut self, info: I) -> Self {
        set_value!(self info info.into())
    }

    pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
        set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
    }

    pub fn paths<P: Into<Paths>>(mut self, paths: P) -> Self {
        set_value!(self paths paths.into())
    }

    pub fn components(mut self, components: Option<Components>) -> Self {
        set_value!(self components components)
    }

    pub fn security<I: IntoIterator<Item = SecurityRequirement>>(
        mut self,
        security: Option<I>,
    ) -> Self {
        set_value!(self security security.map(|security| security.into_iter().collect()))
    }

    pub fn tags<I: IntoIterator<Item = Tag>>(mut self, tags: Option<I>) -> Self {
        set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
    }

    pub fn external_docs(mut self, external_docs: Option<ExternalDocs>) -> Self {
        set_value!(self external_docs external_docs)
    }

    pub fn schema<S: Into<String>>(mut self, schema: S) -> Self {
        set_value!(self schema schema.into())
    }
}

fn merge_servers(target: &mut Option<Vec<Server>>, source: &mut Option<Vec<Server>>) {
    if let Some(other_servers) = source {
        let servers = target.get_or_insert(Vec::new());
        other_servers.retain(|server| !servers.contains(server));
        servers.append(other_servers);
    }
}

fn merge_components(target: &mut Option<Components>, source: &mut Option<Components>) {
    if let Some(other_components) = source {
        let components = target.get_or_insert(Components::default());
        other_components
            .schemas
            .retain(|name, _| !components.schemas.contains_key(name));
        components.schemas.append(&mut other_components.schemas);
        other_components
            .responses
            .retain(|name, _| !components.responses.contains_key(name));
        components.responses.append(&mut other_components.responses);
        other_components
            .security_schemes
            .retain(|name, _| !components.security_schemes.contains_key(name));
        components
            .security_schemes
            .append(&mut other_components.security_schemes);
    }
}

fn merge_vec<T: PartialEq>(target: &mut Option<Vec<T>>, source: &mut Option<Vec<T>>) {
    if let Some(other_items) = source {
        let items = target.get_or_insert(Vec::new());
        other_items.retain(|item| !items.contains(item));
        items.append(other_items);
    }
}