/**
 * IR Builder tests: Recursive alias identity, generic base-class overrides, and flow type refreshing
 */
import { describe, it } from "mocha";
import { expect } from "chai";
import { buildIrModule } from "../builder.js";
import { runVirtualMarkingPass } from "../validation/index.js";
import { createFilesystemTestProgram } from "./_test-helpers.js";
describe("IR Builder", function () {
    this.timeout(90000);
    describe("Native library port regressions – recursive aliases and flow types", () => {
        it("preserves direct recursive alias identity in source parameters and returns", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "type PathSpec = string | RegExp | readonly PathSpec[] | null | undefined;",
                    "export function combine(left: PathSpec, right: PathSpec): PathSpec {",
                    "  return left ?? right;",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const combineFn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "combine");
                expect(combineFn).to.not.equal(undefined);
                if (!combineFn)
                    return;
                const assertAliasReference = (type, expectedName) => {
                    expect(type?.kind).to.equal("referenceType");
                    if (!type || type.kind !== "referenceType")
                        return;
                    expect(type.name).to.equal(expectedName);
                    expect(type.typeId?.tsName).to.equal(expectedName);
                };
                assertAliasReference(combineFn.parameters[0]?.type, "PathSpec");
                assertAliasReference(combineFn.parameters[1]?.type, "PathSpec");
                assertAliasReference(combineFn.returnType, "PathSpec");
            }
            finally {
                fixture.cleanup();
            }
        });
        it("preserves mutually recursive alias identity in source parameters", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "type RequestHandler = (value: string) => void;",
                    "class Router {}",
                    "type MiddlewareParam = RequestHandler | readonly MiddlewareParam[];",
                    "type MiddlewareLike = MiddlewareParam | Router | readonly MiddlewareLike[];",
                    "export function mount(first: MiddlewareLike, rest: readonly MiddlewareLike[]): readonly MiddlewareLike[] {",
                    "  return [first, ...rest];",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const mountFn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "mount");
                expect(mountFn).to.not.equal(undefined);
                if (!mountFn)
                    return;
                expect(mountFn.parameters[0]?.type?.kind).to.equal("referenceType");
                if (mountFn.parameters[0]?.type?.kind === "referenceType") {
                    expect(mountFn.parameters[0].type.name).to.equal("MiddlewareLike");
                    expect(mountFn.parameters[0].type.typeId?.tsName).to.equal("MiddlewareLike");
                }
                expect(mountFn.parameters[1]?.type?.kind).to.equal("arrayType");
                if (mountFn.parameters[1]?.type?.kind === "arrayType") {
                    expect(mountFn.parameters[1].type.origin).to.equal("explicit");
                    expect(mountFn.parameters[1].type.elementType.kind).to.equal("referenceType");
                    if (mountFn.parameters[1].type.elementType.kind === "referenceType") {
                        expect(mountFn.parameters[1].type.elementType.name).to.equal("MiddlewareLike");
                        expect(mountFn.parameters[1].type.elementType.typeId?.tsName).to.equal("MiddlewareLike");
                    }
                }
            }
            finally {
                fixture.cleanup();
            }
        });
        it("marks generic base-class overrides after substituting superclass type arguments", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "class ComparableShowable<T> {",
                    "  compareTo(other: T): number {",
                    "    void other;",
                    "    return 0;",
                    "  }",
                    "  show(): string {",
                    '    return "base";',
                    "  }",
                    "}",
                    "",
                    "export class NumberValue extends ComparableShowable<NumberValue> {",
                    "  override compareTo(other: NumberValue): number {",
                    "    void other;",
                    "    return 1;",
                    "  }",
                    "  override show(): string {",
                    '    return "derived";',
                    "  }",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const virtualResult = runVirtualMarkingPass([result.value]);
                const transformedModule = virtualResult.modules[0];
                expect(transformedModule).to.not.equal(undefined);
                if (!transformedModule)
                    return;
                const numberValueClass = transformedModule.body.find((stmt) => stmt.kind === "classDeclaration" && stmt.name === "NumberValue");
                expect(numberValueClass).to.not.equal(undefined);
                if (!numberValueClass)
                    return;
                const compareTo = numberValueClass.members.find((member) => member.kind === "methodDeclaration" && member.name === "compareTo");
                expect(compareTo).to.not.equal(undefined);
                expect(compareTo?.isOverride).to.equal(true);
                const show = numberValueClass.members.find((member) => member.kind === "methodDeclaration" && member.name === "show");
                expect(show).to.not.equal(undefined);
                expect(show?.isOverride).to.equal(true);
            }
            finally {
                fixture.cleanup();
            }
        });
        it("refreshes local flow types after reassignment following terminating nullish guards", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "class ImageDimensions {",
                    "  readonly width: number;",
                    "  constructor(width: number) {",
                    "    this.width = width;",
                    "  }",
                    "}",
                    "",
                    "declare const Resource: {",
                    "  parsePngDimensions(bytes: string): ImageDimensions | undefined;",
                    "  parseJpegDimensions(bytes: string): ImageDimensions | undefined;",
                    "  parseGifDimensions(bytes: string): ImageDimensions | undefined;",
                    "};",
                    "",
                    "export function parseImageDimensions(bytes: string): ImageDimensions | undefined {",
                    "  let dims = Resource.parsePngDimensions(bytes);",
                    "  if (dims !== undefined) return dims;",
                    "  dims = Resource.parseJpegDimensions(bytes);",
                    "  if (dims !== undefined) return dims;",
                    "  dims = Resource.parseGifDimensions(bytes);",
                    "  if (dims !== undefined) return dims;",
                    "  return undefined;",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" &&
                    stmt.name === "parseImageDimensions");
                expect(fn).to.not.equal(undefined);
                if (!fn)
                    return;
                const ifStatements = fn.body.statements.filter((stmt) => stmt.kind === "ifStatement");
                expect(ifStatements).to.have.length(3);
                const secondIf = ifStatements[1];
                expect(secondIf?.condition.kind).to.equal("binary");
                if (!secondIf || secondIf.condition.kind !== "binary") {
                    return;
                }
                expect(secondIf.condition.left.kind).to.equal("identifier");
                if (secondIf.condition.left.kind !== "identifier") {
                    return;
                }
                expect(secondIf.condition.left.inferredType?.kind).to.equal("unionType");
                const secondReturn = secondIf.thenStatement.kind === "returnStatement"
                    ? secondIf.thenStatement
                    : undefined;
                expect(secondReturn?.expression).to.not.equal(undefined);
                if (!secondReturn?.expression) {
                    return;
                }
                expect(secondReturn.expression.kind).to.equal("typeAssertion");
                if (secondReturn.expression.kind !== "typeAssertion") {
                    return;
                }
                expect(secondReturn.expression.targetType?.kind).to.equal("referenceType");
                if (secondReturn.expression.targetType?.kind !== "referenceType") {
                    return;
                }
                expect(secondReturn.expression.targetType.name).to.equal("ImageDimensions");
            }
            finally {
                fixture.cleanup();
            }
        });
        it("preserves readable getter surfaces after broader setter assignments", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "declare class Assert {",
                    "  static Equal(expected: unknown, actual: unknown): void;",
                    "}",
                    "",
                    "class Proc {",
                    "  private _argv: string[] = [];",
                    "",
                    "  get argv(): string[] {",
                    "    return this._argv;",
                    "  }",
                    "",
                    "  set argv(value: string[] | undefined) {",
                    "    this._argv = value ?? [];",
                    "  }",
                    "}",
                    "",
                    "export function main(p: Proc): void {",
                    "  p.argv = undefined;",
                    "  Assert.Equal(0, p.argv.length);",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "main");
                expect(fn).to.not.equal(undefined);
                if (!fn)
                    return;
                const assertStmt = fn.body.statements[1];
                expect(assertStmt?.kind).to.equal("expressionStatement");
                if (!assertStmt || assertStmt.kind !== "expressionStatement")
                    return;
                expect(assertStmt.expression.kind).to.equal("call");
                if (assertStmt.expression.kind !== "call")
                    return;
                const lengthArg = assertStmt.expression.arguments[1];
                expect(lengthArg?.kind).to.equal("memberAccess");
                if (!lengthArg || lengthArg.kind !== "memberAccess")
                    return;
                expect(lengthArg.property).to.equal("length");
                if (lengthArg.object.kind === "typeAssertion") {
                    expect(lengthArg.object.targetType).to.deep.equal({
                        kind: "arrayType",
                        elementType: { kind: "primitiveType", name: "string" },
                        origin: "explicit",
                    });
                    expect(lengthArg.object.expression.kind).to.equal("memberAccess");
                    if (lengthArg.object.expression.kind !== "memberAccess")
                        return;
                    expect(lengthArg.object.expression.property).to.equal("argv");
                    return;
                }
                expect(lengthArg.object.kind).to.equal("memberAccess");
                if (lengthArg.object.kind !== "memberAccess")
                    return;
                expect(lengthArg.object.property).to.equal("argv");
                expect(lengthArg.object.inferredType).to.deep.equal({
                    kind: "arrayType",
                    elementType: { kind: "primitiveType", name: "string" },
                    origin: "explicit",
                });
            }
            finally {
                fixture.cleanup();
            }
        });
        it("preserves imported getter surfaces after broader setter assignments", () => {
            const fixture = createFilesystemTestProgram({
                "src/process.ts": [
                    "export class Proc {",
                    "  private _argv: string[] = [];",
                    "",
                    "  get argv(): string[] {",
                    "    return this._argv;",
                    "  }",
                    "",
                    "  set argv(value: string[] | undefined) {",
                    "    this._argv = value ?? [];",
                    "  }",
                    "}",
                    "",
                    "export const process = new Proc();",
                ].join("\n"),
                "src/index.ts": [
                    'import { process } from "./process.ts";',
                    "declare class Assert {",
                    "  static Equal(expected: unknown, actual: unknown): void;",
                    "}",
                    "",
                    "export function main(): void {",
                    "  process.argv = undefined;",
                    "  Assert.Equal(0, process.argv.length);",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "main");
                expect(fn).to.not.equal(undefined);
                if (!fn)
                    return;
                const assertStmt = fn.body.statements[1];
                expect(assertStmt?.kind).to.equal("expressionStatement");
                if (!assertStmt || assertStmt.kind !== "expressionStatement")
                    return;
                expect(assertStmt.expression.kind).to.equal("call");
                if (assertStmt.expression.kind !== "call")
                    return;
                const lengthArg = assertStmt.expression.arguments[1];
                expect(lengthArg?.kind).to.equal("memberAccess");
                if (!lengthArg || lengthArg.kind !== "memberAccess")
                    return;
                expect(lengthArg.property).to.equal("length");
                if (lengthArg.object.kind === "typeAssertion") {
                    expect(lengthArg.object.targetType).to.deep.equal({
                        kind: "arrayType",
                        elementType: { kind: "primitiveType", name: "string" },
                        origin: "explicit",
                    });
                    expect(lengthArg.object.expression.kind).to.equal("memberAccess");
                    if (lengthArg.object.expression.kind !== "memberAccess")
                        return;
                    expect(lengthArg.object.expression.property).to.equal("argv");
                    return;
                }
                expect(lengthArg.object.kind).to.equal("memberAccess");
                if (lengthArg.object.kind !== "memberAccess")
                    return;
                expect(lengthArg.object.property).to.equal("argv");
                expect(lengthArg.object.inferredType).to.deep.equal({
                    kind: "arrayType",
                    elementType: { kind: "primitiveType", name: "string" },
                    origin: "explicit",
                });
            }
            finally {
                fixture.cleanup();
            }
        });
        it("substitutes inherited generic member types across renamed superclass type parameters", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "class Box<T> {",
                    "  value: T;",
                    "  constructor(value: T) {",
                    "    this.value = value;",
                    "  }",
                    "}",
                    "",
                    "export class WrappedBox<U> extends Box<U> {",
                    "  wrap(): Box<U> {",
                    "    return new Box(this.value);",
                    "  }",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const wrappedBoxClass = result.value.body.find((stmt) => stmt.kind === "classDeclaration" && stmt.name === "WrappedBox");
                expect(wrappedBoxClass).to.not.equal(undefined);
                if (!wrappedBoxClass)
                    return;
                const wrap = wrappedBoxClass.members.find((member) => member.kind === "methodDeclaration" && member.name === "wrap");
                expect(wrap).to.not.equal(undefined);
                if (!wrap)
                    return;
                expect(wrap.body).to.not.equal(undefined);
                if (!wrap.body)
                    return;
                expect(wrap.returnType?.kind).to.equal("referenceType");
                if (!wrap.returnType || wrap.returnType.kind !== "referenceType") {
                    return;
                }
                expect(wrap.returnType.name).to.equal("Box");
                expect(wrap.returnType.typeArguments).to.deep.equal([
                    { kind: "typeParameterType", name: "U" },
                ]);
                expect(wrap.returnType.structuralMembers).to.deep.equal([
                    {
                        kind: "propertySignature",
                        name: "value",
                        type: { kind: "typeParameterType", name: "U" },
                        isOptional: false,
                        isReadonly: false,
                    },
                ]);
                const returnStmt = wrap.body.statements[0];
                expect(returnStmt?.kind).to.equal("returnStatement");
                if (!returnStmt || returnStmt.kind !== "returnStatement")
                    return;
                const returnExpr = returnStmt.expression;
                expect(returnExpr?.kind).to.equal("new");
                if (!returnExpr || returnExpr.kind !== "new")
                    return;
                expect(returnExpr.inferredType?.kind).to.equal("referenceType");
                if (returnExpr.inferredType?.kind !== "referenceType")
                    return;
                expect(returnExpr.inferredType.name).to.equal("Box");
                expect(returnExpr.inferredType.typeArguments).to.deep.equal([
                    { kind: "typeParameterType", name: "U" },
                ]);
                expect(returnExpr.typeArguments).to.deep.equal([
                    { kind: "typeParameterType", name: "U" },
                ]);
                const valueArg = returnExpr.arguments[0];
                expect(valueArg?.kind).to.equal("memberAccess");
                if (!valueArg || valueArg.kind !== "memberAccess")
                    return;
                expect(valueArg.inferredType).to.deep.equal({
                    kind: "typeParameterType",
                    name: "U",
                });
                const thisExpr = valueArg.object;
                expect(thisExpr?.kind).to.equal("this");
                if (!thisExpr || thisExpr.kind !== "this")
                    return;
                expect(thisExpr.inferredType).to.deep.equal({
                    kind: "referenceType",
                    name: "WrappedBox",
                    typeArguments: [{ kind: "typeParameterType", name: "U" }],
                });
            }
            finally {
                fixture.cleanup();
            }
        });
        it("substitutes inherited generic member types for locals outside the class body", () => {
            const fixture = createFilesystemTestProgram({
                "src/index.ts": [
                    "class Item {",
                    "  readonly name: string;",
                    "  constructor(name: string) {",
                    "    this.name = name;",
                    "  }",
                    "}",
                    "",
                    "class GenericArrayValue<T> {",
                    "  readonly value: T[];",
                    "  constructor(value: T[]) {",
                    "    this.value = value;",
                    "  }",
                    "}",
                    "",
                    "class ItemArrayValue extends GenericArrayValue<Item> {}",
                    "",
                    "export function firstLength(value: ItemArrayValue): int {",
                    "  const items = value.value;",
                    "  if (items.length === 0) return 0 as int;",
                    "  const item = items[0]!;",
                    "  return item.name.length;",
                    "}",
                ].join("\n"),
            }, "src/index.ts");
            try {
                const result = buildIrModule(fixture.sourceFile, fixture.testProgram, fixture.options, fixture.ctx);
                expect(result.ok).to.equal(true);
                if (!result.ok)
                    return;
                const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "firstLength");
                expect(fn).to.not.equal(undefined);
                if (!fn || !fn.body)
                    return;
                const itemsDecl = fn.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
                    stmt.declarations[0]?.name.kind === "identifierPattern" &&
                    stmt.declarations[0]?.name.name === "items");
                const itemDecl = fn.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
                    stmt.declarations[0]?.name.kind === "identifierPattern" &&
                    stmt.declarations[0]?.name.name === "item");
                expect(itemsDecl).to.not.equal(undefined);
                expect(itemDecl).to.not.equal(undefined);
                if (!itemsDecl || !itemDecl)
                    return;
                const itemsInitializer = itemsDecl.declarations[0]?.initializer;
                expect(itemsInitializer?.inferredType?.kind).to.equal("arrayType");
                if (itemsInitializer?.inferredType?.kind !== "arrayType")
                    return;
                expect(itemsInitializer.inferredType.elementType.kind).to.equal("referenceType");
                if (itemsInitializer.inferredType.elementType.kind !== "referenceType") {
                    return;
                }
                expect(itemsInitializer.inferredType.elementType.name).to.equal("Item");
                const itemInitializer = itemDecl.declarations[0]?.initializer;
                expect(itemInitializer?.inferredType?.kind).to.equal("referenceType");
                if (itemInitializer?.inferredType?.kind !== "referenceType")
                    return;
                expect(itemInitializer.inferredType.name).to.equal("Item");
            }
            finally {
                fixture.cleanup();
            }
        });
    });
});
//# sourceMappingURL=recursive-aliases-and-flow-types.test.js.map