import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const PRODUCER_DIR = dirname(fileURLToPath(import.meta.url));
const SIBLING_MANIFEST_PATH = resolve(PRODUCER_DIR, "hyperframe.manifest.json");
const MODULE_RELATIVE_MANIFEST_PATH = resolve(
  PRODUCER_DIR,
  "../../../core/dist/hyperframe.manifest.json",
);
const CWD_RELATIVE_MANIFEST_PATHS = [
  // When bundled to a single file (dist/public-server.js), the manifest
  // is copied as a sibling by build.mjs
  resolve(PRODUCER_DIR, "hyperframe.manifest.json"),
  resolve(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
  resolve(process.cwd(), "../core/dist/hyperframe.manifest.json"),
  resolve(process.cwd(), "core/dist/hyperframe.manifest.json"),
];

type HyperframeRuntimeManifest = {
  sha256?: string;
  artifacts?: {
    iife?: string;
  };
};

export type ResolvedHyperframeRuntime = {
  manifestPath: string;
  runtimePath: string;
  expectedSha256: string;
  actualSha256: string;
  runtimeSource: string;
};

export function resolveHyperframeManifestPath(): string {
  if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
    return process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH;
  }
  const candidates = [
    SIBLING_MANIFEST_PATH,
    ...CWD_RELATIVE_MANIFEST_PATHS,
    MODULE_RELATIVE_MANIFEST_PATH,
  ];
  for (const candidate of candidates) {
    if (existsSync(candidate)) {
      return candidate;
    }
  }
  return MODULE_RELATIVE_MANIFEST_PATH;
}

export function getVerifiedHyperframeRuntimeSource(): string {
  return resolveVerifiedHyperframeRuntime().runtimeSource;
}

export function resolveVerifiedHyperframeRuntime(): ResolvedHyperframeRuntime {
  const manifestPath = resolveHyperframeManifestPath();
  if (!existsSync(manifestPath)) {
    throw new Error(
      `[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`,
    );
  }

  const manifestRaw = readFileSync(manifestPath, "utf8");
  const manifest = JSON.parse(manifestRaw) as HyperframeRuntimeManifest;
  const runtimeFileName = manifest.artifacts?.iife;
  if (!runtimeFileName || !manifest.sha256) {
    throw new Error(
      `[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`,
    );
  }

  const runtimePath = resolve(dirname(manifestPath), runtimeFileName);
  if (!existsSync(runtimePath)) {
    throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
  }

  const runtimeSource = readFileSync(runtimePath, "utf8");
  const runtimeSha = createHash("sha256").update(runtimeSource, "utf8").digest("hex");
  if (runtimeSha !== manifest.sha256) {
    throw new Error(
      `[HyperframeRuntimeLoader] Runtime checksum mismatch. expected=${manifest.sha256} actual=${runtimeSha}`,
    );
  }
  return {
    manifestPath,
    runtimePath,
    expectedSha256: manifest.sha256,
    actualSha256: runtimeSha,
    runtimeSource,
  };
}
