/**
 * Property-Based Tests for Endpoint Analyzer
 *
 * Tests invariants of extractPathParams and inferResourceType
 * using fast-check property-based testing.
 *
 * @module __tests__/scanners/logic/endpoint-analyzer.property
 */
import { describe, it } from "vitest";
import * as fc from "fast-check";
import { extractPathParams, inferResourceType, } from "../../../scanners/logic/endpoint-analyzer.js";
import { arbitraries, expressPath, nextjsPath, flaskPath, springPath, uniqueParamNames, PARAM_DELIMITERS, containsAny, } from "../../property-test-helpers.js";
describe("extractPathParams - Property Tests", () => {
    describe("Invariants", () => {
        it("always returns an array", () => {
            fc.assert(fc.property(fc.string(), (path) => {
                const result = extractPathParams(path);
                return Array.isArray(result);
            }));
        });
        it("never returns params containing delimiter characters", () => {
            fc.assert(fc.property(fc.string(), (path) => {
                const result = extractPathParams(path);
                return result.every((param) => !containsAny(param, PARAM_DELIMITERS));
            }));
        });
        it("is idempotent - same input always produces same output", () => {
            fc.assert(fc.property(fc.string(), (path) => {
                const r1 = extractPathParams(path);
                const r2 = extractPathParams(path);
                return JSON.stringify(r1) === JSON.stringify(r2);
            }));
        });
        it("returns empty array for paths without parameter syntax", () => {
            fc.assert(fc.property(fc.array(arbitraries.pathSegment, { minLength: 1, maxLength: 5 }), (segments) => {
                // Build a path with no parameter syntax
                const path = "/" + segments.join("/");
                const result = extractPathParams(path);
                return result.length === 0;
            }));
        });
    });
    describe("Express-style :param", () => {
        it("extracts all colon-prefixed params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = expressPath(paramNames);
                const result = extractPathParams(path);
                return paramNames.every((p) => result.includes(p));
            }));
        });
        it("extracts correct number of params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = expressPath(paramNames);
                const result = extractPathParams(path);
                return result.length === paramNames.length;
            }));
        });
    });
    describe("Next.js-style [param]", () => {
        it("extracts all bracket params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = nextjsPath(paramNames);
                const result = extractPathParams(path);
                return paramNames.every((p) => result.includes(p));
            }));
        });
        it("extracts correct number of params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = nextjsPath(paramNames);
                const result = extractPathParams(path);
                return result.length === paramNames.length;
            }));
        });
    });
    describe("Flask-style <param>", () => {
        it("extracts all angle bracket params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = flaskPath(paramNames, false);
                const result = extractPathParams(path);
                return paramNames.every((p) => result.includes(p));
            }));
        });
        it("extracts params ignoring type hints", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = flaskPath(paramNames, true);
                const result = extractPathParams(path);
                return paramNames.every((p) => result.includes(p));
            }));
        });
    });
    describe("Spring-style {param}", () => {
        it("extracts all curly brace params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = springPath(paramNames);
                const result = extractPathParams(path);
                return paramNames.every((p) => result.includes(p));
            }));
        });
        it("extracts correct number of params", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const path = springPath(paramNames);
                const result = extractPathParams(path);
                return result.length === paramNames.length;
            }));
        });
    });
    describe("Cross-framework consistency", () => {
        it("all framework styles extract same param names", () => {
            fc.assert(fc.property(uniqueParamNames, (paramNames) => {
                if (paramNames.length === 0)
                    return true;
                const expressResult = new Set(extractPathParams(expressPath(paramNames)));
                const nextjsResult = new Set(extractPathParams(nextjsPath(paramNames)));
                const flaskResult = new Set(extractPathParams(flaskPath(paramNames)));
                const springResult = new Set(extractPathParams(springPath(paramNames)));
                // All should extract the same set of param names
                const expected = new Set(paramNames);
                const setsEqual = (a, b) => a.size === b.size && [...a].every((x) => b.has(x));
                return (setsEqual(expressResult, expected) &&
                    setsEqual(nextjsResult, expected) &&
                    setsEqual(flaskResult, expected) &&
                    setsEqual(springResult, expected));
            }));
        });
    });
});
describe("inferResourceType - Property Tests", () => {
    describe("Singularization properties", () => {
        it("output is never longer than the resource segment", () => {
            fc.assert(fc.property(arbitraries.pluralNoun, (plural) => {
                const path = `/api/${plural}`;
                const result = inferResourceType(path);
                if (!result)
                    return true;
                return result.length <= plural.length;
            }));
        });
        it("returns undefined for empty or root paths", () => {
            fc.assert(fc.property(fc.constantFrom("", "/", "//"), (path) => {
                const result = inferResourceType(path);
                return result === undefined;
            }));
        });
        it("result does not end with 's' for regular plurals (with exceptions)", () => {
            // Words like "users", "orders", "products" should become "user", "order", "product"
            const regularPlurals = fc.constantFrom("users", "orders", "products", "items", "files", "posts");
            fc.assert(fc.property(regularPlurals, (plural) => {
                const path = `/api/${plural}`;
                const result = inferResourceType(path);
                if (!result)
                    return true;
                // Regular plurals (ending in just 's') should be singularized
                return !result.endsWith("s") || result.endsWith("ss");
            }));
        });
        it("'-ies' endings become '-y'", () => {
            const iesPlurals = fc.constantFrom("categories", "companies", "stories", "entries", "policies");
            fc.assert(fc.property(iesPlurals, (plural) => {
                const path = `/api/${plural}`;
                const result = inferResourceType(path);
                if (!result)
                    return true;
                return result.endsWith("y");
            }));
        });
        it("handles '-ses' endings correctly", () => {
            const sesPlurals = fc.constantFrom("addresses", "statuses", "databases");
            fc.assert(fc.property(sesPlurals, (plural) => {
                const path = `/api/${plural}`;
                const result = inferResourceType(path);
                if (!result)
                    return true;
                // "addresses" -> "address", "statuses" -> "status"
                return !result.endsWith("es") || result === plural;
            }));
        });
        it("handles '-xes', '-ches', '-shes' endings", () => {
            const esPlurals = fc.constantFrom("boxes", "matches", "bushes", "taxes");
            fc.assert(fc.property(esPlurals, (plural) => {
                const path = `/api/${plural}`;
                const result = inferResourceType(path);
                if (!result)
                    return true;
                // These should drop "es" but keep the base
                return result.length < plural.length;
            }));
        });
        it("is idempotent after one application", () => {
            fc.assert(fc.property(arbitraries.pluralNoun, (plural) => {
                const path1 = `/api/${plural}`;
                const result1 = inferResourceType(path1);
                if (!result1)
                    return true;
                // Applying again (pluralizing then singularizing) should be stable
                const path2 = `/api/${result1}s`;
                const result2 = inferResourceType(path2);
                // The second singularization should match the first
                // (This tests stability, not exact equality since re-pluralizing changes the input)
                return result2 !== undefined;
            }));
        });
    });
    describe("API path handling", () => {
        it("extracts resource from standard API paths", () => {
            fc.assert(fc.property(arbitraries.apiPrefix, arbitraries.pluralNoun, (prefix, resource) => {
                const path = `${prefix}/${resource}`;
                const result = inferResourceType(path);
                // Should return something for valid paths
                return result !== undefined || path === "/" || path === "";
            }));
        });
        it("ignores numeric ID segments", () => {
            fc.assert(fc.property(arbitraries.pluralNoun, fc.nat({ max: 9999 }), (resource, id) => {
                const path = `/api/${resource}/${id}`;
                const result = inferResourceType(path);
                // Should extract the resource, not the ID
                return result !== undefined && !/^\d+$/.test(result);
            }));
        });
    });
});
//# sourceMappingURL=endpoint-analyzer.property.test.js.map