/* eslint-disable no-console */
// Auto-generate generated/knownMsgs.ts from ALL request Msg* in cosmjs-types/**/tx.d.ts
// - Skip *Response
// - Alias duplicate symbol names across modules
// - Build a dictionary knownList: Record<typeUrl, Decoder>

import fs from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

type FileEntry = {
    pkgDir: string;     // e.g. "cosmos/bank/v1beta1"
    importPath: string; // e.g. "cosmjs-types/cosmos/bank/v1beta1/tx.js"
    symbols: string[];  // ["MsgSend", "MsgMultiSend", ...] (requests only)
};

type MappingEntry = {
    importPath: string;     // from which module to import
    symbol: string;         // original symbol name (e.g., MsgSend)
    local: string;          // unique local alias in our file (e.g., MsgSend__cosmos_bank_v1beta1)
    typeUrl: string;        // "/cosmos.bank.v1beta1.MsgSend"
};

function cosmjsRoot(): string {
    const p = require.resolve("cosmjs-types/package.json");
    return path.dirname(p);
}

function walkTxDts(dir: string, out: string[] = []): string[] {
    for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
        const p = path.join(dir, ent.name);
        if (ent.isDirectory()) walkTxDts(p, out);
        else if (ent.isFile() && ent.name === "tx.d.ts") out.push(p);
    }
    return out;
}

function parseMsgSymbols(dts: string): string[] {
    // Capture exported Msg* declarations and consts
    const found = new Set<string>();
    const reIface = /export\s+(?:interface|type)\s+(Msg[A-Za-z0-9_]+)/g;
    const reConst = /export\s+declare\s+const\s+(Msg[A-Za-z0-9_]+)\s*:/g;
    let m: RegExpExecArray | null;
    while ((m = reIface.exec(dts))) found.add(m[1]);
    while ((m = reConst.exec(dts))) found.add(m[1]);

    // Filter: only request messages (exclude *Response)
    return [...found].filter((s) => s.startsWith("Msg") && !s.endsWith("Response"));
}

function toPkgDirFromDts(cosmjs: string, dtsPath: string): string {
    // dirname(tx.d.ts) relative to cosmjs root, with forward slashes
    return path.relative(cosmjs, path.dirname(dtsPath)).split(path.sep).join("/");
}

function typeUrlFor(pkgDir: string, symbol: string): string {
    // "cosmos/bank/v1beta1" -> "cosmos.bank.v1beta1"
    const dotted = pkgDir.split("/").join(".");
    return `/${dotted}.${symbol}`;
}

function slugFromPkg(pkgDir: string): string {
    // Create a short, safe suffix for aliasing identical names from different packages
    return pkgDir.replace(/[^a-zA-Z0-9]/g, "_");
}

function buildFileEntries(): FileEntry[] {
    const root = cosmjsRoot();
    const files = walkTxDts(root);
    const entries: FileEntry[] = [];

    for (const f of files) {
        const pkgDir = toPkgDirFromDts(root, f);
        const dts = fs.readFileSync(f, "utf8");
        const symbols = parseMsgSymbols(dts);
        if (symbols.length === 0) continue;

        entries.push({
            pkgDir,
            importPath: `cosmjs-types/${pkgDir}/tx.js`,
            symbols,
        });
    }
    return entries;
}

function generate(entries: FileEntry[]): string {
    let header = `/* eslint-disable */\n// Auto-generated by scripts/gen-known-msgs.ts — DO NOT EDIT.\n\n`;

    // Build import mappings with unique local aliases
    const usedLocal = new Set<string>();
    const mappings: MappingEntry[] = [];
    const seenTypeUrl = new Set<string>();

    for (const e of entries) {
        for (const sym of e.symbols) {
            const typeUrl = typeUrlFor(e.pkgDir, sym);
            if (seenTypeUrl.has(typeUrl)) continue; // already mapped (safety)
            seenTypeUrl.add(typeUrl);

            let local = sym;
            if (usedLocal.has(local)) {
                local = `${sym}__${slugFromPkg(e.pkgDir)}`;
            }
            usedLocal.add(local);

            mappings.push({
                importPath: e.importPath,
                symbol: sym,
                local,
                typeUrl,
            });
        }
    }

    // Group imports by importPath with "symbol as local" aliasing
    const byImport = new Map<string, MappingEntry[]>();
    for (const m of mappings) {
        const list = byImport.get(m.importPath) ?? [];
        list.push(m);
        byImport.set(m.importPath, list);
    }

    const importLines: string[] = [];
    for (const [imp, list] of [...byImport.entries()].sort()) {
        const parts = list
            .sort((a, b) => a.local.localeCompare(b.local))
            .map((m) => (m.symbol === m.local ? m.symbol : `${m.symbol} as ${m.local}`));
        importLines.push(`import { ${parts.join(", ")} } from "${imp}";`);
    }

    // Build knownList dictionary lines
    const dictLines = mappings
        .sort((a, b) => a.typeUrl.localeCompare(b.typeUrl))
        .map((m) => `  "${m.typeUrl}": ${m.local},`);

    const body = `
export const knownList: Record<string, any> = {
${dictLines.join("\n")}
};

export function decodeKnown(typeUrl: string, value: Uint8Array): any | undefined {
  return knownList[typeUrl]?.decode(value) ?? undefined;
}
`;

    return header + importLines.join("\n") + "\n" + body;
}

function main() {
    const entries = buildFileEntries();
    if (entries.length === 0) {
        console.error("No tx.d.ts files found in cosmjs-types. Is the package installed?");
        process.exit(1);
    }
    const code = generate(entries);

    const outDir = path.join(process.cwd(), "generated");
    fs.mkdirSync(outDir, { recursive: true });
    const outFile = path.join(outDir, "knownMsgs.ts");
    fs.writeFileSync(outFile, code, "utf8");

    const msgCount = (code.match(/":\s*Msg/g) || []).length;
    console.log(`✅ generated ${outFile}`);
    console.log(`   request Msg* types: ${msgCount}`);
}

main();