import type { IStreamBuilder } from '@tanstack/db-ivm'
import type { Collection } from './collection/index.js'
import type { StandardSchemaV1 } from '@standard-schema/spec'
import type { Transaction } from './transactions'
import type { BasicExpression, OrderBy } from './query/ir.js'
import type { EventEmitter } from './event-emitter.js'
import type { IndexConstructor } from './indexes/base-index.js'
import type { SingleRowRefProxy } from './query/builder/ref-proxy.js'
import type { WithVirtualProps } from './virtual-props.js'

/**
 * Interface for a collection-like object that provides the necessary methods
 * for the change events system to work
 */
export interface CollectionLike<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> extends Pick<
  Collection<T, TKey>,
  `get` | `has` | `entries` | `indexes` | `id` | `compareOptions`
> {}

/**
 * StringSortOpts - Options for string sorting behavior
 *
 * This discriminated union allows for two types of string sorting:
 * - **Lexical**: Simple character-by-character comparison (default)
 * - **Locale**: Locale-aware sorting with optional customization
 *
 * The union ensures that locale options are only available when locale sorting is selected.
 */
export type StringCollationConfig =
  | {
      stringSort?: `lexical`
    }
  | {
      stringSort?: `locale`
      locale?: string
      localeOptions?: object
    }

/**
 * Helper type to extract the output type from a standard schema
 *
 * @internal This is used by the type resolution system
 */
export type InferSchemaOutput<T> = T extends StandardSchemaV1
  ? StandardSchemaV1.InferOutput<T> extends object
    ? StandardSchemaV1.InferOutput<T>
    : Record<string, unknown>
  : Record<string, unknown>

/**
 * Helper type to extract the input type from a standard schema
 *
 * @internal This is used for collection insert type inference
 */
export type InferSchemaInput<T> = T extends StandardSchemaV1
  ? StandardSchemaV1.InferInput<T> extends object
    ? StandardSchemaV1.InferInput<T>
    : Record<string, unknown>
  : Record<string, unknown>

export type TransactionState = `pending` | `persisting` | `completed` | `failed`

/**
 * Represents a utility function that can be attached to a collection
 */
export type Fn = (...args: Array<any>) => any

/**
 * A record of utilities (functions or getters) that can be attached to a collection
 */
export type UtilsRecord = Record<string, any>

/**
 *
 * @remarks `update` and `insert` are both represented as `Partial<T>`, but changes for `insert` could me made more precise by inferring the schema input type. In practice, this has almost 0 real world impact so it's not worth the added type complexity.
 *
 * @see  https://github.com/TanStack/db/pull/209#issuecomment-3053001206
 */
export type ResolveTransactionChanges<
  T extends object = Record<string, unknown>,
  TOperation extends OperationType = OperationType,
> = TOperation extends `delete` ? T : Partial<T>

/**
 * Represents a pending mutation within a transaction
 * Contains information about the original and modified data, as well as metadata
 */
export interface PendingMutation<
  T extends object = Record<string, unknown>,
  TOperation extends OperationType = OperationType,
  TCollection extends Collection<T, any, any, any, any> = Collection<
    T,
    any,
    any,
    any,
    any
  >,
> {
  mutationId: string
  // The state of the object before the mutation.
  original: TOperation extends `insert` ? {} : T
  // The result state of the object after all mutations.
  modified: T
  // Only the actual changes to the object by the mutation.
  changes: ResolveTransactionChanges<T, TOperation>
  globalKey: string

  key: any
  type: TOperation
  metadata: unknown
  syncMetadata: Record<string, unknown>
  /** Whether this mutation should be applied optimistically (defaults to true) */
  optimistic: boolean
  createdAt: Date
  updatedAt: Date
  collection: TCollection
}

/**
 * Configuration options for creating a new transaction
 */
export type MutationFnParams<T extends object = Record<string, unknown>> = {
  transaction: TransactionWithMutations<T>
}

export type MutationFn<T extends object = Record<string, unknown>> = (
  params: MutationFnParams<T>,
) => Promise<any>

/**
 * Represents a non-empty array (at least one element)
 */
export type NonEmptyArray<T> = [T, ...Array<T>]

/**
 * Utility type for a Transaction with at least one mutation
 * This is used internally by the Transaction.commit method
 */
export type TransactionWithMutations<
  T extends object = Record<string, unknown>,
  TOperation extends OperationType = OperationType,
> = Omit<Transaction<T>, `mutations`> & {
  /**
   * We must omit the `mutations` property from `Transaction<T>` before intersecting
   * because TypeScript intersects property types when the same property appears on
   * both sides of an intersection.
   *
   * Without `Omit`:
   * - `Transaction<T>` has `mutations: Array<PendingMutation<T>>`
   * - The intersection would create: `Array<PendingMutation<T>> & NonEmptyArray<PendingMutation<T, TOperation>>`
   * - When mapping over this array, TypeScript widens `TOperation` from the specific literal
   *   (e.g., `"delete"`) to the union `OperationType` (`"insert" | "update" | "delete"`)
   * - This causes `PendingMutation<T, OperationType>` to evaluate the conditional type
   *   `original: TOperation extends 'insert' ? {} : T` as `{} | T` instead of just `T`
   *
   * With `Omit`:
   * - We remove `mutations` from `Transaction<T>` first
   * - Then add back `mutations: NonEmptyArray<PendingMutation<T, TOperation>>`
   * - TypeScript can properly narrow `TOperation` to the specific literal type
   * - This ensures `mutation.original` is correctly typed as `T` (not `{} | T`) when mapping
   */
  mutations: NonEmptyArray<PendingMutation<T, TOperation>>
}

export interface TransactionConfig<T extends object = Record<string, unknown>> {
  /** Unique identifier for the transaction */
  id?: string
  /* If the transaction should autocommit after a mutate call or should commit be called explicitly */
  autoCommit?: boolean
  mutationFn: MutationFn<T>
  /** Custom metadata to associate with the transaction */
  metadata?: Record<string, unknown>
}

/**
 * Options for the createOptimisticAction helper
 */
export interface CreateOptimisticActionsOptions<
  TVars = unknown,
  T extends object = Record<string, unknown>,
> extends Omit<TransactionConfig<T>, `mutationFn`> {
  /** Function to apply optimistic updates locally before the mutation completes */
  onMutate: (vars: TVars) => void
  /** Function to execute the mutation on the server */
  mutationFn: (vars: TVars, params: MutationFnParams<T>) => Promise<any>
}

export type { Transaction }

type Value<TExtensions = never> =
  | string
  | number
  | boolean
  | bigint
  | null
  | TExtensions
  | Array<Value<TExtensions>>
  | { [key: string | number | symbol]: Value<TExtensions> }

export type Row<TExtensions = never> = Record<string, Value<TExtensions>>

export type OperationType = `insert` | `update` | `delete`

/**
 * Subscription status values
 */
export type SubscriptionStatus = `ready` | `loadingSubset`

/**
 * Event emitted when subscription status changes
 */
export interface SubscriptionStatusChangeEvent {
  type: `status:change`
  subscription: Subscription
  previousStatus: SubscriptionStatus
  status: SubscriptionStatus
}

/**
 * Event emitted when subscription status changes to a specific status
 */
export interface SubscriptionStatusEvent<T extends SubscriptionStatus> {
  type: `status:${T}`
  subscription: Subscription
  previousStatus: SubscriptionStatus
  status: T
}

/**
 * Event emitted when subscription is unsubscribed
 */
export interface SubscriptionUnsubscribedEvent {
  type: `unsubscribed`
  subscription: Subscription
}

/**
 * All subscription events
 */
export type SubscriptionEvents = {
  'status:change': SubscriptionStatusChangeEvent
  'status:ready': SubscriptionStatusEvent<`ready`>
  'status:loadingSubset': SubscriptionStatusEvent<`loadingSubset`>
  unsubscribed: SubscriptionUnsubscribedEvent
}

/**
 * Public interface for a collection subscription
 * Used by sync implementations to track subscription lifecycle
 */
export interface Subscription extends EventEmitter<SubscriptionEvents> {
  /** Current status of the subscription */
  readonly status: SubscriptionStatus
}

/**
 * Cursor expressions for pagination, passed separately from the main `where` clause.
 * The sync layer can choose to use cursor-based pagination (combining these with the where)
 * or offset-based pagination (ignoring these and using the `offset` parameter).
 *
 * Neither expression includes the main `where` clause - they are cursor-specific only.
 */
export type CursorExpressions = {
  /**
   * Expression for rows greater than (after) the cursor value.
   * For multi-column orderBy, this is a composite cursor using OR of conditions.
   * Example for [col1 ASC, col2 DESC] with values [v1, v2]:
   *   or(gt(col1, v1), and(eq(col1, v1), lt(col2, v2)))
   */
  whereFrom: BasicExpression<boolean>
  /**
   * Expression for rows equal to the current cursor value (first orderBy column only).
   * Used to handle tie-breaking/duplicates at the boundary.
   * Example: eq(col1, v1) or for Dates: and(gte(col1, v1), lt(col1, v1+1ms))
   */
  whereCurrent: BasicExpression<boolean>
  /**
   * The key of the last item that was loaded.
   * Can be used by sync layers for tracking or deduplication.
   */
  lastKey?: string | number
}

export type LoadSubsetOptions = {
  /** The where expression to filter the data (does NOT include cursor expressions) */
  where?: BasicExpression<boolean>
  /** The order by clause to sort the data */
  orderBy?: OrderBy
  /** The limit of the data to load */
  limit?: number
  /**
   * Cursor expressions for cursor-based pagination.
   * These are separate from `where` - the sync layer should combine them if using cursor-based pagination.
   * Neither expression includes the main `where` clause.
   */
  cursor?: CursorExpressions
  /**
   * Row offset for offset-based pagination.
   * The sync layer can use this instead of `cursor` if it prefers offset-based pagination.
   */
  offset?: number
  /**
   * The subscription that triggered the load.
   * Advanced sync implementations can use this for:
   * - LRU caching keyed by subscription
   * - Reference counting to track active subscriptions
   * - Subscribing to subscription events (e.g., finalization/unsubscribe)
   * @optional Available when called from CollectionSubscription, may be undefined for direct calls
   */
  subscription?: Subscription
}

export type LoadSubsetFn = (options: LoadSubsetOptions) => true | Promise<void>

export type UnloadSubsetFn = (options: LoadSubsetOptions) => void

export type CleanupFn = () => void

export type SyncConfigRes = {
  cleanup?: CleanupFn
  loadSubset?: LoadSubsetFn
  unloadSubset?: UnloadSubsetFn
}
export interface SyncConfig<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> {
  sync: (params: {
    collection: Collection<T, TKey, any, any, any>
    /**
     * Begin a new sync transaction.
     * @param options.immediate - When true, the transaction will be processed immediately
     *   even if there are persisting user transactions. Used by manual write operations.
     */
    begin: (options?: { immediate?: boolean }) => void
    write: (message: ChangeMessageOrDeleteKeyMessage<T, TKey>) => void
    commit: () => void
    markReady: () => void
    truncate: () => void
    metadata?: SyncMetadataApi<TKey>
  }) => void | CleanupFn | SyncConfigRes

  /**
   * Get the sync metadata for insert operations
   * @returns Record containing relation information
   */
  getSyncMetadata?: () => Record<string, unknown>

  /**
   * The row update mode used to sync to the collection.
   * @default `partial`
   * @description
   * - `partial`: Updates contain only the changes to the row.
   * - `full`: Updates contain the entire row.
   */
  rowUpdateMode?: `partial` | `full`
}

export interface SyncMetadataApi<
  TKey extends string | number = string | number,
> {
  row: {
    get: (key: TKey) => unknown | undefined
    set: (key: TKey, metadata: unknown) => void
    delete: (key: TKey) => void
  }
  collection: {
    get: (key: string) => unknown | undefined
    set: (key: string, value: unknown) => void
    delete: (key: string) => void
    list: (prefix?: string) => ReadonlyArray<{
      key: string
      value: unknown
    }>
  }
}

export interface ChangeMessage<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> {
  key: TKey
  value: T
  previousValue?: T
  type: OperationType
  metadata?: Record<string, unknown>
}

export type DeleteKeyMessage<TKey extends string | number = string | number> =
  Omit<ChangeMessage<any, TKey>, `value` | `previousValue` | `type`> & {
    type: `delete`
  }

export type ChangeMessageOrDeleteKeyMessage<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> = Omit<ChangeMessage<T>, `key`> | DeleteKeyMessage<TKey>

export type OptimisticChangeMessage<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> =
  | (ChangeMessage<T> & {
      // Is this change message part of an active transaction. Only applies to optimistic changes.
      isActive?: boolean
    })
  | (DeleteKeyMessage<TKey> & {
      // Is this change message part of an active transaction. Only applies to optimistic changes.
      isActive?: boolean
    })

/**
 * The Standard Schema interface.
 * This follows the standard-schema specification: https://github.com/standard-schema/standard-schema
 */
export type StandardSchema<T> = StandardSchemaV1 & {
  '~standard': {
    types?: {
      input: T
      output: T
    }
  }
}

/**
 * Type alias for StandardSchema
 */
export type StandardSchemaAlias<T = unknown> = StandardSchema<T>

export interface OperationConfig {
  metadata?: Record<string, unknown>
  /** Whether to apply optimistic updates immediately. Defaults to true. */
  optimistic?: boolean
}

export interface InsertConfig {
  metadata?: Record<string, unknown>
  /** Whether to apply optimistic updates immediately. Defaults to true. */
  optimistic?: boolean
}

export type UpdateMutationFnParams<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
> = {
  transaction: TransactionWithMutations<T, `update`>
  collection: Collection<T, TKey, TUtils>
}

export type InsertMutationFnParams<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
> = {
  transaction: TransactionWithMutations<T, `insert`>
  collection: Collection<T, TKey, TUtils>
}
export type DeleteMutationFnParams<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
> = {
  transaction: TransactionWithMutations<T, `delete`>
  collection: Collection<T, TKey, TUtils>
}

export type InsertMutationFn<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
  TReturn = any,
> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>

export type UpdateMutationFn<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
  TReturn = any,
> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>

export type DeleteMutationFn<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TUtils extends UtilsRecord = UtilsRecord,
  TReturn = any,
> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>

/**
 * Collection status values for lifecycle management
 * @example
 * // Check collection status
 * if (collection.status === "loading") {
 *   console.log("Collection is loading initial data")
 * } else if (collection.status === "ready") {
 *   console.log("Collection is ready for use")
 * }
 *
 * @example
 * // Status transitions
 * // idle → loading → ready (when markReady() is called)
 * // Any status can transition to → error or cleaned-up
 */
export type CollectionStatus =
  /** Collection is created but sync hasn't started yet (when startSync config is false) */
  | `idle`
  /** Sync has started and is loading data */
  | `loading`
  /** Collection has been explicitly marked ready via markReady() */
  | `ready`
  /** An error occurred during sync initialization */
  | `error`
  /** Collection has been cleaned up and resources freed */
  | `cleaned-up`

export type SyncMode = `eager` | `on-demand`

export interface BaseCollectionConfig<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  // Let TSchema default to `never` such that if a user provides T explicitly and no schema
  // then TSchema will be `never` otherwise if it would default to StandardSchemaV1
  // then it would conflict with the overloads of createCollection which
  // requires either T to be provided or a schema to be provided but not both!
  TSchema extends StandardSchemaV1 = never,
  TUtils extends UtilsRecord = UtilsRecord,
  TReturn = any,
> {
  // If an id isn't passed in, a UUID will be
  // generated for it.
  id?: string
  schema?: TSchema
  /**
   * Function to extract the ID from an object
   * This is required for update/delete operations which now only accept IDs
   * @param item The item to extract the ID from
   * @returns The ID string for the item
   * @example
   * // For a collection with a 'uuid' field as the primary key
   * getKey: (item) => item.uuid
   */
  getKey: (item: T) => TKey
  /**
   * Time in milliseconds after which the collection will be garbage collected
   * when it has no active subscribers. Defaults to 5 minutes (300000ms).
   */
  gcTime?: number
  /**
   * Whether to eagerly start syncing on collection creation.
   * When true, syncing begins immediately. When false, syncing starts when the first subscriber attaches.
   *
   * Note: Even with startSync=true, collections will pause syncing when there are no active
   * subscribers (typically when components querying the collection unmount), resuming when new
   * subscribers attach. This preserves normal staleTime/gcTime behavior.
   *
   * @default false
   */
  startSync?: boolean
  /**
   * Auto-indexing mode for the collection.
   * When enabled, indexes will be automatically created for simple where expressions.
   * @default "off"
   * @description
   * - "off": No automatic indexing (default). Use explicit indexes for better bundle size.
   * - "eager": Automatically create indexes for simple where expressions in subscribeChanges.
   *            Requires setting defaultIndexType.
   */
  autoIndex?: `off` | `eager`
  /**
   * Default index type to use when creating indexes without an explicit type.
   * Required for auto-indexing. Import from '@tanstack/db'.
   * @example
   * ```ts
   * import { BasicIndex } from '@tanstack/db'
   * const collection = createCollection({
   *   defaultIndexType: BasicIndex,
   *   autoIndex: 'eager',
   *   // ...
   * })
   * ```
   */
  defaultIndexType?: IndexConstructor<TKey>
  /**
   * Optional function to compare two items.
   * This is used to order the items in the collection.
   * @param x The first item to compare
   * @param y The second item to compare
   * @returns A number indicating the order of the items
   * @example
   * // For a collection with a 'createdAt' field
   * compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
   */
  compare?: (x: T, y: T) => number
  /**
   * The mode of sync to use for the collection.
   * @default `eager`
   * @description
   * - `eager`: syncs all data immediately on preload
   * - `on-demand`: syncs data in incremental snapshots when the collection is queried
   * The exact implementation of the sync mode is up to the sync implementation.
   */
  syncMode?: SyncMode
  /**
   * Optional asynchronous handler function called before an insert operation
   * @param params Object containing transaction and collection information
   * @returns Promise resolving to any value
   * @example
   * // Basic insert handler
   * onInsert: async ({ transaction, collection }) => {
   *   const newItem = transaction.mutations[0].modified
   *   await api.createTodo(newItem)
   * }
   *
   * @example
   * // Insert handler with multiple items
   * onInsert: async ({ transaction, collection }) => {
   *   const items = transaction.mutations.map(m => m.modified)
   *   await api.createTodos(items)
   * }
   *
   * @example
   * // Insert handler with error handling
   * onInsert: async ({ transaction, collection }) => {
   *   try {
   *     const newItem = transaction.mutations[0].modified
   *     const result = await api.createTodo(newItem)
   *     return result
   *   } catch (error) {
   *     console.error('Insert failed:', error)
   *     throw error // This will cause the transaction to fail
   *   }
   * }
   *
   * @example
   * // Insert handler with metadata
   * onInsert: async ({ transaction, collection }) => {
   *   const mutation = transaction.mutations[0]
   *   await api.createTodo(mutation.modified, {
   *     source: mutation.metadata?.source,
   *     timestamp: mutation.createdAt
   *   })
   * }
   */
  onInsert?: InsertMutationFn<T, TKey, TUtils, TReturn>

  /**
   * Optional asynchronous handler function called before an update operation
   * @param params Object containing transaction and collection information
   * @returns Promise resolving to any value
   * @example
   * // Basic update handler
   * onUpdate: async ({ transaction, collection }) => {
   *   const updatedItem = transaction.mutations[0].modified
   *   await api.updateTodo(updatedItem.id, updatedItem)
   * }
   *
   * @example
   * // Update handler with partial updates
   * onUpdate: async ({ transaction, collection }) => {
   *   const mutation = transaction.mutations[0]
   *   const changes = mutation.changes // Only the changed fields
   *   await api.updateTodo(mutation.original.id, changes)
   * }
   *
   * @example
   * // Update handler with multiple items
   * onUpdate: async ({ transaction, collection }) => {
   *   const updates = transaction.mutations.map(m => ({
   *     id: m.key,
   *     changes: m.changes
   *   }))
   *   await api.updateTodos(updates)
   * }
   *
   * @example
   * // Update handler with optimistic rollback
   * onUpdate: async ({ transaction, collection }) => {
   *   const mutation = transaction.mutations[0]
   *   try {
   *     await api.updateTodo(mutation.original.id, mutation.changes)
   *   } catch (error) {
   *     // Transaction will automatically rollback optimistic changes
   *     console.error('Update failed, rolling back:', error)
   *     throw error
   *   }
   * }
   */
  onUpdate?: UpdateMutationFn<T, TKey, TUtils, TReturn>
  /**
   * Optional asynchronous handler function called before a delete operation
   * @param params Object containing transaction and collection information
   * @returns Promise resolving to any value
   * @example
   * // Basic delete handler
   * onDelete: async ({ transaction, collection }) => {
   *   const deletedKey = transaction.mutations[0].key
   *   await api.deleteTodo(deletedKey)
   * }
   *
   * @example
   * // Delete handler with multiple items
   * onDelete: async ({ transaction, collection }) => {
   *   const keysToDelete = transaction.mutations.map(m => m.key)
   *   await api.deleteTodos(keysToDelete)
   * }
   *
   * @example
   * // Delete handler with confirmation
   * onDelete: async ({ transaction, collection }) => {
   *   const mutation = transaction.mutations[0]
   *   const shouldDelete = await confirmDeletion(mutation.original)
   *   if (!shouldDelete) {
   *     throw new Error('Delete cancelled by user')
   *   }
   *   await api.deleteTodo(mutation.original.id)
   * }
   *
   * @example
   * // Delete handler with optimistic rollback
   * onDelete: async ({ transaction, collection }) => {
   *   const mutation = transaction.mutations[0]
   *   try {
   *     await api.deleteTodo(mutation.original.id)
   *   } catch (error) {
   *     // Transaction will automatically rollback optimistic changes
   *     console.error('Delete failed, rolling back:', error)
   *     throw error
   *   }
   * }
   */
  onDelete?: DeleteMutationFn<T, TKey, TUtils, TReturn>

  /**
   * Specifies how to compare data in the collection.
   * This should be configured to match data ordering on the backend.
   * E.g., when using the Electric DB collection these options
   *       should match the database's collation settings.
   */
  defaultStringCollation?: StringCollationConfig

  utils?: TUtils
}

export interface CollectionConfig<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TSchema extends StandardSchemaV1 = never,
  TUtils extends UtilsRecord = UtilsRecord,
> extends BaseCollectionConfig<T, TKey, TSchema, TUtils> {
  sync: SyncConfig<T, TKey>
}

export type SingleResult = {
  singleResult: true
}

export type NonSingleResult = {
  singleResult?: never
}

export type MaybeSingleResult = {
  /**
   * If enabled the collection will return a single object instead of an array
   */
  singleResult?: true
}

// Only used for live query collections
export type CollectionConfigSingleRowOption<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
  TSchema extends StandardSchemaV1 = never,
  TUtils extends UtilsRecord = {},
> = CollectionConfig<T, TKey, TSchema, TUtils> & MaybeSingleResult

export type ChangesPayload<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> = Array<ChangeMessage<WithVirtualProps<T, TKey>, TKey>>

/**
 * An input row from a collection
 */
export type InputRow = [unknown, Record<string, unknown>]

/**
 * A keyed stream is a stream of rows
 * This is used as the inputs from a collection to a query
 */
export type KeyedStream = IStreamBuilder<InputRow>

/**
 * Result stream type representing the output of compiled queries
 * Always returns [key, [result, orderByIndex]] where orderByIndex is undefined for unordered queries
 */
export type ResultStream = IStreamBuilder<[unknown, [any, string | undefined]]>

/**
 * A namespaced row is a row withing a pipeline that had each table wrapped in its alias
 */
export type NamespacedRow = Record<string, Record<string, unknown>>

/**
 * A keyed namespaced row is a row with a key and a namespaced row
 * This is the main representation of a row in a query pipeline
 */
export type KeyedNamespacedRow = [unknown, NamespacedRow]

/**
 * A namespaced and keyed stream is a stream of rows
 * This is used throughout a query pipeline and as the output from a query without
 * a `select` clause.
 */
export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>

/**
 * Options for subscribing to collection changes
 */
export interface SubscribeChangesOptions<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> {
  /** Whether to include the current state as initial changes */
  includeInitialState?: boolean
  /**
   * Callback function for filtering changes using a row proxy.
   * The callback receives a proxy object that records property access,
   * allowing you to use query builder functions like `eq`, `gt`, etc.
   *
   * @example
   * ```ts
   * import { eq } from "@tanstack/db"
   *
   * collection.subscribeChanges(callback, {
   *   where: (row) => eq(row.status, "active")
   * })
   * ```
   */
  where?: (row: SingleRowRefProxy<WithVirtualProps<T, TKey>>) => any
  /** Pre-compiled expression for filtering changes */
  whereExpression?: BasicExpression<boolean>
  /**
   * Listener for subscription status changes.
   * Registered BEFORE any snapshot is requested, ensuring no status transitions are missed.
   * @internal
   */
  onStatusChange?: (event: SubscriptionStatusChangeEvent) => void
  /**
   * Optional orderBy to include in loadSubset for query-specific cache keys.
   * @internal
   */
  orderBy?: OrderBy
  /**
   * Optional limit to include in loadSubset for query-specific cache keys.
   * @internal
   */
  limit?: number
  /**
   * Callback that receives the loadSubset result (Promise or true) from requestSnapshot.
   * Allows the caller to directly track the loading promise for isReady status.
   * @internal
   */
  onLoadSubsetResult?: (result: Promise<void> | true) => void
}

export interface SubscribeChangesSnapshotOptions<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> extends Omit<SubscribeChangesOptions<T, TKey>, `includeInitialState`> {
  orderBy?: OrderBy
  limit?: number
}

/**
 * Options for getting current state as changes
 */
export interface CurrentStateAsChangesOptions {
  /** Pre-compiled expression for filtering the current state */
  where?: BasicExpression<boolean>
  orderBy?: OrderBy
  limit?: number
  optimizedOnly?: boolean
}

/**
 * Function type for listening to collection changes
 * @param changes - Array of change messages describing what happened
 * @example
 * // Basic change listener
 * const listener: ChangeListener = (changes) => {
 *   changes.forEach(change => {
 *     console.log(`${change.type}: ${change.key}`, change.value)
 *   })
 * }
 *
 * collection.subscribeChanges(listener)
 *
 * @example
 * // Handle different change types
 * const listener: ChangeListener<Todo> = (changes) => {
 *   for (const change of changes) {
 *     switch (change.type) {
 *       case 'insert':
 *         addToUI(change.value)
 *         break
 *       case 'update':
 *         updateInUI(change.key, change.value, change.previousValue)
 *         break
 *       case 'delete':
 *         removeFromUI(change.key)
 *         break
 *     }
 *   }
 * }
 */
export type ChangeListener<
  T extends object = Record<string, unknown>,
  TKey extends string | number = string | number,
> = (changes: Array<ChangeMessage<WithVirtualProps<T, TKey>, TKey>>) => void

// Adapted from https://github.com/sindresorhus/type-fest
// MIT License Copyright (c) Sindre Sorhus

type BuiltIns =
  | null
  | undefined
  | string
  | number
  | boolean
  | symbol
  | bigint
  | void
  | Date
  | RegExp

type HasMultipleCallSignatures<
  T extends (...arguments_: Array<any>) => unknown,
> = T extends {
  (...arguments_: infer A): unknown
  (...arguments_: infer B): unknown
}
  ? B extends A
    ? A extends B
      ? false
      : true
    : true
  : false

type WritableMapDeep<MapType extends ReadonlyMap<unknown, unknown>> =
  MapType extends ReadonlyMap<infer KeyType, infer ValueType>
    ? Map<WritableDeep<KeyType>, WritableDeep<ValueType>>
    : MapType

type WritableSetDeep<SetType extends ReadonlySet<unknown>> =
  SetType extends ReadonlySet<infer ItemType>
    ? Set<WritableDeep<ItemType>>
    : SetType

type WritableObjectDeep<ObjectType extends object> = {
  -readonly [KeyType in keyof ObjectType]: WritableDeep<ObjectType[KeyType]>
}

type WritableArrayDeep<ArrayType extends ReadonlyArray<unknown>> =
  ArrayType extends readonly []
    ? []
    : ArrayType extends readonly [...infer U, infer V]
      ? [...WritableArrayDeep<U>, WritableDeep<V>]
      : ArrayType extends readonly [infer U, ...infer V]
        ? [WritableDeep<U>, ...WritableArrayDeep<V>]
        : ArrayType extends ReadonlyArray<infer U>
          ? Array<WritableDeep<U>>
          : ArrayType extends Array<infer U>
            ? Array<WritableDeep<U>>
            : ArrayType

export type WritableDeep<T> = T extends BuiltIns
  ? T
  : T extends (...arguments_: Array<any>) => unknown
    ? {} extends WritableObjectDeep<T>
      ? T
      : HasMultipleCallSignatures<T> extends true
        ? T
        : ((...arguments_: Parameters<T>) => ReturnType<T>) &
            WritableObjectDeep<T>
    : T extends ReadonlyMap<unknown, unknown>
      ? WritableMapDeep<T>
      : T extends ReadonlySet<unknown>
        ? WritableSetDeep<T>
        : T extends ReadonlyArray<unknown>
          ? WritableArrayDeep<T>
          : T extends object
            ? WritableObjectDeep<T>
            : unknown

export type MakeOptional<T, K extends keyof T> = Omit<T, K> &
  Partial<Pick<T, K>>
