// engine/best-practices-library.ts — Domain best practices catalog (SPEC-290)
//
// Loads domain best-practice criteria from the 3-layer config system.
// Source: src/config/domain-best-practices.json (system defaults)
// Override: data/global/domain-best-practices-custom.json
//           data/projects/{hash}/config/domain-best-practices-overrides.json
//
// Uses ConfigLoader pattern from SPEC-205.
import { z } from 'zod';
import { ConfigLoader } from './config-loader.js';
// ---------------------------------------------------------------------------
// SPEC-543: Security relevance check — prevents noisy security ACs
// ---------------------------------------------------------------------------
/** Domains considered security-sensitive — their criteria require relevance verification. */
const SECURITY_SENSITIVE_DOMAINS = new Set([
    'authentication',
    'authorization',
    'api-public',
    'multi-tenant',
    'payments',
    'healthcare',
]);
/** Max security/compliance criteria per spec when domain is not explicitly relevant. */
const MAX_SECURITY_CRITERIA_PER_SPEC = 2;
/** Keywords that justify rate-limiting, JWT, auth-related criteria. */
const AUTH_RELEVANT_KEYWORDS = [
    'login',
    'auth',
    'register',
    'token',
    'session',
    'password',
    'api key',
    'endpoint',
    'http',
    'jwt',
    'oauth',
    'credential',
    'sign in',
    'sign up',
];
/** Keywords that justify PCI-DSS / payment criteria. */
const PAYMENT_RELEVANT_KEYWORDS = [
    'payment',
    'card',
    'billing',
    'checkout',
    'stripe',
    'invoice',
    'paypal',
    'subscription',
    'charge',
    'refund',
    'merchant',
    'pci',
];
/** Keywords that justify HIPAA / health data criteria. */
const HEALTH_RELEVANT_KEYWORDS = [
    'health',
    'medical',
    'phi',
    'patient',
    'hipaa',
    'ehr',
    'emr',
    'clinical',
];
/** Keywords that justify encryption / sensitive data criteria. */
const ENCRYPTION_RELEVANT_KEYWORDS = [
    'password',
    'sensitive',
    'pii',
    'phi',
    'financial',
    'encrypt',
    'secret',
];
/**
 * Returns true when the spec content explicitly mentions security-relevant topics
 * for the given domain. Used to suppress irrelevant security ACs.
 */
function isSecurityDomainRelevant(domain, specContent) {
    if (!SECURITY_SENSITIVE_DOMAINS.has(domain)) {
        return true; // non-security domains are always relevant
    }
    const lower = specContent.toLowerCase();
    if (domain === 'authentication' || domain === 'api-public') {
        return AUTH_RELEVANT_KEYWORDS.some((kw) => lower.includes(kw));
    }
    if (domain === 'payments') {
        return PAYMENT_RELEVANT_KEYWORDS.some((kw) => lower.includes(kw));
    }
    if (domain === 'healthcare') {
        return HEALTH_RELEVANT_KEYWORDS.some((kw) => lower.includes(kw));
    }
    // authorization / multi-tenant — rely on auth + encryption keywords
    return (AUTH_RELEVANT_KEYWORDS.some((kw) => lower.includes(kw)) ||
        ENCRYPTION_RELEVANT_KEYWORDS.some((kw) => lower.includes(kw)));
}
// ---------------------------------------------------------------------------
// Zod schema
// ---------------------------------------------------------------------------
const DomainBestPracticeSchema = z.object({
    id: z.string().min(1).describe('Unique practice identifier'),
    domain: z.string().min(1).describe('Domain this practice belongs to'),
    label: z.string().min(1).describe('Short label for the practice'),
    criterion: z.string().min(1).describe('Full acceptance criterion text'),
    critical: z.boolean().describe('Whether missing this criterion is critical'),
    coverageKeywords: z
        .array(z.string())
        .describe('Keywords indicating the criterion is already covered'),
});
const practicesArraySchema = z.array(DomainBestPracticeSchema);
// ---------------------------------------------------------------------------
// Loader singleton
// ---------------------------------------------------------------------------
let _loader = null;
function getLoader() {
    _loader ??= new ConfigLoader('domain-best-practices', practicesArraySchema);
    return _loader;
}
// ---------------------------------------------------------------------------
// Coverage check
// ---------------------------------------------------------------------------
const COVERAGE_THRESHOLD = 0.75; // similarity score to consider criterion covered
/**
 * Checks whether a criterion is already covered in existing spec content.
 * Uses simple keyword-overlap (fuzzy): if any coverageKeyword appears in the spec
 * content, the criterion is considered covered.
 */
export function isCriterionCovered(practice, specContent) {
    const lower = specContent.toLowerCase();
    return practice.coverageKeywords.some((kw) => lower.includes(kw.toLowerCase()));
}
/**
 * Compute a rough coverage score between 0..1 based on keyword overlap.
 * Not used for hard decisions — exposed for testing and future UI use.
 */
export function coverageScore(practice, specContent) {
    if (practice.coverageKeywords.length === 0) {
        return 0;
    }
    const lower = specContent.toLowerCase();
    const matched = practice.coverageKeywords.filter((kw) => lower.includes(kw.toLowerCase()));
    return matched.length / practice.coverageKeywords.length;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Load all best practices from the 3-layer config system.
 */
export function loadBestPractices(projectHash) {
    return getLoader().load(projectHash);
}
/**
 * Compute criteria suggestions for a given list of detected domains and spec content.
 *
 * For each domain:
 * - Filters out criteria already covered in spec content (fuzzy keyword match)
 * - Returns suggested and already-covered criteria separately
 */
export function computeSuggestions(domains, specContent, projectHash) {
    const allPractices = loadBestPractices(projectHash);
    // SPEC-543: Track total security criteria injected across all domains to cap at max
    let totalSecurityCriteriaInjected = 0;
    return domains.map((domain) => {
        const practices = allPractices.filter((p) => p.domain === domain);
        const suggested = [];
        const suggestedIds = [];
        const alreadyCovered = [];
        let hasCriticalGaps = false;
        // SPEC-543: Check if this security-sensitive domain is actually relevant to the spec
        const securityRelevant = isSecurityDomainRelevant(domain, specContent);
        for (const practice of practices) {
            if (isCriterionCovered(practice, specContent)) {
                alreadyCovered.push(practice.label);
            }
            else {
                // SPEC-543: Suppress security criteria when domain is not relevant or cap exceeded
                if (SECURITY_SENSITIVE_DOMAINS.has(domain) &&
                    (!securityRelevant || totalSecurityCriteriaInjected >= MAX_SECURITY_CRITERIA_PER_SPEC)) {
                    // Skip irrelevant/excess security criterion
                    continue;
                }
                suggested.push(practice.criterion);
                suggestedIds.push(practice.id);
                if (SECURITY_SENSITIVE_DOMAINS.has(domain)) {
                    totalSecurityCriteriaInjected += 1;
                }
                if (practice.critical) {
                    hasCriticalGaps = true;
                }
            }
        }
        return {
            domain,
            suggestedCriteria: suggested,
            alreadyCovered,
            suggestedIds,
            hasCriticalGaps,
        };
    });
}
// Re-export threshold for tests
export { COVERAGE_THRESHOLD };
//# sourceMappingURL=best-practices-library.js.map