/**
 * WikiChatSubscriberService — listens on chat-v2's `chat_message` event
 * and fires `WikiIngestService.ingest` for every user-authored message.
 *
 * Phase A (rules-only routing):
 *   senderType === 'user' → ingest into the Crewly project vault.
 *   senderType === 'agent' | 'system' → skip.
 *
 * Phase B (per spec §4.5) will replace the project-only routing with an
 * LLM-driven gate that picks team / project / global per source.
 *
 * Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
 * or filesystem error MUST NOT block the live chat flow. Errors are
 * logged at warn-level and swallowed.
 *
 * @module services/wiki/wiki-chat-subscriber.service
 */
import * as path from 'path';
import { LoggerService } from '../core/logger.service.js';
import { WikiIngestService, detectMessageShape } from './wiki-ingest.service.js';
/**
 * Process-singleton subscriber. Call `start()` once at server boot,
 * `stop()` at shutdown.
 */
export class WikiChatSubscriberService {
    logger;
    chatService;
    projectVaultPath;
    ingestService;
    boundHandler = null;
    started = false;
    constructor(opts) {
        this.logger = LoggerService.getInstance().createComponentLogger('WikiChatSubscriber');
        this.chatService = opts.chatService;
        this.projectVaultPath = opts.projectVaultPath ?? resolveDefaultProjectVaultPath();
        this.ingestService = opts.ingestService ?? WikiIngestService.getInstance();
    }
    /**
     * Subscribe to `chat_message`. Idempotent — calling start() twice
     * leaves a single listener.
     */
    start() {
        if (this.started) {
            this.logger.debug('start() called twice — ignoring');
            return;
        }
        this.boundHandler = (dto) => {
            // Fire-and-forget; any thrown error is logged + swallowed in handle().
            void this.handle(dto);
        };
        this.chatService.on('chat_message', this.boundHandler);
        this.started = true;
        this.logger.info('WikiChatSubscriber started', {
            vaultPath: this.projectVaultPath,
        });
    }
    /** Detach. Safe to call when not started. */
    stop() {
        if (!this.started || !this.boundHandler)
            return;
        this.chatService.off('chat_message', this.boundHandler);
        this.boundHandler = null;
        this.started = false;
        this.logger.info('WikiChatSubscriber stopped');
    }
    /**
     * Handle a single chat message. Public for tests.
     */
    async handle(dto) {
        try {
            if (!this.isIngestable(dto)) {
                return;
            }
            const ingestInput = {
                vaultPath: this.projectVaultPath,
                sourceType: 'user_chat',
                sourceRef: `chat:${dto.channelId}:${dto.id}`,
                sourceBody: dto.content,
                callerSession: `user/${dto.senderId}`,
            };
            // Phase A rules-based promotion: decision-shaped chats land in
            // BOTH log.md (audit) and llm-curated/decisions/<date>-<slug>.md
            // (canonical). Phase B will replace `detectMessageShape` with an
            // LLM gate per v2.1 §4.
            const shape = detectMessageShape(dto.content);
            if (shape === 'decision') {
                const result = await this.ingestService.ingestDecision(ingestInput);
                if (!result.log.ok) {
                    this.logger.warn('WikiChatSubscriber decision-log ingest refused', {
                        reason: result.log.reason,
                        messageId: dto.id,
                    });
                }
                if (!result.decision.ok) {
                    this.logger.warn('WikiChatSubscriber decisions/ promotion refused', {
                        reason: result.decision.reason,
                        messageId: dto.id,
                    });
                }
                return;
            }
            const outcome = await this.ingestService.ingest(ingestInput);
            if (!outcome.ok) {
                this.logger.warn('WikiChatSubscriber ingest refused', {
                    reason: outcome.reason,
                    messageId: dto.id,
                });
            }
        }
        catch (err) {
            // Swallow — chat flow must not block on wiki write failures.
            this.logger.warn('WikiChatSubscriber.handle threw (swallowed)', {
                messageId: dto.id,
                error: err.message,
            });
        }
    }
    /**
     * Decide whether a chat-v2 message should drive a wiki write.
     * Public for tests; logic is conservative — only ingest user-authored
     * text messages, skip agents/system, mentions-only pings, and slash
     * commands.
     */
    isIngestable(dto) {
        if (dto.senderType !== 'user')
            return false;
        if (!dto.content || dto.content.trim().length === 0)
            return false;
        // Skip slash commands — they're orc-controls, not knowledge.
        if (dto.content.trimStart().startsWith('/'))
            return false;
        // chat-v2 defaults `contentType` to 'markdown' when sendMessage doesn't
        // supply one (chat-v2.service.ts ~line 1240). Accept the two readable
        // text-bearing types; reject binary/system flavors like 'system_note'.
        const acceptable = new Set(['text', 'markdown']);
        if (dto.contentType && !acceptable.has(dto.contentType))
            return false;
        return true;
    }
}
/**
 * Resolve a sensible default vault path:
 *   1. `CREWLY_PROJECT_VAULT_PATH` env var (explicit override)
 *   2. `<cwd>/.crewly/wiki` if it exists
 *   3. The Crewly repo's own project vault (the only one with SCHEMA.md today)
 */
function resolveDefaultProjectVaultPath() {
    const fromEnv = process.env['CREWLY_PROJECT_VAULT_PATH'];
    if (fromEnv && path.isAbsolute(fromEnv)) {
        return fromEnv;
    }
    return path.join('/Users/yellowsunhy/Desktop/projects/crewly-projects/crewly', '.crewly/wiki');
}
//# sourceMappingURL=wiki-chat-subscriber.service.js.map