use std::collections::BTreeMap;

use serde::Serialize;
use ts_rs::TS;

use super::kcl_doc;
use super::kcl_doc::ArgKind;
use super::kcl_doc::DocData;

// Export the stdlib signature metadata needed by command-bar type adapters.
#[derive(Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "StdLibCommandTypes.ts")]
struct StdLibCommandShape {
    name: String,
    preferred_name: String,
    qual_name: String,
    module_name: String,
    return_type: Option<String>,
    deprecated: bool,
    deprecated_since: Option<String>,
    experimental: bool,
    doc_hidden: bool,
    args: Vec<StdLibCommandArgShape>,
}

#[derive(Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "StdLibCommandTypes.ts")]
struct StdLibCommandArgShape {
    name: String,
    ty: Option<String>,
    docs: Option<String>,
    required: bool,
    special: bool,
    experimental: bool,
    deprecated: bool,
    deprecated_since: Option<String>,
}

#[test]
fn export_bindings_stdlib_commands() {
    let commands = kcl_doc::walk_stdlib()
        .all_docs()
        .filter_map(|doc| {
            let DocData::Fn(func) = doc else {
                return None;
            };
            Some((
                func.preferred_name.clone(),
                StdLibCommandShape {
                    name: func.name.clone(),
                    preferred_name: func.preferred_name.clone(),
                    qual_name: func.qual_name.clone(),
                    module_name: func.module_name.clone(),
                    return_type: func.return_type.clone(),
                    deprecated: func.properties.deprecated,
                    deprecated_since: func.properties.deprecated_since.as_ref().map(ToString::to_string),
                    experimental: func.properties.experimental,
                    doc_hidden: func.properties.doc_hidden,
                    args: func
                        .args
                        .iter()
                        .map(|arg| StdLibCommandArgShape {
                            name: arg.name.clone(),
                            ty: arg.ty.clone(),
                            docs: arg.docs.clone(),
                            required: arg.kind.required(),
                            special: matches!(arg.kind, ArgKind::Special),
                            experimental: arg.experimental,
                            deprecated: arg.deprecated,
                            deprecated_since: arg.deprecated_since.as_ref().map(ToString::to_string),
                        })
                        .collect(),
                },
            ))
        })
        .collect::<BTreeMap<_, _>>();

    let ts_config = ts_rs::Config::from_env();
    StdLibCommandShape::export_all(&ts_config).unwrap();

    // ts-rs owns the structural TypeScript declarations in
    // StdLibCommandTypes.ts. The stdlib commands themselves are runtime
    // metadata from walk_stdlib(), so this test writes a data-only TypeScript
    // module with `as const` for literal command and argument names.
    let json = serde_json::to_string_pretty(&commands).unwrap();
    let out_dir = ts_config.out_dir();
    std::fs::create_dir_all(out_dir).unwrap();
    std::fs::write(
        out_dir.join("StdLibCommands.ts"),
        format!(
            "// This file was generated by `cargo test -p kcl-lib export_bindings`.\n\
             // Do not edit this file by hand.\n\n\
             export default {json} as const\n"
        ),
    )
    .unwrap();
}
