import { collectBuildDirectoryStats, createBuildInfoFile } from '../common/buildinfo.js';
import { formatBytes } from '../common/files.js';
import { closeLogStreams } from '../common/logger.js';
import { waitForBuildPipelineToFinish } from './build-pipeline.js';
import { getOutputDirectory } from './config.js';
import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';

let level = 0;

/** Create a buildinfo file in the build directory.
 * @param {"build" | "serve"} command
 * @param {import('../types/needleConfig').needleMeta | null | undefined} config
 * @param {import('../types').userSettings} userSettings
 * @returns {import('vite').Plugin | null}
 */
export function needleBuildInfo(command, config, userSettings) {

    if (userSettings?.noBuildInfo) return null;

    // let buildDirectory = null;

    // /** @type {import('../common/buildinfo').BuildInfo | null} */
    // let beforeStats = null;

    return {
        name: 'needle:buildinfo',
        apply: "build",
        enforce: "post",
        buildStart: () => {
            level++;
        },
        buildEnd(error) {
            if (--level > 0) {
                return; // nested build (e.g. worker entry) — skip
            }
            // waitForBuildPipelineToFinish().then(res => {
            //     beforeStats = collectBuildDirectoryStats(buildDirectory);
            // })
        },
        closeBundle: () => {
            if (--level > 0) {
                return; // nested build (e.g. worker entry) — skip
            }

            const buildDirectory = getOutputDirectory();
            const beforeStats = collectBuildDirectoryStats(buildDirectory);

            const handleClose = async () => {

                const task = waitForBuildPipelineToFinish();
                if (task instanceof Promise) {
                    if (process.env.DEBUG) needleLog("needle-buildinfo", "Waiting for build pipeline to finish");
                    await task.catch(() => { });
                }
                // wait for gzip
                await delay(500);

                const closedHandles = closeDanglingNetworkHandles();

                const result = createBuildInfoFile(buildDirectory, { log: false });
                if (!result?.ok) {
                    needleLog("needle-buildinfo", result?.error || "Failed to create build info file", "warn", { dimBody: false });
                    return;
                }

                const supportsColor = needleSupportsColor();
                const key = (text) => supportsColor ? needleBlue(text) : text;

                const sizeDelta = (result.totalSizeBytes || 0) - (beforeStats.totalSizeBytes || 0);
                const fileDelta = (result.fileCount || 0) - (beforeStats.fileCount || 0);
                const sizePct = beforeStats.totalSizeBytes > 0 ? Math.round(sizeDelta / beforeStats.totalSizeBytes * 100) : 0;
                const strTotalSize = beforeStats.totalSizeBytes !== result.totalSizeBytes ? `${formatBytes(beforeStats.totalSizeBytes)} → ${formatBytes(result.totalSizeBytes)} (${sizePct >= 0 ? "+" : ""}${sizePct}%)` : formatBytes(result.totalSizeBytes);
                const strFileCount = beforeStats.fileCount !== result.fileCount ? `${beforeStats.fileCount} → ${result.fileCount} (${fileDelta >= 0 ? "+" : ""}${fileDelta})` : `${result.fileCount}`;

                const lines = [];
                lines.push(`${key("Build info")}: ${strTotalSize}, ${strFileCount} files`);
                lines.push(
                    ...(process.env.DEBUG ? [
                        needleDim(`Begin collecting files in \"${result.buildDirectory}\"`),
                        needleDim(`Collected ${result.fileCount} files (${result.totalSizeInMB.toFixed(2)} MB). Writing build info to \"${result.buildInfoPath}\"`),
                        needleDim(`Build info file successfully written to \"${result.buildInfoPath}\"`),
                    ] : []),
                )
                if (closedHandles > 0) lines.push(`Closed ${closedHandles} dangling network handle(s)`);
                needleLog("needle-buildinfo", lines.join("\n"), "log", { dimBody: false });
                closeLogStreams();

            };
            return handleClose();
        }
    }
}

function delay(ms) {
    return new Promise(res => setTimeout(res, ms));
}

function closeDanglingNetworkHandles() {
    /** @type {() => any[] | undefined} */
    // @ts-ignore private node api
    const getActiveHandles = process._getActiveHandles;
    if (typeof getActiveHandles !== "function") {
        return 0;
    }

    let closed = 0;
    const handles = getActiveHandles() || [];
    for (const handle of handles) {
        if (!handle || handle === process.stdin || handle === process.stdout || handle === process.stderr) continue;
        const hasNetworkShape = typeof handle.destroy === "function" && (typeof handle.remotePort === "number" || typeof handle.remoteAddress === "string");
        if (!hasNetworkShape) continue;

        if (typeof handle.remotePort === "number" && handle.remotePort > 0) {
            try {
                handle.destroy();
                closed++;
            }
            catch {
                // ignore cleanup failures
            }
        }
    }
    return closed;
}