const byteFormatter = new Intl.NumberFormat("en-US");
const formatBytes = (value) => {
    if (value < 1024)
        return `${byteFormatter.format(value)} B`;
    const units = ["KiB", "MiB", "GiB"];
    let num = value;
    for (const unit of units) {
        num /= 1024;
        if (num < 1024) {
            return `${num.toFixed(2)} ${unit}`;
        }
    }
    return `${num.toFixed(2)} TiB`;
};
const formatPercent = (value) => `${value.toFixed(1)}%`;
const sharePercent = (value, total) => {
    if (total === 0)
        return "0.0%";
    return formatPercent((value / total) * 100);
};
const yesNo = (value) => (value ? "yes" : "no");
const formatFilters = (filters) => {
    if (filters == null)
        return "none";
    const values = Array.isArray(filters) ? filters : [filters];
    if (values.length === 0)
        return "none";
    return `\`${JSON.stringify(values)}\``;
};
export const outputMarkdown = (strData, reportConfig) => {
    const data = JSON.parse(strData);
    const SLICE_BUNDLE_COUNT = 8;
    const SLICE_MODULES_PER_BUNDLE = 8;
    const SLICE_MEMBERSHIP_MODULE_COUNT = 12;
    const SLICE_MEMBERSHIP_BUNDLE_PARTS = 6;
    const moduleRows = [];
    const bundleMap = new Map();
    const bundleModuleParts = new Map();
    for (const meta of Object.values(data.nodeMetas)) {
        const row = {
            id: meta.id,
            bundles: 0,
            renderedLength: 0,
            gzipLength: 0,
            brotliLength: 0,
            importedByCount: meta.importedBy.length,
            importedCount: meta.imported.length,
            parts: [],
        };
        for (const [bundleId, partUid] of Object.entries(meta.moduleParts)) {
            const part = data.nodeParts[partUid];
            if (!part)
                continue;
            row.bundles += 1;
            row.renderedLength += part.renderedLength;
            row.gzipLength += part.gzipLength;
            row.brotliLength += part.brotliLength;
            row.parts.push({
                bundleId,
                renderedLength: part.renderedLength,
                gzipLength: part.gzipLength,
                brotliLength: part.brotliLength,
            });
            const bundle = bundleMap.get(bundleId) ?? {
                bundleId,
                modules: 0,
                renderedLength: 0,
                gzipLength: 0,
                brotliLength: 0,
            };
            bundle.modules += 1;
            bundle.renderedLength += part.renderedLength;
            bundle.gzipLength += part.gzipLength;
            bundle.brotliLength += part.brotliLength;
            bundleMap.set(bundleId, bundle);
            const bundleParts = bundleModuleParts.get(bundleId) ?? [];
            bundleParts.push({
                id: row.id,
                renderedLength: part.renderedLength,
                gzipLength: part.gzipLength,
                brotliLength: part.brotliLength,
            });
            bundleModuleParts.set(bundleId, bundleParts);
        }
        if (row.bundles > 0) {
            moduleRows.push(row);
        }
    }
    const bundleRows = [...bundleMap.values()].toSorted((a, b) => b.renderedLength - a.renderedLength);
    const hotModules = [...moduleRows].toSorted((a, b) => b.renderedLength - a.renderedLength);
    const duplicatedModules = [...moduleRows]
        .filter((row) => row.bundles > 1)
        .map((row) => {
        const biggestRendered = row.parts.reduce((max, part) => Math.max(max, part.renderedLength), 0);
        const duplicatedRendered = row.renderedLength - biggestRendered;
        return { ...row, duplicatedRendered };
    })
        .toSorted((a, b) => b.duplicatedRendered - a.duplicatedRendered);
    const packageRegex = /(?:^|[/\\])node_modules[/\\]((?:@[^/\\]+[/\\])?[^/\\]+)/;
    const packages = new Map();
    for (const row of moduleRows) {
        const match = packageRegex.exec(row.id);
        if (!match)
            continue;
        const packageName = match[1].replace(/\\/g, "/");
        const current = packages.get(packageName) ?? { modules: 0, renderedLength: 0 };
        current.modules += 1;
        current.renderedLength += row.renderedLength;
        packages.set(packageName, current);
    }
    const packageRows = [...packages.entries()]
        .map(([name, value]) => ({ name, ...value }))
        .toSorted((a, b) => b.renderedLength - a.renderedLength);
    let staticImports = 0;
    let dynamicImports = 0;
    for (const meta of Object.values(data.nodeMetas)) {
        for (const imported of meta.imported) {
            if (imported.dynamic) {
                dynamicImports += 1;
            }
            else {
                staticImports += 1;
            }
        }
    }
    const totalRendered = moduleRows.reduce((sum, row) => sum + row.renderedLength, 0);
    const totalGzip = moduleRows.reduce((sum, row) => sum + row.gzipLength, 0);
    const totalBrotli = moduleRows.reduce((sum, row) => sum + row.brotliLength, 0);
    const entries = Object.values(data.nodeMetas).filter((meta) => meta.isEntry).length;
    const externals = Object.values(data.nodeMetas).filter((meta) => meta.isExternal).length;
    let output = "";
    output += "# Bundle Report\n\n";
    output += `| Bundles | Modules | Entries | Externals | Static Imports | Dynamic Imports |\n`;
    output += `| ---: | ---: | ---: | ---: | ---: | ---: |\n`;
    output += `| ${String(bundleRows.length)} | ${String(moduleRows.length)} | ${String(entries)} | ${String(externals)} | ${String(staticImports)} | ${String(dynamicImports)} |\n\n`;
    output += `| Metric | Total |\n`;
    output += `| --- | ---: |\n`;
    output += `| Rendered | ${formatBytes(totalRendered)} |\n`;
    if (data.options.gzip) {
        output += `| Gzip | ${formatBytes(totalGzip)} |\n`;
    }
    if (data.options.brotli) {
        output += `| Brotli | ${formatBytes(totalBrotli)} |\n`;
    }
    output += `\n`;
    output += "## Top 10\n\n";
    const topRows = hotModules.slice(0, 10);
    if (topRows.length === 0) {
        output += "- none\n";
    }
    else {
        for (const row of topRows) {
            output += `- \`${row.id}\`: ${sharePercent(row.renderedLength, totalRendered)} (${formatBytes(row.renderedLength)})\n`;
        }
    }
    output += "\n";
    output += "## Hot Modules (Self Size)\n\n";
    output += `| Rendered% | Rendered | Gzip | Brotli | Bundles | Imported By | Imports | Module |\n`;
    output += `| ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |\n`;
    for (const row of hotModules.slice(0, 15)) {
        output += `| ${sharePercent(row.renderedLength, totalRendered)} | ${formatBytes(row.renderedLength)} | ${formatBytes(row.gzipLength)} | ${formatBytes(row.brotliLength)} | ${String(row.bundles)} | ${String(row.importedByCount)} | ${String(row.importedCount)} | \`${row.id}\` |\n`;
    }
    output += "\n";
    output += "## Bundle Breakdown\n\n";
    output += `| Bundle | Modules | Rendered | Gzip | Brotli |\n`;
    output += `| --- | ---: | ---: | ---: | ---: |\n`;
    for (const bundle of bundleRows) {
        output += `| \`${bundle.bundleId}\` | ${String(bundle.modules)} | ${formatBytes(bundle.renderedLength)} | ${formatBytes(bundle.gzipLength)} | ${formatBytes(bundle.brotliLength)} |\n`;
    }
    output += "\n";
    output += "## Duplicate Modules Across Bundles\n\n";
    output += `| Duplicated Rendered | Total Rendered | Bundles | Module |\n`;
    output += `| ---: | ---: | ---: | --- |\n`;
    for (const row of duplicatedModules.slice(0, 15)) {
        output += `| ${formatBytes(row.duplicatedRendered)} | ${formatBytes(row.renderedLength)} | ${String(row.bundles)} | \`${row.id}\` |\n`;
    }
    if (duplicatedModules.length === 0) {
        output += `| 0 B | 0 B | 0 | none |\n`;
    }
    output += "\n";
    output += "## Top Packages\n\n";
    output += `| Package | Modules | Rendered |\n`;
    output += `| --- | ---: | ---: |\n`;
    for (const row of packageRows.slice(0, 15)) {
        output += `| \`${row.name}\` | ${String(row.modules)} | ${formatBytes(row.renderedLength)} |\n`;
    }
    if (packageRows.length === 0) {
        output += `| none | 0 | 0 B |\n`;
    }
    output += "\n";
    output += "## Per-Bundle Top Modules (Slices)\n\n";
    const sliceBundles = bundleRows.slice(0, SLICE_BUNDLE_COUNT);
    if (sliceBundles.length === 0) {
        output += "- none\n\n";
    }
    else {
        for (const bundle of sliceBundles) {
            output += `### \`${bundle.bundleId}\`\n\n`;
            output += `| Module | Rendered% | Rendered | Gzip | Brotli |\n`;
            output += `| --- | ---: | ---: | ---: | ---: |\n`;
            const parts = (bundleModuleParts.get(bundle.bundleId) ?? [])
                .toSorted((a, b) => b.renderedLength - a.renderedLength)
                .slice(0, SLICE_MODULES_PER_BUNDLE);
            if (parts.length === 0) {
                output += `| none | 0.0% | 0 B | 0 B | 0 B |\n`;
            }
            else {
                for (const part of parts) {
                    output += `| \`${part.id}\` | ${sharePercent(part.renderedLength, bundle.renderedLength)} | ${formatBytes(part.renderedLength)} | ${formatBytes(part.gzipLength)} | ${formatBytes(part.brotliLength)} |\n`;
                }
            }
            output += "\n";
        }
    }
    output += "## Top Module Bundle Membership (Slice)\n\n";
    output += `| Module | Bundles | Rendered | Bundle Parts |\n`;
    output += `| --- | ---: | ---: | --- |\n`;
    const membershipRows = hotModules.slice(0, SLICE_MEMBERSHIP_MODULE_COUNT);
    if (membershipRows.length === 0) {
        output += `| none | 0 | 0 B | none |\n`;
    }
    else {
        for (const row of membershipRows) {
            const parts = [...row.parts].toSorted((a, b) => b.renderedLength - a.renderedLength);
            const formattedParts = parts
                .slice(0, SLICE_MEMBERSHIP_BUNDLE_PARTS)
                .map((part) => `\`${part.bundleId}\`: ${formatBytes(part.renderedLength)}`);
            const moreParts = parts.length > SLICE_MEMBERSHIP_BUNDLE_PARTS
                ? `; +${String(parts.length - SLICE_MEMBERSHIP_BUNDLE_PARTS)} more`
                : "";
            output += `| \`${row.id}\` | ${String(row.bundles)} | ${formatBytes(row.renderedLength)} | ${formattedParts.join("; ")}${moreParts} |\n`;
        }
    }
    output += "\n";
    const hasRuntimeConfig = reportConfig != null;
    output += "## Plugin Settings\n\n";
    output += `| Setting | Value |\n`;
    output += `| --- | --- |\n`;
    output += `| rollup version | \`${String(data.env.rollup ?? "unknown")}\` |\n`;
    output += `| sourcemap mode | \`${yesNo(hasRuntimeConfig ? reportConfig.sourcemap : data.options.sourcemap)}\` |\n`;
    output += `| output.sourcemap | \`${hasRuntimeConfig ? yesNo(reportConfig.outputSourcemap) : "not available"}\` |\n`;
    output += `| gzip requested | \`${hasRuntimeConfig ? yesNo(reportConfig.gzipSize.requested) : "not available"}\` |\n`;
    output += `| gzip enabled | \`${hasRuntimeConfig ? yesNo(reportConfig.gzipSize.enabled) : yesNo(data.options.gzip)}\` |\n`;
    output += `| brotli requested | \`${hasRuntimeConfig ? yesNo(reportConfig.brotliSize.requested) : "not available"}\` |\n`;
    output += `| brotli enabled | \`${hasRuntimeConfig ? yesNo(reportConfig.brotliSize.enabled) : yesNo(data.options.brotli)}\` |\n`;
    output += `| include filters | ${hasRuntimeConfig ? formatFilters(reportConfig.include) : "not available"} |\n`;
    output += `| exclude filters | ${hasRuntimeConfig ? formatFilters(reportConfig.exclude) : "not available"} |\n\n`;
    output += "## Notes\n\n";
    output +=
        "- Size precision depends on mode. With `sourcemap: true`, bytes are attributed via source maps. Otherwise bytes come from rendered module code.\n";
    output +=
        "- In plugin mode, enabling `sourcemap` disables gzip/brotli size collection for this report.\n";
    output += "- `include`/`exclude` filters remove unmatched modules from statistics entirely.\n";
    output +=
        "- External modules can appear in dependency links but do not contribute per-bundle size parts.\n";
    return output;
};
