#!/usr/bin/env node

import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import {
  PACK_ARTIFACT_ALLOWED_EXACT_PATHS,
  PACK_ARTIFACT_ALLOWED_PATH_PREFIXES,
  PACK_ARTIFACT_REQUIRED_PATHS,
  findMissingArtifactPaths,
  findUnexpectedArtifactPaths,
} from "./pack-artifact-policy.ts";

const __filename: string = fileURLToPath(import.meta.url);
const __dirname: string = dirname(__filename);
const ROOT: string = join(__dirname, "..", "..");
const npmCommand: string = process.platform === "win32" ? "npm.cmd" : "npm";

function runNpm(args: string[], stdio: "inherit" | "pipe" = "pipe"): string {
  const npmExecPath = process.env.npm_execpath;
  const command = npmExecPath ? process.execPath : npmCommand;
  return execFileSync(command, [...(npmExecPath ? [npmExecPath] : []), ...args], {
    cwd: ROOT,
    encoding: "utf8",
    stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
  });
}

function ensureAppStagingReady(): void {
  const missingAppRequiredPaths = PACK_ARTIFACT_REQUIRED_PATHS.filter((requiredPath) =>
    requiredPath.startsWith("dist/")
  ).filter((requiredPath) => !existsSync(join(ROOT, requiredPath)));

  if (missingAppRequiredPaths.length === 0) return;

  console.log("📦 dist/ staging is missing required runtime files; running npm run build:cli...");
  runNpm(["run", "build:cli"], "inherit");
}

function runPackDryRun(): any {
  const output = runNpm(["pack", "--dry-run", "--json", "--ignore-scripts"]);

  const jsonStart = output.indexOf("[");
  const jsonEnd = output.lastIndexOf("]");
  const jsonPayload =
    jsonStart >= 0 && jsonEnd > jsonStart ? output.slice(jsonStart, jsonEnd + 1) : output;
  const parsed = JSON.parse(jsonPayload);
  const packReport = Array.isArray(parsed) ? parsed[0] : null;

  if (!packReport || !Array.isArray(packReport.files)) {
    throw new Error("npm pack --dry-run --json did not return the expected files[] payload.");
  }

  return packReport;
}

function formatBytes(bytes: number): string {
  if (!Number.isFinite(bytes) || bytes < 1024) {
    return `${bytes || 0} B`;
  }

  const units = ["KB", "MB", "GB"];
  let value = bytes / 1024;
  let unitIndex = 0;

  while (value >= 1024 && unitIndex < units.length - 1) {
    value /= 1024;
    unitIndex++;
  }

  return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unitIndex]}`;
}

try {
  ensureAppStagingReady();
  const packReport = runPackDryRun();
  const artifactPaths: string[] = packReport.files.map((file: any) => file.path);
  const unexpectedPaths: string[] = findUnexpectedArtifactPaths(artifactPaths, {
    exactPaths: PACK_ARTIFACT_ALLOWED_EXACT_PATHS,
    prefixPaths: PACK_ARTIFACT_ALLOWED_PATH_PREFIXES,
  });
  const missingRequiredPaths: string[] = findMissingArtifactPaths(
    artifactPaths,
    PACK_ARTIFACT_REQUIRED_PATHS
  );

  console.log("📦 npm pack artifact summary");
  console.log(`   File:          ${packReport.filename}`);
  console.log(`   Entry count:   ${packReport.entryCount}`);
  console.log(`   Packed size:   ${formatBytes(packReport.size)}`);
  console.log(`   Unpacked size: ${formatBytes(packReport.unpackedSize)}`);

  if (unexpectedPaths.length > 0) {
    console.error("\n❌ Unexpected files were found in the npm publish artifact:");
    for (const unexpectedPath of unexpectedPaths) {
      console.error(`   - ${unexpectedPath}`);
    }
  }

  if (missingRequiredPaths.length > 0) {
    console.error("\n❌ Required runtime files are missing from the npm publish artifact:");
    for (const missingPath of missingRequiredPaths) {
      console.error(`   - ${missingPath}`);
    }
  }

  if (unexpectedPaths.length > 0 || missingRequiredPaths.length > 0) {
    process.exit(1);
  }

  console.log("\n✅ Pack artifact policy check passed.");
} catch (error) {
  console.error(`\n❌ Pack artifact validation failed: ${error.message}`);
  process.exit(1);
}
