use lichen_core::ContractAbi;

use crate::codegen_shared_support::{
    clean_param_name, is_readonly, to_camel_case, to_pascal_case, ts_type,
};

/// Generate a TypeScript client class from a contract ABI
pub(super) fn generate_typescript(abi: &ContractAbi) -> String {
    let class_name = format!("{}Client", to_pascal_case(&abi.name));
    let mut out = String::with_capacity(4096);

    out.push_str(&format!(
        "// Auto-generated by: lichen contract generate-client\n\
         // Contract: {} | ABI version: {}\n\
         // DO NOT EDIT — regenerate with: lichen contract generate-client --abi {}/abi.json\n\n\
         import {{ Connection, Keypair, PublicKey }} from '@lichen/sdk';\n\n",
        abi.name, abi.version, abi.name,
    ));

    out.push_str(&format!("export class {} {{\n", class_name));
    out.push_str("  constructor(\n");
    out.push_str("    private connection: Connection,\n");
    out.push_str("    private contractAddress: string,\n");
    out.push_str("  ) {}\n");

    for function in &abi.functions {
        let method_name = to_camel_case(&function.name);
        let readonly = is_readonly(function);

        let mut params = Vec::new();
        if !readonly {
            params.push("signer: Keypair".to_string());
        }
        for parameter in &function.params {
            let clean = clean_param_name(&parameter.name);
            let ts_name = to_camel_case(&clean);
            params.push(format!("{}: {}", ts_name, ts_type(&parameter.param_type)));
        }

        let args: Vec<String> = function
            .params
            .iter()
            .map(|parameter| {
                let clean = clean_param_name(&parameter.name);
                let ts_name = to_camel_case(&clean);
                format!("{}: {}", parameter.name, ts_name)
            })
            .collect();

        let return_type = if readonly { "any" } else { "string" };
        let call_method = if readonly {
            "queryContract"
        } else {
            "callContract"
        };

        let param_desc: Vec<String> = function
            .params
            .iter()
            .map(|parameter| format!("{}: {:?}", parameter.name, parameter.param_type))
            .collect();
        out.push_str(&format!(
            "\n  /** {}({}) */\n",
            function.name,
            param_desc.join(", ")
        ));

        out.push_str(&format!(
            "  async {}({}): Promise<{}> {{\n",
            method_name,
            params.join(", "),
            return_type,
        ));

        if readonly {
            out.push_str(&format!(
                "    return this.connection.{}(\n\
                 \x20     new PublicKey(this.contractAddress),\n\
                 \x20     '{}',\n\
                 \x20     {{ {} }},\n\
                 \x20   );\n",
                call_method,
                function.name,
                args.join(", "),
            ));
        } else {
            out.push_str(&format!(
                "    return this.connection.{}(\n\
                 \x20     signer,\n\
                 \x20     new PublicKey(this.contractAddress),\n\
                 \x20     '{}',\n\
                 \x20     {{ {} }},\n\
                 \x20   );\n",
                call_method,
                function.name,
                args.join(", "),
            ));
        }

        out.push_str("  }\n");
    }

    out.push_str("}\n");
    out
}
