export type PostHogCoreOptions = {
  /**
   * PostHog API host, usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
   *
   * @default 'https://us.i.posthog.com'
   */
  host?: string
  /**
   * The number of events to queue before sending to PostHog (flushing)
   *
   * @default 20
   */
  flushAt?: number
  /**
   * The interval in milliseconds between periodic flushes
   *
   * @default 10000
   */
  flushInterval?: number
  /**
   * The maximum number of queued messages to be flushed as part of a single batch (must be higher than `flushAt`)
   *
   * @default 100
   */
  maxBatchSize?: number
  /**
   * The maximum number of cached messages either in memory or on the local storage (must be higher than `flushAt`)
   *
   * @default 1000
   */
  maxQueueSize?: number
  /**
   * If set to true the SDK is essentially disabled (useful for local environments where you don't want to track anything)
   *
   * @default false
   */
  disabled?: boolean
  /**
   * If set to false the SDK will not track until the `optIn` function is called.
   *
   * @default true
   */
  defaultOptIn?: boolean
  /**
   * Whether to track that `getFeatureFlag` was called (used by Experiments)
   *
   * @default true
   */
  sendFeatureFlagEvent?: boolean
  /**
   * Whether to load feature flags when initialized or not
   *
   * @default true
   */
  preloadFeatureFlags?: boolean
  /**
   * Whether to load remote config when initialized or not
   * Experimental support
   *
   * @default false
   */
  disableRemoteConfig?: boolean
  /**
   * Whether to load surveys when initialized or not
   * Experimental support
   * Requires the `PostHogSurveyProvider` to be used
   *
   * @default false
   */
  disableSurveys?: boolean
  /** Option to bootstrap the library with given distinctId and feature flags */
  bootstrap?: {
    distinctId?: string
    isIdentifiedId?: boolean
    featureFlags?: Record<string, FeatureFlagValue>
    featureFlagPayloads?: Record<string, JsonType>
  }
  /**
   * How many times we will retry HTTP requests
   *
   * @default 3
   */
  fetchRetryCount?: number
  /**
   * The delay between HTTP request retries in milliseconds
   *
   * @default 3000
   */
  fetchRetryDelay?: number
  /**
   * Timeout in milliseconds for any calls
   *
   * @default 10000
   */
  requestTimeout?: number
  /**
   * Timeout in milliseconds for feature flag calls
   *
   * @default 10000 for stateful clients, 3000 for stateless
   */
  featureFlagsRequestTimeoutMs?: number
  /**
   * Timeout in milliseconds for remote config calls
   *
   * @default 3000
   */
  remoteConfigRequestTimeoutMs?: number
  /**
   * For Session Analysis how long before we expire a session in seconds
   *
   * @default 1800
   */
  sessionExpirationTimeSeconds?: number
  /**
   * Whether to disable GZIP compression
   *
   * @default false
   */
  disableCompression?: boolean
  /**
   * Whether to disable GeoIP lookups
   *
   * @default false
   */
  disableGeoip?: boolean
  /**
   * Special flag to indicate ingested data is for a historical migration
   *
   * @default false
   */
  historicalMigration?: boolean
  /**
   * Evaluation contexts for feature flags.
   * When set, only feature flags that have at least one matching evaluation tag
   * will be evaluated for this SDK instance. Feature flags with no evaluation tags
   * will always be evaluated.
   *
   * Examples: ['production', 'web', 'mobile']
   *
   * @default undefined
   */
  evaluationContexts?: readonly string[]
  /**
   * Evaluation environments for feature flags.
   * @deprecated Use evaluationContexts instead. This property will be removed in a future version.
   */
  evaluationEnvironments?: readonly string[]

  /**
   * Determines when to create Person Profiles for users.
   *
   * - 'always': Always create a person profile for every user (anonymous and identified).
   * - 'identified_only': Only create a person profile when the user is identified via identify(), alias(), or group().
   *   Events captured before identification will NOT have person profiles and will be anonymous events.
   * - 'never': Never create person profiles. identify(), alias(), and group() will be no-ops.
   *
   * @default 'identified_only'
   *
   * @example
   * ```ts
   * // Only create profiles when users are identified (recommended for most apps)
   * const posthog = new PostHog('<api_key>', {
   *   personProfiles: 'identified_only',
   * })
   *
   * // Later when user logs in:
   * posthog.identify('user-123', { email: 'user@example.com' })
   * ```
   *
   * @example
   * ```ts
   * // Always create profiles (for apps where you want to track all users)
   * const posthog = new PostHog('<api_key>', {
   *   personProfiles: 'always',
   * })
   * ```
   *
   * @example
   * ```ts
   * // Never create profiles (anonymous analytics only)
   * const posthog = new PostHog('<api_key>', {
   *   personProfiles: 'never',
   * })
   * ```
   */
  personProfiles?: 'always' | 'identified_only' | 'never'

  /**
   * Allows modification or dropping of events before they're sent to PostHog.
   * If an array is provided, the functions are run in order.
   * If a function returns null, the event will be dropped.
   */
  before_send?: BeforeSendFn | BeforeSendFn[]
}

export enum PostHogPersistedProperty {
  AnonymousId = 'anonymous_id',
  DistinctId = 'distinct_id',
  Props = 'props',
  EnablePersonProcessing = 'enable_person_processing',
  PersonMode = 'person_mode', // 'identified' | 'anonymous'
  FeatureFlagDetails = 'feature_flag_details',
  FeatureFlags = 'feature_flags',
  FeatureFlagPayloads = 'feature_flag_payloads',
  BootstrapFeatureFlagDetails = 'bootstrap_feature_flag_details',
  BootstrapFeatureFlags = 'bootstrap_feature_flags',
  BootstrapFeatureFlagPayloads = 'bootstrap_feature_flag_payloads',
  OverrideFeatureFlags = 'override_feature_flags',
  Queue = 'queue',
  OptedOut = 'opted_out',
  SessionId = 'session_id',
  SessionStartTimestamp = 'session_start_timestamp',
  SessionLastTimestamp = 'session_timestamp',
  PersonProperties = 'person_properties',
  GroupProperties = 'group_properties',
  InstalledAppBuild = 'installed_app_build', // only used by posthog-react-native
  InstalledAppVersion = 'installed_app_version', // only used by posthog-react-native
  SessionReplay = 'session_replay', // only used by posthog-react-native
  SurveyLastSeenDate = 'survey_last_seen_date', // only used by posthog-react-native
  SurveysSeen = 'surveys_seen', // only used by posthog-react-native
  Surveys = 'surveys', // only used by posthog-react-native
  RemoteConfig = 'remote_config',
  FlagsEndpointWasHit = 'flags_endpoint_was_hit', // only used by posthog-react-native
  DeviceId = 'device_id', // only used by posthog-react-native
}

export type PostHogFetchOptions = {
  method: 'GET' | 'POST' | 'PUT' | 'PATCH'
  mode?: 'no-cors'
  credentials?: 'omit'
  headers: { [key: string]: string }
  body?: string | Blob
  signal?: AbortSignal
}

// Check out posthog-js for these additional options and try to keep them in sync
export type PostHogCaptureOptions = {
  /** If provided overrides the auto-generated event ID */
  uuid?: string
  /** If provided overrides the auto-generated timestamp */
  timestamp?: Date
  disableGeoip?: boolean
  /**
   * Internal flag set by captureException() to indicate this $exception
   * event originated from the proper exception capture path. Used to warn users who call
   * capture('$exception') directly.
   */
  _originatedFromCaptureException?: boolean
}

export type PostHogFetchResponse = {
  status: number
  text: () => Promise<string>
  json: () => Promise<any>
  headers?: {
    get(name: string): string | null
  }
}

export type PostHogQueueItem = {
  message: any
  callback?: (err: any) => void
}

export type PostHogEventProperties = {
  [key: string]: JsonType
}

export type PostHogGroupProperties = {
  [type: string]: string | number
}

export type PostHogAutocaptureElement = {
  $el_text?: string
  tag_name: string
  href?: string
  nth_child?: number
  nth_of_type?: number
  order?: number
} & PostHogEventProperties
// Any key prefixed with `attr__` can be added

export enum Compression {
  GZipJS = 'gzip-js',
  Base64 = 'base64',
}

export type PostHogRemoteConfig = {
  sessionRecording?:
    | boolean
    | {
        [key: string]: JsonType
      }

  /**
   * Supported compression algorithms
   */
  supportedCompression?: Compression[]

  /**
   * Whether surveys are enabled
   */
  surveys?: boolean | Survey[]

  /**
   * Indicates if the team has any flags enabled (if not we don't need to load them)
   */
  hasFeatureFlags?: boolean

  /**
   * Error tracking remote config.
   * Either a boolean (false = disabled) or a map with configuration.
   * When a map, `autocaptureExceptions` (boolean) controls whether automatic exception capture is enabled remotely.
   */
  errorTracking?:
    | boolean
    | {
        [key: string]: JsonType
      }

  /**
   * Capture performance remote config.
   * Either a boolean (false = disabled) or a map with configuration.
   * When a map, `network_timing` (boolean) controls whether network timing capture is enabled remotely.
   */
  capturePerformance?:
    | boolean
    | {
        [key: string]: JsonType
      }
}

export type FeatureFlagValue = string | boolean

/**
 * Result of evaluating a feature flag, including both the flag value and its payload.
 */
export type FeatureFlagResult = {
  readonly key: string
  readonly enabled: boolean
  readonly variant?: string
  readonly payload?: JsonType
}

export type FeatureFlagResultOptions = {
  /** Whether to send a $feature_flag_called event. Defaults to true. */
  sendEvent?: boolean
}

export type PostHogFlagsResponse = Omit<PostHogRemoteConfig, 'hasFeatureFlags'> & {
  featureFlags: {
    [key: string]: FeatureFlagValue
  }
  featureFlagPayloads: {
    [key: string]: JsonType
  }
  flags: {
    [key: string]: FeatureFlagDetail
  }
  errorsWhileComputingFlags: boolean
  sessionRecording?:
    | boolean
    | {
        [key: string]: JsonType
      }
  quotaLimited?: string[]
  requestId?: string
  evaluatedAt?: number // Unix timestamp in milliseconds
}

export type PostHogFeatureFlagsResponse = PartialWithRequired<
  PostHogFlagsResponse,
  'flags' | 'featureFlags' | 'featureFlagPayloads' | 'requestId'
>

/**
 * Creates a type with all properties of T, but makes only K properties required while the rest remain optional.
 *
 * @template T - The base type containing all properties
 * @template K - Union type of keys from T that should be required
 *
 * @example
 * interface User {
 *   id: number;
 *   name: string;
 *   email?: string;
 *   age?: number;
 * }
 *
 * // Makes 'id' and 'name' required, but 'email' and 'age' optional
 * type RequiredUser = PartialWithRequired<User, 'id' | 'name'>;
 *
 * const user: RequiredUser = {
 *   id: 1,      // Must be provided
 *   name: "John" // Must be provided
 *   // email and age are optional
 * };
 */
export type PartialWithRequired<T, K extends keyof T> = {
  [P in K]: T[P] // Required fields
} & {
  [P in Exclude<keyof T, K>]?: T[P] // Optional fields
}

/**
 * These are the fields we care about from PostHogFlagsResponse for feature flags.
 */
export type PostHogFeatureFlagDetails = PartialWithRequired<
  PostHogFlagsResponse,
  'flags' | 'featureFlags' | 'featureFlagPayloads' | 'requestId'
>

/**
 * Models the response from the v1 `/flags` endpoint.
 */
export type PostHogV1FlagsResponse = Omit<PostHogFlagsResponse, 'flags'>

/**
 * Models the response from the v2 `/flags` endpoint.
 */
export type PostHogV2FlagsResponse = Omit<PostHogFlagsResponse, 'featureFlags' | 'featureFlagPayloads'>

/**
 * The format of the flags object in persisted storage
 *
 * When we pull flags from persistence, we can normalize them to PostHogFeatureFlagDetails
 * so that we can support v1 and v2 of the API.
 */
export type PostHogFlagsStorageFormat = Pick<PostHogFeatureFlagDetails, 'flags'> &
  Partial<Pick<PostHogFlagsResponse, 'requestId' | 'evaluatedAt'>> & {
    errorsWhileComputingFlags?: boolean
    quotaLimited?: string[]
    requestError?: FeatureFlagRequestError
  }

/**
 * Models legacy flags and payloads return type for many public methods.
 */
export type PostHogFlagsAndPayloadsResponse = Partial<
  Pick<PostHogFlagsResponse, 'featureFlags' | 'featureFlagPayloads'>
>

export type JsonType = string | number | boolean | null | { [key: string]: JsonType } | Array<JsonType> | JsonType[]

export type FetchLike = (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>

/**
 * Error type constants for the $feature_flag_error property.
 *
 * These values are sent in analytics events to track flag evaluation failures.
 * They should not be changed without considering impact on existing dashboards
 * and queries that filter on these values.
 *
 * Error values:
 *   ERRORS_WHILE_COMPUTING: Server returned errorsWhileComputingFlags=true
 *   FLAG_MISSING: Requested flag not in API response
 *   QUOTA_LIMITED: Rate/quota limit exceeded
 *   TIMEOUT: Request timed out
 *   CONNECTION_ERROR: Network connection failed
 *   apiError: HTTP error with status code (e.g., api_error_500)
 */
export const FeatureFlagError = {
  ERRORS_WHILE_COMPUTING: 'errors_while_computing_flags',
  FLAG_MISSING: 'flag_missing',
  QUOTA_LIMITED: 'quota_limited',
  TIMEOUT: 'timeout',
  CONNECTION_ERROR: 'connection_error',
  UNKNOWN_ERROR: 'unknown_error',
  apiError: (status: number): string => `api_error_${status}`,
} as const

export type FeatureFlagErrorType =
  | (typeof FeatureFlagError)[Exclude<keyof typeof FeatureFlagError, 'apiError'>]
  | ReturnType<typeof FeatureFlagError.apiError>
  | string

/**
 * Represents an error that occurred during a feature flag request.
 */
export type FeatureFlagRequestError = {
  type: 'timeout' | 'connection_error' | 'api_error' | 'unknown_error'
  statusCode?: number
}

/**
 * Result type for getFlags that includes either a successful response or error information.
 */
export type GetFlagsResult =
  | { success: true; response: PostHogFeatureFlagsResponse }
  | { success: false; error: FeatureFlagRequestError }

export type FeatureFlagDetail = {
  key: string
  enabled: boolean
  variant: string | undefined
  reason: EvaluationReason | undefined
  metadata: FeatureFlagMetadata | undefined
  failed?: boolean
}

export type FeatureFlagMetadata = {
  id: number | undefined
  version: number | undefined
  description: string | undefined
  // Payloads in the response are always JSON encoded as a string
  payload: string | undefined
}

export type EvaluationReason = {
  code: string | undefined
  condition_index: number | undefined
  description: string | undefined
}

// survey types
export type SurveyAppearance = {
  // keep in sync with frontend/src/types.ts -> SurveyAppearance
  backgroundColor?: string
  // Optional override for main survey text color. If not set, auto-calculated from backgroundColor.
  textColor?: string
  submitButtonColor?: string
  // deprecate submit button text eventually
  submitButtonText?: string
  // Optional override for submit button text color. If not set, auto-calculated from submitButtonColor.
  submitButtonTextColor?: string
  ratingButtonColor?: string
  ratingButtonActiveColor?: string
  inputBackground?: string
  // Optional override for input and rating button text color. If not set, auto-calculated from inputBackground.
  inputTextColor?: string
  autoDisappear?: boolean
  displayThankYouMessage?: boolean
  thankYouMessageHeader?: string
  thankYouMessageDescription?: string
  thankYouMessageDescriptionContentType?: SurveyQuestionDescriptionContentType
  thankYouMessageCloseButtonText?: string
  borderColor?: string
  position?: SurveyPosition
  placeholder?: string
  shuffleQuestions?: boolean
  surveyPopupDelaySeconds?: number
  // widget options
  widgetType?: SurveyWidgetType
  widgetSelector?: string
  widgetLabel?: string
  widgetColor?: string
}

export enum SurveyPosition {
  TopLeft = 'top_left',
  TopCenter = 'top_center',
  TopRight = 'top_right',
  MiddleLeft = 'middle_left',
  MiddleCenter = 'middle_center',
  MiddleRight = 'middle_right',
  Left = 'left',
  Right = 'right',
  Center = 'center',
}

export enum SurveyWidgetType {
  Button = 'button',
  Tab = 'tab',
  Selector = 'selector',
}

export enum SurveyType {
  Popover = 'popover',
  API = 'api',
  Widget = 'widget',
  ExternalSurvey = 'external_survey',
}

export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion

export enum SurveyQuestionDescriptionContentType {
  Html = 'html',
  Text = 'text',
}

// Survey validation types
export enum SurveyValidationType {
  MinLength = 'min_length',
  MaxLength = 'max_length',
}

export interface SurveyValidationRule {
  type: SurveyValidationType
  value?: number
  errorMessage?: string
}

type SurveyQuestionBase = {
  question: string
  id: string
  description?: string
  descriptionContentType?: SurveyQuestionDescriptionContentType
  optional?: boolean
  buttonText?: string
  originalQuestionIndex: number
  branching?: NextQuestionBranching | EndBranching | ResponseBasedBranching | SpecificQuestionBranching
  validation?: SurveyValidationRule[]
}

export type BasicSurveyQuestion = SurveyQuestionBase & {
  type: SurveyQuestionType.Open
}

export type LinkSurveyQuestion = SurveyQuestionBase & {
  type: SurveyQuestionType.Link
  link?: string
}

export type RatingSurveyQuestion = SurveyQuestionBase & {
  type: SurveyQuestionType.Rating
  display: SurveyRatingDisplay
  scale: 2 | 3 | 5 | 7 | 10
  lowerBoundLabel: string
  upperBoundLabel: string
  skipSubmitButton?: boolean
}

export enum SurveyRatingDisplay {
  Number = 'number',
  Emoji = 'emoji',
}

export type MultipleSurveyQuestion = SurveyQuestionBase & {
  type: SurveyQuestionType.SingleChoice | SurveyQuestionType.MultipleChoice
  choices: string[]
  hasOpenChoice?: boolean
  shuffleOptions?: boolean
  skipSubmitButton?: boolean
}

export enum SurveyQuestionType {
  Open = 'open',
  MultipleChoice = 'multiple_choice',
  SingleChoice = 'single_choice',
  Rating = 'rating',
  Link = 'link',
}

export enum SurveyQuestionBranchingType {
  NextQuestion = 'next_question',
  End = 'end',
  ResponseBased = 'response_based',
  SpecificQuestion = 'specific_question',
}

export type NextQuestionBranching = {
  type: SurveyQuestionBranchingType.NextQuestion
}

export type EndBranching = {
  type: SurveyQuestionBranchingType.End
}

export type ResponseBasedBranching = {
  type: SurveyQuestionBranchingType.ResponseBased
  responseValues: Record<string, any>
}

export type SpecificQuestionBranching = {
  type: SurveyQuestionBranchingType.SpecificQuestion
  index: number
}

export type SurveyResponse = {
  surveys: Survey[]
}

export type SurveyCallback = (surveys: Survey[]) => void

export enum SurveyMatchType {
  Regex = 'regex',
  NotRegex = 'not_regex',
  Exact = 'exact',
  IsNot = 'is_not',
  Icontains = 'icontains',
  NotIcontains = 'not_icontains',
}

export enum SurveySchedule {
  Once = 'once',
  Recurring = 'recurring',
  Always = 'always',
}

export type SurveyElement = {
  text?: string
  $el_text?: string
  tag_name?: string
  href?: string
  attr_id?: string
  attr_class?: string[]
  nth_child?: number
  nth_of_type?: number
  attributes?: Record<string, any>
  event_id?: number
  order?: number
  group_id?: number
}
export type SurveyRenderReason = {
  visible: boolean
  disabledReason?: string
}

export type Survey = {
  // Sync this with the backend's SurveyAPISerializer!
  id: string
  name: string
  description?: string
  type: SurveyType
  feature_flag_keys?: {
    key: string
    value?: string
  }[]
  linked_flag_key?: string
  targeting_flag_key?: string
  internal_targeting_flag_key?: string
  questions: SurveyQuestion[]
  appearance?: SurveyAppearance
  conditions?: {
    url?: string
    selector?: string
    seenSurveyWaitPeriodInDays?: number
    urlMatchType?: SurveyMatchType
    events?: {
      repeatedActivation?: boolean
      values?: {
        name: string
      }[]
    }
    actions?: {
      values: SurveyActionType[]
    }
    deviceTypes?: string[]
    deviceTypesMatchType?: SurveyMatchType
    linkedFlagVariant?: string
  }
  start_date?: string
  end_date?: string
  current_iteration?: number
  current_iteration_start_date?: string
  schedule?: SurveySchedule
}

export type SurveyActionType = {
  id: number
  name?: string
  steps?: ActionStepType[]
}

/** Sync with plugin-server/src/types.ts */
export enum ActionStepStringMatching {
  Contains = 'contains',
  Exact = 'exact',
  Regex = 'regex',
}

export type ActionStepType = {
  event?: string
  selector?: string
  text?: string
  /** @default StringMatching.Exact */
  text_matching?: ActionStepStringMatching
  href?: string
  /** @default ActionStepStringMatching.Exact */
  href_matching?: ActionStepStringMatching
  url?: string
  /** @default StringMatching.Contains */
  url_matching?: ActionStepStringMatching
}

export type Logger = {
  info: (...args: any[]) => void
  warn: (...args: any[]) => void
  error: (...args: any[]) => void
  critical: (...args: any[]) => void
  createLogger: (prefix: string) => Logger
}

export const knownUnsafeEditableEvent = [
  '$snapshot',
  '$pageview',
  '$pageleave',
  '$set',
  'survey dismissed',
  'survey sent',
  'survey shown',
  '$identify',
  '$groupidentify',
  '$create_alias',
  '$$client_ingestion_warning',
  '$web_experiment_applied',
  '$feature_enrollment_update',
  '$feature_flag_called',
] as const

/**
 * These events can be processed by the `beforeCapture` function
 * but can cause unexpected confusion in data.
 *
 * Some features of PostHog rely on receiving 100% of these events
 */
export type KnownUnsafeEditableEvent = (typeof knownUnsafeEditableEvent)[number]

/**
 * Represents an event before it's sent to PostHog.
 * This is the interface exposed to the `before_send` hook, matching the web SDK's `CaptureResult`.
 */
export type CaptureEvent = {
  /** UUID for the event (optional to allow compatibility with Node SDK's EventMessage) */
  uuid?: string
  /** The name of the event */
  event: string
  /** Properties associated with the event (optional to allow compatibility with Node SDK's EventMessage) */
  properties?: PostHogEventProperties
  /** Properties to set on the person (overrides existing values) */
  $set?: PostHogEventProperties
  /** Properties to set on the person only once (does not override existing values) */
  $set_once?: PostHogEventProperties
  /** Timestamp for the event */
  timestamp?: Date
}

/**
 * Function type for the `before_send` hook.
 * Receives an event and can return a modified event or null to drop the event.
 */
export type BeforeSendFn = (event: CaptureEvent | null) => CaptureEvent | null
