"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.MeshClientService = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const logger_1 = require("../logger");
const mesh_1 = require("../mesh");
const mesh_client_redis_registry_1 = require("./mesh-client-redis-registry");
const mesh_client_registry_1 = require("./mesh-client-registry");
const types_1 = require("./types");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":2,\"typeName\":\"(nodeId: number, orphaned: RegisteredClient\\u003cTMeta\\u003e[]) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":3},{\"kind\":2,\"typeName\":\"(clientId: string) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":22,\"type\":{\"kind\":3}},{\"kind\":6},{\"kind\":2},{\"kind\":22,\"type\":{\"kind\":8}},{\"kind\":2,\"typeName\":\"K\"},{\"kind\":2,\"typeName\":\"(data: TBroadcasts[K], senderInstanceId: number) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":2,\"typeName\":\"TBroadcasts[K]\"},{\"kind\":18,\"typeName\":\"MeshBroadcastOptions\",\"types\":[{\"kind\":20,\"name\":\"skipSelf\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true}]},{\"kind\":7},{\"kind\":22,\"type\":{\"kind\":2}}]]", []);
let _MeshClientServiceMetadata_1;
// --- MeshClientService ---
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
class MeshClientService {
    // Exposed for MeshSrpcServer to access internals
    /** @internal */
    mesh;
    logger = (0, logger_1.createLogger)(this);
    registry;
    backend;
    clientInvokeFn;
    clientUpdateMetaFn;
    nodeCleanedUpCallbacks = [];
    clientSupersededCallbacks = [];
    constructor(options) {
        this.backend = options.registryBackend ?? new mesh_client_redis_registry_1.MeshClientRedisRegistry(`_mc:${options.key}`);
        this.clientInvokeFn = options.clientInvokeFn;
        this.clientUpdateMetaFn = options.clientUpdateMetaFn;
        this.mesh = new mesh_1.MeshService(`_mc:${options.key}`, options.meshOptions);
        this.mesh.registerHandler('forward', async (req) => {
            try {
                const result = await this.clientInvokeFn(req.clientId, req.type, req.data, req.timeoutMs);
                return { data: result };
            }
            catch (err) {
                return {
                    error: err instanceof Error ? err.message : String(err),
                    errorName: err instanceof Error ? err.name : undefined
                };
            }
        });
        this.mesh.registerHandler('kickClient', async (req) => {
            let kicked = false;
            for (const cb of this.clientSupersededCallbacks) {
                try {
                    await cb(req.clientId);
                    kicked = true;
                }
                catch (err) {
                    this.logger.warn('client superseded callback error', { err, clientId: req.clientId });
                }
            }
            return { kicked };
        });
        this.mesh.registerHandler('updateMeta', async (req) => {
            const metadata = req.metadata;
            if (this.clientUpdateMetaFn && !this.clientUpdateMetaFn(req.clientId, metadata))
                return { updated: false };
            return { updated: await this.registry.updateMetadata(req.clientId, metadata) };
        });
        this.mesh.setNodeCleanedUpCallback(async (nodeId) => {
            const orphaned = await this.backend.cleanupNode(nodeId);
            if (orphaned.length > 0) {
                for (const cb of this.nodeCleanedUpCallbacks) {
                    try {
                        await cb(nodeId, orphaned);
                    }
                    catch (err) {
                        this.logger.warn('node cleanup callback error', { err, nodeId });
                    }
                }
            }
        });
        // Placeholder registry - will be re-created in start() with the real instanceId
        this.registry = new mesh_client_registry_1.MeshClientRegistry(0, this.backend);
    }
    onNodeClientsOrphaned(cb) {
        this.nodeCleanedUpCallbacks.push(cb);
    }
    onClientSuperseded(cb) {
        this.clientSupersededCallbacks.push(cb);
    }
    get instanceId() {
        return this.mesh.instanceId;
    }
    get clientRegistry() {
        return this.registry;
    }
    running = false;
    async start() {
        await this.mesh.start();
        this.registry = new mesh_client_registry_1.MeshClientRegistry(this.mesh.instanceId, this.backend);
        this.running = true;
    }
    async stop() {
        this.running = false;
        try {
            // Clean up our own clients
            if (this.mesh.instanceId !== 0) {
                await this.registry.cleanupNode();
            }
        }
        finally {
            await this.mesh.stop();
        }
    }
    /**
     * Register a client on this node. Returns true if registered, false if
     * another node owns the client and `allowSupersede` is false (conflict).
     */
    async registerClient(clientId, metadata, allowSupersede = true) {
        if (!this.running)
            return true;
        const result = await this.registry.register(clientId, metadata, allowSupersede);
        if (result.status === 'conflict') {
            return false;
        }
        if (result.supersededNodeId !== null) {
            this.mesh.invoke(result.supersededNodeId, 'kickClient', { clientId }).catch(err => {
                this.logger.warn('failed to kick superseded client', {
                    err,
                    clientId,
                    supersededNodeId: result.supersededNodeId
                });
            });
        }
        return true;
    }
    /**
     * Reserve ownership of a clientId without exposing it for lookup/invoke
     * until activation completes.
     */
    async reserveClient(clientId, metadata, allowSupersede = true) {
        if (!this.running)
            return true;
        const result = await this.registry.reserve(clientId, metadata, allowSupersede);
        if (result.status === 'conflict') {
            return false;
        }
        if (result.supersededNodeId !== null) {
            this.mesh.invoke(result.supersededNodeId, 'kickClient', { clientId }).catch(err => {
                this.logger.warn('failed to kick superseded client', {
                    err,
                    clientId,
                    supersededNodeId: result.supersededNodeId
                });
            });
        }
        return true;
    }
    /**
     * Promote a same-node reservation to an active, discoverable client.
     */
    async activateClient(clientId, metadata) {
        if (!this.running)
            return false;
        return this.registry.activate(clientId, metadata);
    }
    async unregisterClient(clientId) {
        if (!this.running)
            return false;
        return this.registry.unregister(clientId);
    }
    async updateClientMetadata(clientId, metadata) {
        if (!this.running)
            return false;
        const client = await this.registry.getClient(clientId);
        if (!client)
            return false;
        // Local - apply to stream.meta and update registry directly.
        if (client.nodeId === this.mesh.instanceId) {
            if (this.clientUpdateMetaFn && !this.clientUpdateMetaFn(clientId, metadata))
                return false;
            return this.registry.updateMetadata(clientId, metadata);
        }
        // Remote - route through mesh to the owning node
        try {
            const response = await this.mesh.invoke(client.nodeId, 'updateMeta', { clientId, metadata });
            return response.updated;
        }
        catch (err) {
            this.logger.warn('cross-pod metadata update failed', {
                err,
                clientId,
                targetNodeId: client.nodeId
            });
            return false;
        }
    }
    registerBroadcastHandler(type, handler) {
        this.mesh.registerBroadcastHandler(type, handler);
    }
    async broadcast(type, data, options) {
        return this.mesh.broadcast(type, data, options);
    }
    async invoke(clientId, type, data, timeoutMs) {
        if (!this.running) {
            throw new types_1.ClientNotFoundError(clientId);
        }
        const client = await this.registry.getClient(clientId);
        if (!client) {
            throw new types_1.ClientNotFoundError(clientId);
        }
        // Local delivery
        if (client.nodeId === this.mesh.instanceId) {
            return this.clientInvokeFn(clientId, type, data, timeoutMs);
        }
        // Remote delivery via mesh
        const response = await this.mesh.invoke(client.nodeId, 'forward', { clientId, type, data, timeoutMs }, timeoutMs);
        if (response.error !== undefined) {
            if (response.errorName === 'ClientDisconnectedError') {
                throw new types_1.ClientDisconnectedError(clientId);
            }
            throw new types_1.ClientInvocationError(response.error);
        }
        return response.data;
    }
    static __tsfType = _MeshClientServiceMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"MeshClientService\",\"typeName\":\"MeshClientService\",\"classType\":{\"$tsf\":0},\"properties\":[{\"name\":\"mesh\",\"type\":{\"kind\":16,\"typeName\":\"MeshService\",\"classType\":{\"$tsfImport\":[1,\"../mesh\",\"MeshService\"]},\"typeArguments\":[{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"forward\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"request\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"type\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"data\",\"type\":{\"kind\":2},\"optional\":false},{\"kind\":20,\"name\":\"timeoutMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true}],\"typeName\":\"ForwardRequest\"},\"optional\":false},{\"kind\":20,\"name\":\"response\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"data\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"error\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"errorName\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true}],\"typeName\":\"ForwardResponse\"},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"kickClient\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"request\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false}],\"typeName\":\"KickClientRequest\"},\"optional\":false},{\"kind\":20,\"name\":\"response\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"kicked\",\"type\":{\"kind\":8},\"optional\":false}],\"typeName\":\"KickClientResponse\"},\"optional\":false}]},\"optional\":false},{\"kind\":20,\"name\":\"updateMeta\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"request\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"metadata\",\"type\":{\"kind\":2},\"optional\":false}],\"typeName\":\"UpdateMetaRequest\"},\"optional\":false},{\"kind\":20,\"name\":\"response\",\"type\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"updated\",\"type\":{\"kind\":8},\"optional\":false}],\"typeName\":\"UpdateMetaResponse\"},\"optional\":false}]},\"optional\":false}],\"typeName\":\"ForwardMessages\"},{\"kind\":16,\"typeName\":\"TBroadcasts\",\"classType\":{\"$tsf\":2}}]},\"optional\":false}],\"methods\":[{\"name\":\"onNodeClientsOrphaned\",\"parameters\":[{\"name\":\"cb\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"onClientSuperseded\",\"parameters\":[{\"name\":\"cb\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"start\",\"parameters\":[],\"returnType\":{\"$tsfType\":3}},{\"name\":\"stop\",\"parameters\":[],\"returnType\":{\"$tsfType\":3}},{\"name\":\"registerClient\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"metadata\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false,\"default\":false},{\"name\":\"allowSupersede\",\"type\":{\"$tsfType\":5},\"optional\":true,\"default\":true}],\"returnType\":{\"$tsfType\":6},\"description\":\"Register a client on this node. Returns true if registered, false if another node owns the client and `allowSupersede` is false (conflict).\"},{\"name\":\"reserveClient\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"metadata\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false,\"default\":false},{\"name\":\"allowSupersede\",\"type\":{\"$tsfType\":5},\"optional\":true,\"default\":true}],\"returnType\":{\"$tsfType\":6},\"description\":\"Reserve ownership of a clientId without exposing it for lookup/invoke until activation completes.\"},{\"name\":\"activateClient\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"metadata\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":6},\"description\":\"Promote a same-node reservation to an active, discoverable client.\"},{\"name\":\"unregisterClient\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":6}},{\"name\":\"updateClientMetadata\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"metadata\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":6}},{\"name\":\"registerBroadcastHandler\",\"parameters\":[{\"name\":\"type\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false},{\"name\":\"handler\",\"type\":{\"$tsfType\":8},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"broadcast\",\"parameters\":[{\"name\":\"type\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false},{\"name\":\"options\",\"type\":{\"$tsfType\":10},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"invoke\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"type\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":5},\"optional\":false,\"default\":false},{\"name\":\"timeoutMs\",\"type\":{\"$tsfType\":11},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":12}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"options\",\"type\":{\"kind\":16,\"typeName\":\"MeshClientServiceOptions\",\"classType\":{\"$tsf\":4},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":3}}]},\"optional\":false,\"default\":false}]}]", [() => MeshClientService, require, () => (typeof TBroadcasts !== "undefined" ? TBroadcasts : (typeof exports !== "undefined" ? exports.TBroadcasts : undefined)), () => (typeof TMeta !== "undefined" ? TMeta : (typeof exports !== "undefined" ? exports.TMeta : undefined)), () => (typeof MeshClientServiceOptions !== "undefined" ? MeshClientServiceOptions : (typeof exports !== "undefined" ? exports.MeshClientServiceOptions : undefined))], __tsf_metadata_type);
}
exports.MeshClientService = MeshClientService;
_MeshClientServiceMetadata_1.classType = MeshClientService;
MeshClientService.__tsfType = _MeshClientServiceMetadata_1;
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"MeshClientServiceOptions\":{\"kind\":18,\"typeName\":\"MeshClientServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"key\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"meshOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"MeshServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"heartbeatIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"nodeTtlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"requestTimeoutMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"leaderOptions\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"LeaderServiceOptions\",\"types\":[{\"kind\":20,\"name\":\"ttlMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"renewalIntervalMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"retryDelayMs\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"registryBackend\",\"type\":{\"kind\":12,\"types\":[{\"kind\":16,\"typeName\":\"MeshClientRegistryBackend\",\"classType\":{\"$tsfImport\":[0,\"./types\",\"MeshClientRegistryBackend\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":1}}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"clientInvokeFn\",\"type\":{\"kind\":2,\"typeName\":\"(clientId: string, type: string, data: unknown, timeoutMs?: number) =\\u003e Promise\\u003cunknown\\u003e\"},\"optional\":false},{\"kind\":20,\"name\":\"clientUpdateMetaFn\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"(clientId: string, metadata: TMeta) =\\u003e boolean\"},{\"kind\":4}]},\"optional\":true}]}}]", [require, () => (typeof TMeta !== "undefined" ? TMeta : (typeof exports !== "undefined" ? exports.TMeta : undefined))], __tsf_metadata_type);
//# sourceMappingURL=mesh-client-service.js.map