import { buildBoundaryRegex, escapeRegex } from './boundary.js';
/**
 * Apply defaults to a partial options object. Defaults match the
 * "semantic-safe" profile documented on `KeywordMatchOptions`.
 */
export function resolveOptions(options) {
    return {
        minWordLength: options?.minWordLength ?? 7,
        requireBoundary: options?.requireBoundary ?? true,
        skipActionVerbs: options?.skipActionVerbs ?? true,
        skipStopwords: options?.skipStopwords ?? true,
        caseSensitive: options?.caseSensitive ?? false,
        customStopwords: options?.customStopwords ?? [],
    };
}
/**
 * Returns true if `keyword` is found in `text` under the active options.
 *
 * When `requireBoundary` is true (default), matching uses `\b` word
 * boundaries so `"auth"` does NOT match inside `"authoritative"`.
 * When false, a plain substring match is performed (case-controlled
 * by `caseSensitive`).
 */
export function matchKeyword(text, keyword, options) {
    if (keyword.length === 0 || text.length === 0) {
        return false;
    }
    const opts = resolveOptions(options);
    if (opts.requireBoundary) {
        const regex = buildBoundaryRegex(keyword, opts.caseSensitive);
        if (regex === null) {
            return false;
        }
        return regex.test(text);
    }
    // Substring path — still escape to avoid treating keyword as a regex.
    if (opts.caseSensitive) {
        return text.includes(keyword);
    }
    const pattern = new RegExp(escapeRegex(keyword), 'i');
    return pattern.test(text);
}
/**
 * Scan `text` for every keyword in `keywords`. Returns the unique list of
 * keywords that matched, in the order they appear in the input array.
 * Empty input or no matches yields an empty array.
 */
export function matchAnyKeyword(text, keywords, options) {
    if (keywords.length === 0 || text.length === 0) {
        return [];
    }
    const seen = new Set();
    const matched = [];
    for (const kw of keywords) {
        if (seen.has(kw)) {
            continue;
        }
        seen.add(kw);
        if (matchKeyword(text, kw, options)) {
            matched.push(kw);
        }
    }
    return matched;
}
/**
 * Density score in [0, 1]: the fraction of provided keywords that appear
 * in `text`. Returns 0 when `keywords` is empty so callers can treat
 * "nothing to score" as "no signal" without a special case.
 */
export function scoreMatches(text, keywords, options) {
    if (keywords.length === 0) {
        return 0;
    }
    // Deduplicate denominator so repeated keywords do not dilute the score.
    const unique = Array.from(new Set(keywords));
    const hits = matchAnyKeyword(text, unique, options);
    return hits.length / unique.length;
}
//# sourceMappingURL=matcher.js.map