import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
import * as __typia_transform__accessExpressionAsString from "typia/lib/internal/_accessExpressionAsString.js";
import { getDefaultResolver } from '../../config.js';
import Resolver from '../../lib/resolver.js';
import { createAssertEquals } from 'typia';
import { KeetaAnchorError } from '../../lib/error.js';
import { SignData } from '../../lib/utils/signing.js';
import { getNotificationRegisterTargetRequestSignable, getNotificationListTargetsRequestSignable, getNotificationDeleteTargetRequestSignable, getNotificationCreateSubscriptionRequestSignable, getNotificationListSubscriptionsRequestSignable, getNotificationDeleteSubscriptionRequestSignable, isKeetaNotificationAnchorRegisterTargetResponseJSON, isKeetaNotificationAnchorListTargetsResponseJSON, isKeetaNotificationAnchorDeleteTargetResponseJSON, isKeetaNotificationAnchorCreateSubscriptionResponseJSON, isKeetaNotificationAnchorListSubscriptionsResponseJSON, isKeetaNotificationAnchorDeleteSubscriptionResponseJSON, parseSubscriptionDetailsWithID, assertNotificationSubscriptionType } from './common.js';
import { addSignatureToURL } from '../../lib/http-server/common.js';
const KeetaNotificationAnchorClientAccessToken = Symbol('KeetaNotificationAnchorClientAccessToken');
const assertServiceMetadataEndpoint = (() => { const _io0 = (input, _exceptionable = true) => "string" === typeof input.url && (undefined === input.options || "object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) && _io1(input.options, true && _exceptionable)) && (1 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["url", "options"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _io1 = (input, _exceptionable = true) => (undefined === input.authentication || "object" === typeof input.authentication && null !== input.authentication && _io2(input.authentication, true && _exceptionable)) && (0 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["authentication"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _io2 = (input, _exceptionable = true) => "keeta-account" === input.method && ("optional" === input.type || "required" === input.type || "none" === input.type) && (2 === Object.keys(input).length || Object.keys(input).every(key => {
    if (["method", "type"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return false;
})); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.url || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".url",
    expected: "string",
    value: input.url
}, _errorFactory)) && (undefined === input.options || ("object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".options",
    expected: "(__type.o1 | undefined)",
    value: input.options
}, _errorFactory)) && _ao1(input.options, _path + ".options", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".options",
    expected: "(__type.o1 | undefined)",
    value: input.options
}, _errorFactory)) && (1 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["url", "options"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const _ao1 = (input, _path, _exceptionable = true) => (undefined === input.authentication || ("object" === typeof input.authentication && null !== input.authentication || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".authentication",
    expected: "(ServiceMetadataAuthenticationType | undefined)",
    value: input.authentication
}, _errorFactory)) && _ao2(input.authentication, _path + ".authentication", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".authentication",
    expected: "(ServiceMetadataAuthenticationType | undefined)",
    value: input.authentication
}, _errorFactory)) && (0 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["authentication"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const _ao2 = (input, _path, _exceptionable = true) => ("keeta-account" === input.method || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".method",
    expected: "\"keeta-account\"",
    value: input.method
}, _errorFactory)) && ("optional" === input.type || "required" === input.type || "none" === input.type || __typia_transform__assertGuard._assertGuard(_exceptionable, {
    method: "createAssertEquals",
    path: _path + ".type",
    expected: "(\"none\" | \"optional\" | \"required\")",
    value: input.type
}, _errorFactory)) && (2 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
    if (["method", "type"].some(prop => key === prop))
        return true;
    const value = input[key];
    if (undefined === value)
        return true;
    return __typia_transform__assertGuard._assertGuard(_exceptionable, {
        method: "createAssertEquals",
        path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
        expected: "undefined",
        value: value
    }, _errorFactory);
}))); const __is = (input, _exceptionable = true) => null !== input && undefined !== input && ("string" === typeof input || "object" === typeof input && null !== input && _io0(input, true)); let _errorFactory; return (input, errorFactory) => {
    if (false === __is(input)) {
        _errorFactory = errorFactory;
        ((input, _path, _exceptionable = true) => (null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && (undefined !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && ("string" === typeof input || ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
            method: "createAssertEquals",
            path: _path + "",
            expected: "(__type | string)",
            value: input
        }, _errorFactory)))(input, "$input", true);
    }
    return input;
}; })();
function validateURL(url) {
    if (!url) {
        throw (new Error('Invalid URL: empty value'));
    }
    return (new URL(url));
}
async function getEndpoints(resolver, criteria, shared, logger) {
    const response = await resolver.lookup('notification', criteria, shared);
    if (response === undefined) {
        return (null);
    }
    const serviceInfoPromises = Object.entries(response).map(async function ([id, serviceInfo]) {
        const operations = await serviceInfo.operations('object');
        const operationsFunctions = {};
        for (const [key, operation] of Object.entries(operations)) {
            if (operation === undefined) {
                continue;
            }
            Object.defineProperty(operationsFunctions, key, {
                get: async function () {
                    const endpoint = assertServiceMetadataEndpoint(await Resolver.Metadata.fullyResolveValuizable(operation));
                    let url;
                    let authentication = {
                        method: 'keeta-account',
                        type: 'none'
                    };
                    if (typeof endpoint === 'string') {
                        url = endpoint;
                    }
                    else {
                        url = endpoint.url;
                        if (endpoint.options?.authentication) {
                            authentication = endpoint.options.authentication;
                        }
                    }
                    return ({
                        url: function (params) {
                            let substitutedURL;
                            try {
                                substitutedURL = decodeURI(url);
                            }
                            catch (error) {
                                logger?.debug('NotificationAnchor:getEndpoints', 'Failed to decode URI, using original URL for substitution', error, url);
                                substitutedURL = url;
                            }
                            for (const [paramKey, paramValue] of Object.entries(params ?? {})) {
                                substitutedURL = substitutedURL.replace(`{${paramKey}}`, encodeURIComponent(paramValue));
                            }
                            return (validateURL(substitutedURL));
                        },
                        options: { authentication }
                    });
                },
                enumerable: true,
                configurable: true
            });
        }
        const supportedChannels = await (async () => {
            if (!serviceInfo.supportedChannels) {
                return (undefined);
            }
            const resolvedValue = await serviceInfo.supportedChannels('object');
            const allChannelTypes = ['FCM'];
            const retval = {};
            for (const type of allChannelTypes) {
                const typeCheck = await resolvedValue[type]?.('array');
                if (!typeCheck) {
                    continue;
                }
                if (!retval[type]) {
                    retval[type] = [];
                }
                for (const item of typeCheck) {
                    const resolvedItem = await item('object');
                    if (type === 'FCM') {
                        const [projectId, messagingSenderId, appId, apiKey, vapidKey, bundleId] = await Promise.all([
                            resolvedItem.projectId('string'),
                            resolvedItem.messagingSenderId('string'),
                            resolvedItem.appId('string'),
                            resolvedItem.apiKey('string'),
                            resolvedItem.vapidKey?.('string'),
                            resolvedItem.bundleId?.('string')
                        ]);
                        if (!projectId || !messagingSenderId || !appId || !apiKey) {
                            logger?.debug('NotificationAnchor:getEndpoints', 'Invalid supported channel configuration, missing required fields', { type, item: resolvedItem });
                            continue;
                        }
                        retval[type].push({
                            projectId,
                            messagingSenderId,
                            appId,
                            apiKey,
                            ...(vapidKey ? { vapidKey } : {}),
                            ...(bundleId ? { bundleId } : {})
                        });
                    }
                }
            }
            return (retval);
        })();
        const supportedSubscriptions = await (async () => {
            if (!serviceInfo.supportedSubscriptions) {
                return (undefined);
            }
            const resolvedValue = await serviceInfo.supportedSubscriptions('array');
            const allValues = await Promise.allSettled(resolvedValue.map(async function (item) {
                return (assertNotificationSubscriptionType(await item('string')));
            }));
            const retval = [];
            for (const result of allValues) {
                if (result.status === 'fulfilled') {
                    retval.push(result.value);
                }
            }
            return (retval);
        })();
        return ([
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            id,
            {
                operations: operationsFunctions,
                ...(supportedChannels ? { supportedChannels } : {}),
                ...(supportedSubscriptions ? { supportedSubscriptions } : {})
            }
        ]);
    });
    if (serviceInfoPromises.length === 0) {
        return (null);
    }
    const entries = await Promise.all(serviceInfoPromises);
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return Object.fromEntries(entries);
}
class KeetaNotificationAnchorBase {
    client;
    logger;
    account;
    constructor(config) {
        this.client = config.client;
        this.logger = config.logger;
        this.account = config.account;
    }
}
class KeetaNotificationAnchorProvider extends KeetaNotificationAnchorBase {
    serviceInfo;
    providerID;
    constructor(serviceInfo, providerID, parent) {
        const parentInternals = parent._internals(KeetaNotificationAnchorClientAccessToken);
        super(parentInternals);
        this.serviceInfo = serviceInfo;
        this.providerID = providerID;
    }
    async #getOperation(operationName) {
        const operationGetter = this.serviceInfo.operations[operationName];
        if (operationGetter === undefined) {
            throw (new Error(`Notification provider does not support ${operationName} operation`));
        }
        const endpoint = await operationGetter;
        if (endpoint === undefined) {
            throw (new Error(`Notification provider does not support ${operationName} operation`));
        }
        if (endpoint.options.authentication.method !== 'keeta-account') {
            throw (new Error(`Unsupported authentication method: ${endpoint.options.authentication.method}`));
        }
        return (endpoint);
    }
    async #parseResponseError(data) {
        try {
            return (await KeetaAnchorError.fromJSON(data));
        }
        catch (error) {
            this.logger?.debug('NotificationAnchor', 'Failed to parse error response', error, data);
            if (typeof data === 'object' && data !== null && 'error' in data && typeof data.error === 'string') {
                return (new Error(data.error));
            }
            return (new Error('Notification provider request failed'));
        }
    }
    #resolveAccount(account) {
        const accountToUse = account ?? this.account;
        if (!accountToUse) {
            throw (new Error('Account is required for this operation'));
        }
        return (accountToUse);
    }
    async registerTarget(input) {
        const endpoint = await this.#getOperation('registerTarget');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification registerTarget operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationRegisterTargetRequestSignable(input));
        const response = await fetch(endpoint.url(), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                account: accountToUse.publicKeyString.get(),
                channel: input.channel,
                signed
            })
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification registerTarget response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorRegisterTargetResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider registerTarget endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({ id: responseJSON.id });
    }
    async listTargets(input) {
        const endpoint = await this.#getOperation('listTargets');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification listTargets operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input?.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationListTargetsRequestSignable());
        const serviceURL = addSignatureToURL(endpoint.url(), { signedField: signed, account: accountToUse.assertAccount() });
        const response = await fetch(serviceURL, {
            method: 'GET',
            headers: { 'Accept': 'application/json' }
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification listTargets response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorListTargetsResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider listTargets endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({ targets: responseJSON.targets });
    }
    async deleteTarget(input) {
        const endpoint = await this.#getOperation('deleteTarget');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification deleteTarget operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationDeleteTargetRequestSignable(input));
        const response = await fetch(endpoint.url(), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                account: accountToUse.publicKeyString.get(),
                id: input.id,
                signed
            })
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification deleteTarget response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorDeleteTargetResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider deleteTarget endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({ ok: responseJSON.ok });
    }
    async createSubscription(input) {
        const endpoint = await this.#getOperation('createSubscription');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification createSubscription operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationCreateSubscriptionRequestSignable(input));
        const serializedSubscription = {
            ...input.subscription,
            ...(input.subscription.locale ? { locale: input.subscription.locale.toString() } : {}),
            ...(input.subscription.toAddress ? { toAddress: input.subscription.toAddress.publicKeyString.get() } : {})
        };
        const response = await fetch(endpoint.url(), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                account: accountToUse.publicKeyString.get(),
                subscription: serializedSubscription,
                signed
            })
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification createSubscription response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorCreateSubscriptionResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider createSubscription endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({ id: responseJSON.id });
    }
    async listSubscriptions(input) {
        const endpoint = await this.#getOperation('listSubscriptions');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification listSubscriptions operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input?.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationListSubscriptionsRequestSignable());
        const serviceURL = addSignatureToURL(endpoint.url(), { signedField: signed, account: accountToUse.assertAccount() });
        const response = await fetch(serviceURL, {
            method: 'GET',
            headers: { 'Accept': 'application/json' }
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification listSubscriptions response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorListSubscriptionsResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider listSubscriptions endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({
            subscriptions: responseJSON.subscriptions.map(function (row) {
                return (parseSubscriptionDetailsWithID(row));
            })
        });
    }
    async deleteSubscription(input) {
        const endpoint = await this.#getOperation('deleteSubscription');
        if (endpoint.options.authentication.type === 'none') {
            throw (new Error('Notification deleteSubscription operation must require account authentication'));
        }
        const accountToUse = this.#resolveAccount(input.account);
        const signed = await SignData(accountToUse.assertAccount(), getNotificationDeleteSubscriptionRequestSignable(input));
        const response = await fetch(endpoint.url(), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                account: accountToUse.publicKeyString.get(),
                id: input.id,
                signed
            })
        });
        let responseJSON;
        try {
            responseJSON = await response.json();
        }
        catch (error) {
            throw (new Error(`Failed to parse notification deleteSubscription response as JSON: ${error}`));
        }
        if (!isKeetaNotificationAnchorDeleteSubscriptionResponseJSON(responseJSON)) {
            throw (new Error('Invalid response from notification provider deleteSubscription endpoint'));
        }
        if (!responseJSON.ok) {
            throw (await this.#parseResponseError(responseJSON));
        }
        return ({ ok: responseJSON.ok });
    }
}
class KeetaNotificationAnchorClient extends KeetaNotificationAnchorBase {
    resolver;
    id;
    constructor(client, config = {}) {
        super({ client, logger: config.logger, account: config.account });
        this.resolver = config.resolver ?? getDefaultResolver(client, config);
        this.id = config.id ?? crypto.randomUUID();
    }
    async #lookup(criteria = {}, shared) {
        const endpoints = await getEndpoints(this.resolver, criteria, shared, this.logger);
        if (endpoints === null) {
            return (null);
        }
        const providers = Object.entries(endpoints).map(([providerID, serviceInfo]) => {
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            return (new KeetaNotificationAnchorProvider(serviceInfo, providerID, this));
        });
        if (providers.length === 0) {
            return (null);
        }
        return (providers);
    }
    async getProviders(criteria, shared) {
        return (await this.#lookup(criteria, shared));
    }
    async getProvider(providerID, shared) {
        const mergedSharedCriteria = {
            ...shared,
            providerIDs: shared?.providerIDs !== undefined ? Array.from(new Set([...shared.providerIDs, providerID])) : [providerID]
        };
        const providers = await this.#lookup({}, mergedSharedCriteria);
        return (providers?.[0] ?? null);
    }
    /** @internal */
    _internals(accessToken) {
        if (accessToken !== KeetaNotificationAnchorClientAccessToken) {
            throw (new Error('invalid access token'));
        }
        return ({
            client: this.client,
            logger: this.logger,
            account: this.account
        });
    }
}
export default KeetaNotificationAnchorClient;
export { KeetaNotificationAnchorProvider };
//# sourceMappingURL=client.js.map