var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
import SwaggerParser from "@apidevtools/swagger-parser";
import { DatabricksPlugin, GraphQLIntegrationPlugin, RegisteredPlugins, } from "@superblocksteam/shared";
import { getBucketeerUrlFromSuperblocksUrl } from "@superblocksteam/util";
import { isEmpty } from "lodash-es";
import yaml from "yaml";
import { getLogger } from "../../util/logger.js";
import llmobs from "../llmobs/index.js";
import { UNSUPPORTED_INTEGRATIONS } from "../transform/api-builder/to-sdk-transformer.js";
import { withRpcTimeout, DEFAULT_RPC_TIMEOUT_MS } from "../util/rpc-timeout.js";
import { getScopedToken } from "../util/scoped-token-utils.js";
import { LocalMetadataStorage } from "./metadata-storage/local.js";
/**
 * TTL (Time To Live) configuration for metadata cache.
 */
export const METADATA_TTL = {
    /** Default TTL: 10 minutes - balances freshness with load */
    DEFAULT: 10 * 60 * 1000,
    /** Minimum TTL: 1 minute - for testing or high-frequency updates */
    MIN: 1 * 60 * 1000,
    /** Maximum TTL: 1 hour - for very stable metadata */
    MAX: 60 * 60 * 1000,
};
export const DEFAULT_METADATA_CACHE_CONFIG = {
    maxCacheSizeMb: 300,
    maxFileSizeMb: 150,
};
let IntegrationStore = (() => {
    let _instanceExtraInitializers = [];
    let _getMetadata_decorators;
    let _getOpenApiSpec_decorators;
    return class IntegrationStore {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            _getMetadata_decorators = [llmobs.decorate({
                    kind: "task",
                    name: "IntegrationStore.getMetadata",
                })];
            _getOpenApiSpec_decorators = [llmobs.decorate({
                    kind: "task",
                    name: "IntegrationStore.getOpenApiSpec",
                })];
            __esDecorate(this, null, _getMetadata_decorators, { kind: "method", name: "getMetadata", static: false, private: false, access: { has: obj => "getMetadata" in obj, get: obj => obj.getMetadata }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _getOpenApiSpec_decorators, { kind: "method", name: "getOpenApiSpec", static: false, private: false, access: { has: obj => "getOpenApiSpec" in obj, get: obj => obj.getOpenApiSpec }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        openApiSpecs = (__runInitializers(this, _instanceExtraInitializers), {});
        pluginRegistry;
        integrations = [];
        metadataStorage;
        pendingMetadataFetches = new Map();
        _pluginExecutionVersions;
        superblocksBaseUrl;
        applicationId;
        getJwt;
        constructor(pluginRegistry = RegisteredPlugins("foo"), rootDir, maxCacheSizeMB, maxFileSizeMB, pluginExecutionVersions, superblocksBaseUrl, applicationId, getJwt) {
            this.pluginRegistry = pluginRegistry;
            this._pluginExecutionVersions = pluginExecutionVersions;
            maxCacheSizeMB ??= DEFAULT_METADATA_CACHE_CONFIG.maxCacheSizeMb;
            maxFileSizeMB ??= DEFAULT_METADATA_CACHE_CONFIG.maxFileSizeMb;
            getLogger().info(`[ai-service] Metadata cache limits: ` +
                `maxCacheSizeMB=${maxCacheSizeMB}, ` +
                `maxFileSizeMB=${maxFileSizeMB}`);
            this.metadataStorage = new LocalMetadataStorage({
                rootDir: rootDir || process.cwd(),
                maxTotalCacheMB: maxCacheSizeMB,
                maxSingleFileMB: maxFileSizeMB,
            });
            this.superblocksBaseUrl = superblocksBaseUrl;
            this.applicationId = applicationId;
            this.getJwt = getJwt;
        }
        /**
         * Get the plugin execution versions from the organization.
         * This can be used to determine which version of a plugin to use.
         */
        get pluginExecutionVersions() {
            return this._pluginExecutionVersions;
        }
        /**
         * Get the execution version for a specific plugin.
         * @param pluginId The plugin ID to get the version for
         * @returns The version string, or undefined if not available
         */
        getPluginExecutionVersion(pluginId) {
            return this._pluginExecutionVersions?.[pluginId];
        }
        async initialize() {
            await this.metadataStorage.initialize();
        }
        async removeIntegrationCache() {
            await this.metadataStorage.cleanup();
        }
        async availableIntegrations() {
            // Filter out integrations that are not yet supported by Clark's API generation
            const integrationHeaders = this.integrations.filter((i) => !UNSUPPORTED_INTEGRATIONS.includes(i.plugin.id));
            getLogger().debug(`[integration-store] availableIntegrations() returning ${integrationHeaders.length} integrations: ${integrationHeaders.map((i) => `${i.name}(${i.id})`).join(", ")}`);
            return integrationHeaders;
        }
        getPlugin(pluginId) {
            return this.pluginRegistry.get(pluginId);
        }
        async fetchIntegrations(editorClient, mentionedIntegrations) {
            getLogger().info(`[integration-store] Fetching integrations from browser...`);
            const integrations = (await withRpcTimeout(editorClient?.call.aiGetIntegrations(), DEFAULT_RPC_TIMEOUT_MS, "aiGetIntegrations")) ?? [];
            getLogger().info(`[integration-store] Registering ${integrations.length} integrations`);
            for (const integration of integrations) {
                const pluginId = integration.type;
                const plugin = this.pluginRegistry.get(pluginId);
                if (!plugin) {
                    // this should never happen
                    getLogger().error(`No plugin found for integration type ${integration.type} - skipping registration`);
                    continue;
                }
                const pluginHeader = {
                    id: plugin?.id,
                    type: plugin.type,
                    openApiSpecRef: plugin.openApiSpecRef,
                    isBuiltIn: this.pluginRegistry
                        .defaultPlugins()
                        .map((p) => p.id)
                        .includes(pluginId),
                };
                const header = {
                    id: integration.id,
                    name: integration.name,
                    plugin: pluginHeader,
                    displayName: integration?.displayName,
                    type: integration.type,
                };
                const existingIndex = this.integrations.findIndex((i) => i.id === header.id);
                if (existingIndex !== -1) {
                    this.integrations[existingIndex] = header;
                }
                else {
                    this.integrations.push(header);
                }
                if (mentionedIntegrations.some((i) => i.id === integration.id)) {
                    void this.getMetadata(integration.id, editorClient).catch((e) => {
                        getLogger().error(`Error prefetching metadata for integration ${integration.id}: ${e}`);
                    });
                }
            }
        }
        getHeader(id) {
            return this.integrations.find((i) => i.id === id);
        }
        async getMetadata(id, editorClient, options) {
            const logger = getLogger();
            // Force refresh bypasses everything
            if (options?.forceRefresh) {
                logger.info(`[integration-store] Force refresh requested for ${id}, invalidating cache`);
                await this.metadataStorage.invalidate(id);
                // Check for in-flight fetch to avoid duplicate requests
                const pending = this.pendingMetadataFetches.get(id);
                if (pending) {
                    logger.debug(`[integration-store] Waiting for in-flight fetch of ${id}`);
                    return pending;
                }
                // Start new fetch
                const fetchPromise = this._fetchMetadata(id, editorClient);
                this.pendingMetadataFetches.set(id, fetchPromise);
                try {
                    return await fetchPromise;
                }
                finally {
                    this.pendingMetadataFetches.delete(id);
                }
            }
            // Check for in-flight fetch to avoid duplicate requests
            const pending = this.pendingMetadataFetches.get(id);
            if (pending) {
                logger.debug(`[integration-store] Waiting for in-flight fetch of ${id}`);
                return pending;
            }
            //  Check cache age and refresh if stale (synchronous/blocking)
            const cachedPath = await this.metadataStorage.loadMetadata(id);
            if (cachedPath) {
                const age = await this.metadataStorage.getAge(id);
                const ttl = options?.ttl ?? METADATA_TTL.DEFAULT;
                if (age !== null && age < ttl) {
                    // Cache is fresh, use it
                    logger.debug(`[integration-store] Using fresh cache for ${id} (age: ${Math.round(age / 1000)}s, TTL: ${Math.round(ttl / 1000)}s)`);
                    return { _gronPath: cachedPath };
                }
                // Cache is stale, fetch fresh data synchronously (blocking)
                if (age !== null) {
                    logger.info(`[integration-store] Cache for ${id} is stale (age: ${Math.round(age / 1000)}s, TTL: ${Math.round(ttl / 1000)}s), refreshing...`);
                }
            }
            // Start new fetch and track it
            const fetchPromise = this._fetchMetadata(id, editorClient, cachedPath);
            this.pendingMetadataFetches.set(id, fetchPromise);
            try {
                const result = await fetchPromise;
                return result;
            }
            finally {
                // Clean up tracking
                this.pendingMetadataFetches.delete(id);
            }
        }
        async _fetchMetadata(id, editorClient, staleCache) {
            const header = this.getHeader(id);
            if (!header) {
                getLogger().warn(`[integration-store] No integration found for id ${id}`);
                return undefined;
            }
            if (!editorClient) {
                getLogger().warn(`[integration-store] No editor client provided, cannot fetch metadata for integration ${id}`);
                // If we have stale cache and no editor client, return stale cache
                if (staleCache) {
                    getLogger().info(`[integration-store] Returning stale cache for ${id} (no editor client)`);
                    return { _gronPath: staleCache };
                }
                return undefined;
            }
            try {
                // Check if we have it cached in filesystem
                // (only check if no staleCache was passed - if staleCache exists, we're refreshing)
                if (!staleCache) {
                    const cachedPath = await this.metadataStorage.loadMetadata(id);
                    if (cachedPath) {
                        // Cache hit! Return metadata with special gron path marker
                        getLogger().debug(`[integration-store] Using filesystem cache for ${id}`);
                        return { _gronPath: cachedPath };
                    }
                }
                // Cache miss or refresh - fetch from server
                getLogger().info(`[integration-store] Fetching metadata for ${id}`);
                if (header.plugin.openApiSpecRef) {
                    // OpenAPI integration - get the spec
                    const spec = await this.getOpenApiSpec(header.id, editorClient);
                    const metadata = { openApiSpec: spec };
                    // Write cache and wait for it to complete
                    try {
                        await this.metadataStorage.saveMetadata(id, metadata);
                        // Return with gron path after successful cache write
                        const gronPath = await this.metadataStorage.loadMetadata(id);
                        if (gronPath) {
                            return { _gronPath: gronPath };
                        }
                    }
                    catch (err) {
                        getLogger().error(`[integration-store] Failed to cache metadata for ${id}: ${err.message}`);
                        // Continue anyway - return the metadata even if caching fails
                    }
                    return metadata;
                }
                // Regular database integration - get metadata
                const integration = await editorClient?.call.aiGetIntegration({
                    integrationId: id,
                });
                // Check if the integration has an openApiSpecRef at the top level
                // This is returned by the UI when a REST API integration has a custom OpenAPI spec
                if (integration.openApiSpecRef) {
                    const spec = await this.getOpenApiSpec(header.id, editorClient, integration.openApiSpecRef);
                    const metadata = {
                        openApiSpec: spec,
                        openApiSpecRef: integration.openApiSpecRef,
                    };
                    // Write cache and wait for it to complete
                    try {
                        await this.metadataStorage.saveMetadata(id, metadata);
                        // Return with gron path after successful cache write
                        const gronPath = await this.metadataStorage.loadMetadata(id);
                        if (gronPath) {
                            return { _gronPath: gronPath };
                        }
                    }
                    catch (err) {
                        getLogger().error(`[integration-store] Failed to cache metadata for ${id}: ${err.message}`);
                        // Continue anyway - return the metadata even if caching fails
                    }
                    return metadata;
                }
                if (Object.keys(integration.metadata).length === 0) {
                    getLogger().warn(`[integration-store] No metadata found for integration ${id}`);
                    return undefined;
                }
                // Write cache and wait for it to complete
                try {
                    await this.metadataStorage.saveMetadata(id, integration.metadata);
                    getLogger().info(`[integration-store] Successfully cached metadata for ${id}`);
                    // Return with gron path after successful cache write
                    const gronPath = await this.metadataStorage.loadMetadata(id);
                    if (gronPath) {
                        return { _gronPath: gronPath };
                    }
                }
                catch (err) {
                    getLogger().error(`[integration-store] Failed to cache metadata for ${id}: ${err.message}`);
                    // Continue anyway - return the metadata even if caching fails
                }
                return integration.metadata;
            }
            catch (fetchError) {
                // Fetch failed - fall back to stale cache if available
                if (staleCache) {
                    getLogger().warn(`[integration-store] Failed to fetch fresh metadata for ${id}: ${fetchError.message}. Falling back to stale cache.`);
                    return { _gronPath: staleCache };
                }
                // No stale cache available - propagate error
                getLogger().error(`[integration-store] Failed to fetch metadata for ${id} and no stale cache available: ${fetchError.message}`);
                throw fetchError;
            }
        }
        async getIntegration(id, editorClient, options) {
            const header = this.getHeader(id);
            if (!header) {
                return undefined;
            }
            const metadata = await this.getMetadata(id, editorClient, options);
            return {
                ...header,
                metadata: metadata || {},
            };
        }
        async getOpenApiSpec(integrationId, editorClient, specRef) {
            if (this.openApiSpecs[integrationId]) {
                return this.openApiSpecs[integrationId];
            }
            const header = this.getHeader(integrationId);
            if (!header) {
                throw new Error(`No integration found for id ${integrationId}`);
            }
            // Use the provided specRef, or fall back to the plugin header's openApiSpecRef
            const finalSpecRef = specRef || header.plugin.openApiSpecRef;
            if (!finalSpecRef) {
                throw new Error(`Integration ${integrationId} is not an OpenAPI integration`);
            }
            if (this.superblocksBaseUrl && this.applicationId && this.getJwt) {
                try {
                    const result = await this.fetchOpenApiSpecDirectly(integrationId, finalSpecRef);
                    if (result) {
                        this.openApiSpecs[header.id] = result;
                        return result;
                    }
                }
                catch (err) {
                    getLogger().warn(`[integration-store] Direct fetch failed for ${integrationId}, falling back to WebSocket: ${err.message}`);
                    // Fall through to WebSocket fallback
                }
            }
            // Fallback: fetch via editor client WebSocket
            if (!editorClient) {
                throw new Error("No peer found and direct fetch not configured");
            }
            getLogger().warn(`[integration-store] Fetching OpenAPI spec via WebSocket for ${integrationId}`);
            const result = await editorClient.call.aiGetOpenApiSpec({
                integrationId: header.id,
                ref: finalSpecRef,
            });
            if (isEmpty(result)) {
                getLogger().warn("[integration-store] Empty OpenAPI spec returned from editor client", `integrationId: ${integrationId}`, `specRef: ${finalSpecRef}`);
                return undefined;
            }
            this.openApiSpecs[header.id] = result;
            return result;
        }
        // Fetch OpenAPI spec directly from the source (bucketeer or remote URL).
        async fetchOpenApiSpecDirectly(integrationId, specRef) {
            const logger = getLogger();
            const isRemoteSpec = specRef.startsWith("http://") || specRef.startsWith("https://");
            let url;
            const headers = {};
            if (isRemoteSpec) {
                // No auth needed for public specs
                url = specRef;
            }
            else {
                // User-uploaded spec in bucketeer - needs auth
                if (!this.superblocksBaseUrl || !this.applicationId || !this.getJwt) {
                    throw new Error("Cannot fetch user-uploaded OpenAPI spec: missing configuration");
                }
                const jwt = this.getJwt();
                if (!jwt) {
                    throw new Error("Cannot fetch user-uploaded OpenAPI spec: no JWT available");
                }
                const bucketeerUrl = getBucketeerUrlFromSuperblocksUrl(this.superblocksBaseUrl);
                url = `${bucketeerUrl}/integration/${specRef}`;
                const scopedToken = await getScopedToken({
                    applicationId: this.applicationId,
                    superblocksBaseUrl: this.superblocksBaseUrl,
                    jwt,
                });
                headers.Authorization = `Bearer ${scopedToken}`;
            }
            logger.info(`[integration-store] Fetching OpenAPI spec ${isRemoteSpec ? "remote" : "user-uploaded"} from ${isRemoteSpec ? "remote" : "bucketeer"}: ${url}`);
            const response = await fetch(url, { headers });
            if (!response.ok) {
                throw new Error(`Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`);
            }
            const contentType = response.headers.get("content-type") ?? "";
            let resultString = await response.text();
            if (!isRemoteSpec) {
                try {
                    const fileObj = JSON.parse(resultString);
                    if (fileObj?.file) {
                        resultString = fileObj.file;
                    }
                }
                catch {
                    // Not a JSON wrapper, use as-is
                }
            }
            let specObject;
            const isYaml = specRef.endsWith(".yaml") ||
                specRef.endsWith(".yml") ||
                contentType.includes("yaml");
            const isJson = specRef.endsWith(".json") || contentType.includes("json");
            if (isYaml) {
                specObject = yaml.parse(resultString);
            }
            else if (isJson) {
                specObject = JSON.parse(resultString);
            }
            else {
                throw new Error(`Unsupported OpenAPI spec format for ${integrationId}: expected .yaml, .yml, or .json file`);
            }
            const parser = new SwaggerParser();
            const dereferencedSpec = (await parser.dereference(specObject, {
                dereference: { circular: "ignore" },
            }));
            logger.info(`[integration-store] Successfully fetched and dereferenced OpenAPI spec for ${integrationId}`);
            return dereferencedSpec;
        }
        /**
         * Get the entity tag for the integration
         * eg @SuperblocksGithub[id=2ce0985a-342b-445d-b897-d17d80a26b8b]
         */
        getEntityTag(id) {
            const header = this.getHeader(id);
            if (!header) {
                return undefined;
            }
            return `@${header.name}[id=${id}]`;
        }
    };
})();
export { IntegrationStore };
export function isOpenApiIntegration(header) {
    return header.plugin.openApiSpecRef !== undefined;
}
export function isDatabricksIntegration(header) {
    return header.plugin.id === DatabricksPlugin.id;
}
export function isGraphQlIntegration(header) {
    return header.plugin.id === GraphQLIntegrationPlugin.id;
}
//# sourceMappingURL=store.js.map