Documentation
use serde::{Deserialize, Serialize};

use super::{HttpMethod, Operation, Parameter};
use crate::openapi::{Server, builder, extensions::Extensions, set_value};

builder! {
    PathItemBuilder;

    #[non_exhaustive]
    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct PathItem {
        #[serde(skip_serializing_if = "Option::is_none")]
        pub summary: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub description: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub servers: Option<Vec<Server>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub parameters: Option<Vec<Parameter>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub get: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub put: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub post: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub delete: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub options: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub head: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub patch: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none")]
        pub trace: Option<Operation>,
        #[serde(skip_serializing_if = "Option::is_none", flatten)]
        pub extensions: Option<Extensions>,
    }
}

impl PathItem {
    pub fn new<O: Into<Operation>>(http_method: HttpMethod, operation: O) -> Self {
        let mut path_item = Self::default();
        path_item.set_operation(http_method, operation.into());
        path_item
    }

    pub fn from_http_methods<I: IntoIterator<Item = HttpMethod>, O: Into<Operation>>(
        http_methods: I,
        operation: O,
    ) -> Self {
        let mut path_item = Self::default();
        path_item.set_operations(http_methods, operation.into());
        path_item
    }

    pub fn merge_operations(&mut self, path_item: PathItem) {
        merge_if_missing(&mut self.get, path_item.get);
        merge_if_missing(&mut self.put, path_item.put);
        merge_if_missing(&mut self.post, path_item.post);
        merge_if_missing(&mut self.delete, path_item.delete);
        merge_if_missing(&mut self.options, path_item.options);
        merge_if_missing(&mut self.head, path_item.head);
        merge_if_missing(&mut self.patch, path_item.patch);
        merge_if_missing(&mut self.trace, path_item.trace);
    }

    pub(crate) fn operation(&self, http_method: HttpMethod) -> Option<&Operation> {
        match http_method {
            HttpMethod::Get => self.get.as_ref(),
            HttpMethod::Put => self.put.as_ref(),
            HttpMethod::Post => self.post.as_ref(),
            HttpMethod::Delete => self.delete.as_ref(),
            HttpMethod::Options => self.options.as_ref(),
            HttpMethod::Head => self.head.as_ref(),
            HttpMethod::Patch => self.patch.as_ref(),
            HttpMethod::Trace => self.trace.as_ref(),
        }
    }

    pub(crate) fn set_operations<I: IntoIterator<Item = HttpMethod>>(
        &mut self,
        http_methods: I,
        operation: Operation,
    ) {
        for method in http_methods {
            self.set_operation(method, operation.clone());
        }
    }

    fn set_operation(&mut self, http_method: HttpMethod, operation: Operation) {
        match http_method {
            HttpMethod::Get => self.get = Some(operation),
            HttpMethod::Put => self.put = Some(operation),
            HttpMethod::Post => self.post = Some(operation),
            HttpMethod::Delete => self.delete = Some(operation),
            HttpMethod::Options => self.options = Some(operation),
            HttpMethod::Head => self.head = Some(operation),
            HttpMethod::Patch => self.patch = Some(operation),
            HttpMethod::Trace => self.trace = Some(operation),
        }
    }
}

impl PathItemBuilder {
    pub fn operation<O: Into<Operation>>(mut self, http_method: HttpMethod, operation: O) -> Self {
        self.set_operation(http_method, operation.into());
        self
    }

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

    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
        set_value!(self description description.map(|description| description.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 parameters<I: IntoIterator<Item = Parameter>>(mut self, parameters: Option<I>) -> Self {
        set_value!(self parameters parameters.map(|parameters| parameters.into_iter().collect()))
    }

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

    fn set_operation(&mut self, http_method: HttpMethod, operation: Operation) {
        match http_method {
            HttpMethod::Get => self.get = Some(operation),
            HttpMethod::Put => self.put = Some(operation),
            HttpMethod::Post => self.post = Some(operation),
            HttpMethod::Delete => self.delete = Some(operation),
            HttpMethod::Options => self.options = Some(operation),
            HttpMethod::Head => self.head = Some(operation),
            HttpMethod::Patch => self.patch = Some(operation),
            HttpMethod::Trace => self.trace = Some(operation),
        }
    }
}

fn merge_if_missing<T>(target: &mut Option<T>, source: Option<T>) {
    if target.is_none() {
        *target = source;
    }
}