import { expect, test } from "bun:test";
import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
  compileWireArtifacts,
  compileWireArtifactsAsync,
  getWrnCompileMetrics,
  invalidateModule,
  loadModule,
  resetWrnCompileMetrics,
  setCompileCacheDir,
  setDevCompilerPipeline,
} from "../src/pipeline.ts";

test("development compilation awaits plugin AST and code transforms", async () => {
  const root = mkdtempSync(join(tmpdir(), "wrnexus-plugin-pipeline-"));
  const file = join(root, "page.wrn");
  let astTransformed = false;
  setCompileCacheDir(join(root, ".wrnexus"));
  setDevCompilerPipeline({
    async transformAst(ast) {
      await Promise.resolve();
      astTransformed = true;
      return ast;
    },
    async transformCode(code) {
      await Promise.resolve();
      return `${code}\nexport const pluginTransformed = true;\n`;
    },
    virtualModules: new Map(),
  });
  try {
    writeFileSync(file, "page Home { view { <h1>Plugin</h1> } }\n");
    const artifact = await compileWireArtifactsAsync(file);
    expect(astTransformed).toBeTrue();
    expect((await import(artifact.main)).pluginTransformed).toBeTrue();
  } finally {
    setDevCompilerPipeline(null);
    rmSync(root, { recursive: true, force: true });
  }
});

test("WRN compilation exposes cache hit, miss, timing, and error metrics", () => {
  const root = mkdtempSync(join(tmpdir(), "wrnexus-cache-metrics-"));
  const file = join(root, "page.wrn");
  setCompileCacheDir(join(root, ".wrnexus"));
  resetWrnCompileMetrics();
  try {
    writeFileSync(file, "page Home { view { <h1>Metrics</h1> } }\n");
    compileWireArtifacts(file);
    compileWireArtifacts(file);
    writeFileSync(file, "page Broken { view { <h1> } }\n");
    expect(() => compileWireArtifacts(file)).toThrow();
    expect(getWrnCompileMetrics()).toMatchObject({
      hits: 1,
      misses: 2,
      compilations: 1,
      errors: 1,
    });
    expect(getWrnCompileMetrics().totalDurationMs).toBeGreaterThanOrEqual(0);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test("invalidateModule loads changed server modules without restarting the process", async () => {
  const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-"));
  const file = join(root, "page.ts");
  setCompileCacheDir(join(root, ".wrnexus"));

  try {
    writeFileSync(file, "export const value = 1;\n");
    expect((await loadModule(file)).value).toBe(1);

    writeFileSync(file, "export const value = 2;\n");
    expect((await loadModule(file)).value).toBe(1);

    invalidateModule(file);
    expect((await loadModule(file)).value).toBe(2);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test("invalidateModule recompiles changed WRN files with watcher-normalized separators", async () => {
  const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-"));
  const file = join(root, "page.wrn");
  setCompileCacheDir(join(root, ".wrnexus"));

  try {
    writeFileSync(file, "page Home { view { <h1>First</h1> } }\n");
    const first = (await loadModule(file)).default as (ctx: unknown) => unknown;
    expect(String(first({}))).toContain("First");

    writeFileSync(file, "page Home { view { <h1>Second</h1> } }\n");
    invalidateModule(file.replace(/\\/g, "/"));
    const second = (await loadModule(file)).default as (ctx: unknown) => unknown;
    expect(String(second({}))).toContain("Second");
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test("WRN cache follows content when a replacement has an older mtime", async () => {
  const root = mkdtempSync(join(tmpdir(), "wrnexus-cache-"));
  const file = join(root, "component.wrn");
  setCompileCacheDir(join(root, ".wrnexus"));

  try {
    writeFileSync(file, "component Example { view { <p>Old</p> } }\n");
    const first = (await loadModule(file)).render as () => unknown;
    expect(String(first())).toContain("Old");

    writeFileSync(file, "component Example { view { <p>Current</p> } }\n");
    utimesSync(file, new Date(0), new Date(0));
    invalidateModule(file);

    const current = (await loadModule(file)).render as () => unknown;
    expect(String(current())).toContain("Current");
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});
