"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlineVariantGenerationService = 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 log = (0, index_core_1.createLogger)('InlineVariantGenerationService');
const INLINE_VARIANT_LEASE_HEARTBEAT_MS = 60_000;
const INLINE_VARIANT_BRIEF_DIRECTIONS = [
    {
        label: 'Refined Focus',
        body: 'Subtle polish with tighter hierarchy and calmer emphasis.',
    },
    {
        label: 'Bold Contrast',
        body: 'Stronger contrast with clearer visual weight and presence.',
    },
    {
        label: 'Airy Layout',
        body: 'More breathing room with lighter spacing and softer rhythm.',
    },
    {
        label: 'Editorial Polish',
        body: 'Magazine-like composition with crisp details and confident tone.',
    },
    {
        label: 'Playful Accent',
        body: 'Expressive accents with lively color and approachable energy.',
    },
    {
        label: 'Calm Minimal',
        body: 'Reduced ornament with quiet structure and restrained styling.',
    },
    {
        label: 'Dense Utility',
        body: 'Compact information density with practical controls and clarity.',
    },
    {
        label: 'Sharp Detail',
        body: 'Precise edges with stronger alignment and cleaner finishing.',
    },
];
const hasWhitespace = (value) => /\s/.test(value);
const deriveTarget = (element) => {
    const filePaths = element.filePaths ?? [];
    if (filePaths.length === 1) {
        return { type: 'file', ref: filePaths[0] };
    }
    if (element.rivetId) {
        return {
            type: 'element',
            ref: `[data-rivet-id="${element.rivetId}"]`,
        };
    }
    if (element.id && !hasWhitespace(element.id)) {
        return { type: 'element', ref: `#${element.id}` };
    }
    if (element.xpath) {
        return { type: 'element', ref: element.xpath };
    }
    const classTokens = element.className.trim().split(/\s+/).filter(Boolean);
    if (classTokens.length === 1) {
        return { type: 'element', ref: `.${classTokens[0]}` };
    }
    return { type: 'element', ref: element.tagName.toLowerCase() };
};
const getWorktreePath = (workItem) => {
    if (!workItem.input || typeof workItem.input !== 'object') {
        return null;
    }
    const worktreePath = workItem.input
        .worktreePath;
    return typeof worktreePath === 'string' && worktreePath.trim()
        ? worktreePath
        : null;
};
const getBrief = (workItem) => {
    const input = workItem.input && typeof workItem.input === 'object'
        ? workItem.input
        : {};
    return {
        label: typeof input.briefLabel === 'string' && input.briefLabel.trim()
            ? input.briefLabel
            : 'Variant',
        body: typeof input.briefBody === 'string' && input.briefBody.trim()
            ? input.briefBody
            : 'Create a distinct inline variation.',
    };
};
const isInsidePath = (rootPath, candidatePath) => {
    const relativePath = path_1.default.relative(rootPath, candidatePath);
    return (relativePath === '' ||
        (!relativePath.startsWith('..') && !path_1.default.isAbsolute(relativePath)));
};
const rebaseFilePath = (args) => {
    const trimmedPath = args.filePath.trim();
    if (!trimmedPath) {
        return null;
    }
    if (!path_1.default.isAbsolute(trimmedPath)) {
        const worktreeRelativePath = path_1.default.resolve(args.worktreeProjectPath, trimmedPath);
        return isInsidePath(args.worktreeProjectPath, worktreeRelativePath)
            ? worktreeRelativePath
            : null;
    }
    if (isInsidePath(args.worktreePath, trimmedPath)) {
        return trimmedPath;
    }
    if (args.baseProjectPath && isInsidePath(args.baseProjectPath, trimmedPath)) {
        return path_1.default.join(args.worktreeProjectPath, path_1.default.relative(args.baseProjectPath, trimmedPath));
    }
    return null;
};
const rebaseFilePaths = (args) => Array.from(new Set(args.filePaths
    .map((filePath) => rebaseFilePath({
    filePath,
    baseProjectPath: args.baseProjectPath,
    worktreePath: args.worktreePath,
    worktreeProjectPath: args.worktreeProjectPath,
}))
    .filter((filePath) => Boolean(filePath))));
const rebaseElementFilePaths = (args) => ({
    ...args.element,
    filePaths: rebaseFilePaths({
        filePaths: args.element.filePaths,
        baseProjectPath: args.baseProjectPath,
        worktreePath: args.worktreePath,
        worktreeProjectPath: args.worktreeProjectPath,
    }),
});
const buildInlineVariantBriefs = (count) => INLINE_VARIANT_BRIEF_DIRECTIONS.slice(0, count).map((brief) => ({
    ...brief,
}));
const buildVariantChangeRequest = (args) => {
    const brief = getBrief(args.workItem);
    const instruction = [
        `Create inline variant "${brief.label}".`,
        `Variant direction: ${brief.body}`,
        `Original request: ${args.comment.instruction}`,
        'Apply the change only in this variant worktree. Keep the edit scoped to the selected/commented element context.',
    ].join('\n');
    return {
        ...args.baseRequest,
        worktreePath: args.worktreePath,
        sourceFiles: rebaseFilePaths({
            filePaths: args.baseRequest.sourceFiles,
            baseProjectPath: args.baseProjectPath,
            worktreePath: args.worktreePath,
            worktreeProjectPath: args.worktreeProjectPath,
        }),
        changes: [
            {
                ...args.comment,
                element: rebaseElementFilePaths({
                    element: args.comment.element,
                    baseProjectPath: args.baseProjectPath,
                    worktreePath: args.worktreePath,
                    worktreeProjectPath: args.worktreeProjectPath,
                }),
                elements: args.comment.elements?.map((element) => rebaseElementFilePaths({
                    element,
                    baseProjectPath: args.baseProjectPath,
                    worktreePath: args.worktreePath,
                    worktreeProjectPath: args.worktreeProjectPath,
                })),
                instruction,
                variantRequest: undefined,
            },
        ],
    };
};
/**
 * Runs selected-element variant requests through the server-owned variants lifecycle.
 */
class InlineVariantGenerationService {
    bridge;
    orchestrator;
    runner;
    createSessionId;
    leaseHeartbeatMs;
    constructor(options) {
        this.bridge = options.bridge;
        this.orchestrator = options.orchestrator;
        this.runner = options.runner;
        this.createSessionId = options.createSessionId ?? crypto_1.randomUUID;
        this.leaseHeartbeatMs =
            options.leaseHeartbeatMs ?? INLINE_VARIANT_LEASE_HEARTBEAT_MS;
    }
    /**
     * @effect Starts an inline variants session, runs each leased worktree with an isolated terminal agent, and reports each result.
     */
    async processVariantRequest(request) {
        if (!(0, changeBatchClassification_1.isSingleVariantRequestBatch)(request)) {
            this.bridge.reportFeedback('Inline variants require one variant comment.');
            this.bridge.setStatus('READY');
            return { success: false, error: 'Invalid inline variant request' };
        }
        const comment = request.changes[0];
        const count = comment.variantRequest?.count;
        if (typeof count !== 'number') {
            this.bridge.reportFeedback('Inline variant request is missing a count.');
            this.bridge.setStatus('READY');
            return { success: false, error: 'Missing variant count' };
        }
        const agentLabel = this.runner.getDisplayName?.() ?? 'Claude Code';
        this.bridge.setStatus('APPLYING');
        this.bridge.reportFeedback(`Starting ${count} inline variants.`);
        try {
            const startResult = await this.orchestrator.startUnified({
                prompt: comment.instruction,
                target: deriveTarget(comment.element),
                projectContext: { kind: 'existing' },
                leaseOwner: `rivet-agent-${agentLabel.toLowerCase().replace(/\s+/g, '-')}`,
                briefs: buildInlineVariantBriefs(count),
            });
            const leasedItems = startResult.leasedWorkItems ?? [];
            this.bridge.reportFeedback(`Creating ${leasedItems.length} worktrees.`);
            if (leasedItems.length === 0) {
                const error = 'No inline variant work items were leased';
                this.bridge.reportFeedback(`${error}.`);
                await this.orchestrator
                    .cancel({
                    sessionId: startResult.sessionId,
                    reason: error,
                })
                    .catch((cancelError) => {
                    log.warn('Inline variant session cancellation failed', cancelError);
                });
                this.bridge.setStatus('READY');
                return { success: false, error };
            }
            const results = await Promise.all(leasedItems.map((workItem, index) => this.runLeasedVariantSafely({
                request,
                comment,
                workItem,
                leaseId: startResult.leaseId ?? '',
                sessionId: startResult.sessionId,
                index,
            })));
            const isSuccess = results.every((result) => result.success);
            this.bridge.reportFeedback(isSuccess
                ? `${agentLabel} finished generating inline variants.`
                : `${agentLabel} finished with one or more variant failures.`);
            this.bridge.reportFeedback('__RIVET_REFRESH_GIT__');
            this.bridge.setStatus('READY');
            return isSuccess
                ? { success: true }
                : {
                    success: false,
                    error: results.find((result) => !result.success)?.error ??
                        'Inline variant generation failed',
                };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            log.error('Inline variant generation failed', error);
            this.bridge.reportFeedback(`${agentLabel} failed variants: ${message}`);
            this.bridge.reportFeedback('__RIVET_REFRESH_GIT__');
            this.bridge.setStatus('READY');
            return { success: false, error: message };
        }
    }
    /**
     * @effect Converts per-variant setup/reporting failures into failed results after sibling runs settle.
     */
    async runLeasedVariantSafely(args) {
        try {
            return await this.runLeasedVariant(args);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            const brief = getBrief(args.workItem);
            log.error(`Inline variant ${brief.label} failed`, error);
            this.bridge.reportFeedback(`Variant ${brief.label} failed: ${message}`);
            await this.orchestrator
                .reportComplete({
                sessionId: args.sessionId,
                workItemId: args.workItem.id,
                leaseId: args.leaseId,
                attempt: args.workItem.attempt,
                status: 'failed',
                error: { code: 'VARIANT_RUN_FAILED', message },
            })
                .catch((reportError) => {
                log.warn('Inline variant failure report failed', reportError);
            });
            return { success: false, error: message };
        }
    }
    /**
     * @effect Runs one leased code-generation item and reports its terminal status to the variants orchestrator.
     */
    async runLeasedVariant(args) {
        const worktreePath = getWorktreePath(args.workItem);
        const brief = getBrief(args.workItem);
        if (!worktreePath) {
            const error = `Variant ${brief.label} has no worktree path.`;
            await this.orchestrator.reportComplete({
                sessionId: args.sessionId,
                workItemId: args.workItem.id,
                leaseId: args.leaseId,
                attempt: args.workItem.attempt,
                status: 'failed',
                error: { code: 'MISSING_WORKTREE', message: error },
            });
            this.bridge.reportFeedback(error);
            return { success: false, error };
        }
        const agentLabel = this.runner.getDisplayName?.() ?? 'Claude Code';
        const terminalSessionId = this.createSessionId();
        const worktreeProjectPath = await this.orchestrator.getProjectCwdInWorktree(worktreePath);
        this.bridge.reportFeedback(`Running ${agentLabel} for variant ${args.index + 1}: ${brief.label}.`);
        await this.orchestrator.reportComplete({
            sessionId: args.sessionId,
            workItemId: args.workItem.id,
            leaseId: args.leaseId,
            attempt: args.workItem.attempt,
            status: 'running',
        });
        const stopHeartbeat = this.startLeaseHeartbeat({
            sessionId: args.sessionId,
            workItem: args.workItem,
            leaseId: args.leaseId,
        });
        let result;
        try {
            result = await this.runner.runChangeRequest({
                request: buildVariantChangeRequest({
                    baseRequest: args.request,
                    comment: args.comment,
                    workItem: args.workItem,
                    worktreePath,
                    worktreeProjectPath,
                    baseProjectPath: this.bridge.getProjectPath(),
                }),
                cwd: worktreeProjectPath,
                sessionId: terminalSessionId,
                resumeSession: false,
                onFeedback: (message) => this.bridge.reportFeedback(`${brief.label}: ${message}`),
            });
        }
        finally {
            stopHeartbeat();
        }
        if (!result.success) {
            const error = result.error ?? 'unknown error';
            await this.orchestrator.reportComplete({
                sessionId: args.sessionId,
                workItemId: args.workItem.id,
                leaseId: args.leaseId,
                attempt: args.workItem.attempt,
                status: 'failed',
                error: { code: 'AGENT_FAILED', message: error },
            });
            this.bridge.reportFeedback(`${agentLabel} failed variant ${brief.label}: ${error}`);
            return { success: false, error };
        }
        await this.orchestrator.reportComplete({
            sessionId: args.sessionId,
            workItemId: args.workItem.id,
            leaseId: args.leaseId,
            attempt: args.workItem.attempt,
            status: 'succeeded',
            output: { worktreePath, changedFiles: [] },
        });
        this.bridge.reportFeedback(`${agentLabel} completed variant ${brief.label}.`);
        return { success: true };
    }
    /**
     * @effect Periodically reports a running status so long terminal agent runs keep their variant lease.
     */
    startLeaseHeartbeat(args) {
        const interval = setInterval(() => {
            void this.orchestrator
                .reportComplete({
                sessionId: args.sessionId,
                workItemId: args.workItem.id,
                leaseId: args.leaseId,
                attempt: args.workItem.attempt,
                status: 'running',
            })
                .catch((error) => {
                log.warn('Inline variant lease heartbeat failed', error);
            });
        }, this.leaseHeartbeatMs);
        return () => clearInterval(interval);
    }
}
exports.InlineVariantGenerationService = InlineVariantGenerationService;
//# sourceMappingURL=InlineVariantGenerationService.js.map