import { type Children } from "@alloy-js/core";
import { SourceFile } from "@alloy-js/csharp";
import type { Model, ModelProperty } from "@typespec/compiler";
import type { BasicTestRunner } from "@typespec/compiler/testing";
import { beforeEach, describe, expect, it } from "vitest";
import { createEmitterFrameworkTestRunner } from "../../../test/typescript/test-host.js";
import { Output } from "../../core/index.js";
import { ClassDeclaration } from "./class/declaration.js";
import { TypeExpression } from "./type-expression.jsx";

let runner: BasicTestRunner;

beforeEach(async () => {
  runner = await createEmitterFrameworkTestRunner();
});

function Wrapper(props: { children: Children }) {
  return (
    <Output program={runner.program}>
      <SourceFile path="test.ts">{props.children}</SourceFile>
    </Output>
  );
}

async function compileType(ref: string) {
  const { test } = await runner.compile(`
    model Test {
      @test test: ${ref};
    }
  `);

  return (test as ModelProperty).type;
}

describe("map scalar to c# built-in types", () => {
  it.each([
    ["string", "string"],
    ["int32", "int"],
    ["int64", "long"],
  ])("%s => %s", async (tspType, csType) => {
    const type = await compileType(tspType);
    expect(
      <Wrapper>
        <TypeExpression type={type} />
      </Wrapper>,
    ).toRenderTo(`
        ${csType}
        `);
  });
});

it("maps array to c# array", async () => {
  const type = await compileType("int32[]");
  expect(
    <Wrapper>
      <TypeExpression type={type} />
    </Wrapper>,
  ).toRenderTo(`
    int[]
  `);
});

describe("Record map to IDictionary", () => {
  it("for primitive types", async () => {
    const type = await compileType("Record<int32>");
    expect(
      <Wrapper>
        <TypeExpression type={type} />
      </Wrapper>,
    ).toRenderTo(`
      IDictionary<string, int>
    `);
  });

  it("for models", async () => {
    const { test, Pet } = (await runner.compile(`
      model Test {
        @test test: Record<Pet>;
      }
      @test model Pet {}
    `)) as { test: ModelProperty; Pet: Model };

    expect(
      <Wrapper>
        <ClassDeclaration type={Pet} />
        <hbr />
        <TypeExpression type={test.type} />
      </Wrapper>,
    ).toRenderTo(`
      class Pet
      {
      
      }
      IDictionary<string, Pet>
    `);
  });
});

describe("Nullable union", () => {
  it("nullable boolean", async () => {
    const { Pet } = (await runner.compile(`
      @test model Pet {
        @test name: boolean | null;
      }
    `)) as { Pet: Model };

    expect(
      <Wrapper>
        <ClassDeclaration type={Pet} />
      </Wrapper>,
    ).toRenderTo(`
      class Pet
      {
          public required bool? name { get; set; }
      }
    `);
  });
});

describe("Literal types", () => {
  it("literal types (string, int, double, bool)", async () => {
    const { Pet } = (await runner.compile(`
      @test model Pet {
        @test boolName: true;
        @test intName: 42;
        @test doubleName: 3.14;
        @test stringName: "Hello";
      }
    `)) as { Pet: Model };

    expect(
      <Wrapper>
        <ClassDeclaration type={Pet} />
      </Wrapper>,
    ).toRenderTo(`
      class Pet
      {
          public required bool boolName { get; set; }
          public required int intName { get; set; }
          public required double doubleName { get; set; }
          public required string stringName { get; set; }
      }
    `);
  });
});
