import {
  cpSync,
  existsSync,
  mkdirSync,
  readdirSync,
  readFileSync,
  rmSync,
  statSync,
  writeFileSync,
} from "node:fs";
import { basename, dirname, join, relative, resolve } from "node:path";
import { brotliCompressSync, gzipSync } from "node:zlib";
import { repoRoot, runCommand } from "./lib/moonbit-tool-runner.mjs";

export const runtimeAssetPaths = [
  "moui_web_renderer/runtime.js",
  "moui/backend/web/browser_runtime.js",
  "moui_web_renderer/canvas2d_runtime.js",
];

export function usageAndExit(message, usage) {
  if (message) {
    console.error(message);
  }
  console.error(usage);
  process.exit(message ? 1 : 0);
}

export function parseCommonArgs(argv, usage) {
  const options = {
    packagePath: "",
    json: false,
    noBuild: false,
    outDir: "",
  };
  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    switch (arg) {
      case "--json":
        options.json = true;
        break;
      case "--no-build":
        options.noBuild = true;
        break;
      case "--out":
        options.outDir = argv[++index] || "";
        break;
      case "-h":
      case "--help":
        usageAndExit("", usage);
        break;
      default:
        if (arg.startsWith("-")) {
          usageAndExit(`Unknown option: ${arg}`, usage);
        }
        if (options.packagePath) {
          usageAndExit(`Unexpected argument: ${arg}`, usage);
        }
        options.packagePath = arg;
        break;
    }
  }
  if (!options.packagePath) {
    usageAndExit("Missing web package path.", usage);
  }
  return options;
}

export function normalizePackagePath(packagePath) {
  return packagePath.replace(/^\.\/+/, "").replace(/\/+$/, "");
}

export function buildWebPackage(packagePath) {
  runCommand("moon", [
    "build",
    packagePath,
    "--target",
    "wasm-gc",
    "--release",
    "--strip",
  ], {
    env: {
      ...process.env,
      MOUI_SKIA_DISABLE_PREBUILD_SKIA:
        process.env.MOUI_SKIA_DISABLE_PREBUILD_SKIA || "1",
    },
  });
}

export function wasmArtifactPath(packagePath) {
  const normalized = normalizePackagePath(packagePath);
  return join(
    repoRoot,
    "_build",
    "wasm-gc",
    "release",
    "build",
    normalized,
    `${basename(normalized)}.wasm`,
  );
}

export function packageRoot(packagePath) {
  return join(repoRoot, normalizePackagePath(packagePath));
}

export function measureFile(filePath, { name, kind, group }) {
  const bytes = readFileSync(filePath);
  return {
    name,
    kind,
    group,
    path: relative(repoRoot, filePath),
    rawBytes: bytes.length,
    gzipBytes: gzipSync(bytes).length,
    brotliBytes: brotliCompressSync(bytes).length,
  };
}

export function collectAssetFiles(root, prefix = "") {
  if (!existsSync(root)) {
    return [];
  }
  const entries = [];
  for (const name of readdirSync(root).sort()) {
    const fullPath = join(root, name);
    const relativePath = prefix ? `${prefix}/${name}` : name;
    const stat = statSync(fullPath);
    if (stat.isDirectory()) {
      entries.push(...collectAssetFiles(fullPath, relativePath));
    } else if (stat.isFile()) {
      entries.push({ fullPath, relativePath });
    }
  }
  return entries;
}

export function collectBundleSize(packagePath) {
  const normalized = normalizePackagePath(packagePath);
  const wasmPath = wasmArtifactPath(normalized);
  if (!existsSync(wasmPath)) {
    throw new Error(`Wasm artifact not found: ${relative(repoRoot, wasmPath)}`);
  }
  const runtimeAssets = runtimeAssetPaths.map(assetPath => {
    const filePath = join(repoRoot, assetPath);
    return measureFile(filePath, {
      name: basename(assetPath),
      kind: "js",
      group: "runtime",
    });
  });
  const wasmAsset = measureFile(wasmPath, {
    name: basename(wasmPath),
    kind: "wasm",
    group: "app",
  });
  const assetsRoot = join(packageRoot(normalized), "assets");
  const appAssets = collectAssetFiles(assetsRoot).map(asset => measureFile(
    asset.fullPath,
    {
      name: `assets/${asset.relativePath}`,
      kind: "asset",
      group: "assets",
    },
  ));
  const assets = [wasmAsset, ...runtimeAssets, ...appAssets];
  const groupAssets = group =>
    assets.filter(asset => asset.group === group);
  const groupTotal = (group, key) =>
    groupAssets(group).reduce((total, asset) => total + asset[key], 0);
  const sum = key => assets.reduce((total, asset) => total + asset[key], 0);
  return {
    schemaVersion: 1,
    mode: "moui-web-bundle-size",
    package: normalized,
    wasmPath: relative(repoRoot, wasmPath),
    assets,
    groupTotals: {
      app: {
        rawBytes: groupTotal("app", "rawBytes"),
        gzipBytes: groupTotal("app", "gzipBytes"),
        brotliBytes: groupTotal("app", "brotliBytes"),
      },
      runtime: {
        rawBytes: groupTotal("runtime", "rawBytes"),
        gzipBytes: groupTotal("runtime", "gzipBytes"),
        brotliBytes: groupTotal("runtime", "brotliBytes"),
      },
      assets: {
        rawBytes: groupTotal("assets", "rawBytes"),
        gzipBytes: groupTotal("assets", "gzipBytes"),
        brotliBytes: groupTotal("assets", "brotliBytes"),
      },
    },
    totals: {
      rawBytes: sum("rawBytes"),
      gzipBytes: sum("gzipBytes"),
      brotliBytes: sum("brotliBytes"),
    },
  };
}

export function printBundleTable(manifest) {
  const rows = [
    ["Asset", "Kind", "Group", "Raw", "Gzip", "Brotli"],
    ...manifest.assets.map(asset => [
      asset.name,
      asset.kind,
      asset.group,
      String(asset.rawBytes),
      String(asset.gzipBytes),
      String(asset.brotliBytes),
    ]),
    [
      "TOTAL",
      "",
      "",
      String(manifest.totals.rawBytes),
      String(manifest.totals.gzipBytes),
      String(manifest.totals.brotliBytes),
    ],
  ];
  const widths = rows[0].map((_, index) =>
    Math.max(...rows.map(row => row[index].length)),
  );
  for (const row of rows) {
    console.log(row.map((cell, index) => cell.padEnd(widths[index])).join("  "));
  }
}

export function cleanOutputDir(outDir) {
  const resolved = resolve(repoRoot, outDir);
  if (resolved === repoRoot || !relative(repoRoot, resolved)) {
    throw new Error("Refusing to use repository root as package output directory.");
  }
  rmSync(resolved, { recursive: true, force: true });
  mkdirSync(resolved, { recursive: true });
  return resolved;
}

export function copyFileEnsuringDir(source, destination) {
  mkdirSync(dirname(destination), { recursive: true });
  cpSync(source, destination);
}

export function copyDirectory(source, destination) {
  if (!existsSync(source)) {
    return false;
  }
  cpSync(source, destination, { recursive: true });
  return true;
}

export function writeCompressedSiblings(filePath) {
  const bytes = readFileSync(filePath);
  writeFileSync(`${filePath}.gz`, gzipSync(bytes));
  writeFileSync(`${filePath}.br`, brotliCompressSync(bytes));
}

export function rewriteIndexForPackage(indexHtml, wasmFileName) {
  let rewritten = indexHtml.replace(
    /from\s+["'][^"']*moui_web_renderer\/runtime\.js["']/,
    'from "./runtime.js"',
  );
  rewritten = rewritten.replace(
    /(["'])[^"']*_build\/wasm-gc\/[^"']*\.wasm\1/,
    `"./${wasmFileName}"`,
  );
  return rewritten;
}

export function rewriteRuntimeForPackage(runtimeSource) {
  return runtimeSource.replace(
    /from\s+["']\.\.\/moui\/backend\/web\/browser_runtime\.js["']/,
    'from "./browser_runtime.js"',
  );
}