//! JavaScript API method generation.

use std::fmt::Write;

use crate::{
    Endpoint, Parameter,
    generators::{normalize_return_type, write_description},
    to_camel_case,
};

/// Generate API methods for the BrkClient class.
pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
    for endpoint in endpoints {
        if !endpoint.should_generate() {
            continue;
        }

        let method_name = endpoint_to_method_name(endpoint);
        let base_return_type =
            normalize_return_type(endpoint.response_type.as_deref().unwrap_or("*"));
        let return_type = if endpoint.supports_csv {
            format!("{} | string", base_return_type)
        } else {
            base_return_type
        };

        writeln!(output, "  /**").unwrap();
        if let Some(summary) = &endpoint.summary {
            writeln!(output, "   * {}", summary).unwrap();
        }
        if let Some(desc) = &endpoint.description
            && endpoint.summary.as_ref() != Some(desc)
        {
            writeln!(output, "   *").unwrap();
            write_description(output, desc, "   * ", "   *");
        }

        // Add endpoint path
        writeln!(output, "   *").unwrap();
        writeln!(
            output,
            "   * Endpoint: `{} {}`",
            endpoint.method.to_uppercase(),
            endpoint.path
        )
        .unwrap();

        if !endpoint.path_params.is_empty() || !endpoint.query_params.is_empty() {
            writeln!(output, "   *").unwrap();
        }

        for param in &endpoint.path_params {
            let desc = format_param_desc(param.description.as_deref());
            writeln!(
                output,
                "   * @param {{{}}} {}{}",
                param.param_type, param.name, desc
            )
            .unwrap();
        }
        for param in &endpoint.query_params {
            let optional = if param.required { "" } else { "=" };
            let desc = format_param_desc(param.description.as_deref());
            writeln!(
                output,
                "   * @param {{{}{}}} [{}]{}",
                param.param_type, optional, param.name, desc
            )
            .unwrap();
        }

        writeln!(output, "   * @returns {{Promise<{}>}}", return_type).unwrap();
        writeln!(output, "   */").unwrap();

        let params = build_method_params(endpoint);
        writeln!(output, "  async {}({}) {{", method_name, params).unwrap();

        let path = build_path_template(&endpoint.path, &endpoint.path_params);

        if endpoint.query_params.is_empty() {
            writeln!(output, "    return this.getJson(`{}`);", path).unwrap();
        } else {
            writeln!(output, "    const params = new URLSearchParams();").unwrap();
            for param in &endpoint.query_params {
                let ident = sanitize_ident(&param.name);
                if param.required {
                    writeln!(
                        output,
                        "    params.set('{}', String({}));",
                        param.name, ident
                    )
                    .unwrap();
                } else {
                    writeln!(
                        output,
                        "    if ({} !== undefined) params.set('{}', String({}));",
                        ident, param.name, ident
                    )
                    .unwrap();
                }
            }
            writeln!(output, "    const query = params.toString();").unwrap();
            writeln!(
                output,
                "    const path = `{}${{query ? '?' + query : ''}}`;",
                path
            )
            .unwrap();

            if endpoint.supports_csv {
                writeln!(output, "    if (format === 'csv') {{").unwrap();
                writeln!(output, "      return this.getText(path);").unwrap();
                writeln!(output, "    }}").unwrap();
                writeln!(output, "    return this.getJson(path);").unwrap();
            } else {
                writeln!(output, "    return this.getJson(path);").unwrap();
            }
        }

        writeln!(output, "  }}\n").unwrap();
    }
}

fn endpoint_to_method_name(endpoint: &Endpoint) -> String {
    to_camel_case(&endpoint.operation_name())
}

fn build_method_params(endpoint: &Endpoint) -> String {
    let mut params = Vec::new();
    for param in &endpoint.path_params {
        params.push(sanitize_ident(&param.name));
    }
    for param in &endpoint.query_params {
        params.push(sanitize_ident(&param.name));
    }
    params.join(", ")
}

/// Strip characters invalid in JS identifiers (e.g. `[]` from `txId[]`).
fn sanitize_ident(name: &str) -> String {
    name.replace(['[', ']'], "")
}

fn build_path_template(path: &str, path_params: &[Parameter]) -> String {
    let mut result = path.to_string();
    for param in path_params {
        let placeholder = format!("{{{}}}", param.name);
        let interpolation = format!("${{{}}}", param.name);
        result = result.replace(&placeholder, &interpolation);
    }
    result
}

/// Format param description with dash prefix, or empty string if no description.
fn format_param_desc(desc: Option<&str>) -> String {
    match desc {
        Some(d) if !d.is_empty() => format!(" - {}", d),
        _ => String::new(),
    }
}
