import { normalizeText } from './internal/utils.js';
import { createWorldEvolutionSelectorReadFacade, getRuntimeWorldEvolutionSelectorReadProvider, setRuntimeWorldEvolutionSelectorReadProvider, } from './internal/world-evolution-selector-read.js';
import { Realm } from './realm/client.js';
import { asNimiError, createNimiError } from './runtime/errors.js';
import { AccountCallerMode, AccountSessionState } from './runtime/generated/runtime/v1/account.js';
import { Runtime } from './runtime/runtime.js';
import { ReasonCode } from './types/index.js';
import { detectTauriTransport } from './platform-client-tauri.js';
import { createRuntimeFullAppRegistration } from './platform-client-runtime-registration.js';
import { createPlatformWorldGovernanceDomain, } from './platform-client-world-governance.js';
// Single shared client per JS execution context. Call clearPlatformClient()
// before switching tenants or auth sessions in the same process.
let currentPlatformClient = null;
import { createDisabledRuntime, createLocalFirstPartyAuthRouteError, DEFAULT_PLATFORM_APP_ID, discardExpiredRuntimeAccessToken, PLATFORM_RUNTIME_SESSION_APP_INSTANCE_SUFFIX, PLATFORM_RUNTIME_SESSION_DEVICE_ID, PLATFORM_RUNTIME_SESSION_REFRESH_SKEW_MS, PLATFORM_RUNTIME_SESSION_TTL_SECONDS, resolvePlatformRealmBaseUrl, resolveToken, timestampToMillis, } from './platform-client-auth-session.js';
function createDomains(runtime, realm, authMode, accountCaller) {
    const toListConnectorsInput = (input) => ({
        pageSize: Number(input?.pageSize || 0),
        pageToken: String(input?.pageToken || ''),
        kindFilter: Number(input?.kindFilter || 0),
        statusFilter: Number(input?.statusFilter || 0),
        providerFilter: String(input?.providerFilter || ''),
    });
    let localFirstPartyRefreshInflight = null;
    const refreshLocalFirstPartyRuntimeAccount = async () => {
        if (authMode !== 'local-first-party-runtime') {
            return false;
        }
        if (!localFirstPartyRefreshInflight) {
            localFirstPartyRefreshInflight = (async () => {
                const response = await runtime.account.refreshAccountSession({ caller: accountCaller });
                return Boolean(response.accepted);
            })();
        }
        try {
            return await localFirstPartyRefreshInflight;
        }
        finally {
            localFirstPartyRefreshInflight = null;
        }
    };
    const withLocalFirstPartyAuthRefresh = async (operation) => {
        try {
            return await operation();
        }
        catch (error) {
            const normalized = asNimiError(error, {
                reasonCode: ReasonCode.RUNTIME_CALL_FAILED,
                actionHint: 'retry_or_check_runtime_status',
                source: 'runtime',
            });
            if (normalized.reasonCode !== ReasonCode.AUTH_TOKEN_INVALID) {
                throw error;
            }
            if (!await refreshLocalFirstPartyRuntimeAccount()) {
                throw error;
            }
            return operation();
        }
    };
    return {
        auth: authMode === 'local-first-party-runtime' ? {
            checkEmail: async () => createLocalFirstPartyAuthRouteError('checkEmail'),
            passwordLogin: async () => createLocalFirstPartyAuthRouteError('passwordLogin'),
            oauthLogin: async () => createLocalFirstPartyAuthRouteError('oauthLogin'),
            requestEmailOtp: async () => createLocalFirstPartyAuthRouteError('requestEmailOtp'),
            verifyEmailOtp: async () => createLocalFirstPartyAuthRouteError('verifyEmailOtp'),
            verifyTwoFactor: async () => createLocalFirstPartyAuthRouteError('verifyTwoFactor'),
            walletChallenge: async () => createLocalFirstPartyAuthRouteError('walletChallenge'),
            walletLogin: async () => createLocalFirstPartyAuthRouteError('walletLogin'),
            updatePassword: (input) => realm.services.AuthService.updatePassword(input),
            getCurrentUser: async () => createLocalFirstPartyAuthRouteError('MeService.getMe'),
        } : {
            checkEmail: (input) => realm.services.AuthService.checkEmail(input),
            passwordLogin: (input) => realm.services.AuthService.passwordLogin({
                identifier: normalizeText(input.identifier || input.email),
                password: input.password,
            }),
            oauthLogin: (input) => realm.services.AuthService.oauthLogin(input),
            requestEmailOtp: (input) => realm.services.AuthService.requestEmailOtp(input),
            verifyEmailOtp: (input) => realm.services.AuthService.verifyEmailOtp(input),
            verifyTwoFactor: (input) => realm.services.AuthService.verifyTwoFactor(input),
            walletChallenge: (input) => realm.services.AuthService.walletChallenge(input),
            walletLogin: (input) => realm.services.AuthService.walletLogin(input),
            updatePassword: (input) => realm.services.AuthService.updatePassword(input),
            getCurrentUser: () => realm.services.MeService.getMe(),
        },
        me: authMode === 'local-first-party-runtime' ? {
            getCurrentUser: async () => createLocalFirstPartyAuthRouteError('MeService.getMe'),
            listMyRealmAgents: async () => createLocalFirstPartyAuthRouteError('MeService.listMyRealmAgents'),
            getMyRealmAgent: async () => createLocalFirstPartyAuthRouteError('MeService.getMyRealmAgent'),
        } : {
            getCurrentUser: () => realm.services.MeService.getMe(),
            listMyRealmAgents: () => realm.services.MeService.listMyRealmAgents(),
            getMyRealmAgent: (agentId) => realm.services.MeService.getMyRealmAgent(agentId),
        },
        social: {
            startChat: (input) => realm.services.HumanChatsService.startChat(input),
            listMessages: (chatId, limit) => realm.services.HumanChatsService.listMessages(chatId, limit),
            markChatRead: (chatId) => realm.services.HumanChatsService.markChatRead(chatId),
            sendMessage: (chatId, input) => realm.services.HumanChatsService.sendMessage(chatId, input),
            listFriends: (limit, cursor) => realm.services.MeService.listMyFriendsWithDetails(cursor, limit),
        },
        world: {
            getWorldTruth: (worldId) => realm.services.WorldsService.worldControllerGetWorld(worldId),
            getWorldviewTruth: (worldId) => realm.services.WorldsService.worldControllerGetWorldview(worldId),
            listWorlds: (status) => realm.services.WorldsService.worldControllerListWorlds(status),
            getWorld: (worldId) => realm.services.WorldsService.worldControllerGetWorld(worldId),
            getWorldview: (worldId) => realm.services.WorldsService.worldControllerGetWorldview(worldId),
            getWorldDetailWithAgents: (worldId, take = 4) => realm.services.WorldsService.worldControllerGetWorldDetailWithAgents(worldId, take),
            getWorldState: (worldId) => realm.services.WorldControlService.worldControlControllerGetState(worldId),
            commitWorldState: (worldId, input) => realm.services.WorldControlService.worldControlControllerCommitState(worldId, input),
            listWorldHistory: (worldId) => realm.services.WorldControlService.worldControlControllerListWorldHistory(worldId),
            appendWorldHistory: (worldId, input) => realm.services.WorldControlService.worldControlControllerAppendWorldHistory(worldId, input),
            listMyWorlds: () => realm.services.WorldControlService.worldControlControllerListMyWorlds(),
            listWorldLorebooks: (worldId) => realm.services.WorldsService.worldControllerGetWorldLorebooks(worldId),
            listWorldBindings: (worldId) => realm.services.WorldsService.worldControllerGetWorldBindings(worldId),
            getWorldHistory: (worldId) => realm.services.WorldsService.worldControllerGetWorldHistory(worldId),
            listWorldLevelAudits: (worldId, take) => realm.services.WorldsService.worldControllerGetWorldLevelAudits(worldId, take),
        },
        worldGovernance: createPlatformWorldGovernanceDomain(realm),
        creator: {
            listAgents: () => realm.services.CreatorService.creatorControllerListAgents(),
            getAgent: (agentId) => realm.services.CreatorService.creatorControllerGetAgent(agentId),
            createAgent: (input) => realm.services.CreatorService.creatorControllerCreateAgent(input),
            updateAgent: (agentId, input) => realm.services.CreatorService.creatorControllerUpdateAgent(agentId, input),
            batchCreateAgents: (input) => realm.services.CreatorService.creatorControllerBatchCreateAgents(input),
        },
        resources: {
            createImageDirectUpload: (input) => realm.services.ResourcesService.createImageDirectUpload(String(input?.requireSignedUrls ?? '')),
            createVideoDirectUpload: (input) => realm.services.ResourcesService.createVideoDirectUpload(String(input?.requireSignedUrls ?? '')),
            createAudioDirectUpload: (input = {}) => realm.services.ResourcesService.createAudioDirectUpload(input),
            finalizeResource: (resourceId, input = {}) => realm.services.ResourcesService.finalizeResource(resourceId, input),
            getResource: (resourceId) => realm.services.ResourcesService.getResource(resourceId),
            createPost: (input) => realm.services.PostsService.createPost(input),
        },
        runtimeAdmin: {
            listProviderCatalog: (input = {}, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.listProviderCatalog(input, options)),
            listConnectors: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.listConnectors(toListConnectorsInput(input), options)),
            createConnector: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.createConnector(input, options)),
            updateConnector: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.updateConnector(input, options)),
            deleteConnector: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.deleteConnector(input, options)),
            testConnector: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.testConnector(input, options)),
            listConnectorModels: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.listConnectorModels(input, options)),
            listModelCatalogProviders: (input = {}, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.listModelCatalogProviders(input, options)),
            listCatalogProviderModels: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.listCatalogProviderModels(input, options)),
            getCatalogModelDetail: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.getCatalogModelDetail(input, options)),
            upsertModelCatalogProvider: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.upsertModelCatalogProvider(input, options)),
            deleteModelCatalogProvider: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.deleteModelCatalogProvider(input, options)),
            upsertCatalogModelOverlay: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.upsertCatalogModelOverlay(input, options)),
            deleteCatalogModelOverlay: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.connector.deleteCatalogModelOverlay(input, options)),
            listAuditEvents: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.audit.listAuditEvents(input, options)),
            exportAuditEvents: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.audit.exportAuditEvents(input, options)),
            listUsageStats: (input, options) => withLocalFirstPartyAuthRefresh(() => runtime.audit.listUsageStats(input, options)),
            getRuntimeHealth: (input = {}, options) => withLocalFirstPartyAuthRefresh(() => runtime.audit.getRuntimeHealth(input, options)),
            listAIProviderHealth: (input = {}, options) => withLocalFirstPartyAuthRefresh(() => runtime.audit.listAIProviderHealth(input, options)),
            healthEvents: (input = {}, options) => runtime.healthEvents(input, options),
            providerHealthEvents: (input = {}, options) => runtime.providerHealthEvents(input, options),
        },
        publicContent: {
            getPublicPost: (postId) => realm.services.PostsService.getPublicPost(postId),
        },
    };
}
function createPlatformWorldEvolution(runtime) {
    return createWorldEvolutionSelectorReadFacade(() => getRuntimeWorldEvolutionSelectorReadProvider(runtime));
}
export async function createPlatformClient(input) {
    const appId = normalizeText(input.appId) || DEFAULT_PLATFORM_APP_ID;
    const authMode = input.authMode || 'web-cloud';
    const tokenValue = normalizeText(input.accessToken);
    const sessionStore = input.sessionStore ?? null;
    const explicitRuntimeAppSession = input.runtimeAppSession
        && normalizeText(input.runtimeAppSession.sessionId)
        && normalizeText(input.runtimeAppSession.sessionToken)
        ? {
            sessionId: normalizeText(input.runtimeAppSession.sessionId),
            sessionToken: normalizeText(input.runtimeAppSession.sessionToken),
        }
        : null;
    const realmBaseUrl = resolvePlatformRealmBaseUrl(input.realmBaseUrl);
    const isLocalFirstPartyRuntime = authMode === 'local-first-party-runtime';
    if (isLocalFirstPartyRuntime && (tokenValue
        || input.accessTokenProvider
        || input.refreshTokenProvider
        || input.subjectUserIdProvider
        || sessionStore
        || explicitRuntimeAppSession)) {
        throw createNimiError({
            message: 'local first-party Runtime mode rejects app-owned token, refresh, subject, and session store inputs',
            reasonCode: ReasonCode.SDK_AUTH_MODE_INVALID,
            actionHint: 'remove_app_owned_auth_inputs',
            source: 'sdk',
        });
    }
    const accountCaller = {
        appId,
        appInstanceId: `${appId}.local-first-party`,
        deviceId: 'local-first-party-device',
        mode: AccountCallerMode.LOCAL_FIRST_PARTY_APP,
        scopes: [],
    };
    const runtimeDefaults = isLocalFirstPartyRuntime
        ? {
            ...(input.runtimeDefaults || {}),
            appInstanceId: normalizeText(input.runtimeDefaults?.appInstanceId) || accountCaller.appInstanceId,
        }
        : input.runtimeDefaults;
    const runtimeAccountAccessTokenProvider = async () => {
        const response = await runtime.account.getAccessToken({
            caller: accountCaller,
            requestedScopes: [],
        });
        const accessToken = normalizeText(response.accessToken);
        if (!response.accepted || !accessToken) {
            throw createNimiError({
                message: `runtime account access token unavailable: ${String(response.accountReasonCode || response.reasonCode || 'unknown')}`,
                reasonCode: ReasonCode.PRINCIPAL_UNAUTHORIZED,
                actionHint: 'complete_runtime_account_login',
                source: 'runtime',
            });
        }
        return accessToken;
    };
    const runtimeAccountProjectionUserIdProvider = async () => {
        const response = await runtime.account.getAccountSessionStatus({ caller: accountCaller });
        if (response.state !== AccountSessionState.AUTHENTICATED || !response.accountProjection?.accountId) {
            return '';
        }
        return normalizeText(response.accountProjection.accountId);
    };
    const runtimeAccessTokenProvider = async () => isLocalFirstPartyRuntime
        ? runtimeAccountAccessTokenProvider()
        : discardExpiredRuntimeAccessToken(await resolveToken(tokenValue, input.accessTokenProvider, sessionStore?.getAccessToken));
    const runtimeSubjectUserIdProvider = async () => {
        if (isLocalFirstPartyRuntime) {
            return runtimeAccountProjectionUserIdProvider();
        }
        if (sessionStore?.getSubjectUserId) {
            const subjectUserId = normalizeText(await sessionStore.getSubjectUserId());
            if (subjectUserId) {
                return subjectUserId;
            }
        }
        if (input.subjectUserIdProvider) {
            const subjectUserId = normalizeText(await input.subjectUserIdProvider());
            if (subjectUserId) {
                return subjectUserId;
            }
        }
        return '';
    };
    let runtime;
    let runtimeSessionCache = null;
    let runtimeSessionInflight = null;
    const ensureRuntimeSessionRegistered = createRuntimeFullAppRegistration(() => runtime, {
        appId,
        appInstanceId: `${appId}${PLATFORM_RUNTIME_SESSION_APP_INSTANCE_SUFFIX}`,
        deviceId: PLATFORM_RUNTIME_SESSION_DEVICE_ID,
        rejectionLabel: 'platform runtime session registration rejected',
    });
    const runtimeAppSessionProvider = async () => {
        if (explicitRuntimeAppSession) {
            return explicitRuntimeAppSession;
        }
        const subjectUserId = normalizeText(await runtimeSubjectUserIdProvider());
        if (!subjectUserId) {
            runtimeSessionCache = null;
            return undefined;
        }
        const nowMs = Date.now();
        if (runtimeSessionCache
            && runtimeSessionCache.subjectUserId === subjectUserId
            && runtimeSessionCache.expiresAtMs - nowMs > PLATFORM_RUNTIME_SESSION_REFRESH_SKEW_MS) {
            return {
                sessionId: runtimeSessionCache.sessionId,
                sessionToken: runtimeSessionCache.sessionToken,
            };
        }
        if (runtimeSessionInflight) {
            return runtimeSessionInflight;
        }
        runtimeSessionInflight = (async () => {
            await ensureRuntimeSessionRegistered();
            const response = await runtime.auth.openSession({
                appId,
                appInstanceId: `${appId}${PLATFORM_RUNTIME_SESSION_APP_INSTANCE_SUFFIX}`,
                deviceId: PLATFORM_RUNTIME_SESSION_DEVICE_ID,
                subjectUserId,
                ttlSeconds: PLATFORM_RUNTIME_SESSION_TTL_SECONDS,
            });
            const sessionId = normalizeText(response.sessionId);
            const sessionToken = normalizeText(response.sessionToken);
            if (!sessionId || !sessionToken) {
                throw createNimiError({
                    message: `platform runtime session open failed: ${String(response.reasonCode || 'unknown')}`,
                    reasonCode: ReasonCode.RUNTIME_CALL_FAILED,
                    actionHint: 'open_runtime_app_session',
                    source: 'runtime',
                });
            }
            runtimeSessionCache = {
                subjectUserId,
                sessionId,
                sessionToken,
                expiresAtMs: timestampToMillis(response.expiresAt) || (Date.now() + PLATFORM_RUNTIME_SESSION_TTL_SECONDS * 1000),
            };
            return {
                sessionId,
                sessionToken,
            };
        })();
        try {
            return await runtimeSessionInflight;
        }
        finally {
            runtimeSessionInflight = null;
        }
    };
    runtime = input.runtimeTransport === null
        ? createDisabledRuntime(appId)
        : new Runtime({
            ...input.runtimeOptions,
            appId,
            transport: input.runtimeTransport || detectTauriTransport() || undefined,
            defaults: runtimeDefaults,
            auth: {
                accessToken: runtimeAccessTokenProvider,
                appSession: runtimeAppSessionProvider,
            },
            subjectContext: {
                getSubjectUserId: runtimeSubjectUserIdProvider,
            },
        });
    const ensureLocalFirstPartyAccountCallerRegistered = createRuntimeFullAppRegistration(() => runtime, {
        appId,
        appInstanceId: accountCaller.appInstanceId,
        deviceId: accountCaller.deviceId,
        rejectionLabel: 'local first-party Runtime account caller registration rejected',
    });
    if (isLocalFirstPartyRuntime) {
        await ensureLocalFirstPartyAccountCallerRegistered();
    }
    let realm;
    realm = new Realm({
        baseUrl: realmBaseUrl,
        auth: input.allowAnonymousRealm && !isLocalFirstPartyRuntime && !tokenValue && !input.accessTokenProvider && !sessionStore?.getAccessToken
            ? null
            : isLocalFirstPartyRuntime
                ? {
                    mode: 'runtime_account',
                    accessToken: runtimeAccountAccessTokenProvider,
                }
                : {
                    mode: 'external_principal',
                    accessToken: async () => resolveToken(tokenValue, input.accessTokenProvider, sessionStore?.getAccessToken),
                    refreshToken: async () => resolveToken('', input.refreshTokenProvider, sessionStore?.getRefreshToken),
                    onTokenRefreshed: async (result) => {
                        if (!sessionStore?.setAuthSession) {
                            return;
                        }
                        const user = sessionStore.getCurrentUser
                            ? await sessionStore.getCurrentUser()
                            : null;
                        await sessionStore.setAuthSession(user ?? null, result.accessToken, result.refreshToken);
                    },
                    onRefreshFailed: async () => {
                        realm.clearAuth();
                        await sessionStore?.clearAuthSession?.();
                    },
                },
        fetchImpl: input.realmFetchImpl,
    });
    const client = {
        runtime,
        realm,
        domains: createDomains(runtime, realm, authMode, accountCaller),
        worldEvolution: createPlatformWorldEvolution(runtime),
    };
    currentPlatformClient = client;
    return client;
}
export function createLocalFirstPartyRuntimePlatformClient(input) {
    return createPlatformClient({
        ...input,
        authMode: 'local-first-party-runtime',
    });
}
export function getPlatformClient() {
    if (!currentPlatformClient) {
        throw createNimiError({
            message: 'platform client is not ready; call createPlatformClient() first',
            reasonCode: ReasonCode.SDK_PLATFORM_CLIENT_NOT_READY,
            actionHint: 'call_create_platform_client_first',
            source: 'sdk',
        });
    }
    return currentPlatformClient;
}
export function clearPlatformClient() {
    currentPlatformClient = null;
}
// Bootstrap-only attachment seam for first-party hosts. This is intentionally
// unstable and does not publish selector-read methods or provider semantics on
// the Runtime surface itself.
export function unstable_attachPlatformWorldEvolutionSelectorReadProvider(client, provider) {
    setRuntimeWorldEvolutionSelectorReadProvider(client.runtime, provider);
}
//# sourceMappingURL=platform-client.js.map