import { resolveOptions } from './matcher.js';
import { isActionVerb, isCustomStopword, isStopword } from './stopwords.js';
/**
 * Clauses that, when present, indicate the rest of the sentence names
 * forbidden concepts ("never commit secrets", "must not expose data").
 * Ordered by specificity — longer forms match before single-word ones.
 */
const FORBIDDEN_CLAUSES = [
    /\bmust\s+not\s+([^.!?;:\n]+)/gi,
    /\bshould\s+not\s+([^.!?;:\n]+)/gi,
    /\bshall\s+not\s+([^.!?;:\n]+)/gi,
    /\bcannot\s+([^.!?;:\n]+)/gi,
    /\bdo\s+not\s+([^.!?;:\n]+)/gi,
    /\bdon'?t\s+([^.!?;:\n]+)/gi,
    /\bnever\s+([^.!?;:\n]+)/gi,
];
/**
 * Extract semantic keywords from a forbidden-rule phrase.
 *
 * The extractor looks for "never X" / "must not X" / "do not X" clauses
 * in `phrase`, collects the words inside each clause, and returns a
 * deduplicated list filtered by the active options (length, action verbs,
 * stopwords, custom stopwords).
 *
 * When no forbidden clause is present, returns an empty array — unlike
 * naive tokenizers, this guarantees the caller never gets generic filler.
 */
export function extractSemanticKeywords(phrase, options) {
    if (phrase.length === 0) {
        return [];
    }
    const opts = resolveOptions(options);
    const fragments = collectForbiddenFragments(phrase);
    if (fragments.length === 0) {
        return [];
    }
    const out = [];
    const seen = new Set();
    for (const fragment of fragments) {
        const words = fragment.match(/[A-Za-z][A-Za-z0-9_-]*/g) ?? [];
        for (const raw of words) {
            const lower = raw.toLowerCase();
            if (seen.has(lower)) {
                continue;
            }
            if (raw.length < opts.minWordLength) {
                continue;
            }
            if (opts.skipActionVerbs && isActionVerb(raw)) {
                continue;
            }
            if (opts.skipStopwords && isStopword(raw)) {
                continue;
            }
            if (isCustomStopword(raw, opts.customStopwords)) {
                continue;
            }
            seen.add(lower);
            out.push(raw);
        }
    }
    return out;
}
/**
 * Run every FORBIDDEN_CLAUSE regex over `phrase` and collect the
 * captured group (the text that follows the negation cue). Each regex
 * is declared with the `g` flag so its `lastIndex` must be reset
 * between runs — we do that via `exec` in a fresh loop.
 */
function collectForbiddenFragments(phrase) {
    const fragments = [];
    for (const source of FORBIDDEN_CLAUSES) {
        // Clone the regex so the caller's flags/lastIndex are isolated.
        const regex = new RegExp(source.source, source.flags);
        let match;
        while ((match = regex.exec(phrase)) !== null) {
            const captured = match[1];
            if (captured !== undefined && captured.trim().length > 0) {
                fragments.push(captured);
            }
        }
    }
    return fragments;
}
//# sourceMappingURL=extractor.js.map