import { expect, test } from "bun:test";
import { generate, generateTargets, parse } from "../src/index.ts";

test("page computed values derived from request state are declared during SSR", () => {
  const code = generate(
    parse(`import UserCard from "./UserCard.wrn"
page Chat {
  state {
    currentUserId = ctx.url.searchParams.get("as") ?? "asha"
    users = [{ id: "asha", name: "Asha" }, { id: "rohan", name: "Rohan" }]
  }
  computed {
    currentUser = users.find((user) => user.id === currentUserId) ?? users[0]
  }
  view {
    <h1>{currentUser.name}</h1>
    {#each users as user}
      <UserCard user='{user}' />
      <div data-user='{user.id}'></div>
    {/each}
  }
}`),
  );

  expect(code).toContain("const currentUser =");
  expect(code).toContain("__wrnexusEscapeHtml(currentUser.name)");
  expect(code).toContain("__wrnexusPropAttr(user)");
  expect(code).toContain("__wrnexusEscapeHtml(user.id)");
  expect(code).not.toContain("__wrnexusPropAttr(user.id)");
  expect(code).toContain('encoded = value === undefined ? "undefined" : JSON.stringify(value)');
  expect(code).not.toContain("String((__state as any)[key])");
});

test("computed values that reference ctx directly stay dynamic during SSR", () => {
  const code = generate(
    parse(`page DirectRequestComputed {
  computed { identity = ctx.url.searchParams.get("as") ?? "asha" }
  view { <p>{identity}</p> }
}`),
  );
  expect(code).toContain("const identity =");
  expect(code).toContain("__wrnexusEscapeHtml(identity)");
});

test("computed values derived from server loads stay dynamic during SSR", () => {
  const code = generate(
    parse(`page LoadedComputed {
  load server overview { return { count: 4 } }
  computed { total = overview.count + 1 }
  view { <p>{total}</p> }
}`),
  );
  expect(code).toContain('const overview = ctx["overview"]');
  expect(code).toContain("__wrnexusEscapeHtml(total)");
});

test("request-derived state dependencies stay dynamic during SSR", async () => {
  const code = generate(
    parse(`page Identity {
  state {
    userId = ctx.url.searchParams.get("as") ?? "asha"
    label = "User: " + userId
  }
  view { <p>{label}</p> }
}`),
  );
  expect(code).toContain("__wrnexusEscapeHtml(label)");
  expect(code).not.toContain('<span data-text="label"></span>');
  const javascript = new Bun.Transpiler({ loader: "ts" }).transformSync(code);
  const moduleUrl = `data:text/javascript;base64,${Buffer.from(javascript).toString("base64")}`;
  const rendered = await (
    await import(moduleUrl)
  ).default({
    url: new URL("http://localhost/?as=rohan"),
  });
  expect(rendered).toContain("User: rohan");
});

test("Async aliases and invalidation tags are scoped in generated templates", () => {
  const code = generate(
    parse(`page Uploads {
  load client uploads { return { name: "report.pdf" } }
  view {
    <Async source="uploads" tags="uploads,files">
      <Loading>Loading</Loading>
      <Success data="file"><a href="/files/{file.name}">{file.name}</a></Success>
      <Error error="problem"><p>{problem.message}</p></Error>
    </Async>
  }
}`),
  );

  expect(code).toContain('data-wrn-async-tags="uploads,files"');
  expect(code).toContain('data-wrn-async-alias="file"');
  expect(code).toContain('data-wrn-async-alias="problem"');
  expect(code).toContain("const file =");
  expect(code).toContain("const problem =");
});

test("browser codegen prunes SSR-only UI imports and keeps client dependencies", () => {
  const browser = generateTargets(
    parse(`import { Button } from "@wrnexus/ui"
import { save } from "./helper.ts"
component Settings {
  state { enabled = true }
  functions {
    client function persist(): void { save(enabled) }
  }
  view {
    <Button label="Save" />
    <button @click='persist()'>Save</button>
  }
}`),
  ).browser;

  expect(browser).not.toContain("@wrnexus/ui");
  expect(browser).toContain('import { save } from "./helper.ts"');
  expect(browser).toContain('"persist": function');
});

test("reactive view imports trigger a browser module without hydrating static SSR helpers", () => {
  const reactiveAst = parse(`import { formatName } from "./format.ts"
page Profile {
  state { name = "Asha" }
  view { <p>{formatName(name)}</p> }
}`);
  const staticAst = parse(`import { readFileSync } from "node:fs"
page StaticProfile {
  runtime = "server"
  view { <p>{readFileSync("profile.txt", "utf8")}</p> }
}`);

  expect(generate(reactiveAst)).toContain('data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__"');
  expect(generateTargets(reactiveAst).browser).toContain(
    'import { formatName } from "./format.ts"',
  );
  expect(generate(staticAst)).not.toContain("data-wrn-client-module");
});

test("state dependency cycles fail code generation", () => {
  expect(() =>
    generate(
      parse(`page CyclicState {
  state { first = second + 1 second = first + 1 }
  view { <p>{first}</p> }
}`),
    ),
  ).toThrow("WRN-STATE-CYCLE");
});

test("computed dependency cycles fail code generation", () => {
  expect(() =>
    generate(
      parse(`page Cyclic {
  computed { first = second + 1 second = first + 1 }
  view { <p>{first}</p> }
}`),
    ),
  ).toThrow("WRN-COMPUTED-CYCLE");
});
