/**
 * @file Base class for all rule-based scorers
 * Provides common functionality for rule evaluation
 */
import { logger } from "../../../utils/logger.js";
import { BaseScorer, DEFAULT_SCORE_SCALE } from "../baseScorer.js";
/**
 * Default rule scorer configuration
 */
export const DEFAULT_RULE_SCORER_CONFIG = {
    enabled: true,
    threshold: 0.7,
    weight: 1.0,
    timeout: 1000,
    retries: 0,
    ruleCombination: "all",
};
/**
 * Abstract base class for rule-based scorers
 */
export class BaseRuleScorer extends BaseScorer {
    _ruleConfig;
    constructor(metadata, config) {
        super(metadata, config);
        this._ruleConfig = {
            ...DEFAULT_RULE_SCORER_CONFIG,
            ...metadata.defaultConfig,
            ...config,
        };
    }
    /**
     * Get rule-specific configuration
     */
    get ruleConfig() {
        return this._ruleConfig;
    }
    /**
     * Main scoring method
     */
    async score(input) {
        return this.executeWithTiming(async () => {
            // Validate input
            const validation = this.validateInput(input);
            if (!validation.valid) {
                return this.createErrorResult(`Invalid input: ${validation.errors.join(", ")}`);
            }
            try {
                const rules = this.getRules();
                if (rules.length === 0) {
                    return this.createScoreResult(10, "No rules configured - passing by default");
                }
                // Evaluate all rules
                const results = rules.map((rule) => ({
                    rule,
                    result: this.evaluateRule(rule, input),
                }));
                // Combine results based on configuration
                const combinedScore = this.combineRuleResults(results.map((r) => ({
                    ruleId: r.rule.id,
                    passed: r.result.passed,
                    score: r.result.score,
                })), rules);
                // Generate reasoning
                const reasoning = this.generateReasoning(results);
                return this.createScoreResult(combinedScore, reasoning, {
                    metadata: {
                        ruleResults: results.map((r) => ({
                            ruleId: r.rule.id,
                            ruleDescription: r.rule.description,
                            passed: r.result.passed,
                            score: r.result.score,
                        })),
                        ruleCount: rules.length,
                        passedCount: results.filter((r) => r.result.passed).length,
                        combinationMethod: this._ruleConfig.ruleCombination ?? "all",
                    },
                });
            }
            catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
                logger.error(`Rule scorer ${this._metadata.id} failed`, {
                    error: errorMessage,
                });
                return this.createErrorResult(errorMessage);
            }
        });
    }
    /**
     * Combine rule results based on configuration
     */
    combineRuleResults(results, rules) {
        const combination = this._ruleConfig.ruleCombination ?? "all";
        switch (combination) {
            case "all": {
                // All rules must pass for full score
                const passedCount = results.filter((r) => r.passed).length;
                return (passedCount / results.length) * DEFAULT_SCORE_SCALE.max;
            }
            case "any": {
                // Any rule passing gives partial credit
                const maxScore = Math.max(...results.map((r) => r.score));
                return maxScore * DEFAULT_SCORE_SCALE.max;
            }
            case "weighted": {
                // Weight-based combination
                let totalWeight = 0;
                let weightedScore = 0;
                for (let i = 0; i < results.length; i++) {
                    const rule = rules[i];
                    const weight = rule.weight ?? 1.0;
                    totalWeight += weight;
                    weightedScore += results[i].score * weight;
                }
                return totalWeight > 0
                    ? (weightedScore / totalWeight) * DEFAULT_SCORE_SCALE.max
                    : 0;
            }
            default:
                return ((results.filter((r) => r.passed).length / results.length) *
                    DEFAULT_SCORE_SCALE.max);
        }
    }
    /**
     * Generate reasoning from rule results
     */
    generateReasoning(results) {
        const passed = results.filter((r) => r.result.passed);
        const failed = results.filter((r) => !r.result.passed);
        const parts = [];
        if (passed.length > 0) {
            parts.push(`${passed.length} rule(s) passed: ${passed.map((r) => r.rule.id).join(", ")}`);
        }
        if (failed.length > 0) {
            parts.push(`${failed.length} rule(s) failed: ${failed.map((r) => r.rule.id).join(", ")}`);
        }
        return parts.join(". ");
    }
    /**
     * Helper: Check if text matches a regex pattern
     */
    matchesRegex(text, pattern, flags = "gi") {
        if (pattern.length > 200) {
            logger.warn(`[BaseRuleScorer] Regex pattern too long (${pattern.length} chars), skipping`);
            return false;
        }
        try {
            const regex = new RegExp(pattern, flags);
            return regex.test(text);
        }
        catch {
            logger.warn(`[BaseRuleScorer] Invalid regex pattern: ${pattern.substring(0, 50)}`);
            return false;
        }
    }
    /**
     * Helper: Check if text contains keyword with word boundaries
     */
    containsKeyword(text, keyword, caseInsensitive = true) {
        const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        const flags = caseInsensitive ? "gi" : "g";
        const regex = new RegExp(`\\b${escapedKeyword}\\b`, flags);
        return regex.test(text);
    }
    /**
     * Helper: Count occurrences of a pattern
     */
    countOccurrences(text, pattern, caseInsensitive = true) {
        if (pattern.length > 200) {
            logger.warn(`[BaseRuleScorer] Regex pattern too long (${pattern.length} chars), skipping`);
            return 0;
        }
        try {
            const flags = caseInsensitive ? "gi" : "g";
            const regex = new RegExp(pattern, flags);
            const matches = text.match(regex);
            return matches ? matches.length : 0;
        }
        catch {
            logger.warn(`[BaseRuleScorer] Invalid regex pattern: ${pattern.substring(0, 50)}`);
            return 0;
        }
    }
    /**
     * Helper: Get word count
     */
    getWordCount(text) {
        return text
            .trim()
            .split(/\s+/)
            .filter((word) => word.length > 0).length;
    }
    /**
     * Helper: Get character count (excluding whitespace)
     */
    getCharacterCount(text, includeWhitespace = true) {
        if (includeWhitespace) {
            return text.length;
        }
        return text.replace(/\s/g, "").length;
    }
    /**
     * Helper: Check text length is within bounds
     */
    isWithinLengthBounds(text, minWords, maxWords, minChars, maxChars) {
        const wordCount = this.getWordCount(text);
        const charCount = text.length;
        if (minWords !== undefined && wordCount < minWords) {
            return {
                passed: false,
                reason: `Word count ${wordCount} is below minimum ${minWords}`,
            };
        }
        if (maxWords !== undefined && wordCount > maxWords) {
            return {
                passed: false,
                reason: `Word count ${wordCount} exceeds maximum ${maxWords}`,
            };
        }
        if (minChars !== undefined && charCount < minChars) {
            return {
                passed: false,
                reason: `Character count ${charCount} is below minimum ${minChars}`,
            };
        }
        if (maxChars !== undefined && charCount > maxChars) {
            return {
                passed: false,
                reason: `Character count ${charCount} exceeds maximum ${maxChars}`,
            };
        }
        return { passed: true, reason: "Length within acceptable bounds" };
    }
}
//# sourceMappingURL=baseRuleScorer.js.map