import { access, cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";

import { findWorkspaceRootSync } from "../../findWorkspaceRoot.js";
import { Config } from "../../oss-extract/config.js";

import type { OssExtractOpts } from "./program.js";

const workspaceRoot = findWorkspaceRootSync();

async function ossExtractExecute(opts: OssExtractOpts) {
    const configFile = join(workspaceRoot, ".oss.ts");
    const outputFolder = join(workspaceRoot, "oss-extract");

    await access(configFile);

    const exported = await import(configFile);
    if (!exported?.oss) {
        throw new Error(`Please export a function oss(config: Config)!`);
    }

    const config = new Config(workspaceRoot);

    console.log(`Searching for files in ${workspaceRoot}...`);
    await exported.oss(config);

    // Additionally, find all `.oss.{extension}` files which are then copied as `.{extension}` files
    const ossFiles = await config.search({
        search: "\\.oss\\.\\w+$",
        type: "file",
        addToPaths: false,
    });

    console.log(`Found ${config.paths.size} files and folders.`);

    let now = performance.now();
    await rm(outputFolder, { recursive: true, force: true });
    await mkdir(outputFolder, { recursive: true });
    const promises: Promise<void>[] = [];

    for (const ossFile of ossFiles) {
        const src = join(workspaceRoot, ossFile);
        const dest = join(outputFolder, ossFile.replace(".oss.", "."));
        console.log(`Copying ${src} to ${dest}...`);
        promises.push(cp(src, dest, { recursive: true }));
    }

    for (const path of config.paths) {
        const src = join(workspaceRoot, path);
        const dest = join(outputFolder, path);
        console.log(`Copying ${src} to ${dest}...`);
        promises.push(cp(src, dest, { recursive: true }));
    }

    await Promise.all(promises);
    console.log(
        `Successfully copied ${config.paths.size} files and folders to ${outputFolder} in ${Math.round(performance.now() - now)}ms.`,
    );

    // List all files in output folder
    console.log(`Transforming files in ${outputFolder}...`);
    now = performance.now();
    const files = (await readdir(outputFolder, { recursive: true, withFileTypes: true })).filter((file) =>
        file.isFile(),
    );

    // Get all package.json files and create a map of package name to package path
    const packageJsonFiles = files.filter((file) => file.name === "package.json");
    const packageNameToPath: Record<string, string> = {};
    for (const file of packageJsonFiles) {
        const packageJson = JSON.parse(await readFile(`${file.parentPath}/${file.name}`, { encoding: "utf8" }));
        packageNameToPath[packageJson.name] = file.parentPath;
    }

    // Apply transformers which modify the file content
    const { transformers } = config;
    let transformed = 0;
    const awaitTransformations: Promise<void>[] = [];
    for (const file of files) {
        const foundTransformers: typeof transformers = [];
        for (const transformer of transformers) {
            if (file.name.match(transformer.match)) {
                foundTransformers.push(transformer);
                transformed++;
            }
        }

        if (foundTransformers.length > 0) {
            awaitTransformations.push(
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    const pathToFile = join(file.parentPath, file.name);
                    let fileContent = await readFile(pathToFile, { encoding: "utf8" });
                    for (const transformer of foundTransformers) {
                        fileContent = await transformer.transform(fileContent, {
                            path: pathToFile.slice(outputFolder.length + 1),
                            packages: packageNameToPath,
                        });
                    }
                    await writeFile(pathToFile, fileContent, { encoding: "utf8" });
                    resolve();
                }),
            );
        }
    }

    await Promise.all(awaitTransformations);
    console.log(
        `Successfully transformed ${transformed} files in ${outputFolder} in ${Math.round(performance.now() - now)}ms.`,
    );

    now = performance.now();
    let teardownCount = 0;
    // Execute teardown functions
    for (const teardown of config.teardown) {
        await teardown({
            root: outputFolder,
            packages: packageNameToPath,
        });
        teardownCount++;
    }

    console.log(
        `Successfully executed ${teardownCount} teardown functions in ${Math.round(performance.now() - now)}ms.`,
    );
}

export { ossExtractExecute };
