use std::{
    fs::{File, read_to_string},
    io::Write,
};
use yaml_rust::{Yaml, YamlLoader};
const OPENAI_YML_FILE_PATH: &str = "./openapi.documented.yml";
fn camel_to_snake(s: &str) -> String {
    let mut snake = String::new();
    for (i, ch) in s.char_indices() {
        if ch.is_uppercase() {
            if i != 0 {
                snake.push('_');
            }
            snake.extend(ch.to_lowercase());
        } else {
            snake.push(ch);
        }
    }
    snake
}
fn str_to_camel_case(s: &str) -> String {
    let mut camel = String::new();
    let mut next_is_uppercase = false;
    for (i, ch) in s.char_indices() {
        if ch == '[' || ch == ']' {
            continue;
        }
        if i == 0 {
            if ch.is_numeric() {
                camel.push_str("Size");
                camel.push(ch);
            } else if ch == '/' {
                next_is_uppercase = true;
            } else {
                camel.extend(ch.to_uppercase())
            }
        } else if ch == '_' || ch == '.' || ch == '-' || ch == '/' {
            next_is_uppercase = true;
        } else if next_is_uppercase {
            camel.extend(ch.to_uppercase());
            next_is_uppercase = false;
        } else {
            camel.push(ch);
        }
    }
    camel
}
fn get_object_name_from_reference(reference: &str) -> &str {
    let i = reference.rfind("/").unwrap();
    &reference[i + 1..]
}
fn str_to_snake_case(s: &str) -> String {
    let mut snake = String::new();
    for (i, ch) in s.char_indices() {
        if ch == '_' || ch == '.' || ch == '-' || ch == '/' {
            snake.push('_');
        } else {
            if ch.is_uppercase() && i != 0 {
                snake.push('_');
            }
            snake.extend(ch.to_lowercase())
        }
    }
    snake
}
fn generate_inner_object_name(object_name: &str, field_name: &str) -> String {
    let camel_field_name = str_to_camel_case(field_name);
    format!("{object_name}{camel_field_name}",)
}
fn parse_string_enum(name: &str, schema: &Yaml, output_file: &mut File) {
    let enum_items = schema
        .as_hash()
        .unwrap()
        .get(&Yaml::String("enum".to_string()))
        .unwrap()
        .as_vec()
        .unwrap();
    writeln!(
        output_file,
        "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
    )
    .unwrap();
    writeln!(output_file, "pub enum {name} {{",).unwrap();
    for item in enum_items {
        writeln!(
            output_file,
            "\t#[serde(rename=\"{}\")]",
            item.as_str().unwrap()
        )
        .unwrap();
        writeln!(
            output_file,
            "\t{},",
            str_to_camel_case(item.as_str().unwrap())
        )
        .unwrap();
    }
    writeln!(output_file, "}}\n").unwrap();
}
fn parse_typedef_type(name: &str, schema: &Yaml, output_file: &mut File) {
    if let Some(doc) = schema["description"].as_str() {
        writeln!(
            output_file,
            "/** {} */",
            doc.trim_end().replace("```", "***")
        )
        .unwrap();
    }
    let type_name = schema["type"].as_str().unwrap();
    match type_name {
        "string" => {
            if let Some("map") = schema["x-oaiTypeLabel"].as_str() {
                writeln!(output_file, "pub type {name} = HashMap<String, String>;\n",).unwrap()
            } else {
                writeln!(output_file, "pub type {name} = String;\n",).unwrap()
            }
        }
        "array" => {
            let array_items = schema["items"].as_hash().unwrap();
            if let Some(typeref) = array_items.get(&Yaml::String("$ref".to_string())) {
                writeln!(
                    output_file,
                    "pub type {} = Vec<{}>;\n",
                    name,
                    get_object_name_from_reference(typeref.as_str().unwrap())
                )
                .unwrap();
            } else if array_items
                .get(&Yaml::String("type".to_string()))
                .unwrap()
                .as_str()
                .unwrap()
                == "string"
            {
                writeln!(output_file, "pub type {name} = Vec<String>;\n",).unwrap();
            }
        }
        "boolean" => writeln!(output_file, "pub type {name} = bool;\n",).unwrap(),
        _ => unimplemented!("{}", type_name),
    }
}
fn parse_object_type(name: &str, schema: &Yaml, output_file: &mut File) {
    let schema_map = schema.as_hash().unwrap();
    if let Some(schema_properties) = schema_map.get(&Yaml::String("properties".to_string())) {
        let schema_properties_map = schema_properties.as_hash().unwrap();
                for (property_name, property_value) in schema_properties_map {
            let property_name = property_name.as_str().unwrap();
            let property_hash = property_value.as_hash().unwrap();
            if let Some(property_type) = property_hash.get(&Yaml::String("type".to_string())) {
                if property_type == &Yaml::String("object".to_string()) {
                    parse_object_type(
                        &generate_inner_object_name(name, property_name),
                        property_value,
                        output_file,
                    )
                } else if property_type == &Yaml::String("string".to_string()) {
                    if property_hash
                        .get(&Yaml::String("enum".to_string()))
                        .is_some()
                    {
                        parse_string_enum(
                            &generate_inner_object_name(name, property_name),
                            property_value,
                            output_file,
                        );
                    }
                } else if property_type == &Yaml::String("array".to_string()) {
                    let property_items = property_hash
                        .get(&Yaml::String("items".to_string()))
                        .unwrap();
                    let items_hash = property_items.as_hash().unwrap();
                    if items_hash.get(&Yaml::String("type".to_string()))
                        == Some(&Yaml::String("object".to_string()))
                    {
                        parse_object_type(
                            &generate_inner_object_name(name, property_name),
                            property_items,
                            output_file,
                        );
                    } else if items_hash.get(&Yaml::String("oneOf".to_string())).is_some() {
                        parse_oneof_type(
                            &generate_inner_object_name(name, property_name),
                            property_items,
                            output_file,
                        );
                    } else if items_hash.get(&Yaml::String("allOf".to_string())).is_some() {
                        parse_allof_type(
                            &generate_inner_object_name(name, property_name),
                            property_items,
                            output_file,
                        );
                    }
                }
            } else if property_hash
                .get(&Yaml::String("oneOf".to_string()))
                .is_some()
                || property_hash
                    .get(&Yaml::String("anyOf".to_string()))
                    .is_some()
            {
                parse_oneof_type(
                    &generate_inner_object_name(name, property_name),
                    property_value,
                    output_file,
                );
            } else if property_hash
                .get(&Yaml::String("allOf".to_string()))
                .is_some()
            {
                parse_allof_type(
                    &generate_inner_object_name(name, property_name),
                    property_value,
                    output_file,
                );
            }
        }
        if let Some(doc) = schema_map
            .get(&Yaml::String("description".to_string()))
            .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
        {
            writeln!(output_file, "/** {doc} */",).unwrap();
        }
        writeln!(
            output_file,
            "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
        )
        .unwrap();
        writeln!(output_file, "pub struct {name} {{",).unwrap();
        let object_required_list = schema_map
            .get(&Yaml::String("required".to_string()))
            .map(|s| s.as_vec().unwrap().clone())
            .unwrap_or_default();
        for (property_name, property_value) in schema_properties_map {
            let mut field_name = property_name.as_str().unwrap().to_string();
            let property_hash = property_value.as_hash().unwrap();
            let field_type = if let Some(field_type_yaml) =
                property_hash.get(&Yaml::String("type".to_string()))
            {
                let field_type_str = field_type_yaml.as_str().unwrap();
                match field_type_str {
                    "string" => {
                        if property_hash
                            .get(&Yaml::String("enum".to_string()))
                            .is_some()
                        {
                            generate_inner_object_name(name, &field_name)
                        } else if property_hash.get(&Yaml::String("format".to_string()))
                            == Some(&Yaml::String("binary".to_string()))
                        {
                            if field_name == "file" {
                                "crate::multipart::File".to_string()
                            } else {
                                "Vec<u8>".to_string()
                            }
                        } else {
                            "String".to_string()
                        }
                    }
                    "integer" => "u64".to_string(),
                    "object" => generate_inner_object_name(name, &field_name),
                    "array" => {
                        let items_hash = property_hash
                            .get(&Yaml::String("items".to_string()))
                            .unwrap()
                            .as_hash()
                            .unwrap();
                        if let Some(item_type) = items_hash.get(&Yaml::String("type".to_string())) {
                            let item_type = item_type.as_str().unwrap();
                            match item_type {
                                "string" => "Vec<String>".to_string(),
                                "integer" => "Vec<u64>".to_string(),
                                "number" => "Vec<f32>".to_string(),
                                "object" => {
                                    format!(
                                        "Vec<{}>",
                                        generate_inner_object_name(name, &field_name)
                                    )
                                }
                                _ => unimplemented!("Array variant with type {}", item_type),
                            }
                        } else if let Some(item_ref) =
                            items_hash.get(&Yaml::String("$ref".to_string()))
                        {
                            format!(
                                "Vec<{}>",
                                get_object_name_from_reference(item_ref.as_str().unwrap())
                            )
                        } else if items_hash.get(&Yaml::String("oneOf".to_string())).is_some()
                            || items_hash.get(&Yaml::String("allOf".to_string())).is_some()
                        {
                            format!("Vec<{}>", generate_inner_object_name(name, &field_name))
                        } else {
                            unimplemented!()
                        }
                    }
                    "boolean" => "bool".to_string(),
                    "number" => "f32".to_string(),
                    _ => unimplemented!("Object {} with field type {}", name, field_type_str),
                }
            } else if let Some(field_ref) = property_hash.get(&Yaml::String("$ref".to_string())) {
                get_object_name_from_reference(field_ref.as_str().unwrap()).to_string()
            } else if property_hash
                .get(&Yaml::String("oneOf".to_string()))
                .is_some()
                || property_hash
                    .get(&Yaml::String("anyOf".to_string()))
                    .is_some()
                || property_hash
                    .get(&Yaml::String("allOf".to_string()))
                    .is_some()
            {
                generate_inner_object_name(name, property_name.as_str().unwrap()).to_string()
            } else {
                unimplemented!("{:?} {:?}", property_name, property_value)
            };
            if field_name == "type" {
                writeln!(output_file, "\t#[serde(rename=\"{field_name}\")]",).unwrap();
                field_name = "r#type".to_string();
            } else if field_name == "static" {
                writeln!(output_file, "\t#[serde(rename=\"{field_name}\")]",).unwrap();
                field_name = "r#static".to_string();
            } else if field_name.contains('.') {
                writeln!(output_file, "\t#[serde(rename=\"{field_name}\")]",).unwrap();
                field_name = field_name.replace('.', "_");
            } else if field_name.contains("[]") {
                writeln!(output_file, "\t#[serde(rename=\"{field_name}\")]",).unwrap();
                field_name = field_name.replace("[]", "");
            }
            if let Some(doc) = property_hash
                .get(&Yaml::String("description".to_string()))
                .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
            {
                writeln!(output_file, "\t/** {doc} */",).unwrap();
            }
            let is_nullable = if let Some(Yaml::Boolean(n)) =
                property_hash.get(&Yaml::String("nullable".to_string()))
            {
                *n
            } else {
                false
            };
            if object_required_list.contains(property_name) && !is_nullable {
                if field_name.contains(['-', '/']) {
                    writeln!(output_file, "\t#[serde(rename = \"{field_name}\")]",).unwrap();
                    writeln!(
                        output_file,
                        "\tpub {}: {field_type},",
                        str_to_snake_case(&field_name),
                    )
                    .unwrap();
                } else {
                    writeln!(output_file, "\tpub {field_name}: {field_type},",).unwrap();
                }
            } else {
                writeln!(
                    output_file,
                    "\t#[serde(skip_serializing_if = \"Option::is_none\")]"
                )
                .unwrap();
                if field_name.contains(['-', '/']) {
                    writeln!(output_file, "\t#[serde(rename = \"{field_name}\")]",).unwrap();
                    writeln!(
                        output_file,
                        "\tpub {}: Option<{field_type}>,",
                        str_to_snake_case(&field_name),
                    )
                    .unwrap();
                } else {
                    writeln!(output_file, "\tpub {field_name}: Option<{field_type}>,",).unwrap();
                }
            }
        }
        writeln!(output_file, "}}\n").unwrap();
    } else {
                if let Some(doc) = schema_map
            .get(&Yaml::String("description".to_string()))
            .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
        {
            writeln!(output_file, "\t/** {doc} */",).unwrap();
        }
        writeln!(
            output_file,
            "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
        )
        .unwrap();
        if let Some(type_label) = schema_map.get(&Yaml::String("x-oaiTypeLabel".to_string())) {
            let type_label_str = type_label.as_str().unwrap();
            match type_label_str {
                "map" => {
                    writeln!(output_file, "pub struct {name}(pub serde_json::Value);\n",).unwrap()
                }
                _ => unimplemented!("{name} with type label {type_label:?}",),
            }
        } else {
            writeln!(output_file, "pub struct {name}(pub String);\n",).unwrap();
        }
    }
}
fn parse_oneof_type(name: &str, schema: &Yaml, output_file: &mut File) {
    let schema_map = schema.as_hash().unwrap();
    let one_of_list = schema_map
        .get(&Yaml::String("oneOf".to_string()))
        .or(schema_map.get(&Yaml::String("anyOf".to_string())))
        .unwrap()
        .as_vec()
        .unwrap();
        for one_of_variant in one_of_list {
        let one_of_variant_hash = one_of_variant.as_hash().unwrap();
        if one_of_variant_hash.get(&Yaml::String("type".to_string()))
            == Some(&Yaml::String("object".to_string()))
        {
            if let Some(Yaml::Hash(schema_properties)) =
                one_of_variant_hash.get(&Yaml::String("properties".to_string()))
            {
                for (property_name, property_value) in schema_properties {
                    let property_hash = property_value.as_hash().unwrap();
                    if property_hash.get(&Yaml::String("type".to_string()))
                        == Some(&Yaml::String("array".to_string()))
                    {
                        let property_items = property_hash
                            .get(&Yaml::String("items".to_string()))
                            .unwrap();
                        if property_items
                            .as_hash()
                            .unwrap()
                            .get(&Yaml::String("oneOf".to_string()))
                            .is_some()
                        {
                            parse_oneof_type(
                                &generate_inner_object_name(name, property_name.as_str().unwrap()),
                                property_items,
                                output_file,
                            );
                        }
                    } else if property_hash.get(&Yaml::String("type".to_string()))
                        == Some(&Yaml::String("object".to_string()))
                    {
                        parse_object_type(
                            &generate_inner_object_name(name, property_name.as_str().unwrap()),
                            property_value,
                            output_file,
                        );
                    }
                }
            }
        } else if one_of_variant_hash.get(&Yaml::String("type".to_string()))
            == Some(&Yaml::String("array".to_string()))
        {
            let property_items = one_of_variant_hash
                .get(&Yaml::String("items".to_string()))
                .unwrap();
            if property_items
                .as_hash()
                .unwrap()
                .get(&Yaml::String("oneOf".to_string()))
                .is_some()
            {
                parse_oneof_type(
                    &generate_inner_object_name(name, "Array"),
                    property_items,
                    output_file,
                );
            }
        }
    }
    if let Some(doc) = schema_map
        .get(&Yaml::String("description".to_string()))
        .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
    {
        writeln!(output_file, "/** {doc} */",).unwrap();
    }
    writeln!(
        output_file,
        "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
    )
    .unwrap();
    writeln!(output_file, "#[serde(untagged)]").unwrap();
    writeln!(output_file, "pub enum {name} {{",).unwrap();
    let mut string_enum_already_processed = false;
    for (index, one_of_variant) in one_of_list.iter().enumerate() {
        let one_of_variant_hash = one_of_variant.as_hash().unwrap();
        if let Some(doc) = one_of_variant_hash
            .get(&Yaml::String("description".to_string()))
            .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
        {
            writeln!(output_file, "\t/** {doc} */",).unwrap();
        }
        if let Some(variant_ref) = one_of_variant_hash.get(&Yaml::String("$ref".to_string())) {
            let variant_name = get_object_name_from_reference(variant_ref.as_str().unwrap());
            writeln!(output_file, "\t{variant_name}({variant_name}),",).unwrap();
        } else if one_of_variant_hash
            .get(&Yaml::String("$recursiveRef".to_string()))
            .is_some()
        {
                                    writeln!(output_file, "\tRecursive(CompoundFilter),").unwrap();
        } else if let Some(Yaml::String(variant_type)) =
            one_of_variant_hash.get(&Yaml::String("type".to_string()))
        {
            match variant_type.as_str() {
                "string" => {
                    if !string_enum_already_processed {
                        writeln!(output_file, "\tString(String),").unwrap();
                        string_enum_already_processed = true;
                    }
                }
                "integer" => {
                    writeln!(output_file, "\tInteger(u64),").unwrap();
                }
                "number" => {
                    writeln!(output_file, "\tNumber(f32),").unwrap();
                }
                "boolean" => {
                    writeln!(output_file, "\tBoolean(bool),").unwrap();
                }
                "null" => {
                    writeln!(output_file, "\tNone,").unwrap();
                }
                "object" => {
                    let variant_title = if let Some(t) =
                        one_of_variant_hash.get(&Yaml::String("title".to_string()))
                    {
                        t.as_str().unwrap().to_string().replace(" ", "")
                    } else {
                        (char::from(b'A' + index as u8)).to_string()
                    };
                    let schema_properties = if let Some(s) =
                        one_of_variant_hash.get(&Yaml::String("properties".to_string()))
                    {
                        s
                    } else {
                                                writeln!(output_file, "\tMap(String),").unwrap();
                        continue;
                    };
                    writeln!(output_file, "\t{variant_title} {{",).unwrap();
                    let schema_properties_map = schema_properties.as_hash().unwrap();
                    for (property_name, property_value) in schema_properties_map {
                        let mut property_name = property_name.as_str().unwrap();
                        let property_hash = property_value.as_hash().unwrap();
                        let property_type = if let Some(Yaml::String(property_type)) =
                            property_hash.get(&Yaml::String("type".to_string()))
                        {
                            match property_type.as_str() {
                                "string" => "String".to_string(),
                                "boolean" => "bool".to_string(),
                                "array" => {
                                    let items_hash = property_hash
                                        .get(&Yaml::String("items".to_string()))
                                        .unwrap()
                                        .as_hash()
                                        .unwrap();
                                    if let Some(Yaml::String(item_type)) =
                                        items_hash.get(&Yaml::String("type".to_string()))
                                    {
                                        match item_type.as_str() {
                                            "string" => "Vec<String>".to_string(),
                                            _ => unimplemented!("{}", item_type),
                                        }
                                    } else if items_hash
                                        .get(&Yaml::String("oneOf".to_string()))
                                        .is_some()
                                    {
                                        format!(
                                            "Vec<{}>",
                                            generate_inner_object_name(name, property_name)
                                        )
                                    } else {
                                        unimplemented!("{:?}", items_hash)
                                    }
                                }
                                "object" => generate_inner_object_name(name, property_name),
                                _ => unimplemented!("{:?}", property_type),
                            }
                        } else if let Some(property_ref) =
                            property_hash.get(&Yaml::String("$ref".to_string()))
                        {
                            get_object_name_from_reference(property_ref.as_str().unwrap())
                                .to_string()
                        } else {
                            unimplemented!(
                                "Variant object {:?} from {:?} with properties {:?}",
                                property_name,
                                name,
                                property_hash
                            )
                        };
                        if property_name == "type" {
                            writeln!(output_file, "\t\t#[serde(rename=\"{property_name}\")]",)
                                .unwrap();
                            property_name = "r#type";
                        }
                        writeln!(output_file, "\t\t{property_name}: {property_type},",).unwrap();
                    }
                    writeln!(output_file, "\t}},",).unwrap();
                }
                "array" => {
                    let items_hash = one_of_variant_hash
                        .get(&Yaml::String("items".to_string()))
                        .unwrap()
                        .as_hash()
                        .unwrap();
                    if let Some(Yaml::String(item_type)) =
                        items_hash.get(&Yaml::String("type".to_string()))
                    {
                        match item_type.as_str() {
                            "string" => {
                                writeln!(output_file, "\tArrayString(Vec<String>),").unwrap()
                            }
                            "integer" => writeln!(output_file, "\tArrayNumber(Vec<u64>),").unwrap(),
                            "array" => {
                                let items_items_hash = items_hash
                                    .get(&Yaml::String("items".to_string()))
                                    .unwrap()
                                    .as_hash()
                                    .unwrap();
                                let items_items_type = items_items_hash
                                    .get(&Yaml::String("type".to_string()))
                                    .unwrap()
                                    .as_str()
                                    .unwrap();
                                match items_items_type {
                                    "integer" => {
                                        writeln!(output_file, "\tArrayListNumber(Vec<Vec<u64>>),")
                                            .unwrap()
                                    }
                                    _ => unimplemented!("{}", items_items_type),
                                }
                            }
                            _ => unimplemented!("Array variant with type {}", item_type),
                        }
                    } else if let Some(Yaml::String(item_ref)) =
                        items_hash.get(&Yaml::String("$ref".to_string()))
                    {
                        let variant_name = get_object_name_from_reference(item_ref);
                        writeln!(output_file, "\tArrayList(Vec<{variant_name}>),").unwrap();
                    } else if items_hash.get(&Yaml::String("oneOf".to_string())).is_some() {
                        writeln!(
                            output_file,
                            "\tArrayList(Vec<{}>),",
                            generate_inner_object_name(name, "Array")
                        )
                        .unwrap();
                    } else {
                        unimplemented!("{:?}", items_hash)
                    }
                }
                _ => panic!("{variant_type:?}",),
            }
        } else {
            unimplemented!("{one_of_variant_hash:?}",)
        }
    }
    writeln!(output_file, "}}\n").unwrap();
}
fn parse_allof_type(name: &str, schema: &Yaml, output_file: &mut File) {
    let schema_map = schema.as_hash().unwrap();
    let all_of_list = schema_map
        .get(&Yaml::String("allOf".to_string()))
        .unwrap()
        .as_vec()
        .unwrap();
    for all_of_item in all_of_list {
        let all_of_item_hash = all_of_item.as_hash().unwrap();
        if all_of_item_hash.get(&Yaml::String("type".to_string()))
            == Some(&Yaml::String("object".to_string()))
        {
            parse_object_type(
                &generate_inner_object_name(name, "Object"),
                all_of_item,
                output_file,
            );
        }
    }
    if let Some(doc) = schema_map
        .get(&Yaml::String("description".to_string()))
        .map(|x| x.as_str().unwrap().trim_end().replace("```", "***"))
    {
        writeln!(output_file, "/** {doc} */",).unwrap();
    }
    writeln!(
        output_file,
        "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
    )
    .unwrap();
    writeln!(output_file, "pub struct {name} {{",).unwrap();
    for all_of_item in all_of_list {
        let all_of_item_hash = all_of_item.as_hash().unwrap();
        if let Some(item_ref) = all_of_item_hash.get(&Yaml::String("$ref".to_string())) {
            let item_name = get_object_name_from_reference(item_ref.as_str().unwrap());
            let field_name = camel_to_snake(item_name);
            writeln!(output_file, "\t#[serde(flatten)]").unwrap();
            writeln!(output_file, "\tpub {field_name}: {item_name},",).unwrap();
        } else if let Some(Yaml::String(variant_type)) =
            all_of_item_hash.get(&Yaml::String("type".to_string()))
        {
            match variant_type.as_str() {
                "object" => {
                    writeln!(output_file, "\t#[serde(flatten)]").unwrap();
                    writeln!(
                        output_file,
                        "\tpub object: {},",
                        generate_inner_object_name(name, "Object")
                    )
                    .unwrap();
                }
                _ => unimplemented!("allOf variant type: {}", variant_type),
            }
        }
    }
    writeln!(output_file, "}}\n").unwrap();
}
fn parse_component_schema(schema_name: &str, schema_value: &Yaml, output_file: &mut File) {
    let schema_value_map = schema_value.as_hash().unwrap();
    if let Some(Yaml::String(schema_type)) = schema_value_map.get(&Yaml::String("type".to_string()))
    {
        match schema_type.as_str() {
            "object" => {
                parse_object_type(schema_name, schema_value, output_file);
            }
            "string" | "array" | "boolean" => {
                parse_typedef_type(schema_name, schema_value, output_file);
            }
            _ => unimplemented!(),
        }
    } else if let Some(_schema_all_of) = schema_value_map.get(&Yaml::String("allOf".to_string())) {
        parse_allof_type(schema_name, schema_value, output_file);
    } else if schema_value_map
        .get(&Yaml::String("oneOf".to_string()))
        .is_some()
        || schema_value_map
            .get(&Yaml::String("anyOf".to_string()))
            .is_some()
    {
        parse_oneof_type(schema_name, schema_value, output_file);
    } else {
        unimplemented!("Invalid object")
    }
}
fn parse_endpoint_path(path_schema: &Yaml, client_output_file: &mut File) {
    writeln!(
        client_output_file,
        "use crate::{{ConversaError, ConversaResult, OpenAIClient}};"
    )
    .unwrap();
    writeln!(client_output_file, "use crate::types::*;").unwrap();
    writeln!(
        client_output_file,
        "use serde::{{Serialize, Deserialize}};\n"
    )
    .unwrap();
    let schema_list = path_schema.as_hash().unwrap();
        for (_, path_hash) in schema_list {
        let path_operations = path_hash.as_hash().unwrap();
        for (_, path_operation_hash) in path_operations {
            let operation_name =
                str_to_snake_case(path_operation_hash["operationId"].as_str().unwrap());
            if let Some(Yaml::Array(parameters_list)) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("parameters".to_string()))
            {
                for parameter in parameters_list {
                    let parameter_schema = parameter["schema"].as_hash().unwrap();
                    if let Some(Yaml::String(schema_type)) =
                        parameter_schema.get(&Yaml::String("type".to_string()))
                    {
                        if schema_type == "object" {
                            parse_object_type(
                                &str_to_camel_case(&format!("{operation_name}_query")),
                                ¶meter["schema"],
                                client_output_file,
                            );
                        }
                    }
                }
            }
            if let Some(request_body_hash) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("requestBody".to_string()))
            {
                let request_body_content = request_body_hash["content"].as_hash().unwrap();
                assert_eq!(request_body_content.len(), 1);
                let body_content_schema = &request_body_content.front().unwrap().1["schema"];
                if body_content_schema
                    .as_hash()
                    .unwrap()
                    .get(&Yaml::String("type".to_string()))
                    == Some(&Yaml::String("object".to_string()))
                {
                    parse_object_type(
                        &str_to_camel_case(&format!("{operation_name}_request_body")),
                        body_content_schema,
                        client_output_file,
                    );
                }
            }
            let responses_hash = path_operation_hash["responses"].as_hash().unwrap();
            if let Some(ok_response) = responses_hash.get(&Yaml::String("200".to_string())) {
                if let Some(Yaml::Hash(response_content_hash)) = ok_response
                    .as_hash()
                    .unwrap()
                    .get(&Yaml::String("content".to_string()))
                {
                    if response_content_hash.len() == 1 {
                        let response_schema_hash =
                            &response_content_hash.front().unwrap().1["schema"];
                        if response_schema_hash
                            .as_hash()
                            .unwrap()
                            .get(&Yaml::String("oneOf".to_string()))
                            .is_some()
                        {
                            parse_oneof_type(
                                &str_to_camel_case(&format!("{operation_name}_response")),
                                response_schema_hash,
                                client_output_file,
                            );
                        } else if response_schema_hash
                            .as_hash()
                            .unwrap()
                            .get(&Yaml::String("type".to_string()))
                            == Some(&Yaml::String("object".to_string()))
                        {
                            parse_object_type(
                                &str_to_camel_case(&format!("{operation_name}_response")),
                                response_schema_hash,
                                client_output_file,
                            );
                        }
                    } else {
                        writeln!(
                            client_output_file,
                            "#[derive(Debug, PartialEq, Serialize, Deserialize)]"
                        )
                        .unwrap();
                        writeln!(
                            client_output_file,
                            "pub enum {} {{",
                            str_to_camel_case(&format!("{operation_name}_response"))
                        )
                        .unwrap();
                        for (response_name, response_hash) in response_content_hash {
                            let response_variant_hash = response_hash["schema"].as_hash().unwrap();
                            let response_variant_type = if let Some(Yaml::String(response_type)) =
                                response_variant_hash.get(&Yaml::String("type".to_string()))
                            {
                                if response_type == "string" {
                                    let response_string_format = response_variant_hash
                                        .get(&Yaml::String("format".to_string()))
                                        .unwrap()
                                        .as_str()
                                        .unwrap();
                                    if response_string_format == "binary" {
                                        "Vec<u8>"
                                    } else {
                                        unimplemented!()
                                    }
                                } else {
                                    unimplemented!()
                                }
                            } else if let Some(Yaml::String(response_ref)) =
                                response_variant_hash.get(&Yaml::String("$ref".to_string()))
                            {
                                &format!(
                                    "crate::types::{}",
                                    get_object_name_from_reference(response_ref)
                                )
                            } else if let Some(Yaml::Array(response_oneof)) =
                                response_variant_hash.get(&Yaml::String("oneOf".to_string()))
                            {
                                                                                                assert_eq!(response_oneof.len(), 2);
                                writeln!(
                                    client_output_file,
                                    "\t{}(crate::types::{}),",
                                    str_to_camel_case(response_name.as_str().unwrap()),
                                    get_object_name_from_reference(
                                        response_oneof[0]["$ref"].as_str().unwrap()
                                    )
                                )
                                .unwrap();
                                writeln!(
                                    client_output_file,
                                    "\t{}Verbose(crate::types::{}),",
                                    str_to_camel_case(response_name.as_str().unwrap()),
                                    get_object_name_from_reference(
                                        response_oneof[1]["$ref"].as_str().unwrap()
                                    )
                                )
                                .unwrap();
                                continue;
                            } else {
                                unimplemented!()
                            };
                            writeln!(
                                client_output_file,
                                "\t{}({}),",
                                str_to_camel_case(response_name.as_str().unwrap()),
                                response_variant_type,
                            )
                            .unwrap();
                        }
                        writeln!(client_output_file, "}}",).unwrap();
                    }
                }
            } else if let Some(created_response) =
                responses_hash.get(&Yaml::String("201".to_string()))
            {
                let response_content_hash = created_response["content"].as_hash().unwrap();
                if response_content_hash.len() == 1 {
                    let response_schema_hash = &response_content_hash.front().unwrap().1["schema"];
                    if response_schema_hash
                        .as_hash()
                        .unwrap()
                        .get(&Yaml::String("oneOf".to_string()))
                        .is_some()
                    {
                        parse_oneof_type(
                            &str_to_camel_case(&format!("{operation_name}_response")),
                            response_schema_hash,
                            client_output_file,
                        );
                    } else if response_schema_hash
                        .as_hash()
                        .unwrap()
                        .get(&Yaml::String("object".to_string()))
                        .is_some()
                    {
                        parse_object_type(
                            &str_to_camel_case(&format!("{operation_name}_response")),
                            response_schema_hash,
                            client_output_file,
                        );
                    }
                } else {
                    unimplemented!()
                }
            }
        }
    }
            writeln!(client_output_file, "impl OpenAIClient {{").unwrap();
    for (path_name, path_hash) in schema_list {
        let path_operations = path_hash.as_hash().unwrap();
        for (path_operation_name, path_operation_hash) in path_operations {
            let operation_name =
                str_to_snake_case(path_operation_hash["operationId"].as_str().unwrap());
            if let Some(d) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("summary".to_string()))
            {
                writeln!(
                    client_output_file,
                    "\t/** {} */",
                    d.as_str().unwrap().trim_end()
                )
                .unwrap();
            }
            write!(
                client_output_file,
                "\tpub async fn {operation_name}(&self, ",
            )
            .unwrap();
            if let Some(parameters_list) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("parameters".to_string()))
            {
                for parameter in parameters_list.as_vec().unwrap() {
                    let parameter_hash = parameter.as_hash().unwrap();
                    let parameter_schema = parameter["schema"].as_hash().unwrap();
                    let mut parameter_type = if let Some(Yaml::String(schema_type)) =
                        parameter_schema.get(&Yaml::String("type".to_string()))
                    {
                        match schema_type.as_str() {
                            "string" => "&str".to_string(),
                            "integer" => "u64".to_string(),
                            "object" => str_to_camel_case(&format!("{operation_name}_query")),
                            "array" => {
                                let array_items = parameter_schema
                                    .get(&Yaml::String("items".to_string()))
                                    .unwrap();
                                if let Some(array_type) = array_items["type"].as_str() {
                                    match array_type {
                                        "string" => "&[String]".to_string(),
                                        _ => unimplemented!("{:?}", array_items),
                                    }
                                } else if let Some(array_ref) = array_items["$ref"].as_str() {
                                    format!(
                                        "&[crate::types::{}]",
                                        get_object_name_from_reference(array_ref)
                                    )
                                } else {
                                    unimplemented!()
                                }
                            }
                            "boolean" => "bool".to_string(),
                            _ => unimplemented!("{:?}", schema_type),
                        }
                    } else if let Some(Yaml::String(schema_ref)) =
                        parameter_schema.get(&Yaml::String("$ref".to_string()))
                    {
                        format!(
                            "&crate::types::{}",
                            get_object_name_from_reference(schema_ref.as_str())
                        )
                    } else {
                        unimplemented!("{:?}", parameter_schema)
                    };
                    let parameter_required = if let Some(Yaml::Boolean(parameter_required)) =
                        parameter_hash.get(&Yaml::String("required".to_string()))
                    {
                        *parameter_required
                    } else {
                        false
                    };
                    if !parameter_required {
                        parameter_type = format!("Option<{parameter_type}>",);
                    }
                    write!(
                        client_output_file,
                        "{}: {parameter_type}, ",
                        parameter["name"].as_str().unwrap().replace("[]", ""),
                    )
                    .unwrap();
                }
            }
            if let Some(request_body_hash) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("requestBody".to_string()))
            {
                let request_body_type = {
                    let request_body_content = request_body_hash["content"].as_hash().unwrap();
                    assert_eq!(request_body_content.len(), 1);
                    let body_content_schema = request_body_content.front().unwrap().1["schema"]
                        .as_hash()
                        .unwrap();
                    if let Some(Yaml::String(body_content_ref)) =
                        body_content_schema.get(&Yaml::String("$ref".to_string()))
                    {
                        format!(
                            "crate::types::{}",
                            get_object_name_from_reference(body_content_ref)
                        )
                    } else if body_content_schema.get(&Yaml::String("type".to_string()))
                        == Some(&Yaml::String("object".to_string()))
                    {
                        str_to_camel_case(&format!("{operation_name}_request_body"))
                    } else {
                        unimplemented!("{:?}", body_content_schema)
                    }
                };
                let request_body_is_required =
                    request_body_hash["required"].as_bool().unwrap_or(false);
                write!(client_output_file, "request_body: ").unwrap();
                if request_body_is_required {
                    write!(client_output_file, "{request_body_type}, ",).unwrap();
                } else {
                    write!(client_output_file, "Option<{request_body_type}>, ",).unwrap();
                }
            }
            let responses_hash = path_operation_hash["responses"].as_hash().unwrap();
            let result_type = if let Some(ok_response) =
                responses_hash.get(&Yaml::String("200".to_string()))
            {
                if let Some(Yaml::Hash(response_content_hash)) = ok_response
                    .as_hash()
                    .unwrap()
                    .get(&Yaml::String("content".to_string()))
                {
                    if response_content_hash.len() == 1 {
                        let response_schema_hash =
                            response_content_hash.front().unwrap().1["schema"]
                                .as_hash()
                                .unwrap();
                        if let Some(Yaml::String(response_ref)) =
                            response_schema_hash.get(&Yaml::String("$ref".to_string()))
                        {
                            format!(
                                "crate::types::{}",
                                get_object_name_from_reference(response_ref.as_str())
                            )
                        } else if response_schema_hash
                            .get(&Yaml::String("oneOf".to_string()))
                            .is_some()
                            || response_schema_hash.get(&Yaml::String("type".to_string()))
                                == Some(&Yaml::String("object".to_string()))
                        {
                            str_to_camel_case(&format!("{operation_name}_response"))
                        } else if response_schema_hash.get(&Yaml::String("type".to_string()))
                            == Some(&Yaml::String("string".to_string()))
                        {
                            "String".to_string()
                        } else {
                            unimplemented!("{:?}", response_schema_hash)
                        }
                    } else {
                        str_to_camel_case(&format!("{operation_name}_response"))
                    }
                } else {
                    "()".to_string()
                }
            } else if let Some(created_response) =
                responses_hash.get(&Yaml::String("201".to_string()))
            {
                let response_content_hash = created_response["content"].as_hash().unwrap();
                if response_content_hash.len() == 1 {
                    let response_schema_hash = response_content_hash.front().unwrap().1["schema"]
                        .as_hash()
                        .unwrap();
                    if let Some(Yaml::String(response_ref)) =
                        response_schema_hash.get(&Yaml::String("$ref".to_string()))
                    {
                        format!(
                            "crate::types::{}",
                            get_object_name_from_reference(response_ref.as_str())
                        )
                    } else if response_schema_hash
                        .get(&Yaml::String("oneOf".to_string()))
                        .is_some()
                        || response_schema_hash
                            .get(&Yaml::String("object".to_string()))
                            .is_some()
                    {
                        str_to_camel_case(&format!("{operation_name}_response"))
                    } else {
                        unimplemented!("{:?}", response_schema_hash)
                    }
                } else {
                    str_to_camel_case(&format!("{operation_name}_response"))
                }
            } else {
                unimplemented!("{:?}", responses_hash)
            };
            writeln!(client_output_file, ") -> ConversaResult<{result_type}> {{",).unwrap();
            writeln!(
                client_output_file,
                "\t\tlet address = format!(\"{{}}{}\", self.base_address);",
                path_name.as_str().unwrap()
            )
            .unwrap();
            writeln!(
                client_output_file,
                "\t\tlet mut request = self.client.{}(&address);",
                path_operation_name.as_str().unwrap(),
            )
            .unwrap();
            writeln!(
                client_output_file,
                "\t\trequest = request.bearer_auth(&self.api_key);",
            )
            .unwrap();
                        if let Some(Yaml::Array(parameters_list)) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("parameters".to_string()))
            {
                for parameter in parameters_list {
                    if parameter["in"].as_str().unwrap() == "query" {
                        let parameter_required = parameter["required"].as_bool().unwrap_or(false);
                        if parameter_required {
                            writeln!(
                                client_output_file,
                                "\t\trequest = request.query(&[(\"{}\",{})]);",
                                parameter["name"].as_str().unwrap().replace("[]", ""),
                                parameter["name"].as_str().unwrap().replace("[]", "")
                            )
                            .unwrap();
                        } else {
                            writeln!(
                                client_output_file,
                                "\t\tif let Some(q) = {} {{\n\t\t\trequest = request.query(&[(\"{}\", q)]);\n\t\t}}",
                                parameter["name"].as_str().unwrap().replace("[]", ""),
                                parameter["name"].as_str().unwrap().replace("[]", "")
                            )
                            .unwrap();
                        }
                    }
                }
            }
            if let Some(request_body_hash) = path_operation_hash
                .as_hash()
                .unwrap()
                .get(&Yaml::String("requestBody".to_string()))
            {
                let request_body_is_required =
                    request_body_hash["required"].as_bool().unwrap_or(false);
                let request_body_content = request_body_hash["content"].as_hash().unwrap();
                debug_assert!(request_body_content.len() == 1);
                                let request_body_content_type =
                    request_body_content.front().unwrap().0.as_str().unwrap();
                if request_body_content_type == "application/json" {
                    if request_body_is_required {
                        writeln!(
                            client_output_file,
                            "\t\trequest = request.json(&request_body);",
                        )
                        .unwrap();
                    } else {
                        writeln!(
                        client_output_file,
                        "\t\tif let Some(b) = request_body {{\n\t\t\trequest = request.body(serde_json::to_string(&b)?);\n\t\t}}",
                    )
                    .unwrap();
                    }
                } else if request_body_content_type == "multipart/form-data" {
                    writeln!(
                        client_output_file,
                        "\t\trequest = request.multipart(request_body.into_multipart_form());",
                    )
                    .unwrap();
                } else {
                    unimplemented!("Request body type: {}", request_body_content_type);
                }
            }
            writeln!(
                client_output_file,
                "\t\tlet result = request.send().await?;",
            )
            .unwrap();
            writeln!(
                client_output_file,
                "\t\tlet status_code = result.status().as_u16();",
            )
            .unwrap();
            writeln!(
                client_output_file,
                "\t\tlet _content_type = result.headers()[reqwest::header::CONTENT_TYPE].to_str()?.to_string();"
            )
            .unwrap();
            writeln!(
                client_output_file,
                "\t\tlet response_bytes = result.bytes().await?;",
            )
            .unwrap();
            let responses_hash = path_operation_hash["responses"].as_hash().unwrap();
                        writeln!(
                    client_output_file,
                    "\t\tif status_code == 400 {{\n\t\t\treturn Err(ConversaError::ErrorResponse(serde_json::from_slice(&response_bytes)?))\n\t\t}}"
                )
                .unwrap();
            writeln!(
                    client_output_file,
                    "\t\tif status_code == 404 {{\n\t\t\treturn Err(ConversaError::Error(serde_json::from_slice(&response_bytes)?))\n\t\t}}"
                )
                .unwrap();
                        let (ok_response_code, ok_response) = responses_hash
                .iter()
                .find(|(x, _)| x.as_str().unwrap() == "200" || x.as_str().unwrap() == "201")
                .unwrap();
            writeln!(
                    client_output_file,
                    "\t\tif status_code != {} {{\n\t\t\treturn Err(ConversaError::UnexpectedStatusCode{{code: status_code, response: String::from_utf8(response_bytes.to_vec())?}})\n\t\t}}",
                    ok_response_code.as_str().unwrap()
                )
                .unwrap();
            if let Some(response_content_hash) = ok_response["content"].as_hash() {
                if response_content_hash.len() == 1 {
                                        if result_type == "String" {
                        writeln!(
                            client_output_file,
                            "\t\tOk(String::from_utf8(response_bytes.to_vec())?)"
                        )
                        .unwrap();
                    } else {
                        writeln!(
                            client_output_file,
                            "\t\tOk(serde_json::from_slice(&response_bytes)?)"
                        )
                        .unwrap();
                    }
                } else {
                    writeln!(client_output_file, "\t\tmatch _content_type.as_str() {{").unwrap();
                    for (response_name, _) in response_content_hash {
                        writeln!(
                            client_output_file,
                            "\t\t\t\"{}\" => Ok({}::{}(serde_json::from_slice(&response_bytes)?)),",
                            response_name.as_str().unwrap(),
                            str_to_camel_case(&format!("{operation_name}_response")),
                            str_to_camel_case(response_name.as_str().unwrap()),
                        )
                        .unwrap();
                    }
                    writeln!(
                        client_output_file,
                        "\t\t\t_ => Err(ConversaError::UnexpectedContentType(_content_type)),"
                    )
                    .unwrap();
                    writeln!(client_output_file, "\t\t}}").unwrap();
                }
            } else {
                writeln!(client_output_file, "\t\tOk(())").unwrap();
            }
            writeln!(client_output_file, "\t}}\n",).unwrap();
            println!("\t{path_operation_name:?}",);
        }
    }
    writeln!(client_output_file, "\n}}").unwrap();
}
fn main() {
    if std::env::var("DOCS_RS").is_ok() {
        return;
    }
    println!("cargo::rerun-if-changed=./openapi.documented.yml");
    println!("cargo::rerun-if-changed=src/lib.rs");
    let openai_yml_file = read_to_string(OPENAI_YML_FILE_PATH).unwrap();
    let openai_yml = YamlLoader::load_from_str(&openai_yml_file).unwrap();
    let mut output_file = File::create("src/types.rs").unwrap();
    let schema_list = openai_yml[0]["components"]["schemas"].as_hash().unwrap();
    writeln!(output_file, "use std::collections::HashMap;").unwrap();
    writeln!(output_file, "use serde::{{Deserialize, Serialize}};\n").unwrap();
    for (schema_name, schema_value) in schema_list {
        let name = schema_name.as_str().unwrap();
        parse_component_schema(name, schema_value, &mut output_file);
    }
    let mut client_output_file = File::create("src/client.rs").unwrap();
    let path_schema = &openai_yml[0]["paths"];
    parse_endpoint_path(path_schema, &mut client_output_file);
}