import { describe, it, expect } from "vitest";
import { isAssignable } from "./assignability.js";
import { formatTypeHint } from "../cli/util.js";
describe("functionRefType assignability", () => {
    const fnRef = {
        type: "functionRefType",
        name: "deploy",
        params: [
            { type: "functionParameter", name: "env", typeHint: { type: "primitiveType", value: "string" } },
        ],
        returnType: { type: "primitiveType", value: "void" },
    };
    it("is assignable to any", () => {
        expect(isAssignable(fnRef, { type: "primitiveType", value: "any" }, {})).toBe(true);
    });
    it("is assignable to function primitive", () => {
        expect(isAssignable(fnRef, { type: "primitiveType", value: "function" }, {})).toBe(true);
    });
    it("function primitive is assignable to any", () => {
        expect(isAssignable({ type: "primitiveType", value: "function" }, { type: "primitiveType", value: "any" }, {})).toBe(true);
    });
    it("two functionRefTypes with compatible signatures are mutually assignable", () => {
        const other = {
            type: "functionRefType",
            name: "redeploy",
            params: [
                { type: "functionParameter", name: "environment", typeHint: { type: "primitiveType", value: "string" } },
            ],
            returnType: { type: "primitiveType", value: "void" },
        };
        expect(isAssignable(fnRef, other, {})).toBe(true);
        expect(isAssignable(other, fnRef, {})).toBe(true);
    });
    it("two functionRefTypes with incompatible params are not assignable", () => {
        const other = {
            type: "functionRefType",
            name: "add",
            params: [
                { type: "functionParameter", name: "a", typeHint: { type: "primitiveType", value: "number" } },
                { type: "functionParameter", name: "b", typeHint: { type: "primitiveType", value: "number" } },
            ],
            returnType: { type: "primitiveType", value: "number" },
        };
        expect(isAssignable(fnRef, other, {})).toBe(false);
    });
    it("functionRefType is not assignable to string", () => {
        expect(isAssignable(fnRef, { type: "primitiveType", value: "string" }, {})).toBe(false);
    });
    it("function primitive is not assignable to functionRefType", () => {
        expect(isAssignable({ type: "primitiveType", value: "function" }, fnRef, {})).toBe(false);
    });
    it("formats functionRefType with formatTypeHint", () => {
        expect(formatTypeHint(fnRef)).toBe("function deploy(env: string): void");
        const noParams = {
            type: "functionRefType",
            name: "ping",
            params: [],
            returnType: null,
        };
        expect(formatTypeHint(noParams)).toBe("function ping()");
    });
    describe("object primitive ↔ objectType", () => {
        const objectPrim = { type: "primitiveType", value: "object" };
        const emptyStruct = { type: "objectType", properties: [] };
        const nonEmptyStruct = {
            type: "objectType",
            properties: [
                { key: "foo", value: { type: "primitiveType", value: "string" } },
            ],
        };
        it("objectType is assignable to object primitive", () => {
            expect(isAssignable(emptyStruct, objectPrim, {})).toBe(true);
            expect(isAssignable(nonEmptyStruct, objectPrim, {})).toBe(true);
        });
        it("object primitive is assignable to the empty objectType {}", () => {
            // {} imposes no structural requirements, so any object satisfies it.
            expect(isAssignable(objectPrim, emptyStruct, {})).toBe(true);
        });
        it("object primitive is NOT assignable to a non-empty objectType", () => {
            // An arbitrary object isn't guaranteed to have specific properties.
            expect(isAssignable(objectPrim, nonEmptyStruct, {})).toBe(false);
        });
    });
    it("two functionRefTypes with incompatible return types are not assignable", () => {
        const other = {
            type: "functionRefType",
            name: "deploy2",
            params: [
                { type: "functionParameter", name: "env", typeHint: { type: "primitiveType", value: "string" } },
            ],
            returnType: { type: "primitiveType", value: "number" },
        };
        expect(isAssignable(fnRef, other, {})).toBe(false);
    });
});
describe("unresolved typeAliasVariable assignability", () => {
    // Regression: if a user references an undeclared type name (typo,
    // forgotten import, missing export) on both sides of an assignment,
    // `isAssignable` used to fall through to its `return false` tail
    // and emit the confusing diagnostic "Foo is not assignable to Foo".
    // Two unresolved aliases with the same name are now treated as
    // equal; the real diagnostic ("type alias not defined") is the
    // job of validateTypeReferences.
    const undefinedFoo = {
        type: "typeAliasVariable",
        aliasName: "Foo",
    };
    const undefinedFooAgain = {
        type: "typeAliasVariable",
        aliasName: "Foo",
    };
    const undefinedBar = {
        type: "typeAliasVariable",
        aliasName: "Bar",
    };
    it("same-name unresolved aliases are assignable to each other", () => {
        expect(isAssignable(undefinedFoo, undefinedFooAgain, {})).toBe(true);
    });
    it("different-name unresolved aliases are NOT assignable", () => {
        expect(isAssignable(undefinedFoo, undefinedBar, {})).toBe(false);
    });
    it("does not affect aliases that ARE defined", () => {
        // A normal defined alias resolves to its body and goes through the
        // structural-equality path, not the unresolved-equality short-circuit.
        const aliases = {
            Color: {
                body: {
                    type: "stringLiteralType",
                    value: "red",
                },
            },
        };
        const colorRef = {
            type: "typeAliasVariable",
            aliasName: "Color",
        };
        // "red" is assignable to "red" (resolves through alias body)
        expect(isAssignable(colorRef, colorRef, aliases)).toBe(true);
        // The new short-circuit must NOT fire here: source resolves to
        // stringLiteralType, target stays as the unresolved Foo. They are
        // not the same type, so the result is `false`.
        expect(isAssignable(colorRef, undefinedFoo, aliases)).toBe(false);
    });
});
