#!/usr/bin/env -S node --import @swc-node/register/esm-register --enable-source-maps

import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { Engine } from "php-parser";

const KEY_KIND = "kind";
const ALLOWED = ["class", "interface", "function", "trait"] as const;

(async () => {
    const pathes = process.argv
        .pop()
        .split(" ")
        .map((p) => resolve(p));

    const whitelist = await extractGlobalStubIdentifiers(pathes);

    console.log(JSON.stringify(whitelist));
})();

function findKinds(obj: any, namespace = "") {
    let list: { key: (typeof ALLOWED)[number]; name: string; kind: string; inNamespace: string }[] = [];
    if (!obj) return list;
    if (obj instanceof Array) {
        for (const i in obj) {
            list = list.concat(findKinds(obj[i], namespace));
        }
        return list;
    }

    if (ALLOWED.indexOf(obj[KEY_KIND]) > -1 && obj.name) {
        list.push({ ...obj.name, key: obj[KEY_KIND], inNamespace: namespace });
    }

    if (typeof obj == "object" && obj !== null) {
        const children = Object.keys(obj);
        if (children.length > 0) {
            // Correctly set namespace for next children
            const appendNamespace =
                obj.kind === "namespace" && typeof obj.name === "string"
                    ? `${obj.name.split("\\").filter(Boolean).join("\\")}\\`
                    : namespace;
            for (let i = 0; i < children.length; i++) {
                list = list.concat(findKinds(obj[children[i]], appendNamespace));
            }
        }
    }
    return list;
}

/**
 * Due to the fact that php-scoper does not support external global dependencies like WordPress
 * functions and classes we need to whitelist them. The best approach is to use the already
 * used stubs. Stubs are needed for PHP Intellisense so they need to be up2date, too.
 *
 * @see https://github.com/humbug/php-scoper/issues/303
 * @see https://github.com/humbug/php-scoper/issues/378
 */
async function extractGlobalStubIdentifiers(files: string[]) {
    const result: Record<string, string[]> = {};
    for (const key of ALLOWED) {
        result[key] = [];
    }

    const parser = new Engine({
        parser: {
            extractDoc: true,
        },
        ast: {
            withSource: false,
            withPositions: false,
        },
    });

    for (const file of files) {
        const parsed = parser.parseCode(await readFile(file, { encoding: "utf-8" }), file);
        for (const { inNamespace, name, key } of findKinds(parsed)) {
            result[key].push(inNamespace + name);
        }
    }

    return result;
}
