import { BaseExecutionContext } from '../../execution/base.js';
import { LocalExecutionContext } from '../../execution/local.js';
import { RemoteExecutionContext } from './remote.js';
export class HybridExecutionContext extends BaseExecutionContext {
    id;
    environment = 'hybrid';
    localCtx;
    remoteCtxs = new Map();
    remoteTargets;
    routingStrategy;
    roundRobinIndex = 0;
    constructor(options) {
        super();
        this.id = options.id;
        this.remoteTargets = options.remotes;
        this.routingStrategy = options.routingStrategy ?? 'local-first';
        this.localCtx = new LocalExecutionContext({
            id: `${options.id}_local`,
            cwd: options.local.cwd,
            fsAccess: options.local.fsAccess,
            envVars: options.local.envVars,
            capabilities: options.local.capabilities,
            shell: options.local.shell,
        });
        this.localCtx.on((event) => this.emit(event));
        for (const [i, target] of options.remotes.entries()) {
            const remoteId = `${options.id}_remote_${i}`;
            const remote = new RemoteExecutionContext({
                id: remoteId,
                target,
            });
            remote.on((event) => this.emit(event));
            this.remoteCtxs.set(remoteId, remote);
        }
    }
    async doInitialize() {
        await this.localCtx.initialize();
        const initPromises = [];
        for (const remote of this.remoteCtxs.values()) {
            initPromises.push(remote.initialize());
        }
        await Promise.all(initPromises);
        this.log.info(`Hybrid context initialized: local(${this.localCtx.getCwd()}) + ${this.remoteCtxs.size} remote(s), strategy=${this.routingStrategy}`);
    }
    async doTeardown() {
        const teardownPromises = [];
        for (const remote of this.remoteCtxs.values()) {
            teardownPromises.push(remote.teardown());
        }
        await Promise.allSettled(teardownPromises);
        await this.localCtx.teardown();
    }
    getLocal() {
        return this.localCtx;
    }
    getRemote(remoteId) {
        return this.remoteCtxs.get(remoteId);
    }
    getRemotes() {
        return Array.from(this.remoteCtxs.values());
    }
    getRoutingStrategy() {
        return this.routingStrategy;
    }
    setRoutingStrategy(strategy) {
        this.routingStrategy = strategy;
        this.roundRobinIndex = 0;
    }
    async connectAllRemotes() {
        const promises = [];
        for (const remote of this.remoteCtxs.values()) {
            promises.push(remote.connect());
        }
        await Promise.all(promises);
    }
    async disconnectAllRemotes() {
        const promises = [];
        for (const remote of this.remoteCtxs.values()) {
            promises.push(remote.disconnect());
        }
        await Promise.allSettled(promises);
    }
    async executeCommand(command, args = [], options) {
        switch (this.routingStrategy) {
            case 'local-first':
                return this.localCtx.executeCommand(command, args, options);
            case 'remote-first': {
                const connectedRemote = this.getFirstConnectedRemote();
                if (connectedRemote) {
                    const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command;
                    return connectedRemote.executeRemote(fullCommand, options);
                }
                return this.localCtx.executeCommand(command, args, options);
            }
            case 'round-robin': {
                const targets = this.getRoundRobinTargets();
                if (targets.length === 0) {
                    return this.localCtx.executeCommand(command, args, options);
                }
                const targetIndex = this.roundRobinIndex % targets.length;
                this.roundRobinIndex++;
                const target = targets[targetIndex];
                if (target === undefined) {
                    return this.localCtx.executeCommand(command, args, options);
                }
                if (target === this.localCtx) {
                    return this.localCtx.executeCommand(command, args, options);
                }
                const fullCommand = args.length > 0 ? `${command} ${args.join(' ')}` : command;
                return target.executeRemote(fullCommand, options);
            }
            default: {
                const _exhaustive = this.routingStrategy;
                throw new Error(`Unhandled routing strategy: ${_exhaustive}`);
            }
        }
    }
    toConfig() {
        return {
            id: this.id,
            environment: 'hybrid',
            local: {
                cwd: this.localCtx.getCwd(),
                fsAccess: this.localCtx.hasFsAccess(),
                envVars: this.localCtx.getEnvVars(),
            },
            remotes: this.remoteTargets.map((t) => ({ ...t })),
            routingStrategy: this.routingStrategy,
        };
    }
    getFirstConnectedRemote() {
        for (const remote of this.remoteCtxs.values()) {
            if (remote.isConnected()) {
                return remote;
            }
        }
        return undefined;
    }
    getRoundRobinTargets() {
        const targets = [this.localCtx];
        for (const remote of this.remoteCtxs.values()) {
            if (remote.isConnected()) {
                targets.push(remote);
            }
        }
        return targets;
    }
}
//# sourceMappingURL=hybrid.js.map