import { describe, it } from "mocha";
import { expect } from "chai";
import * as ts from "typescript";
import { createBinding } from "../../../binding/index.js";
import { convertType } from "./orchestrator.js";
const createTestProgram = (source, fileName = "test.ts") => {
    const compilerOptions = {
        target: ts.ScriptTarget.ES2022,
        module: ts.ModuleKind.NodeNext,
        strict: true,
        noEmit: true,
    };
    const host = ts.createCompilerHost(compilerOptions);
    const originalGetSourceFile = host.getSourceFile;
    const originalFileExists = host.fileExists;
    const originalReadFile = host.readFile;
    host.getSourceFile = (name, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
        if (name === fileName) {
            return ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
        }
        return originalGetSourceFile.call(host, name, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
    };
    host.fileExists = (name) => name === fileName || originalFileExists.call(host, name);
    host.readFile = (name) => name === fileName ? source : originalReadFile.call(host, name);
    const program = ts.createProgram([fileName], compilerOptions, host);
    const sourceFile = program.getSourceFile(fileName);
    if (!sourceFile) {
        throw new Error("missing source file");
    }
    return { sourceFile, binding: createBinding(program.getTypeChecker()) };
};
const convertAlias = (source, aliasName) => {
    const { sourceFile, binding } = createTestProgram(source);
    let alias;
    for (const statement of sourceFile.statements) {
        if (ts.isTypeAliasDeclaration(statement) &&
            statement.name.text === aliasName) {
            alias = statement;
            break;
        }
    }
    if (!alias) {
        throw new Error(`type alias ${aliasName} not found`);
    }
    return convertType(alias.type, binding);
};
const makeUnion = (...types) => ({
    kind: "unionType",
    types,
});
describe("Type Converter - Tuple Rest Lowering", () => {
    it("keeps a locally declared Array<T> class nominal instead of lowering it to arrayType", () => {
        const { sourceFile, binding } = createTestProgram([
            "export {};",
            "export class Array<T> {",
            "  clone(): Array<T> {",
            "    throw new Error('nope');",
            "  }",
            "}",
            "type T = Array<string>;",
        ].join("\n"));
        let alias;
        for (const statement of sourceFile.statements) {
            if (ts.isTypeAliasDeclaration(statement) && statement.name.text === "T") {
                alias = statement;
                break;
            }
        }
        if (!alias) {
            throw new Error("type alias T not found");
        }
        const converted = convertType(alias.type, binding);
        expect(converted.kind).to.equal("referenceType");
        if (converted.kind !== "referenceType") {
            return;
        }
        expect(converted.name).to.equal("Array");
        expect(converted.typeArguments).to.deep.equal([
            { kind: "primitiveType", name: "string" },
        ]);
    });
    it("lowers pure variadic tuple to array type", () => {
        const converted = convertAlias("type T = [...number[]];", "T");
        expect(converted).to.deep.equal({
            kind: "arrayType",
            elementType: { kind: "primitiveType", name: "number" },
            origin: "explicit",
            tupleRestElementType: { kind: "primitiveType", name: "number" },
        });
    });
    it("lowers fixed + variadic tuple to array with union element", () => {
        const converted = convertAlias("type T = [string, ...number[]];", "T");
        expect(converted).to.deep.equal({
            kind: "arrayType",
            elementType: makeUnion({ kind: "primitiveType", name: "number" }, { kind: "primitiveType", name: "string" }),
            origin: "explicit",
            tuplePrefixElementTypes: [{ kind: "primitiveType", name: "string" }],
            tupleRestElementType: { kind: "primitiveType", name: "number" },
        });
    });
    it("lowers fixed + variadic + fixed tuple to array with full union", () => {
        const converted = convertAlias("type T = [number, ...string[], boolean];", "T");
        expect(converted).to.deep.equal({
            kind: "arrayType",
            elementType: makeUnion({ kind: "primitiveType", name: "boolean" }, { kind: "primitiveType", name: "number" }, { kind: "primitiveType", name: "string" }),
            origin: "explicit",
        });
    });
    it("handles named tuple members with rest", () => {
        const converted = convertAlias("type T = [head: string, ...tail: number[]];", "T");
        expect(converted).to.deep.equal({
            kind: "arrayType",
            elementType: makeUnion({ kind: "primitiveType", name: "number" }, { kind: "primitiveType", name: "string" }),
            origin: "explicit",
            tuplePrefixElementTypes: [{ kind: "primitiveType", name: "string" }],
            tupleRestElementType: { kind: "primitiveType", name: "number" },
        });
    });
});
describe("Type Converter - Mapped/Conditional Syntax", () => {
    const expectUnknown = (type) => {
        expect(type).to.deep.equal({ kind: "unknownType" });
    };
    it("lowers direct mapped type syntax to unknownType (never anyType)", () => {
        const converted = convertAlias("type T<U> = { [K in keyof U]: U[K] | null };", "T");
        expectUnknown(converted);
    });
    it("lowers direct conditional syntax to unknownType (never anyType)", () => {
        const converted = convertAlias("type T<U> = U extends Promise<infer V> ? V : U;", "T");
        expectUnknown(converted);
    });
    it("lowers parenthesized mapped syntax to unknownType", () => {
        const converted = convertAlias("type T<U> = ({ [K in keyof U]: U[K] });", "T");
        expectUnknown(converted);
    });
    it("lowers parenthesized conditional syntax to unknownType", () => {
        const converted = convertAlias("type T<U> = (U extends string ? number : boolean);", "T");
        expectUnknown(converted);
    });
    it("lowers infer-only syntax to unknownType", () => {
        const converted = convertAlias("type T<U> = U extends Promise<infer V> ? V : never;", "T");
        expectUnknown(converted);
    });
    it("never leaks mapped syntax to anyType when used in unions", () => {
        const converted = convertAlias("type T<U> = ({ [K in keyof U]: U[K] } | string);", "T");
        expect(converted.kind).to.equal("unionType");
        if (converted.kind !== "unionType") {
            return;
        }
        expect(converted.types.some((member) => member.kind === "anyType" || member.kind === "unknownType")).to.equal(true);
        expect(converted.types.some((member) => member.kind === "anyType")).to.equal(false);
    });
    it("never leaks conditional syntax to anyType when used in arrays", () => {
        const converted = convertAlias("type T<U> = Array<U extends string ? number : boolean>;", "T");
        expect(converted.kind).to.equal("arrayType");
        if (converted.kind !== "arrayType") {
            return;
        }
        expect(converted.elementType.kind).to.equal("unknownType");
    });
    it("lowers direct infer type node fallback to unknownType", () => {
        const converted = convertAlias("type T<U> = (U extends infer V ? V : never) extends infer X ? X : never;", "T");
        expectUnknown(converted);
    });
    it("preserves readonly type operator while still lowering nested conditional to unknown", () => {
        const converted = convertAlias("type T<U> = readonly (U extends string ? number : boolean)[];", "T");
        expect(converted.kind).to.equal("arrayType");
        if (converted.kind !== "arrayType") {
            return;
        }
        expect(converted.elementType.kind).to.equal("unknownType");
    });
    it("lowers ReadonlyArray<T> references to arrayType", () => {
        const converted = convertAlias("type T = ReadonlyArray<string>;", "T");
        expect(converted).to.deep.equal({
            kind: "arrayType",
            elementType: { kind: "primitiveType", name: "string" },
            origin: "explicit",
        });
    });
});
describe("Type Converter - TypeQuery from awaited locals", () => {
    it("preserves array element identity for typeof awaitedImportedArray[0]", () => {
        const converted = convertAlias([
            "class Channel {",
            '  Id: string = "";',
            '  Name: string = "";',
            "}",
            "declare function getChannels(): Promise<Channel[]>;",
            "const allChannels = await getChannels();",
            "type T = typeof allChannels[0];",
            "export {};",
        ].join("\n"), "T");
        expect(converted.kind).to.equal("referenceType");
        if (converted.kind !== "referenceType") {
            return;
        }
        expect(converted.name).to.equal("Channel");
    });
});
describe("Type Converter - Deterministic Type Operators", () => {
    it("lowers keyof object literal types to a union of key literals", () => {
        const converted = convertAlias("type T = keyof { alpha: number; beta: string };", "T");
        expect(converted).to.deep.equal(makeUnion({ kind: "literalType", value: "alpha" }, { kind: "literalType", value: "beta" }));
    });
    it("lowers keyof constrained type parameters via their constraint", () => {
        const converted = convertAlias("type Keys<T extends { id: number; slug: string }> = keyof T;", "Keys");
        expect(converted).to.deep.equal(makeUnion({ kind: "literalType", value: "id" }, { kind: "literalType", value: "slug" }));
    });
    it("resolves indexed-access property lookups with literal keys", () => {
        const converted = convertAlias('type T = { id: number; name: string }["id"];', "T");
        expect(converted).to.deep.equal({ kind: "primitiveType", name: "number" });
    });
    it("resolves indexed-access unions of literal keys", () => {
        const converted = convertAlias('type T = { id: number; name: string }["id" | "name"];', "T");
        expect(converted).to.deep.equal(makeUnion({ kind: "primitiveType", name: "number" }, { kind: "primitiveType", name: "string" }));
    });
    it("resolves indexed-access on constrained type parameters", () => {
        const converted = convertAlias("type V<T extends { id: number; name: string }, K extends keyof T> = T[K];", "V");
        expect(converted).to.deep.equal(makeUnion({ kind: "primitiveType", name: "number" }, { kind: "primitiveType", name: "string" }));
    });
    it("expands finite template-literal types to literal unions", () => {
        const converted = convertAlias('type T = `prefix-${"a" | "b"}`;', "T");
        expect(converted).to.deep.equal(makeUnion({ kind: "literalType", value: "prefix-a" }, { kind: "literalType", value: "prefix-b" }));
    });
    it("falls back template-literal types to string when expansion is non-finite", () => {
        const converted = convertAlias("type T<U> = `prefix-${U}`;", "T");
        expect(converted).to.deep.equal({ kind: "primitiveType", name: "string" });
    });
    it("falls back template-literal types to string when expansion is too large", () => {
        const converted = convertAlias('type T = `${"a"|"b"|"c"|"d"|"e"}${"f"|"g"|"h"|"i"|"j"}${"k"|"l"|"m"|"n"|"o"}`;', "T");
        expect(converted).to.deep.equal({ kind: "primitiveType", name: "string" });
    });
});
describe("Type Converter - Symbol Dictionary Keys", () => {
    it("lowers symbol keyword types to object reference types", () => {
        const converted = convertAlias("type T = symbol;", "T");
        expect(converted).to.deep.equal({
            kind: "referenceType",
            name: "object",
            typeArguments: [],
        });
    });
    it("converts symbol index signatures to dictionaryType with object keys", () => {
        const converted = convertAlias("type T = { [key: symbol]: number };", "T");
        expect(converted).to.deep.equal({
            kind: "dictionaryType",
            keyType: { kind: "referenceType", name: "object" },
            valueType: { kind: "primitiveType", name: "number" },
        });
    });
    it("converts Record<symbol, V> to dictionaryType with object keys", () => {
        const converted = convertAlias("type T = Record<symbol, number>;", "T");
        expect(converted).to.deep.equal({
            kind: "dictionaryType",
            keyType: { kind: "referenceType", name: "object" },
            valueType: { kind: "primitiveType", name: "number" },
        });
    });
    it("converts Record<string | symbol, V> to dictionaryType with object keys", () => {
        const converted = convertAlias("type T = Record<string | symbol, number>;", "T");
        expect(converted).to.deep.equal({
            kind: "dictionaryType",
            keyType: { kind: "referenceType", name: "object" },
            valueType: { kind: "primitiveType", name: "number" },
        });
    });
    it("converts symbol-only interface index signatures to dictionaryType with object keys", () => {
        const converted = convertAlias(`
        interface SymbolMap {
          [key: symbol]: number;
        }
        type T = SymbolMap;
      `, "T");
        expect(converted).to.deep.equal({
            kind: "dictionaryType",
            keyType: { kind: "referenceType", name: "object" },
            valueType: { kind: "primitiveType", name: "number" },
        });
    });
});
//# sourceMappingURL=orchestrator.test.js.map