"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentSessionService = void 0;
const crypto_1 = require("crypto");
const path_1 = __importDefault(require("path"));
const index_core_1 = require("../index-core");
const changeBatchClassification_1 = require("../mcp/changeBatchClassification");
const ComponentSearchService_1 = require("./ComponentSearchService");
const elementFileHintSignals_1 = require("../utils/elementFileHintSignals");
const log = (0, index_core_1.createLogger)('AgentSessionService');
class ComponentFileResolver {
    searchService = new ComponentSearchService_1.ComponentSearchService();
    /**
     * @effect Searches project source files for the component most likely to contain an element.
     */
    async findMatchingFiles(projectPath, element) {
        const matches = await this.searchService.findMatchingComponents(projectPath, element);
        return matches.map((match) => match.filePath);
    }
}
const WAIT_FOR_CHANGES_TIMEOUT_MS = 60_000;
class AgentSessionService {
    bridge;
    projectPath;
    runner;
    fileResolver;
    inlineVariantService;
    worktreeProjectLocator;
    continuousSessionId;
    createSessionId;
    hasContinuousSessionStarted = false;
    isStopped = false;
    loopPromise = null;
    constructor(options) {
        this.bridge = options.bridge;
        this.projectPath = options.projectPath;
        this.runner = options.runner;
        this.fileResolver = options.fileResolver ?? new ComponentFileResolver();
        this.inlineVariantService = options.inlineVariantService;
        this.worktreeProjectLocator = options.worktreeProjectLocator;
        this.continuousSessionId = options.continuousSessionId ?? (0, crypto_1.randomUUID)();
        this.createSessionId = options.createSessionId ?? crypto_1.randomUUID;
    }
    getContinuousSessionId() {
        return this.continuousSessionId;
    }
    /**
     * @effect Starts the bridge consumption loop for queued Rivet visual changes.
     */
    start() {
        if (this.loopPromise) {
            return;
        }
        this.loopPromise = this.runLoop();
    }
    /**
     * @effect Stops the bridge consumption loop after the current wait/run settles.
     */
    stop() {
        this.isStopped = true;
    }
    /**
     * @effect Requests cancellation of active terminal-agent child processes.
     */
    cancelActiveRun() {
        return { stoppedCount: this.runner.stopActiveRuns?.('SIGTERM') ?? 0 };
    }
    /**
     * @effect Applies one queued change request by spawning the configured terminal agent and updating bridge status.
     */
    async processChangeRequest(request) {
        if ((0, changeBatchClassification_1.isMixedVariantBatch)(request)) {
            const rejectedRequest = this.bridge.rejectMixedVariantRequestBatch(request);
            if (rejectedRequest === undefined) {
                this.bridge.setStatus('READY');
                return {
                    success: false,
                    error: 'Mixed variant request was superseded',
                };
            }
            this.bridge.reportFeedback('Variant requests must be sent separately from other edits.');
            this.bridge.setStatus('READY');
            return { success: false, error: 'Mixed variant request batch' };
        }
        if ((0, changeBatchClassification_1.isSingleVariantRequestBatch)(request)) {
            if (!this.inlineVariantService) {
                const unavailableRequest = this.bridge.consumeVariantRequestBatch(request);
                if (!unavailableRequest) {
                    this.bridge.setStatus('READY');
                    return {
                        success: false,
                        error: 'Variant request was superseded',
                    };
                }
                this.bridge.reportFeedback('Inline variants are not available in this session.');
                this.bridge.setStatus('READY');
                return { success: false, error: 'Inline variants unavailable' };
            }
            const variantRequest = this.bridge.consumeVariantRequestBatch(request);
            if (!variantRequest) {
                this.bridge.setStatus('READY');
                return {
                    success: false,
                    error: 'Variant request was superseded',
                };
            }
            return this.inlineVariantService.processVariantRequest(variantRequest);
        }
        const isVariantWork = Boolean(request.worktreePath) ||
            request.changes.some((change) => change.kind === 'variant');
        let cwd;
        try {
            cwd = await this.resolveRunCwd(request);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.bridge.clearPending(request);
            this.bridge.reportFeedback(`Failed to prepare terminal agent run: ${message}`);
            this.bridge.setStatus('READY');
            return { success: false, error: message };
        }
        const sessionId = isVariantWork
            ? this.createSessionId()
            : this.continuousSessionId;
        const resumeSession = !isVariantWork && this.hasContinuousSessionStarted;
        this.bridge.setStatus('APPLYING');
        this.bridge.startApplyingRun(request);
        this.bridge.clearPending(request);
        const agentLabel = this.runner.getDisplayName?.() ?? 'Claude Code';
        this.bridge.reportFeedback(isVariantWork
            ? `${agentLabel} is applying this variant in an isolated session.`
            : `${agentLabel} is applying your Rivet changes.`);
        const enrichedRequest = await this.enrichRequestFileHints(request, cwd);
        const result = await this.runner.runChangeRequest({
            request: enrichedRequest,
            cwd,
            sessionId,
            resumeSession,
            onFeedback: (message) => this.bridge.reportFeedback(message),
        });
        this.bridge.completeActiveRunCorrelation();
        if (result.success) {
            if (!isVariantWork) {
                this.hasContinuousSessionStarted = true;
            }
            this.bridge.reportFeedback(`${agentLabel} finished applying changes.`);
        }
        else {
            this.bridge.reportFeedback(`${agentLabel} failed: ${result.error ?? 'unknown error'}`);
        }
        this.bridge.reportFeedback('__RIVET_REFRESH_GIT__');
        this.bridge.setStatus('READY');
        return result;
    }
    /**
     * Resolves the project directory where a terminal agent should apply a queued request.
     */
    async resolveRunCwd(request) {
        if (!request.worktreePath) {
            return this.projectPath;
        }
        return ((await this.worktreeProjectLocator?.getProjectCwdInWorktree(request.worktreePath)) ?? request.worktreePath);
    }
    /**
     * @effect Resolves missing element file hints before handing the request to the agent.
     */
    async enrichRequestFileHints(request, projectPath) {
        const changes = [];
        const sourceFiles = new Set(this.filterFilePathsForProject(request.sourceFiles ?? [], projectPath));
        for (const change of request.changes) {
            const enrichedChange = await this.enrichChangeFileHints(change, projectPath);
            changes.push(enrichedChange);
            for (const element of this.getChangeElements(enrichedChange)) {
                for (const filePath of element.filePaths) {
                    sourceFiles.add(filePath);
                }
            }
        }
        return {
            ...request,
            changes,
            sourceFiles: Array.from(sourceFiles),
        };
    }
    /**
     * @effect Resolves file hints for every element-bearing change item in a request.
     */
    async enrichChangeFileHints(change, projectPath) {
        if (change.kind === 'variant' || change.kind === 'chat') {
            return change;
        }
        const element = await this.enrichElementFileHints(change.element, projectPath);
        if (change.kind === 'comment') {
            const elements = change.elements
                ? await Promise.all(change.elements.map((item) => this.enrichElementFileHints(item, projectPath)))
                : undefined;
            return { ...change, element, ...(elements ? { elements } : {}) };
        }
        return { ...change, element };
    }
    /**
     * @effect Searches source files when an element does not already carry file hints.
     */
    async enrichElementFileHints(element, projectPath) {
        const currentFilePaths = this.filterFilePathsForProject(element.filePaths ?? [], projectPath);
        if (currentFilePaths.length > 0) {
            return currentFilePaths.length === element.filePaths.length
                ? element
                : { ...element, filePaths: currentFilePaths };
        }
        if (!(0, elementFileHintSignals_1.shouldSearchElementFileHints)(element)) {
            return element;
        }
        try {
            const filePaths = await this.fileResolver.findMatchingFiles(projectPath, element);
            return filePaths.length > 0 ? { ...element, filePaths } : element;
        }
        catch (error) {
            log.warn('Failed to enrich agent element file paths', {
                xpath: element.xpath,
                error: error instanceof Error ? error.message : String(error),
            });
            return element;
        }
    }
    filterFilePathsForProject(filePaths, projectPath) {
        return filePaths.filter((filePath) => {
            const candidatePath = path_1.default.isAbsolute(filePath)
                ? filePath
                : path_1.default.resolve(projectPath, filePath);
            const relative = path_1.default.relative(projectPath, candidatePath);
            const isInsideProject = relative === '' ||
                (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
            return isInsideProject;
        });
    }
    getChangeElements(change) {
        if (change.kind === 'variant' || change.kind === 'chat') {
            return [];
        }
        if (change.kind === 'comment') {
            return [change.element, ...(change.elements ?? [])];
        }
        return [change.element];
    }
    async runLoop() {
        this.bridge.setStatus('READY');
        while (!this.isStopped && this.bridge.isActive()) {
            let request;
            try {
                request = await this.bridge.waitForChanges(WAIT_FOR_CHANGES_TIMEOUT_MS);
            }
            catch {
                continue;
            }
            try {
                await this.processChangeRequest(request);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                log.error('Agent session failed to process change request', error);
                this.bridge.completeActiveRunCorrelation();
                const agentLabel = this.runner.getDisplayName?.() ?? 'Claude Code';
                this.bridge.reportFeedback(`${agentLabel} failed: ${message}`);
                this.bridge.reportFeedback('__RIVET_REFRESH_GIT__');
                this.bridge.setStatus('READY');
            }
        }
    }
}
exports.AgentSessionService = AgentSessionService;
//# sourceMappingURL=AgentSessionService.js.map