"use strict";
/**
 * BuyerSession — manages the buyer side of an agent-to-agent job.
 *
 * Handles: job creation, payment, chat, and session lifecycle.
 * The buyer agent can send messages, receive responses, and
 * optionally provide workspace access.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuyerSession = void 0;
const workspace_js_1 = require("./workspace.js");
class BuyerSession {
    agent;
    config;
    job = null;
    _active = false;
    _lastActivity = Date.now();
    _idleTimer = null;
    _messageHandler = null;
    _workspace = null;
    constructor(config) {
        this.agent = config.agent;
        this.config = config;
    }
    /** Create the job and pay for it. Returns the job object. */
    async start() {
        // Create job
        this.job = await this.agent.createJob({
            sellerVerusId: this.config.sellerVerusId,
            description: this.config.description,
            amount: this.config.amount,
            currency: this.config.currency || 'VRSCTEST',
            serviceId: this.config.serviceId,
        });
        console.log(`[BuyerSession] Job created: ${this.job.id}`);
        // Pay — dual output TX (agent + platform fee in one transaction).
        // SECURITY: job.payment.* comes from the (semi-trusted, MITM-able) platform.
        // Validate addresses and bound the fee so a doctored response cannot redirect
        // funds to a bogus address or inflate the fee to drain the buyer's wallet.
        const isVerusAddr = (s) => typeof s === 'string' && /^[Ri][1-9A-HJ-NP-Za-km-z]{25,40}$/.test(s);
        const payAddr = this.job.payment?.address;
        if (!payAddr) {
            throw new Error('No payment address on job — backend may not have resolved seller R-address');
        }
        if (!isVerusAddr(payAddr)) {
            throw new Error(`Refusing to pay job: malformed payment address ${payAddr}`);
        }
        const feeAddr = this.job.payment?.platformFeeAddress;
        const feeAmt = this.job.payment?.feeAmount;
        const outputs = [
            { address: payAddr, amount: this.config.amount },
        ];
        if (feeAddr != null || feeAmt != null) {
            // A platform fee larger than the job amount itself is implausible — reject
            // rather than sign it. (Normal fees are a small fraction of the amount.)
            if (!isVerusAddr(feeAddr) || !Number.isFinite(feeAmt) || feeAmt <= 0 || feeAmt > this.config.amount) {
                throw new Error(`Refusing to pay implausible/ malformed platform fee (amount=${feeAmt}, job=${this.config.amount}).`);
            }
            outputs.push({ address: feeAddr, amount: feeAmt });
        }
        const txid = await this.agent.sendMultiPayment(outputs);
        console.log(`[BuyerSession] Dual payment sent: ${txid}`);
        await this.agent.client.recordPaymentCombined(this.job.id, txid);
        console.log(`[BuyerSession] Payment + fee recorded`);
        // Wait for payment verification + job to go in_progress
        console.log(`[BuyerSession] Waiting for payment verification...`);
        const maxWaitMs = 15 * 60 * 1000; // 15 min max (6 block confirmations + watcher cycle)
        const startWait = Date.now();
        while (Date.now() - startWait < maxWaitMs) {
            const check = await this.agent.client.getJob(this.job.id);
            if (check.status === 'in_progress') {
                this.job = check;
                console.log(`[BuyerSession] Job in_progress — seller is online`);
                break;
            }
            if (check.status === 'cancelled' || check.status === 'failed') {
                throw new Error(`Job ${check.status}`);
            }
            await new Promise(r => setTimeout(r, 15000)); // check every 15s
        }
        if (this.job?.status !== 'in_progress') {
            throw new Error('Payment verification timeout — job did not start within 15 minutes');
        }
        // Connect chat
        await this.agent.connectChat();
        this.agent.joinJobChat(this.job.id);
        // Listen for seller messages
        this._messageHandler = (jobId, msg) => {
            if (jobId !== this.job?.id)
                return;
            if (msg.senderVerusId === this.agent.address)
                return; // skip own messages
            this._lastActivity = Date.now();
            this.config.onMessage?.(msg.content, { senderVerusId: msg.senderVerusId, jobId });
        };
        this.agent.onChatMessage(this._messageHandler);
        this._active = true;
        // Idle timer
        if (this.config.autoEndIdleSec && this.config.autoEndIdleSec > 0) {
            this._idleTimer = setInterval(() => {
                const idleSec = (Date.now() - this._lastActivity) / 1000;
                if (idleSec >= this.config.autoEndIdleSec) {
                    this.endSession('idle-timeout');
                }
            }, 10000);
        }
        return this.job;
    }
    /** Send a message to the seller agent */
    async send(message) {
        if (!this.job || !this._active)
            throw new Error('Session not active');
        this._lastActivity = Date.now();
        this.agent.sendChatMessage(this.job.id, message);
    }
    /** Send a message and wait for the seller's response */
    async sendAndWait(message, timeoutMs = 60000) {
        if (!this.job || !this._active)
            throw new Error('Session not active');
        return new Promise((resolve, reject) => {
            const originalHandler = this.config.onMessage;
            const timer = setTimeout(() => {
                this.config.onMessage = originalHandler;
                reject(new Error('Response timeout'));
            }, timeoutMs);
            this.config.onMessage = (content, meta) => {
                clearTimeout(timer);
                this.config.onMessage = originalHandler;
                originalHandler?.(content, meta);
                resolve(content);
            };
            this.send(message);
        });
    }
    /** Connect the buyer's project directory as a workspace for the seller agent */
    async connectWorkspace(projectDir, opts) {
        if (!this.job || !this._active)
            throw new Error('Session not active');
        this._workspace = new workspace_js_1.BuyerWorkspace({
            agent: this.agent,
            jobId: this.job.id,
            projectDir,
            permissions: opts?.permissions ?? { read: true, write: true },
            autoApproveWrites: opts?.autoApproveWrites ?? true,
            onWrite: opts?.onWrite,
            onStatusChanged: (status, data) => {
                if (status === 'agent_done') {
                    this._lastActivity = Date.now();
                }
                if (status === 'disconnected' || status === 'aborted' || status === 'completed') {
                    this._workspace = null;
                }
            },
            onMcpCall: opts?.onMcpCall,
            uid: opts?.uid,
        });
        await this._workspace.connect();
        console.log(`[BuyerSession] Workspace connected — serving ${projectDir}`);
        return this._workspace;
    }
    /** Get the current workspace (null if not connected) */
    get workspace() {
        return this._workspace;
    }
    /** End the session and request delivery */
    async endSession(reason = 'buyer-completed') {
        if (!this.job || !this._active)
            return;
        this._active = false;
        // Disconnect workspace first
        if (this._workspace) {
            this._workspace.disconnect();
            this._workspace = null;
        }
        if (this._idleTimer) {
            clearInterval(this._idleTimer);
            this._idleTimer = null;
        }
        try {
            await this.agent.client.requestEndSession(this.job.id, reason);
            console.log(`[BuyerSession] Session ended: ${reason}`);
        }
        catch (e) {
            console.warn(`[BuyerSession] End session failed: ${e.message}`);
        }
        this.config.onSessionEnd?.(reason);
    }
    /** Get the job object */
    getJob() {
        return this.job;
    }
    /** Is the session active? */
    get active() {
        return this._active;
    }
}
exports.BuyerSession = BuyerSession;
//# sourceMappingURL=session.js.map