import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { mkdtemp, rm, mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { run } from "../src/main.ts";

const OFFENDING_STYLE =
  "<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>";

async function makeTmpDir(): Promise<string> {
  return await mkdtemp(join(tmpdir(), "hoist-astro-styles-"));
}

async function writeFile(path: string, contents: string): Promise<void> {
  await Bun.write(path, contents);
}

async function readFile(path: string): Promise<string> {
  return await Bun.file(path).text();
}

describe("hoist-astro-styles", () => {
  let dir: string;

  beforeEach(async () => {
    dir = await makeTmpDir();
  });

  afterEach(async () => {
    await rm(dir, { recursive: true, force: true });
  });

  test("rewrites a file with offending style", async () => {
    const filePath = join(dir, "index.html");
    const original = `<!doctype html><html><head><title>t</title></head><body><div>${OFFENDING_STYLE}<astro-island></astro-island></div></body></html>`;
    await writeFile(filePath, original);

    const result = await run({ directory: dir, dryRun: false, quiet: true });

    expect(result.rewritten).toBe(1);
    expect(result.total).toBe(1);

    const after = await readFile(filePath);
    expect(after.includes("<body><div><astro-island>")).toBe(true);
    expect(after.includes(`${OFFENDING_STYLE}</head>`)).toBe(true);
    // The offending style should only appear once now (inside <head>).
    const matches = after.match(
      /<style>astro-island,astro-slot,astro-static-slot\{display:contents\}<\/style>/g,
    );
    expect(matches?.length).toBe(1);
  });

  test("noop on file without offending style", async () => {
    const filePath = join(dir, "clean.html");
    const original = `<!doctype html><html><head><title>t</title></head><body><p>hi</p></body></html>`;
    await writeFile(filePath, original);

    const result = await run({ directory: dir, dryRun: false, quiet: true });

    expect(result.rewritten).toBe(0);
    expect(result.total).toBe(1);

    const after = await readFile(filePath);
    expect(after).toBe(original);
  });

  test("dry-run does not write to disk", async () => {
    const filePath = join(dir, "page.html");
    const original = `<!doctype html><html><head></head><body>${OFFENDING_STYLE}</body></html>`;
    await writeFile(filePath, original);

    const result = await run({ directory: dir, dryRun: true, quiet: true });

    expect(result.rewritten).toBe(1);
    const after = await readFile(filePath);
    expect(after).toBe(original);
  });

  test("handles file without </head>", async () => {
    const filePath = join(dir, "fragment.html");
    const original = `<div>${OFFENDING_STYLE}<p>hi</p></div>`;
    await writeFile(filePath, original);

    const result = await run({ directory: dir, dryRun: false, quiet: true });

    expect(result.rewritten).toBe(0);
    expect(result.skippedNoHead.length).toBe(1);

    const after = await readFile(filePath);
    expect(after).toBe(original);
  });

  test("recursive walk picks up nested files", async () => {
    const nested = join(dir, "a", "b");
    await mkdir(nested, { recursive: true });

    const f1 = join(dir, "root.html");
    const f2 = join(nested, "leaf.html");
    const html = `<!doctype html><html><head></head><body>${OFFENDING_STYLE}</body></html>`;
    await writeFile(f1, html);
    await writeFile(f2, html);

    const result = await run({ directory: dir, dryRun: false, quiet: true });

    expect(result.total).toBe(2);
    expect(result.rewritten).toBe(2);
  });
});
