import { execa } from "execa";

class Config {
    private readonly workspaceRoot: string;

    public readonly transformers: {
        match: RegExp;
        transform: (
            content: string,
            info: {
                /**
                 * The path to the file or directory relative to the workspace root.
                 */
                path: string;
                /**
                 * A map of package name to package path of packages found during the OSS extraction.
                 */
                packages: Record<string, string>;
            },
        ) => Promise<string>;
    }[] = [];

    public readonly teardown: ((opts: {
        /**
         * The root folder of the OSS extraction.
         */
        root: string;
        /**
         * A map of package name to package path of packages found during the OSS extraction.
         */
        packages: Record<string, string>;
    }) => Promise<void>)[] = [];

    public readonly paths: Set<string> = new Set();

    public constructor(workspaceRoot: Config["workspaceRoot"]) {
        this.workspaceRoot = workspaceRoot;
    }

    public addTeardown(teardown: Config["teardown"][number]) {
        this.teardown.push(teardown);
    }

    public addTransformer(transformer: Config["transformers"][number]) {
        this.transformers.push(transformer);
    }

    /**
     * Add files or directories from a search within the monorepo respecting the `.gitignore` file.
     * Under the hood, this uses the `fd` command.
     *
     * @see https://github.com/sharkdp/fd?tab=readme-ov-file#command-line-options
     */
    public async search({
        search,
        searchType = "regex",
        type = "file",
        excludeByGlob,
        depth,
        addToPaths = true,
    }: {
        search: string;
        searchType?: "regex" | "glob";
        type?: "file" | "directory";
        excludeByGlob?: string[];
        depth?: number;
        addToPaths?: boolean;
    }) {
        let useSearch = search;
        if (searchType === "regex" && search.startsWith("^")) {
            useSearch = `^${this.workspaceRoot}/${search.slice(1)}`;
        }

        const files: string[] = [];
        for await (const line of execa({
            cwd: this.workspaceRoot,
        })`fdfind ${useSearch} ${searchType === "glob" ? "--glob" : []} ${excludeByGlob?.map((glob) => ["--exclude", glob]).flat() || []} ${["--type", type === "file" ? "f" : "d"]} --full-path ${typeof depth === "number" ? ["--max-depth", depth] : []}`) {
            const lineString = line.toString();
            files.push(lineString);
            if (addToPaths) {
                this.add(files);
            }
        }

        return files;
    }

    public async add(files: string[]) {
        for (const file of files) {
            if (file) {
                this.paths.add(file);
            }
        }
    }
}

export { Config };
