/* eslint-disable no-console */
// Dynamic .proto loader & Any decoder (protobufjs, CJS default import)

import fs from "node:fs";
import path from "node:path";
import protobuf from "protobufjs";

/** Recursively collect all .proto files under a directory */
export function collectProtoFiles(dir: string): string[] {
    const out: string[] = [];
    (function walk(d: string) {
        const entries = fs.readdirSync(d, { withFileTypes: true });
        for (const e of entries) {
            const p = path.join(d, e.name);
            if (e.isDirectory()) walk(p);
            else if (e.isFile() && p.endsWith(".proto")) out.push(p);
        }
    })(dir);
    return out;
}

/** Load with progress: loads files in batches and reports progress callback */
// ...
export async function loadProtoRootWithProgress(
    protoDir: string,
    onProgress?: (loaded: number, total: number) => void,
    batchSize = 200
): Promise<protobuf.Root> {
    const files = collectProtoFiles(protoDir);
    if (files.length === 0) throw new Error(`No .proto files found in ${protoDir}`);

    // Keep original field names from .proto (snake_case in Cosmos)
    const root = new protobuf.Root({ keepCase: true });

    root.resolvePath = (_origin, target) => {
        if (path.isAbsolute(target)) return target;
        return path.join(protoDir, target);
    };

    let loaded = 0;
    const total = files.length;

    // Load in batches to allow progress updates
    for (let i = 0; i < files.length; i += batchSize) {
        const batch = files.slice(i, i + batchSize);
        await new Promise<void>((resolve, reject) => {
            root.load(batch, (err) => (err ? reject(err) : resolve()));
        });
        loaded += batch.length;
        onProgress?.(Math.min(loaded, total), total);
    }

    root.resolveAll();
    return root;
}

/** Backward-compatible loader without progress */
export async function loadProtoRoot(protoDir: string): Promise<protobuf.Root> {
    return loadProtoRootWithProgress(protoDir);
}

/** Map typeUrl ("/package.Message") -> "package.Message" */
export function typeUrlToFullName(typeUrl: string): string {
    return typeUrl.startsWith("/") ? typeUrl.slice(1) : typeUrl;
}

/** Decode Any using protobufjs Root */
export function decodeAnyWithRoot(
    typeUrl: string,
    value: Uint8Array,
    root: protobuf.Root
): Record<string, unknown> {
    const fullName = typeUrlToFullName(typeUrl);
    const Type = root.lookupType(fullName);
    if (!Type) throw new Error(`Type not found in proto root: ${fullName}`);
    const msg = Type.decode(value);
    const obj = Type.toObject(msg, {
        longs: String,
        enums: String,
        bytes: (b: Uint8Array) => Buffer.from(b).toString("base64"),
        defaults: true,
        arrays: true,
        objects: true,
        oneofs: true,
    }) as Record<string, unknown>;
    obj["@type"] = typeUrl;
    return obj;
}