// engine/property-test-generator.ts — SPEC-315
// Extracts invariants from acceptance criteria and generates PropertyTest objects.
// ---------------------------------------------------------------------------
// Framework detection
// ---------------------------------------------------------------------------
/** Map a language/stack to the best property-based test framework. */
export function detectPropertyFramework(language, stack = []) {
    const lang = language.toLowerCase();
    const stackLower = stack.map((s) => s.toLowerCase());
    if (stackLower.includes('fast-check') || stackLower.includes('fastcheck')) {
        return 'fast-check';
    }
    if (stackLower.includes('hypothesis')) {
        return 'hypothesis';
    }
    if (stackLower.includes('jqwik')) {
        return 'jqwik';
    }
    if (lang === 'typescript' || lang === 'javascript') {
        return 'fast-check';
    }
    if (lang === 'python') {
        return 'hypothesis';
    }
    if (lang === 'java' || lang === 'kotlin') {
        return 'jqwik';
    }
    return 'generic';
}
// ---------------------------------------------------------------------------
// Invariant pattern matchers
// ---------------------------------------------------------------------------
const INVARIANT_PATTERNS = [
    {
        // "price must be positive" / "amount should be positive" / "value must be >= 0"
        pattern: /(?:price|amount|value|cost|total|balance)\s+(?:must|should)\s+be\s+(?:positive|>?\s*0|greater than 0)/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — output is always > 0`,
            inputDomain: 'positive floats (min: 0.01)',
        }),
    },
    {
        // "must never be negative" / "should never be negative"
        pattern: /(?:must|should)\s+never\s+be\s+negative/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — result is never negative`,
            inputDomain: 'non-negative floats (min: 0)',
        }),
    },
    {
        // "idempotent" / "idempotente"
        pattern: /idempoten(?:t|te)/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — applying N times equals applying once`,
            inputDomain: 'arbitrary valid inputs',
        }),
    },
    {
        // "commutative" / "conmutativo"
        pattern: /commutativ(?:e|ity)|conmutativ(?:o|idad)/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — f(a,b) === f(b,a)`,
            inputDomain: 'pairs of arbitrary values',
        }),
    },
    {
        // "always returns / always produces"
        pattern: /always\s+(?:returns?|produces?|yields?)\s+(.+)/i,
        extract: (m, raw) => ({
            invariant: `${raw.trim()} — output is always ${m[1] ?? 'defined'}`,
            inputDomain: 'arbitrary valid inputs',
        }),
    },
    {
        // "must not be empty" / "should not be empty"
        pattern: /(?:must|should)\s+not\s+be\s+empty/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — result is never empty`,
            inputDomain: 'non-empty strings or arrays',
        }),
    },
    {
        // "for any input" / "for all inputs" / "para cualquier" / "para todos"
        pattern: /(?:for\s+(?:any|all)\s+(?:valid\s+)?input|para\s+(?:cualquier|todos))/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — holds for all valid inputs`,
            inputDomain: 'arbitrary valid inputs',
        }),
    },
    {
        // "length must be between X and Y"
        pattern: /length\s+must\s+be\s+(?:between\s+)?(\d+)\s+and\s+(\d+)/i,
        extract: (m, raw) => ({
            invariant: `${raw.trim()} — length is within [${m[1] ?? '0'}, ${m[2] ?? '∞'}]`,
            inputDomain: `strings of length ${m[1] ?? '0'}..${m[2] ?? '100'}`,
        }),
    },
    {
        // "must be unique" / "should be unique"
        pattern: /(?:must|should)\s+be\s+unique/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — no duplicates in output`,
            inputDomain: 'arrays of arbitrary comparable values',
        }),
    },
    {
        // "must be sorted" / "should be sorted" / "maintains order"
        pattern: /(?:must|should)\s+be\s+sorted|maintains?\s+(?:sort\s+)?order/i,
        extract: (_m, raw) => ({
            invariant: `${raw.trim()} — output is sorted`,
            inputDomain: 'arrays of comparable values',
        }),
    },
];
// ---------------------------------------------------------------------------
// Code generators per framework
// ---------------------------------------------------------------------------
function buildFastCheckCode(invariant, inputDomain) {
    return `fc.assert(
  fc.property(
    fc.float({ min: 0.01, noNaN: true }), // ${inputDomain}
    (input) => {
      const result = functionUnderTest(input);
      // ${invariant}
      return result !== null && result !== undefined;
    },
  ),
);`;
}
function buildHypothesisCode(invariant, inputDomain) {
    const safeName = invariant
        .toLowerCase()
        .replace(/[^a-z0-9]/g, '_')
        .slice(0, 40);
    return `@given(st.floats(min_value=0.01, allow_nan=False))  # ${inputDomain}
@settings(max_examples=100, deadline=None)
def test_${safeName}(input_value: float) -> None:
    """${invariant}"""
    result = function_under_test(input_value)
    assert result is not None`;
}
function buildJqwikCode(invariant, inputDomain) {
    const safeName = invariant.replace(/[^a-zA-Z0-9]/g, '').slice(0, 40);
    return `@Property
boolean ${safeName}(@ForAll @Positive double input) {
    // ${inputDomain}
    // ${invariant}
    Object result = functionUnderTest(input);
    return result != null;
}`;
}
function buildGenericCode(invariant, inputDomain) {
    return `// Property: ${invariant}
// Input domain: ${inputDomain}
// For each arbitrary valid input in the domain:
//   1. Generate input using your framework's generator
//   2. Call the function under test
//   3. Assert: ${invariant}`;
}
function buildGeneratorCode(framework, invariant, inputDomain) {
    switch (framework) {
        case 'fast-check':
            return buildFastCheckCode(invariant, inputDomain);
        case 'hypothesis':
            return buildHypothesisCode(invariant, inputDomain);
        case 'jqwik':
            return buildJqwikCode(invariant, inputDomain);
        case 'generic':
            return buildGenericCode(invariant, inputDomain);
    }
}
function getShrinkStrategy(framework) {
    switch (framework) {
        case 'fast-check':
            return 'automatic (built-in shrinker reduces to minimal failing case)';
        case 'hypothesis':
            return 'automatic (Hypothesis shrinks via @settings(deriving=True))';
        case 'jqwik':
            return 'automatic (jqwik shrinks @ForAll parameters)';
        case 'generic':
            return 'manual (implement shrink by binary search or domain reduction)';
    }
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Extract PropertyTest objects from acceptance criteria strings.
 * Each criterion is analyzed against known invariant patterns.
 * Criteria with no detected pattern are skipped.
 */
export function extractPropertyTests(input) {
    const framework = detectPropertyFramework(input.language, input.stack);
    const shrinkStrategy = getShrinkStrategy(framework);
    const results = [];
    for (const criterion of input.criteria) {
        for (const { pattern, extract } of INVARIANT_PATTERNS) {
            const match = criterion.match(pattern);
            if (match) {
                const { invariant, inputDomain } = extract(match, criterion);
                results.push({
                    invariant,
                    inputDomain,
                    generatorCode: buildGeneratorCode(framework, invariant, inputDomain),
                    framework,
                    shrinkStrategy,
                });
                break; // one pattern per criterion
            }
        }
    }
    return results;
}
/**
 * Full extraction with metadata: detected framework and analyzed criteria list.
 */
export function extractPropertyTestsWithMeta(input) {
    const detectedFramework = detectPropertyFramework(input.language, input.stack);
    const tests = extractPropertyTests(input);
    return {
        tests,
        detectedFramework,
        analyzedCriteria: input.criteria,
    };
}
//# sourceMappingURL=property-test-generator.js.map