import { describe, it, expect } from "vitest";
import { valueAccessParser, asyncValueAccessParser, syncValueAccessParser, } from "./parsers.js";
describe("valueAccessParser", () => {
    describe("bare variable names (unwrapped)", () => {
        it('should parse "obj" as a VariableNameLiteral', () => {
            const result = valueAccessParser("obj");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({ type: "variableName", value: "obj" });
            }
        });
    });
    describe("bare function calls (unwrapped)", () => {
        it('should parse "func()" as a FunctionCall', () => {
            const result = valueAccessParser("func()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "functionCall",
                    functionName: "func",
                    arguments: [],
                });
            }
        });
    });
    describe("index access", () => {
        const testCases = [
            {
                input: "arr[0]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "arr" },
                        chain: [{ kind: "index", index: { type: "number", value: "0" } }],
                    },
                },
            },
            {
                input: "items[5]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "items" },
                        chain: [{ kind: "index", index: { type: "number", value: "5" } }],
                    },
                },
            },
            {
                input: "arr[i]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "arr" },
                        chain: [
                            {
                                kind: "index",
                                index: { type: "variableName", value: "i" },
                            },
                        ],
                    },
                },
            },
            {
                input: 'obj["key"]',
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "obj" },
                        chain: [
                            {
                                kind: "index",
                                index: {
                                    type: "string",
                                    segments: [{ type: "text", value: "key" }],
                                },
                            },
                        ],
                    },
                },
            },
            {
                input: "getData()[0]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: {
                            type: "functionCall",
                            functionName: "getData",
                            arguments: [],
                        },
                        chain: [{ kind: "index", index: { type: "number", value: "0" } }],
                    },
                },
            },
            {
                input: "arr[getIndex()]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "arr" },
                        chain: [
                            {
                                kind: "index",
                                index: {
                                    type: "functionCall",
                                    functionName: "getIndex",
                                    arguments: [],
                                },
                            },
                        ],
                    },
                },
            },
            {
                input: "arr[-1]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "arr" },
                        chain: [{ kind: "index", index: { type: "number", value: "-1" } }],
                    },
                },
            },
            {
                input: "a[0]",
                expected: {
                    success: true,
                    result: {
                        type: "valueAccess",
                        base: { type: "variableName", value: "a" },
                        chain: [{ kind: "index", index: { type: "number", value: "0" } }],
                    },
                },
            },
        ];
        testCases.forEach(({ input, expected }) => {
            it(`should parse "${input}" successfully`, () => {
                const result = valueAccessParser(input);
                expect(result.success).toBe(true);
                if (result.success) {
                    expect(result.result).toEqualWithoutLoc(expected.result);
                }
            });
        });
        // Failure cases
        it('should fail to parse "arr[]"', () => {
            // arr is parsed as variableName, [] fails chain - but arr still succeeds as bare variable
            const result = valueAccessParser("arr[]");
            expect(result.success).toBe(true);
            if (result.success) {
                // Parses "arr" as variableName, leaves "[]" as rest
                expect(result.result).toEqualWithoutLoc({ type: "variableName", value: "arr" });
                expect(result.rest).toBe("[]");
            }
        });
        it('should fail to parse "[0]"', () => {
            const result = valueAccessParser("[0]");
            expect(result.success).toBe(false);
        });
        it('should fail to parse ""', () => {
            const result = valueAccessParser("");
            expect(result.success).toBe(false);
        });
    });
    describe("slice access", () => {
        it('should parse "arr[1:3]" with both bounds', () => {
            const result = valueAccessParser("arr[1:3]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            start: { type: "number", value: "1" },
                            end: { type: "number", value: "3" },
                        }],
                });
            }
        });
        it('should parse "arr[1:]" with start only', () => {
            const result = valueAccessParser("arr[1:]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            start: { type: "number", value: "1" },
                        }],
                });
            }
        });
        it('should parse "arr[:3]" with end only', () => {
            const result = valueAccessParser("arr[:3]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            end: { type: "number", value: "3" },
                        }],
                });
            }
        });
        it('should parse "arr[:]" with no bounds', () => {
            const result = valueAccessParser("arr[:]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{ kind: "slice" }],
                });
            }
        });
        it('should parse "arr[-2:]" with negative start', () => {
            const result = valueAccessParser("arr[-2:]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            start: { type: "number", value: "-2" },
                        }],
                });
            }
        });
        it('should parse "arr?.[1:3]" with optional chaining', () => {
            const result = valueAccessParser("arr?.[1:3]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            start: { type: "number", value: "1" },
                            end: { type: "number", value: "3" },
                            optional: true,
                        }],
                });
            }
        });
        it('should parse "arr[1:3][0:1]" with chained slices', () => {
            const result = valueAccessParser("arr[1:3][0:1]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [
                        {
                            kind: "slice",
                            start: { type: "number", value: "1" },
                            end: { type: "number", value: "3" },
                        },
                        {
                            kind: "slice",
                            start: { type: "number", value: "0" },
                            end: { type: "number", value: "1" },
                        },
                    ],
                });
            }
        });
        it('should parse "arr[n:]" with variable bound', () => {
            const result = valueAccessParser("arr[n:]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{
                            kind: "slice",
                            start: { type: "variableName", value: "n" },
                        }],
                });
            }
        });
        it('should still parse "arr[0]" as index access, not slice', () => {
            const result = valueAccessParser("arr[0]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{ kind: "index", index: { type: "number", value: "0" } }],
                });
            }
        });
    });
    describe("dot property access", () => {
        const testCases = [
            {
                input: "obj.foo",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [{ kind: "property", name: "foo" }],
                },
            },
            {
                input: "response.status",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "response" },
                    chain: [{ kind: "property", name: "status" }],
                },
            },
            {
                input: "x.y",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "x" },
                    chain: [{ kind: "property", name: "y" }],
                },
            },
        ];
        testCases.forEach(({ input, expected }) => {
            it(`should parse "${input}" successfully`, () => {
                const result = valueAccessParser(input);
                expect(result.success).toBe(true);
                if (result.success) {
                    expect(result.result).toEqualWithoutLoc(expected);
                }
            });
        });
    });
    describe("dot function call", () => {
        const testCases = [
            {
                input: "story.json()",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "story" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "json",
                                arguments: [],
                            },
                        },
                    ],
                },
            },
            {
                input: "obj.method(42)",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "method",
                                arguments: [{ type: "number", value: "42" }],
                            },
                        },
                    ],
                },
            },
            {
                input: "x.f()",
                expected: {
                    type: "valueAccess",
                    base: { type: "variableName", value: "x" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "f",
                                arguments: [],
                            },
                        },
                    ],
                },
            },
        ];
        testCases.forEach(({ input, expected }) => {
            it(`should parse "${input}" successfully`, () => {
                const result = valueAccessParser(input);
                expect(result.success).toBe(true);
                if (result.success) {
                    expect(result.result).toEqualWithoutLoc(expected);
                }
            });
        });
    });
    describe("chained expressions (flat chain)", () => {
        it('should parse "fetch().json()" with flat chain', () => {
            const result = valueAccessParser("fetch().json()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: {
                        type: "functionCall",
                        functionName: "fetch",
                        arguments: [],
                    },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "json",
                                arguments: [],
                            },
                        },
                    ],
                });
            }
        });
        it('should parse "foo.bar().baz()[3].a1" with flat 4-element chain', () => {
            const result = valueAccessParser("foo.bar().baz()[3].a1");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "foo" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "bar",
                                arguments: [],
                            },
                        },
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "baz",
                                arguments: [],
                            },
                        },
                        { kind: "index", index: { type: "number", value: "3" } },
                        { kind: "property", name: "a1" },
                    ],
                });
            }
        });
        it('should parse "response.data.items" with flat chain', () => {
            const result = valueAccessParser("response.data.items");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "response" },
                    chain: [
                        { kind: "property", name: "data" },
                        { kind: "property", name: "items" },
                    ],
                });
            }
        });
    });
    describe("optional chaining", () => {
        it('should parse "foo?.bar" with optional property access', () => {
            const result = valueAccessParser("foo?.bar");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "foo" },
                    chain: [{ kind: "property", name: "bar", optional: true }],
                });
            }
        });
        it('should parse "foo?.bar?.baz" with chained optional access', () => {
            const result = valueAccessParser("foo?.bar?.baz");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "foo" },
                    chain: [
                        { kind: "property", name: "bar", optional: true },
                        { kind: "property", name: "baz", optional: true },
                    ],
                });
            }
        });
        it('should parse "foo?.bar.baz" mixing optional and regular access', () => {
            const result = valueAccessParser("foo?.bar.baz");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "foo" },
                    chain: [
                        { kind: "property", name: "bar", optional: true },
                        { kind: "property", name: "baz" },
                    ],
                });
            }
        });
        it('should parse "arr?.[0]" with optional index access', () => {
            const result = valueAccessParser("arr?.[0]");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "arr" },
                    chain: [{ kind: "index", index: { type: "number", value: "0" }, optional: true }],
                });
            }
        });
        it('should parse "obj?.method()" with optional method call', () => {
            const result = valueAccessParser("obj?.method()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [{
                            kind: "methodCall",
                            functionCall: { type: "functionCall", functionName: "method", arguments: [] },
                            optional: true,
                        }],
                });
            }
        });
    });
    describe("failure cases", () => {
        it('should fail to parse ""', () => {
            expect(valueAccessParser("").success).toBe(false);
        });
        it('should fail to parse ".property"', () => {
            expect(valueAccessParser(".property").success).toBe(false);
        });
    });
    describe("syncValueAccessParser", () => {
        it('should parse "sync obj.foo" with async: false', () => {
            const result = syncValueAccessParser("sync obj.foo");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [{ kind: "property", name: "foo" }],
                    async: false,
                });
            }
        });
        it('should parse "await obj.foo" with async: false', () => {
            const result = syncValueAccessParser("await obj.foo");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [{ kind: "property", name: "foo" }],
                    async: false,
                });
            }
        });
        it('should parse "sync Promise.resolve()" with async: false', () => {
            const result = syncValueAccessParser("sync Promise.resolve()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "resolve",
                                arguments: [],
                            },
                        },
                    ],
                    async: false,
                });
            }
        });
        it('should parse "await Promise.race(res1, res2)" with async: false', () => {
            const result = syncValueAccessParser("await Promise.race(res1, res2)");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "race",
                                arguments: [
                                    { type: "variableName", value: "res1" },
                                    { type: "variableName", value: "res2" },
                                ],
                            },
                        },
                    ],
                    async: false,
                });
            }
        });
        it('should parse "sync response.data.items" with async: false', () => {
            const result = syncValueAccessParser("sync response.data.items");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "response" },
                    chain: [
                        { kind: "property", name: "data" },
                        { kind: "property", name: "items" },
                    ],
                    async: false,
                });
            }
        });
        // sync on bare function call
        it('should parse "sync fetch()" with async: false', () => {
            const result = syncValueAccessParser("sync fetch()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "functionCall",
                    functionName: "fetch",
                    arguments: [],
                    async: false,
                });
            }
        });
        // Failure cases
        it("should fail on async keyword", () => {
            expect(syncValueAccessParser("async obj.foo").success).toBe(false);
        });
        it("should fail without keyword", () => {
            expect(syncValueAccessParser("obj.foo").success).toBe(false);
        });
        it("should fail on empty string", () => {
            expect(syncValueAccessParser("").success).toBe(false);
        });
    });
    describe("asyncValueAccessParser", () => {
        it('should parse "async obj.foo" with async: true', () => {
            const result = asyncValueAccessParser("async obj.foo");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [{ kind: "property", name: "foo" }],
                    async: true,
                });
            }
        });
        it('should parse "async Promise.sayHi()" with async: true', () => {
            const result = asyncValueAccessParser("async Promise.sayHi()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "sayHi",
                                arguments: [],
                            },
                        },
                    ],
                    async: true,
                });
            }
        });
        it('should parse "async obj.method(42)" with async: true', () => {
            const result = asyncValueAccessParser("async obj.method(42)");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "obj" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "method",
                                arguments: [{ type: "number", value: "42" }],
                            },
                        },
                    ],
                    async: true,
                });
            }
        });
        it('should parse "async fetch().json()" with async: true', () => {
            const result = asyncValueAccessParser("async fetch().json()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: {
                        type: "functionCall",
                        functionName: "fetch",
                        arguments: [],
                    },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "json",
                                arguments: [],
                            },
                        },
                    ],
                    async: true,
                });
            }
        });
        // async on bare function call
        it('should parse "async fetch()" with async: true', () => {
            const result = asyncValueAccessParser("async fetch()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "functionCall",
                    functionName: "fetch",
                    arguments: [],
                    async: true,
                });
            }
        });
        // Failure cases
        it("should fail on sync keyword", () => {
            expect(asyncValueAccessParser("sync obj.foo").success).toBe(false);
        });
        it("should fail on await keyword", () => {
            expect(asyncValueAccessParser("await obj.foo").success).toBe(false);
        });
        it("should fail without keyword", () => {
            expect(asyncValueAccessParser("obj.foo").success).toBe(false);
        });
        it("should fail on empty string", () => {
            expect(asyncValueAccessParser("").success).toBe(false);
        });
    });
    describe("valueAccessParser with async/sync/await keywords", () => {
        it("should parse 'await' keyword and set async: false", () => {
            const result = valueAccessParser("await Promise.bar()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "bar",
                                arguments: [],
                            },
                        },
                    ],
                    async: false,
                });
            }
        });
        it("should parse 'async' keyword and set async: true", () => {
            const result = valueAccessParser("async Promise.sayHi()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "sayHi",
                                arguments: [],
                            },
                        },
                    ],
                    async: true,
                });
            }
        });
        it("should parse 'sync' keyword and set async: false", () => {
            const result = valueAccessParser("sync Promise.sayHi()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "sayHi",
                                arguments: [],
                            },
                        },
                    ],
                    async: false,
                });
            }
        });
        it("should parse without keyword and not set async field", () => {
            const result = valueAccessParser("Promise.sayHi()");
            expect(result.success).toBe(true);
            if (result.success) {
                expect(result.result).toEqualWithoutLoc({
                    type: "valueAccess",
                    base: { type: "variableName", value: "Promise" },
                    chain: [
                        {
                            kind: "methodCall",
                            functionCall: {
                                type: "functionCall",
                                functionName: "sayHi",
                                arguments: [],
                            },
                        },
                    ],
                });
            }
        });
    });
});
