import { collectBuildDirectoryStats, createBuildInfoFile } from '../common/buildinfo.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;

    return {
        name: 'needle:buildinfo',
        apply: "build",
        enforce: "post",
        buildStart: () => {
            level++;
        },
        closeBundle: async () => {
            if (--level > 0) {
                return; // nested build (e.g. worker entry) — skip
            }
            const buildDirectory = getOutputDirectory();
            const beforeStats = collectBuildDirectoryStats(buildDirectory);
            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 result = createBuildInfoFile(buildDirectory, { log: false });
            const closedHandles = closeDanglingNetworkHandles();
            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 lines = [
                "Build pipeline finished!",
                `${key("Build info")}: ${formatBytes(beforeStats.totalSizeBytes)} → ${formatBytes(result.totalSizeBytes)} (${sizePct >= 0 ? "+" : ""}${sizePct}%), ${beforeStats.fileCount} → ${result.fileCount} files (${fileDelta >= 0 ? "+" : ""}${fileDelta})`,
                ...(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();
        }
    }
}

function formatBytes(bytes) {
    const value = Number(bytes || 0);
    if (value < 1024) return `${value} B`;
    if (value < 1024 * 1024) return `${(value / 1024).toFixed(2)} kB`;
    if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MB`;
    return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}

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;
}