Documentation
use crate::Path;
use serde::{Deserialize, Serialize};

use super::{HttpMethod, Operation, PathItem, PathItemBuilder, PathsMap};
use crate::openapi::{builder, extensions::Extensions, set_value};

builder! {
    PathsBuilder;

    #[non_exhaustive]
    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
    pub struct Paths {
        #[serde(flatten)]
        pub paths: PathsMap<String, PathItem>,
        #[serde(skip_serializing_if = "Option::is_none", flatten)]
        pub extensions: Option<Extensions>,
    }
}

impl Paths {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn get_path_item<P: AsRef<str>>(&self, path: P) -> Option<&PathItem> {
        self.paths.get(path.as_ref())
    }

    pub fn get_path_operation<P: AsRef<str>>(
        &self,
        path: P,
        http_method: HttpMethod,
    ) -> Option<&Operation> {
        self.paths
            .get(path.as_ref())
            .and_then(|path| path.operation(http_method))
    }

    pub fn add_path_operation<P: AsRef<str>, O: Into<Operation>>(
        &mut self,
        path: P,
        http_methods: Vec<HttpMethod>,
        operation: O,
    ) {
        let path = path.as_ref();
        let operation = operation.into();
        if let Some(existing_item) = self.paths.get_mut(path) {
            existing_item.set_operations(http_methods, operation);
        } else {
            self.paths.insert(
                String::from(path),
                PathItem::from_http_methods(http_methods, operation),
            );
        }
    }

    pub fn merge(&mut self, other_paths: Paths) {
        for (path, that) in other_paths.paths {
            if let Some(this) = self.paths.get_mut(&path) {
                this.merge_operations(that);
            } else {
                self.paths.insert(path, that);
            }
        }

        if let Some(other_paths_extensions) = other_paths.extensions {
            let paths_extensions = self.extensions.get_or_insert(Extensions::default());
            paths_extensions.merge(other_paths_extensions);
        }
    }
}

impl PathsBuilder {
    pub fn path<I: Into<String>>(mut self, path: I, item: PathItem) -> Self {
        let path_string = path.into();
        if let Some(existing_item) = self.paths.get_mut(&path_string) {
            existing_item.merge_operations(item);
        } else {
            self.paths.insert(path_string, item);
        }

        self
    }

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

    pub fn path_from<P: Path>(self) -> Self {
        let methods = P::methods();
        let operation = P::operation();
        let path_item = if methods.len() == 1 {
            PathItem::new(
                methods
                    .into_iter()
                    .next()
                    .expect("must have one operation method"),
                operation,
            )
        } else {
            methods
                .into_iter()
                .fold(PathItemBuilder::new(), |path_item, method| {
                    path_item.operation(method, operation.clone())
                })
                .build()
        };

        self.path(P::path(), path_item)
    }
}