/**
 * Funnel routing for catalog selection.
 *
 * A pure pre-filter that narrows the full item catalog down to domain-relevant
 * candidates before the heavier `resolveCatalogVotes` scoring pass runs.
 * This cuts the specialist/skill scorer work from O(N=139) to O(shortlist~15)
 * on typical tasks.
 *
 * Stage events emitted via nexusEventBus allow the dashboard command-bar strip
 * to show live funnel progress: classify → shortlist → vote → compose → execute.
 */
import { nexusEventBus } from '../event-bus.js';
// Minimum shortlist: always pass through at least this many items even if
// no domain keyword matched, so we never return an empty catalog.
const MIN_SHORTLIST = 8;
// Per-taskType domain keywords that catalog item body/name must contain to
// be included in the shortlist. Mirrors the intent-keyword map in scoring.ts
// (lines 113-129) without duplicating the scoring logic.
const DOMAIN_KEYWORDS = {
    frontend: ['frontend', 'ui', 'ux', 'react', 'css', 'component', 'design', 'layout', 'html', 'web', 'dashboard', 'accessibility'],
    backend: ['backend', 'api', 'server', 'database', 'sql', 'endpoint', 'service', 'runtime', 'orchestration', 'architecture', 'microservice', 'node'],
    ai: ['ai', 'ml', 'llm', 'rag', 'embedding', 'prompt', 'inference', 'agent', 'model', 'neural', 'vector', 'machine-learning'],
    test: ['test', 'coverage', 'jest', 'cypress', 'e2e', 'unit', 'integration', 'qa', 'validation', 'regression', 'spec'],
    bugfix: ['debug', 'fix', 'bug', 'hotfix', 'patch', 'repair', 'troubleshoot', 'investigate', 'diagnose', 'root-cause'],
    feature: ['feature', 'implement', 'build', 'create', 'develop', 'extend', 'enhance', 'new', 'add', 'introduce'],
    review: ['review', 'audit', 'quality', 'code-review', 'inspect', 'assess', 'security', 'performance', 'standards'],
    refactor: ['refactor', 'cleanup', 'restructure', 'reorganize', 'extract', 'decouple', 'simplify', 'tech-debt'],
    research: ['research', 'investigate', 'explore', 'analysis', 'feasibility', 'spike', 'discovery', 'evaluate'],
    ops: ['ops', 'devops', 'infra', 'infrastructure', 'ci', 'cd', 'docker', 'kubernetes', 'deploy', 'pipeline', 'monitoring', 'sre'],
    pm: ['pm', 'product', 'planning', 'roadmap', 'requirements', 'story', 'ticket', 'epic', 'jira', 'backlog'],
    release: ['release', 'publish', 'deploy', 'version', 'tag', 'changelog', 'semver', 'ship'],
    marketing: ['marketing', 'seo', 'content', 'copy', 'campaign', 'growth', 'blog', 'social'],
    sales: ['sales', 'crm', 'prospect', 'lead', 'pitch', 'outreach', 'deal'],
    data: ['data', 'analytics', 'metrics', 'query', 'etl', 'bi', 'reporting', 'warehouse', 'pipeline'],
};
/**
 * Narrow `items` to those whose name/body contains at least one keyword from
 * the intent's primary or secondary domain.  Always returns ≥ MIN_SHORTLIST
 * items so the downstream voter never operates on an empty set.
 *
 * @param items   Full catalog for this artifact kind (e.g., all 139 specialists).
 * @param intent  Classified intent from `classifyIntent`.
 * @param kind    Human-readable kind label for event payloads (e.g., 'specialist').
 * @returns       Filtered subset of `items`.
 */
export function categoryFilter(items, intent, kind) {
    if (items.length <= MIN_SHORTLIST)
        return items;
    const primary = DOMAIN_KEYWORDS[intent.taskType] ?? [];
    const secondary = intent.secondaryType ? DOMAIN_KEYWORDS[intent.secondaryType] ?? [] : [];
    // Also include the raw taskType string itself as a keyword
    const allKeywords = [...new Set([...primary, ...secondary, intent.taskType])];
    const matched = [];
    const unmatched = [];
    for (const item of items) {
        const haystack = `${item.name} ${item.body}`.toLowerCase();
        if (allKeywords.some((kw) => haystack.includes(kw))) {
            matched.push(item);
        }
        else {
            unmatched.push(item);
        }
    }
    if (matched.length >= MIN_SHORTLIST)
        return matched;
    // Supplement with unmatched items (sorted by catalogPriority desc, then name)
    const supplement = [...unmatched]
        .sort((a, b) => (b.catalogPriority ?? 0) - (a.catalogPriority ?? 0))
        .slice(0, MIN_SHORTLIST - matched.length);
    return [...matched, ...supplement];
}
/**
 * Emit a single funnel stage event on the shared event bus.
 * Dashboard SSE subscribes to `orchestrator.funnel.stage` and renders
 * the command-bar strip progress indicator.
 */
export function emitFunnelStage(payload) {
    const eventPayload = {
        ...payload,
        at: payload.at ?? new Date().toISOString(),
    };
    const handle = setImmediate(() => {
        try {
            nexusEventBus.emit('orchestrator.funnel.stage', eventPayload);
        }
        catch { /* funnel telemetry must not block orchestration */ }
    });
    handle.unref?.();
}
