import { createFlagsResponseFromFlagsAndPayloads, flagDetailsToResults, getEnabledFromValue, getFeatureFlagValue, getFlagValuesFromFlags, getPayloadsFromFlags, getVariantFromValue, normalizeFlagsResponse, parsePayload, updateFlagValue } from "./featureFlagUtils.mjs";
import { Compression, FeatureFlagError, PostHogPersistedProperty } from "./types.mjs";
import { PostHogCoreStateless, QuotaLimitedFeature, maybeAdd } from "./posthog-core-stateless.mjs";
import { uuidv7 } from "./vendor/uuidv7.mjs";
import { getEventUuid, getPersonPropertiesHash, isArray, isEmptyObject, isNullish, isObject, isString } from "./utils/index.mjs";
class PostHogCore extends PostHogCoreStateless {
    constructor(apiKey, options){
        const disableGeoipOption = options?.disableGeoip ?? false;
        const featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 10000;
        super(apiKey, {
            ...options,
            disableGeoip: disableGeoipOption,
            featureFlagsRequestTimeoutMs
        }), this.flagCallReported = {}, this._sessionMaxLengthSeconds = 86400, this.sessionProps = {}, this._cachedPersonProperties = null;
        this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true;
        this.disableRemoteFeatureFlags = options?.disableRemoteFeatureFlags ?? false;
        this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800;
        this._personProfiles = options?.personProfiles ?? 'identified_only';
        this._beforeSend = options?.before_send;
    }
    setupBootstrap(options) {
        const bootstrap = options?.bootstrap;
        if (!bootstrap) return;
        if (bootstrap.distinctId) if (bootstrap.isIdentifiedId) {
            const distinctId = this.getPersistedProperty(PostHogPersistedProperty.DistinctId);
            if (!distinctId) {
                this.setPersistedProperty(PostHogPersistedProperty.DistinctId, bootstrap.distinctId);
                this.setPersistedProperty(PostHogPersistedProperty.PersonMode, 'identified');
            }
        } else {
            const anonymousId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId);
            if (!anonymousId) this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, bootstrap.distinctId);
        }
        const bootstrapFeatureFlags = bootstrap.featureFlags;
        const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {};
        if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
            const normalizedBootstrapFeatureFlagDetails = createFlagsResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
            if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
                this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails);
                const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || {
                    flags: {},
                    requestId: void 0
                };
                const newFeatureFlagDetails = {
                    flags: {
                        ...normalizedBootstrapFeatureFlagDetails.flags,
                        ...currentFeatureFlagDetails.flags
                    },
                    requestId: normalizedBootstrapFeatureFlagDetails.requestId
                };
                this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
            }
        }
    }
    clearProps() {
        this.props = void 0;
        this.sessionProps = {};
        this.flagCallReported = {};
    }
    on(event, cb) {
        return this._events.on(event, cb);
    }
    reset(propertiesToKeep) {
        this.wrap(()=>{
            const allPropertiesToKeep = [
                PostHogPersistedProperty.Queue,
                PostHogPersistedProperty.LogsQueue,
                ...propertiesToKeep || []
            ];
            this.clearProps();
            this._cachedPersonProperties = null;
            for (const key of Object.keys(PostHogPersistedProperty))if (!allPropertiesToKeep.includes(PostHogPersistedProperty[key])) this.setPersistedProperty(PostHogPersistedProperty[key], null);
            if (this.disableRemoteFeatureFlags && !this.disabled) {
                if (!allPropertiesToKeep.includes(PostHogPersistedProperty.FeatureFlagDetails)) this.setKnownFeatureFlagDetails({
                    flags: {}
                });
            } else this.reloadFeatureFlags();
        });
    }
    async _shutdown(shutdownTimeoutMs = 30000) {
        try {
            return await super._shutdown(shutdownTimeoutMs);
        } finally{
            this.flagCallReported = {};
        }
    }
    getCommonEventProperties() {
        const featureFlags = this.getFeatureFlags();
        const featureVariantProperties = {};
        if (featureFlags) for (const [feature, variant] of Object.entries(featureFlags))featureVariantProperties[`$feature/${feature}`] = variant;
        return {
            ...maybeAdd('$active_feature_flags', featureFlags ? Object.keys(featureFlags) : void 0),
            ...featureVariantProperties,
            ...super.getCommonEventProperties()
        };
    }
    enrichProperties(properties) {
        return {
            ...this.props,
            ...this.sessionProps,
            ...properties || {},
            ...this.getCommonEventProperties(),
            $session_id: this.getSessionId()
        };
    }
    getSessionId() {
        if (!this._isInitialized) return '';
        let sessionId = this.getPersistedProperty(PostHogPersistedProperty.SessionId);
        const sessionLastTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
        const sessionStartTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp) || 0;
        const now = Date.now();
        const sessionLastDif = now - sessionLastTimestamp;
        const sessionStartDif = now - sessionStartTimestamp;
        if (!sessionId || sessionLastDif > 1000 * this._sessionExpirationTimeSeconds || sessionStartDif > 1000 * this._sessionMaxLengthSeconds) {
            sessionId = uuidv7();
            this.setPersistedProperty(PostHogPersistedProperty.SessionId, sessionId);
            this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, now);
        }
        this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, now);
        return sessionId;
    }
    resetSessionId() {
        this.wrap(()=>{
            this.setPersistedProperty(PostHogPersistedProperty.SessionId, null);
            this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, null);
            this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, null);
        });
    }
    getAnonymousId() {
        if (!this._isInitialized) return '';
        let anonId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId);
        if (!anonId) {
            anonId = uuidv7();
            this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, anonId);
        }
        return anonId;
    }
    getDistinctId() {
        if (!this._isInitialized) return '';
        return this.getPersistedProperty(PostHogPersistedProperty.DistinctId) || this.getAnonymousId();
    }
    registerForSession(properties) {
        this.sessionProps = {
            ...this.sessionProps,
            ...properties
        };
    }
    unregisterForSession(property) {
        delete this.sessionProps[property];
    }
    identify(distinctId, properties, options) {
        this.wrap(()=>{
            if (!this._requirePersonProcessing('posthog.identify')) return;
            const previousDistinctId = this.getDistinctId();
            distinctId = distinctId || previousDistinctId;
            if (properties?.$groups) this.groups(properties.$groups);
            const userPropsOnce = properties?.$set_once;
            delete properties?.$set_once;
            const userProps = properties?.$set || properties;
            const allProperties = this.enrichProperties({
                $anon_distinct_id: this.getAnonymousId(),
                ...maybeAdd('$set', userProps),
                ...maybeAdd('$set_once', userPropsOnce)
            });
            const userPropsObj = isObject(userProps) ? userProps : void 0;
            const userPropsOnceObj = isObject(userPropsOnce) ? userPropsOnce : void 0;
            if (distinctId !== previousDistinctId) {
                this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, previousDistinctId);
                this.setPersistedProperty(PostHogPersistedProperty.DistinctId, distinctId);
                this.setPersistedProperty(PostHogPersistedProperty.PersonMode, 'identified');
                this.reloadFeatureFlags();
                super.identifyStateless(distinctId, allProperties, options);
                this._cachedPersonProperties = getPersonPropertiesHash(distinctId, userPropsObj, userPropsOnceObj);
            } else if (userPropsObj || userPropsOnceObj) this.setPersonProperties(userPropsObj, userPropsOnceObj);
        });
    }
    capture(event, properties, options) {
        if ('$exception' === event && !options?._originatedFromCaptureException) this._logger.warn("Using `posthog.capture('$exception')` is unreliable because it does not attach required metadata. Use `posthog.captureException(error)` instead, which attaches required metadata automatically.");
        this.wrap(()=>{
            const distinctId = this.getDistinctId();
            if (properties?.$groups) this.groups(properties.$groups);
            const allProperties = this.enrichProperties(properties);
            const hasPersonProcessing = this._hasPersonProcessing();
            allProperties['$process_person_profile'] = hasPersonProcessing;
            allProperties['$is_identified'] = this._isIdentified();
            if (hasPersonProcessing) this._requirePersonProcessing('capture');
            super.captureStateless(distinctId, event, allProperties, options);
        });
    }
    alias(alias) {
        this.wrap(()=>{
            if (!this._requirePersonProcessing('posthog.alias')) return;
            const distinctId = this.getDistinctId();
            const allProperties = this.enrichProperties({});
            super.aliasStateless(alias, distinctId, allProperties);
        });
    }
    autocapture(eventType, elements, properties = {}, options) {
        this.wrap(()=>{
            const distinctId = this.getDistinctId();
            const payload = {
                distinct_id: distinctId,
                event: '$autocapture',
                properties: {
                    ...this.enrichProperties(properties),
                    $event_type: eventType,
                    $elements: elements
                }
            };
            this.enqueue('autocapture', payload, options);
        });
    }
    groups(groups) {
        this.wrap(()=>{
            if (!this._requirePersonProcessing('posthog.group')) return;
            const existingGroups = this.props.$groups || {};
            this.register({
                $groups: {
                    ...existingGroups,
                    ...groups
                }
            });
            if (Object.keys(groups).find((type)=>existingGroups[type] !== groups[type])) this.reloadFeatureFlags();
        });
    }
    group(groupType, groupKey, groupProperties, options) {
        this.wrap(()=>{
            const existingGroups = this.props.$groups || {};
            const isNewGroup = existingGroups[groupType] !== groupKey;
            this.groups({
                [groupType]: groupKey
            });
            if (isNewGroup || groupProperties) this.groupIdentify(groupType, groupKey, groupProperties, options);
        });
    }
    groupIdentify(groupType, groupKey, groupProperties, options) {
        this.wrap(()=>{
            if (!this._requirePersonProcessing('posthog.groupIdentify')) return;
            const distinctId = this.getDistinctId();
            const eventProperties = this.enrichProperties({});
            super.groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties);
        });
    }
    setPersonPropertiesForFlags(properties, reloadFeatureFlags = true) {
        this.wrap(()=>{
            const existingProperties = this.getPersistedProperty(PostHogPersistedProperty.PersonProperties) || {};
            const propsToSet = properties?.['$set'] || (properties?.['$set_once'] ? {} : properties);
            const propsToSetOnce = properties?.['$set_once'];
            const setOnceProps = {};
            if (propsToSetOnce) {
                for(const key in propsToSetOnce)if (Object.prototype.hasOwnProperty.call(propsToSetOnce, key)) {
                    if (!(key in existingProperties)) setOnceProps[key] = propsToSetOnce[key];
                }
            }
            this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, {
                ...existingProperties,
                ...setOnceProps,
                ...propsToSet
            });
            if (reloadFeatureFlags) this.reloadFeatureFlags();
        });
    }
    resetPersonPropertiesForFlags() {
        this.wrap(()=>{
            this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, null);
        });
    }
    setGroupPropertiesForFlags(properties) {
        this.wrap(()=>{
            const existingProperties = this.getPersistedProperty(PostHogPersistedProperty.GroupProperties) || {};
            if (0 !== Object.keys(existingProperties).length) Object.keys(existingProperties).forEach((groupType)=>{
                existingProperties[groupType] = {
                    ...existingProperties[groupType],
                    ...properties[groupType]
                };
                delete properties[groupType];
            });
            this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, {
                ...existingProperties,
                ...properties
            });
        });
    }
    resetGroupPropertiesForFlags() {
        this.wrap(()=>{
            this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, null);
        });
    }
    async remoteConfigAsync() {
        await this._initPromise;
        if (this.disabled) return;
        if (this._remoteConfigResponsePromise) return this._remoteConfigResponsePromise;
        return this._remoteConfigAsync();
    }
    async flagsAsync(options) {
        const { sendAnonDistinctId = true, fetchConfig = false, triggerOnRemoteConfig = false } = options ?? {};
        await this._initPromise;
        if (this.disabled) return;
        if (this.disableRemoteFeatureFlags && !fetchConfig) return void this._logger.info('Feature flags are disabled (disableRemoteFeatureFlags), skipping reload.');
        if (this._flagsResponsePromise) {
            this._logger.info('Feature flags are being loaded already, queuing reload.');
            if (this._pendingFlagsRequest) this._flagsResponsePromise.then(this._pendingFlagsRequest.resolve).catch(this._pendingFlagsRequest.reject);
            return new Promise((resolve, reject)=>{
                this._pendingFlagsRequest = {
                    sendAnonDistinctId,
                    fetchConfig,
                    triggerOnRemoteConfig,
                    resolve,
                    reject
                };
            });
        }
        return this._flagsAsync({
            sendAnonDistinctId,
            fetchConfig,
            triggerOnRemoteConfig
        });
    }
    onRemoteConfig(_response) {}
    maybeNotifyRemoteConfig(shouldTrigger, response) {
        if (shouldTrigger) try {
            this.onRemoteConfig(response);
        } catch (e) {
            this._logger.error('Error in onRemoteConfig callback:', e);
        }
    }
    cacheSessionReplay(source, response) {
        const sessionReplay = response?.sessionRecording;
        if (sessionReplay) {
            this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
            this._logger.info(`Session replay config from ${source}: `, JSON.stringify(sessionReplay));
        } else if ('boolean' == typeof sessionReplay && false === sessionReplay) {
            this._logger.info(`Session replay config from ${source} disabled.`);
            this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
        }
    }
    async _remoteConfigAsync() {
        this._remoteConfigResponsePromise = this._initPromise.then(()=>{
            let remoteConfig = this.getPersistedProperty(PostHogPersistedProperty.RemoteConfig);
            this._logger.info('Cached remote config: ', JSON.stringify(remoteConfig));
            return super.getRemoteConfig().then((response)=>{
                if (response) {
                    const remoteConfigWithoutSurveys = {
                        ...response
                    };
                    delete remoteConfigWithoutSurveys.surveys;
                    this._logger.info('Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys));
                    if (false === this.disableSurveys) {
                        const surveys = response.surveys;
                        let hasSurveys = true;
                        if (Array.isArray(surveys)) this._logger.info('Surveys fetched from remote config: ', JSON.stringify(surveys));
                        else {
                            this._logger.info('There are no surveys.');
                            hasSurveys = false;
                        }
                        if (hasSurveys) this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
                        else this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
                    } else this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
                    this.setPersistedProperty(PostHogPersistedProperty.RemoteConfig, remoteConfigWithoutSurveys);
                    this.cacheSessionReplay('remote config', response);
                    let willLoadFlags = false;
                    if (false === response.hasFeatureFlags) {
                        if (!this.disableRemoteFeatureFlags) this.setKnownFeatureFlagDetails({
                            flags: {}
                        });
                        this._logger.warn('Remote config has no feature flags, will not load feature flags.');
                    } else if (false !== this.preloadFeatureFlags && !this.disableRemoteFeatureFlags) {
                        willLoadFlags = true;
                        this.flagsAsync({
                            sendAnonDistinctId: true,
                            fetchConfig: true,
                            triggerOnRemoteConfig: true
                        });
                    }
                    if (!response.supportedCompression?.includes(Compression.GZipJS)) this.disableCompression = true;
                    this.maybeNotifyRemoteConfig(!willLoadFlags, response);
                    remoteConfig = response;
                }
                return remoteConfig;
            });
        }).finally(()=>{
            this._remoteConfigResponsePromise = void 0;
        });
        return this._remoteConfigResponsePromise;
    }
    async _flagsAsync(options) {
        const sendAnonDistinctId = options?.sendAnonDistinctId ?? true;
        const fetchConfig = options?.fetchConfig ?? false;
        const triggerOnRemoteConfig = options?.triggerOnRemoteConfig ?? false;
        this._flagsResponsePromise = this._initPromise.then(async ()=>{
            const distinctId = this.getDistinctId();
            const groups = this.props.$groups || {};
            const personProperties = this.getPersistedProperty(PostHogPersistedProperty.PersonProperties) || {};
            const groupProperties = this.getPersistedProperty(PostHogPersistedProperty.GroupProperties) || {};
            const deviceId = this.getPersistedProperty(PostHogPersistedProperty.DeviceId);
            const extraProperties = {
                $anon_distinct_id: sendAnonDistinctId ? this.getAnonymousId() : void 0,
                $device_id: deviceId ?? void 0,
                ...this.disableRemoteFeatureFlags ? {
                    disable_flags: true
                } : {}
            };
            const result = await super.getFlags(distinctId, groups, personProperties, groupProperties, extraProperties, fetchConfig);
            if (!result.success) {
                if (!this.disableRemoteFeatureFlags) this.setKnownFeatureFlagDetails({
                    flags: this.getKnownFeatureFlagDetails()?.flags ?? {},
                    requestError: result.error
                });
                return;
            }
            const res = result.response;
            if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
                if (!this.disableRemoteFeatureFlags) this.setKnownFeatureFlagDetails({
                    flags: this.getKnownFeatureFlagDetails()?.flags ?? {},
                    quotaLimited: res.quotaLimited
                });
                this._logger.warn('[FEATURE FLAGS] Feature flags quota limit exceeded. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
                this.maybeNotifyRemoteConfig(triggerOnRemoteConfig, res);
                return res;
            }
            if (res?.featureFlags) {
                if (this.disableRemoteFeatureFlags) {
                    this.cacheSessionReplay('flags', res);
                    this.maybeNotifyRemoteConfig(triggerOnRemoteConfig, res);
                    return res;
                }
                let newFeatureFlagDetails = res;
                if (res.errorsWhileComputingFlags) {
                    const currentFlagDetails = this.getKnownFeatureFlagDetails();
                    this._logger.info('Cached feature flags: ', JSON.stringify(currentFlagDetails));
                    const filteredFlags = {};
                    for(const key in res.flags)if (!res.flags[key].failed) filteredFlags[key] = res.flags[key];
                    newFeatureFlagDetails = {
                        ...res,
                        flags: {
                            ...currentFlagDetails?.flags,
                            ...filteredFlags
                        }
                    };
                }
                this.cacheSessionReplay('flags', res);
                this.setKnownFeatureFlagDetails({
                    flags: newFeatureFlagDetails.flags,
                    requestId: res.requestId,
                    evaluatedAt: res.evaluatedAt,
                    errorsWhileComputingFlags: res.errorsWhileComputingFlags,
                    quotaLimited: res.quotaLimited
                });
                this.setPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit, true);
                this.maybeNotifyRemoteConfig(triggerOnRemoteConfig, res);
            }
            return res;
        }).finally(()=>{
            this._flagsResponsePromise = void 0;
            const pendingRequest = this._pendingFlagsRequest;
            if (pendingRequest) {
                this._pendingFlagsRequest = void 0;
                this._logger.info('Executing pending feature flags reload.');
                this.flagsAsync({
                    sendAnonDistinctId: pendingRequest.sendAnonDistinctId,
                    fetchConfig: pendingRequest.fetchConfig,
                    triggerOnRemoteConfig: pendingRequest.triggerOnRemoteConfig
                }).then(pendingRequest.resolve).catch(pendingRequest.reject);
            }
        });
        return this._flagsResponsePromise;
    }
    setKnownFeatureFlagDetails(flagsResponse) {
        this.wrap(()=>{
            this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, flagsResponse);
            this._events.emit('featureflags', getFlagValuesFromFlags(flagsResponse?.flags ?? {}));
        });
    }
    getKnownFeatureFlagDetails() {
        const storedDetails = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails);
        if (!storedDetails) {
            const featureFlags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags);
            const featureFlagPayloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads);
            if (void 0 === featureFlags && void 0 === featureFlagPayloads) return;
            return createFlagsResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
        }
        return normalizeFlagsResponse(storedDetails);
    }
    getStoredFlagDetails() {
        return this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails);
    }
    getKnownFeatureFlags() {
        const featureFlagDetails = this.getKnownFeatureFlagDetails();
        if (!featureFlagDetails) return;
        return getFlagValuesFromFlags(featureFlagDetails.flags);
    }
    getBootstrappedFeatureFlagDetails() {
        const details = this.getPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails);
        if (!details) return;
        return details;
    }
    setBootstrappedFeatureFlagDetails(details) {
        this.setPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails, details);
    }
    getBootstrappedFeatureFlags() {
        const details = this.getBootstrappedFeatureFlagDetails();
        if (!details) return;
        return getFlagValuesFromFlags(details.flags);
    }
    getBootstrappedFeatureFlagPayloads() {
        const details = this.getBootstrappedFeatureFlagDetails();
        if (!details) return;
        return getPayloadsFromFlags(details.flags);
    }
    getFeatureFlagResult(key, options) {
        return this._getFeatureFlagResult(key, options);
    }
    getAllFeatureFlags() {
        return flagDetailsToResults(this.getFeatureFlagDetails()?.flags ?? {});
    }
    _getFeatureFlagResult(key, options = {}) {
        const storedDetails = this.getStoredFlagDetails();
        const details = this.getFeatureFlagDetails();
        const isQuotaLimited = storedDetails?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags);
        const featureFlag = details?.flags[key];
        const flagValue = getFeatureFlagValue(featureFlag);
        const sendEvent = (options.sendEvent ?? this.sendFeatureFlagEvent) && !this.flagCallReported[key]?.has(flagValue);
        if (sendEvent) {
            const errors = [];
            if (storedDetails?.requestError) {
                const { type, statusCode } = storedDetails.requestError;
                if ('timeout' === type) errors.push(FeatureFlagError.TIMEOUT);
                else if ('api_error' === type && void 0 !== statusCode) errors.push(FeatureFlagError.apiError(statusCode));
                else if ('connection_error' === type) errors.push(FeatureFlagError.CONNECTION_ERROR);
                else errors.push(FeatureFlagError.UNKNOWN_ERROR);
            } else if (storedDetails) {
                if (storedDetails.errorsWhileComputingFlags) errors.push(FeatureFlagError.ERRORS_WHILE_COMPUTING);
                if (isQuotaLimited) errors.push(FeatureFlagError.QUOTA_LIMITED);
                else if (void 0 === flagValue && !featureFlag) errors.push(FeatureFlagError.FLAG_MISSING);
            }
            const bootstrappedResponse = this.getBootstrappedFeatureFlags()?.[key];
            const bootstrappedPayload = this.getBootstrappedFeatureFlagPayloads()?.[key];
            const featureFlagError = errors.length > 0 ? errors.join(',') : void 0;
            this.flagCallReported[key] = this.flagCallReported[key] ?? new Set();
            this.flagCallReported[key].add(flagValue);
            const properties = {
                $feature_flag: key,
                $feature_flag_response: flagValue,
                ...maybeAdd('$feature_flag_id', featureFlag?.metadata?.id),
                ...maybeAdd('$feature_flag_version', featureFlag?.metadata?.version),
                ...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
                ...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
                ...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
                $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit),
                ...maybeAdd('$feature_flag_request_id', details?.requestId),
                ...maybeAdd('$feature_flag_evaluated_at', details?.evaluatedAt),
                ...maybeAdd('$feature_flag_error', featureFlagError)
            };
            this.capture('$feature_flag_called', properties);
        }
        if (void 0 === flagValue) switch(options.missingFlagBehavior){
            case 'getFeatureFlagPayload':
                return details ? {
                    key,
                    enabled: false,
                    payload: null
                } : void 0;
            case 'getFeatureFlag':
                return details && Object.keys(details.flags).length ? {
                    key,
                    enabled: false,
                    payload: null
                } : void 0;
            case void 0:
                return;
        }
        const rawPayload = featureFlag?.metadata?.payload;
        const payload = void 0 !== rawPayload ? parsePayload(rawPayload) : null;
        return {
            key,
            enabled: getEnabledFromValue(flagValue),
            variant: getVariantFromValue(flagValue),
            payload
        };
    }
    getFeatureFlag(key) {
        const result = this._getFeatureFlagResult(key, {
            missingFlagBehavior: 'getFeatureFlag'
        });
        return result?.variant ?? result?.enabled;
    }
    getFeatureFlagPayload(key) {
        const result = this._getFeatureFlagResult(key, {
            missingFlagBehavior: 'getFeatureFlagPayload',
            sendEvent: false
        });
        return result?.payload;
    }
    getFeatureFlagPayloads() {
        return this.getFeatureFlagDetails()?.featureFlagPayloads;
    }
    getFeatureFlags() {
        return this.getFeatureFlagDetails()?.featureFlags;
    }
    getFeatureFlagDetails() {
        let details = this.getKnownFeatureFlagDetails();
        const overriddenFlags = this.getPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags);
        if (!overriddenFlags) return details;
        details = details ?? {
            featureFlags: {},
            featureFlagPayloads: {},
            flags: {}
        };
        const flags = {
            ...details.flags ?? {}
        };
        for(const key in overriddenFlags)if (overriddenFlags[key]) flags[key] = updateFlagValue(flags[key], overriddenFlags[key]);
        else delete flags[key];
        const result = {
            ...details,
            flags
        };
        return normalizeFlagsResponse(result);
    }
    getFeatureFlagsAndPayloads() {
        const flags = this.getFeatureFlags();
        const payloads = this.getFeatureFlagPayloads();
        return {
            flags,
            payloads
        };
    }
    isFeatureEnabled(key) {
        const response = this.getFeatureFlag(key);
        if (void 0 === response) return;
        return !!response;
    }
    reloadFeatureFlags(options) {
        if (this.disableRemoteFeatureFlags && !this.disabled) return void this._initPromise.then(()=>options?.cb?.(void 0, this.getFeatureFlags()));
        this.flagsAsync({
            sendAnonDistinctId: true
        }).then((res)=>{
            options?.cb?.(void 0, res?.featureFlags);
        }).catch((e)=>{
            options?.cb?.(e, void 0);
            if (!options?.cb) this._logger.info('Error reloading feature flags', e);
        });
    }
    async reloadRemoteConfigAsync() {
        return await this.remoteConfigAsync();
    }
    async reloadFeatureFlagsAsync(sendAnonDistinctId) {
        if (this.disableRemoteFeatureFlags && !this.disabled) {
            await this._initPromise;
            return this.getFeatureFlags();
        }
        return (await this.flagsAsync({
            sendAnonDistinctId: sendAnonDistinctId
        }))?.featureFlags;
    }
    onFeatureFlags(cb) {
        return this.on('featureflags', async ()=>{
            const flags = this.getFeatureFlags();
            if (flags) cb(flags);
        });
    }
    onFeatureFlag(key, cb) {
        return this.on('featureflags', async ()=>{
            const flagResponse = this.getFeatureFlag(key);
            if (void 0 !== flagResponse) cb(flagResponse);
        });
    }
    async overrideFeatureFlag(flags) {
        this.wrap(()=>{
            if (null === flags) return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, null);
            return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, flags);
        });
    }
    updateFlags(flags, payloads, options) {
        this.wrap(()=>{
            const existingDetails = options?.merge ? this.getKnownFeatureFlagDetails()?.flags : void 0;
            const existingFlags = existingDetails ? getFlagValuesFromFlags(existingDetails) : {};
            const existingPayloads = {};
            for(const key in existingDetails){
                const storedPayload = existingDetails[key].metadata?.payload;
                if (void 0 !== storedPayload) existingPayloads[key] = parsePayload(storedPayload);
            }
            const finalFlags = {
                ...existingFlags,
                ...flags
            };
            const finalPayloads = {
                ...existingPayloads,
                ...payloads ?? {}
            };
            const flagDetails = {};
            for (const [key, value] of Object.entries(finalFlags)){
                const payload = finalPayloads[key];
                let serializedPayload;
                if (void 0 !== payload) try {
                    serializedPayload = JSON.stringify(payload);
                } catch (e) {
                    this._logger.error(`updateFlags: could not serialize the payload for flag "${key}", dropping it.`, e);
                }
                flagDetails[key] = {
                    key,
                    enabled: getEnabledFromValue(value),
                    variant: getVariantFromValue(value),
                    reason: void 0,
                    metadata: {
                        id: void 0,
                        version: void 0,
                        description: void 0,
                        payload: serializedPayload
                    }
                };
            }
            this.setKnownFeatureFlagDetails({
                flags: flagDetails
            });
        });
    }
    captureException(error, additionalProperties, hint) {
        try {
            const exceptionProperties = this.getErrorPropertiesBuilder().buildFromUnknown(error, hint);
            this.capture('$exception', {
                ...exceptionProperties,
                ...additionalProperties
            }, {
                _originatedFromCaptureException: true
            });
        } catch (e) {
            this._logger.error('Error while capturing $exception:', e);
        }
    }
    captureTraceFeedback(traceId, userFeedback) {
        this.capture('$ai_feedback', {
            $ai_feedback_text: userFeedback,
            $ai_trace_id: String(traceId)
        });
    }
    captureTraceMetric(traceId, metricName, metricValue) {
        this.capture('$ai_metric', {
            $ai_metric_name: metricName,
            $ai_metric_value: String(metricValue),
            $ai_trace_id: String(traceId)
        });
    }
    _isIdentified() {
        const personMode = this.getPersistedProperty(PostHogPersistedProperty.PersonMode);
        if ('identified' === personMode) return true;
        if (void 0 === personMode) {
            const distinctId = this.getPersistedProperty(PostHogPersistedProperty.DistinctId);
            const anonymousId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId);
            if (distinctId && anonymousId && distinctId !== anonymousId) return true;
        }
        return false;
    }
    _getGroups() {
        return this.props.$groups || {};
    }
    _hasPersonProcessing() {
        if ('always' === this._personProfiles) return true;
        if ('never' === this._personProfiles) return false;
        const isIdentified = this._isIdentified();
        const hasGroups = Object.keys(this._getGroups()).length > 0;
        const personProcessingEnabled = true === this.getPersistedProperty(PostHogPersistedProperty.EnablePersonProcessing);
        return isIdentified || hasGroups || personProcessingEnabled;
    }
    _requirePersonProcessing(functionName) {
        if ('never' === this._personProfiles) {
            this._logger.error(`${functionName} was called, but personProfiles is set to "never". This call will be ignored.`);
            return false;
        }
        this.setPersistedProperty(PostHogPersistedProperty.EnablePersonProcessing, true);
        return true;
    }
    createPersonProfile() {
        if (this._hasPersonProcessing()) return;
        if (!this._requirePersonProcessing('posthog.createPersonProfile')) return;
        this.capture('$set', {
            $set: {},
            $set_once: {}
        });
    }
    setPersonProperties(userPropertiesToSet, userPropertiesToSetOnce, reloadFeatureFlags = true) {
        this.wrap(()=>{
            const isSetEmpty = isNullish(userPropertiesToSet) || isEmptyObject(userPropertiesToSet);
            const isSetOnceEmpty = isNullish(userPropertiesToSetOnce) || isEmptyObject(userPropertiesToSetOnce);
            if (isSetEmpty && isSetOnceEmpty) return;
            if (!this._requirePersonProcessing('posthog.setPersonProperties')) return;
            const hash = getPersonPropertiesHash(this.getDistinctId(), userPropertiesToSet, userPropertiesToSetOnce);
            if (this._cachedPersonProperties === hash) return void this._logger.info('A duplicate setPersonProperties call was made with the same properties. It has been ignored.');
            this.setPersonPropertiesForFlags({
                $set: userPropertiesToSet || {},
                $set_once: userPropertiesToSetOnce || {}
            }, reloadFeatureFlags);
            this.capture('$set', {
                $set: userPropertiesToSet || {},
                $set_once: userPropertiesToSetOnce || {}
            });
            this._cachedPersonProperties = hash;
        });
    }
    unsetPersonProperties(propertyNames, reloadFeatureFlags = true) {
        this.wrap(()=>{
            const names = (isArray(propertyNames) ? propertyNames : [
                propertyNames
            ]).filter((name)=>isString(name) && name.length > 0);
            if (0 === names.length) return;
            if (!this._requirePersonProcessing('posthog.unsetPersonProperties')) return;
            const existingProperties = this.getPersistedProperty(PostHogPersistedProperty.PersonProperties) || {};
            const nextProperties = {
                ...existingProperties
            };
            for (const name of names)delete nextProperties[name];
            this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, nextProperties);
            if (reloadFeatureFlags) this.reloadFeatureFlags();
            this.capture('$set', {
                $unset: names
            });
            this._cachedPersonProperties = null;
        });
    }
    processBeforeEnqueue(message) {
        if (!this._beforeSend) return message;
        const timestamp = message.timestamp;
        const props = message.properties || {};
        const captureEvent = {
            uuid: message.uuid,
            event: message.event,
            properties: props,
            $set: props.$set,
            $set_once: props.$set_once,
            timestamp: 'string' == typeof timestamp ? new Date(timestamp) : timestamp
        };
        const result = this._runBeforeSend(captureEvent);
        if (!result) return null;
        const resultProps = {
            ...result.properties ?? props
        };
        if (void 0 !== result.$set) resultProps.$set = result.$set;
        else delete resultProps.$set;
        if (void 0 !== result.$set_once) resultProps.$set_once = result.$set_once;
        else delete resultProps.$set_once;
        return {
            ...message,
            uuid: getEventUuid(result.uuid ?? message.uuid, uuidv7),
            event: result.event,
            properties: resultProps,
            timestamp: result.timestamp
        };
    }
    _runBeforeSend(captureEvent) {
        const beforeSend = this._beforeSend;
        if (!beforeSend) return captureEvent;
        const fns = Array.isArray(beforeSend) ? beforeSend : [
            beforeSend
        ];
        let result = captureEvent;
        for (const fn of fns)try {
            result = fn(result);
            if (!result) {
                this._logger.info(`Event '${captureEvent.event}' was rejected in before_send function`);
                return null;
            }
        } catch (e) {
            this._logger.error(`Error in before_send function for event '${captureEvent.event}':`, e);
        }
        return result;
    }
}
export { PostHogCore };
