import path from 'path';
import { existsSync } from 'fs';
import { tryGetNeedleEngineVersion, tryGetPackageVersion } from '../common/version.js';
import { needleLog } from './logging.js';

/**
 * @type {string[]}
 */
export const preloadScriptPaths = [];


/**
 * Returns true when \`@needle-tools/engine\` is installed as a local package
 * (i.e. it has its own nested `node_modules`). Vite's optimiser must skip such
 * packages, otherwise it tries to pre-bundle source that was never meant to be
 * pre-bundled and fails at dev-server start.
 *
 * @param {string} [root] – project root; defaults to `process.cwd()`.
 * @returns {boolean}
 */
export function isLocalNeedleEngineInstalled(root = process.cwd()) {
    const lockPath = path.resolve(root, 'node_modules', '@needle-tools/engine', 'node_modules', '.package-lock.json');
    return existsSync(lockPath);
}

/**
 * @param {"build" | "serve"} command
 * @param {import('../types/needleConfig').needleMeta | null | undefined} config
 * @param {import('../types').userSettings} userSettings
 * @returns {import('vite').Plugin[]}
 */
export function needleDependencies(command, config, userSettings) {

    /**
     * @type {import('vite').Plugin}
     */
    return [
        {
            name: 'needle:dependencies',
            enforce: 'pre',
            /**
             * @param {import('vite').UserConfig} config
             */
            config: (config, env) => {
                handleOptimizeDeps(config);
                handleManualChunks(config);
            },
        },
    ]
}

const excludeDependencies = [
    "three-mesh-bvh",
    "@needle-tools/gltf-progressive"
]

/**
 * @param {import('vite').UserConfig} config
 */
function handleOptimizeDeps(config) {
    excludeDependencies.forEach(dep => {
        if (config.optimizeDeps?.include?.includes(dep)) {
            needleLog("needle-dependencies", `${dep} is included in the optimizeDeps.include array. This may cause issues with the worker import.`, "warn", { dimBody: false });
        }
        else {
            if (!config.optimizeDeps) {
                config.optimizeDeps = {};
            }
            if (!config.optimizeDeps.exclude) {
                config.optimizeDeps.exclude = [];
            }

            // This needs to be excluded from optimization because otherwise the worker import fails
            // three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker
            // same for gltf-progressive
            needleLog("needle-dependencies", `Adding ${dep} to the optimizeDeps.exclude array to support workers.`);
            config.optimizeDeps.exclude.push(dep);

            if (!config.server) config.server = {};
            if (!config.server.fs) config.server.fs = {};
            if (config.server.fs.strict === undefined) {
                // we need to disable strictness to allow importing the worker from three-mesh-bvh node_modules in our GenerateMeshBVHWorker.js file
                config.server.fs.strict = false;
            }
        }
    });
    // is needle engine a local package? exclude it from vite's optimizeDeps
    if (isLocalNeedleEngineInstalled()) {
        config.optimizeDeps ??= {};
        config.optimizeDeps.exclude ??= [];
        if (!config.optimizeDeps.include?.includes('@needle-tools/engine') &&
            !config.optimizeDeps.exclude.includes('@needle-tools/engine')) {
            config.optimizeDeps.exclude.push('@needle-tools/engine');
            needleLog("needle-dependencies", 'Detected local @needle-tools/engine package → will exclude it from optimization');
        }
    }
}

/**
 * @param {import('vite').UserConfig} config
 */
function handleManualChunks(config) {
    if (!config.build) {
        config.build = {};
    }
    if (!config.build.rollupOptions) {
        config.build.rollupOptions = {};
    }
    if (!config.build.rollupOptions.output) {
        config.build.rollupOptions.output = {};
    }

    const rollupOutput = config.build.rollupOptions.output;

    if (Array.isArray(rollupOutput)) {
        // append the manualChunks function to the array
        if (process.env.DEBUG) needleLog("needle-dependencies", "registering manualChunks");
        rollupOutput.push({
            manualChunks: needleManualChunks
        })
    }
    else {
        // If preserveModules is true we can not modify the chunkFileNames
        let allowManualChunks = true;
        if ("manualChunks" in rollupOutput) {
            allowManualChunks = false;
            // if the user has already defined manualChunks (even when set to undefined), we don't want to overwrite it
            needleLog("needle-dependencies", "manualChunks already found in vite config - will not overwrite it");
        }
        else if (rollupOutput.preserveModules === true) {
            allowManualChunks = false;
            needleLog("needle-dependencies", "manualChunks can not be registered because preserveModules is true");
        }
        if (rollupOutput.inlineDynamicImports === true) {
            allowManualChunks = false;
            needleLog("needle-dependencies", "manualChunks can not be registered because inlineDynamicImports is true");
        }

        if (allowManualChunks) {
            if (process.env.DEBUG) needleLog("needle-dependencies", "registering manualChunks");
            rollupOutput.manualChunks = needleManualChunks;
        }

        if (rollupOutput.chunkFileNames) {
            needleLog("needle-dependencies", "chunkFileNames already defined");
        }
        else {
            rollupOutput.chunkFileNames = handleChunkFileNames;
        }

        if (rollupOutput.assetFileNames) {
            needleLog("needle-dependencies", "assetFileNames already defined");
        }
        else {
            rollupOutput.assetFileNames = assetFileNames;
        }
    }


    // TODO: this was a test if it allows us to remove the sync import of postprocessing due to n8ao's import
    // config.build.rollupOptions.external = (source, importer, isResolved) => {
    //     if (importer?.includes("node_modules/n8ao/") || importer?.includes("node_modules/postprocessing/")) {
    //         console.log("EXTERNAL", importer);
    //         return true;
    //     }
    // }

    /** https://rollupjs.org/configuration-options/#output-assetfilenames
     * @param {import("vite").Rollup.PreRenderedAsset} chunkInfo */
    function assetFileNames(chunkInfo) {
        // "assets/..." is the default

        // this happens if e.g. a glTF or GLB file is link preloaded
        if (chunkInfo.name?.toLowerCase().includes(".glb") || chunkInfo.name?.toLowerCase().includes(".gltf")) {
            return `assets/[name][extname]`;
        }
        return `assets/[name].[hash][extname]`;
    }

    /** @param {import("vite").Rollup.PreRenderedChunk} chunk */
    function handleChunkFileNames(chunk) {
        if (chunk.name === 'needle-engine') {
            try {
                const version = tryGetNeedleEngineVersion();
                if (version) {
                    const name = `assets/needle-engine@${version}.js`;
                    preloadScriptPaths.push(`./${name}`);
                    return name;
                }
            }
            catch (e) {
                needleLog("needle-dependencies", "Error reading version " + e, "warn");
            }
        }
        else if (chunk.name === 'three' || chunk.name === 'three-examples') {
            // 'three' may be aliased to @needle-tools/three — check both package locations
            const version = tryGetPackageVersion("three") ?? tryGetPackageVersion("@needle-tools/three");
            const name = version
                ? `assets/${chunk.name}@${version}.js`
                : `assets/${chunk.name}.js`;
            // Only eagerly preload three (the core math/scene-graph lib).
            // three-examples (OrbitControls, loaders, etc.) is loaded on-demand — don't preload it.
            if (chunk.name === 'three') {
                preloadScriptPaths.push(`./${name}`);
            }
            return name;
        }
        // else if(chunk.name === 'index') {
        //     console.log(chunk);
        //     debugger
        //     // this is the main chunk
        //     // we don't want to add a hash here to be able to easily import the main script
        //     return `index.js`;
        // }
        return `assets/[name].[hash].js`;
    }

    /** 
     * @param {string} id 
     * @param {import('vite').Rollup.ManualChunkMeta | null} meta
    */
    function needleManualChunks(id, meta) {

        // console.log(id);

        if (id.includes("three/examples")) {
            return "three-examples";
        }
        if (id.includes('/three/')) {
            return "three";
        }

        if (id.includes("node_modules/n8ao/")) {
            detectSyncImports(id, meta);
            return "postprocessing.ao";
        }

        if (id.includes("node_modules/postprocessing/")) {
            // postprocessing bundle is preloaded https://github.com/vitejs/vite/pull/9938
            detectSyncImports(id, meta);
            return "postprocessing";
        }

        if (id.includes("@dimforge/rapier3d")) {
            detectSyncImports(id, meta);
            return "rapier3d";
        }

        if (id.includes("@needle-tools/materialx")) {
            detectSyncImports(id, meta);
            return "materialx";
        }

        if (id.includes("node_modules/three-mesh-ui")) {
            return "three-mesh-ui";
        }

        if (id.includes("node_modules/three.quarks")) {
            return "three-quarks";
        }
        // we want to bundle gltf-progressive separately because it also initiates Draco and KTX worker preloading
        if (id.includes("/gltf-progressive/")) {
            return "gltf-progressive";
        }
        if (id.includes("node_modules/needle-engine")
            // DEV
            // || id.includes("/package~/src/")
        ) {
            return "needle-engine";
        }
    }
}


/** 
 * @param {string} id 
 * @param {import('vite').Rollup.ManualChunkMeta | null} meta
*/
function detectSyncImports(id, meta) {
    if (meta) {
        if (meta.getModuleInfo) {
            const info = meta.getModuleInfo(id);
            if (info && info.importers.length > 0) {
                const isNeedleDev = id.includes("/package~/src/");
                if (isNeedleDev) {
                    console.warn(`WARN: SYNC IMPORTER DETECTED of ${id}`);
                    console.warn(info.importers)
                }
            }
        }
    }
}