import {Observable} from 'rxjs'
import {RawPatch} from 'mendoza'
import {ReconnectEvent} from '@sanity/client'
import {SanityClient} from '@sanity/client'
import {WelcomeEvent} from '@sanity/client'

export declare interface AccessibleDocumentResult {
  id: string
  document: SanityDocumentBase
  accessible: true
}

export declare type AnyArray<T = any> = T[] | readonly T[]

export declare type AnyEmptyArray = [] | readonly []

export declare type AnyOp = SetOp<unknown> | SetIfMissingOp<unknown> | UnsetOp

export declare type ArrayOp =
  | InsertOp<AnyArray, RelativePosition, Index | KeyedPathElement>
  | UpsertOp<AnyArray, RelativePosition, Index | KeyedPathElement>
  | ReplaceOp<AnyArray, Index | KeyedPathElement>
  | TruncateOp
  | RemoveOp<Index | KeyedPathElement>

export declare type AssignOp<T extends object = object> = {
  type: 'assign'
  value: T
}

export declare type ByIndex<P extends number, T extends AnyArray> = T[P]

export declare type Concat<
  R extends Result<any, any>,
  Arr extends any[],
> = R[1] extends any[] ? Ok<[...R[1], ...Arr]> : R

export declare type ConcatInner<
  R extends Result<any, any>,
  R2 extends Result<any, any>,
> = R2[1] extends any[] ? Concat<R, R2[1]> : R2

export declare type Conflict = {
  path: Path
  error: Error
  base: SanityDocumentBase | undefined
  local: SanityDocumentBase | undefined
}

/**
 * Creates a function that can be used to listen for events that happens in a single document
 * Features
 *  - builtin retrying and connection recovery (track disconnected state by listening for `reconnect` events)
 *  - builtin mutation event ordering (they might arrive out of order), lost events detection (/listen endpoint doesn't guarantee delivery) and recovery
 *  - discards already-applied mutation events received while fetching the initial document snapshot
 * @param options
 */
export declare function createDocumentEventListener(options: {
  loadDocument: DocumentLoader
  listenerEvents: Observable<
    WelcomeEvent | ListenerMutationEvent | ReconnectEvent
  >
}): <Doc extends SanityDocumentBase>(
  documentId: string,
) => Observable<ListenerEvent>

/**
 * Creates a "dataloader" style document loader that fetches from the /doc endpoint
 * @param {FetchDocuments} fetchDocuments - The client instance used for fetching documents.
 * @param options
 */
export declare function createDocumentLoader(
  fetchDocuments: FetchDocuments,
  options?: {
    durationSelector?: () => Observable<unknown>
    tag?: string
  },
): (key: string) => Observable<DocumentResult>

export declare function createDocumentLoaderFromClient(
  client: SanityClient,
  options?: {
    durationSelector?: () => Observable<unknown>
    tag?: string
  },
): (key: string) => Observable<DocumentResult>

/**
 * Creates a function that can be used to listen for document updates
 * Emits the latest snapshot of the document along with the latest event
 * @param options
 */
export declare function createDocumentUpdateListener(options: {
  listenDocumentEvents: (documentId: string) => Observable<ListenerEvent>
}): <Doc extends SanityDocumentBase>(
  documentId: string,
) => Observable<DocumentUpdate<Doc>>

export declare function createIdSetListener(
  listen: IdSetListenFn,
  fetch: FetchDocumentIdsFn,
): (
  queryFilter: string,
  params: QueryParams,
  options?: {
    tag?: string
  },
) => Observable<DocumentIdSetEvent>

export declare function createIdSetListenerFromClient(
  client: SanityClient,
): void

export declare type CreateIfNotExistsMutation<Doc extends SanityDocumentBase> =
  {
    type: 'createIfNotExists'
    document: Doc
  }

export declare function createMockBackendAPI(): MockBackendAPI

export declare type CreateMutation<
  Doc extends Optional<SanityDocumentBase, '_id'>,
> = {
  type: 'create'
  document: Doc
}

/**
 * Creates a local dataset that allows subscribing to documents by id and submitting mutations to be optimistically applied
 * @param backend
 */
export declare function createOptimisticStore(
  backend: OptimisticStoreBackend,
): OptimisticStore

export declare function createOptimisticStoreClientBackend(
  client: SanityClient,
): OptimisticStoreBackend

export declare function createOptimisticStoreMockBackend(
  backendAPI: MockBackendAPI,
): OptimisticStoreBackend

export declare type CreateOrReplaceMutation<Doc extends SanityDocumentBase> = {
  type: 'createOrReplace'
  document: Doc
}

/**
 * @param listenDocumentUpdates – a function that takes a document id and returns  an observable of document snapshots
 * @param options
 */
export declare function createReadOnlyStore(
  listenDocumentUpdates: DocumentUpdateListener<SanityDocumentBase>,
  options?: {
    shutdownDelay?: number
  },
): ReadOnlyDocumentStore

/**
 * Creates a (low level) shared listener that will emit 'welcome' for all new subscribers immediately, and thereafter emit every listener event, including welcome, mutation, and reconnects
 * Useful for cases where you need control of how the listen request is set up
 */
export declare function createSharedListener(
  listen: SharedListenerListenFn,
  options?: ListenerOptions,
): Observable<WelcomeEvent | ListenerMutationEvent | ReconnectEvent>

/**
 * Creates a (low level) shared listener that will emit 'welcome' for all new subscribers immediately, and thereafter emit every listener event, including welcome, mutation, and reconnects
 * Requires a Sanity client instance
 */
export declare function createSharedListenerFromClient(
  client: SanityClient,
  options?: ListenerOptions,
): Observable<WelcomeEvent | ListenerMutationEvent | ReconnectEvent>

export declare type DecOp<Amount extends number> = {
  type: 'dec'
  amount: Amount
}

export declare type DeleteMutation = {
  type: 'delete'
  id: string
}

export declare type DiffMatchPatchOp = {
  type: 'diffMatchPatch'
  value: string
}

export declare type Digit =
  | '0'
  | '1'
  | '2'
  | '3'
  | '4'
  | '5'
  | '6'
  | '7'
  | '8'
  | '9'

export declare interface DocEndpointResponse {
  documents: SanityDocumentBase[]
  omitted: OmittedDocument[]
}

export declare type DocumentIdSetEvent =
  | {
      type: 'sync'
      documentIds: string[]
    }
  | {
      type: 'reconnect'
    }
  | {
      type: 'op'
      op: 'add' | 'remove'
      documentId: string
    }

export declare type DocumentIdSetState = {
  status: 'connecting' | 'reconnecting' | 'connected'
  event: DocumentIdSetEvent | InitialEvent
  snapshot: string[]
}

export declare type DocumentLoader = (
  documentIds: string,
) => Observable<DocumentResult>

export declare type DocumentMap<Doc extends SanityDocumentBase> = {
  get(id: string): Doc | undefined
  set(id: string, doc: Doc | undefined): void
  delete(id: string): void
}

export declare interface DocumentMutationUpdate<
  Doc extends SanityDocumentBase,
> {
  documentId: string
  snapshot: Doc | undefined
  event: ListenerMutationEvent
}

export declare interface DocumentReconnectUpdate<
  Doc extends SanityDocumentBase,
> {
  documentId: string
  snapshot: Doc | undefined
  event: ListenerReconnectEvent
}

export declare type DocumentResult =
  | AccessibleDocumentResult
  | InaccessibleDocumentResult

export declare interface DocumentSyncUpdate<Doc extends SanityDocumentBase> {
  documentId: string
  snapshot: Doc | undefined
  event: ListenerSyncEvent<Doc>
}

export declare type DocumentUpdate<Doc extends SanityDocumentBase> =
  | DocumentSyncUpdate<Doc>
  | DocumentMutationUpdate<Doc>
  | DocumentReconnectUpdate<any>

export declare type DocumentUpdateListener<Doc extends SanityDocumentBase> = (
  id: string,
) => Observable<DocumentUpdate<Doc>>

export declare type ElementType<T extends AnyArray> =
  T extends AnyArray<infer E> ? E : unknown

export declare type Err<E> = Result<E, null>

export declare type FetchDocumentIdsFn = (
  query: string,
  params?: QueryParams,
  options?: {
    tag?: string
  },
) => Observable<string[]>

export declare type FetchDocuments = (
  ids: string[],
) => Observable<DocEndpointResponse>

export declare type FindBy<P, T extends AnyArray> = T extends AnyEmptyArray
  ? undefined
  : T[0] extends P
    ? T[0]
    : T extends [any, ...infer Tail] | readonly [any, ...infer Tail]
      ? FindBy<P, Tail>
      : ElementType<T>

export declare type FindInArray<
  P extends KeyedPathElement | number,
  T extends AnyArray,
> = P extends KeyedPathElement
  ? FindBy<P, T>
  : P extends number
    ? ByIndex<P, T>
    : never

export declare type Get<
  P extends number | KeyedPathElement | Readonly<KeyedPathElement> | string,
  T,
> = T extends AnyArray
  ? P extends KeyedPathElement | Readonly<KeyedPathElement> | number
    ? FindInArray<P, T>
    : undefined
  : P extends keyof T
    ? T[P]
    : never

export declare type GetAtPath<
  P extends readonly PathElement[],
  T,
> = P extends []
  ? T
  : P extends [infer Head, ...infer Tail]
    ? Head extends PathElement
      ? Tail extends PathElement[]
        ? GetAtPath<Tail, Get<Head, T>>
        : undefined
      : undefined
    : undefined

export declare function getAtPath<const Head extends PathElement, const T>(
  path: [head: Head],
  value: T,
): Get<Head, T>

export declare function getAtPath<
  const Head extends PathElement,
  const Tail extends PathElement[],
  T,
>(path: [head: Head, ...tail: Tail], value: T): GetAtPath<[Head, ...Tail], T>

export declare function getAtPath<T>(path: [], value: T): T

export declare function getAtPath(path: Path, value: unknown): unknown

export declare type IdSetListenFn = (
  query: string,
  params?: QueryParams,
  options?: {
    visibility: 'transaction'
    events: ['welcome', 'mutation', 'reconnect']
    includeResult: false
    includeMutations: false
    tag?: string
  },
) => Observable<ListenerEndpointEvent>

export declare interface InaccessibleDocumentResult {
  accessible: false
  id: string
  reason: InaccessibleReason
}

export declare type InaccessibleReason = 'existence' | 'permission'

export declare type IncOp<Amount extends number> = {
  type: 'inc'
  amount: Amount
}

export declare type Index = number

export declare type InitialEvent = {
  type: 'connect'
}

export declare type Insert = {
  before?: string
  after?: string
  replace?: string
  items: any[]
}

export declare type InsertMethod = 'sorted' | 'prepend' | 'append'

export declare type InsertOp<
  Items extends AnyArray,
  Pos extends RelativePosition,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'insert'
  referenceItem: ReferenceItem
  position: Pos
  items: Items
}

export declare function isArrayElement(
  element: PathElement,
): element is KeyedPathElement | number

export declare function isElementEqual(
  segmentA: PathElement,
  segmentB: PathElement,
): boolean

export declare function isEqual(path: Path, otherPath: Path): boolean

export declare function isIndexElement(segment: PathElement): segment is number

export declare function isKeyedElement(
  element: PathElement,
): element is KeyedPathElement

export declare function isKeyElement(
  segment: PathElement,
): segment is KeyedPathElement

export declare function isPropertyElement(
  element: PathElement,
): element is string

export declare type KeyedPathElement = {
  _key: string
}

export declare type ListenerChannelErrorEvent = {
  type: 'channelError'
  message: string
}

export declare type ListenerDisconnectEvent = {
  type: 'disconnect'
  reason: string
}

export declare type ListenerEndpointEvent =
  | ListenerWelcomeEvent
  | ListenerMutationEvent
  | ListenerReconnectEvent
  | ListenerChannelErrorEvent
  | ListenerDisconnectEvent

export declare type ListenerEvent<
  Doc extends SanityDocumentBase = SanityDocumentBase,
> = ListenerSyncEvent<Doc> | ListenerMutationEvent | ListenerReconnectEvent

export declare interface ListenerMutationEvent {
  type: 'mutation'
  documentId: string
  transactionId: string
  resultRev?: string
  previousRev?: string
  effects?: {
    apply: RawPatch
  }
  mutations: SanityMutation[]
  transition: 'update' | 'appear' | 'disappear'
}

export declare interface ListenerOptions {
  /**
   * Provide a custom filter to the listener. By default, this listener will include all events
   * Note: make sure the filter includes events from documents you will subscribe to.
   */
  filter?: string
  /**
   * Whether to include system documents or not
   * This will be ignored if a custom filter is provided
   */
  includeSystemDocuments?: boolean
  /**
   * How long after the last subscriber is unsubscribed to keep the connection open
   */
  shutdownDelay?: number
  /**
   * Include mutations in listener events
   */
  includeMutations?: boolean
  /**
   * Request tag
   */
  tag?: string
}

export declare interface ListenerReconnectEvent {
  type: 'reconnect'
}

export declare interface ListenerSyncEvent<
  Doc extends SanityDocumentBase = SanityDocumentBase,
> {
  type: 'sync'
  document: Doc | undefined
}

export declare type ListenerWelcomeEvent = {
  type: 'welcome'
  listenerName: string
}

export declare type MapTuple<T, U> = {
  [K in keyof T]: U
}

export declare type Merge<R extends Result<any, any>, E> = R[0] extends null
  ? Ok<R[1] & E>
  : R

export declare type MergeInner<
  R extends Result<any, any>,
  R2 extends Result<any, any>,
> = R2[0] extends null ? Merge<R, R2[1]> : R

/**
 * This is the interface that a mock backend instance needs to implement
 */
export declare interface MockBackendAPI {
  listen(query: string): Observable<ListenerEndpointEvent>
  getDocuments(ids: string[]): Observable<DocEndpointResponse>
  submit(transaction: Transaction): Observable<SubmitResult>
}

export declare type Mutation<Doc extends SanityDocumentBase = any> =
  | CreateMutation<Doc>
  | CreateIfNotExistsMutation<Doc>
  | CreateOrReplaceMutation<Doc>
  | DeleteMutation
  | PatchMutation

/**
 * A mutation group represents an incoming, locally added group of mutations
 * They can either be transactional or non-transactional
 * - Transactional means that they must be submitted as a separate transaction (with an optional id) and no other mutations can be mixed with it
 * – Non-transactional means that they can be combined with other mutations
 */
export declare type MutationGroup =
  | NonTransactionalMutationGroup
  | TransactionalMutationGroup

export declare interface MutationResult {}

export declare type NodePatch<
  P extends Path = Path,
  O extends Operation = Operation,
> = {
  path: P
  op: O
}

export declare type NodePatchList =
  | [NodePatch, ...NodePatch[]]
  | NodePatch[]
  | readonly NodePatch[]
  | readonly [NodePatch, ...NodePatch[]]

export declare interface NonTransactionalMutationGroup {
  transaction: false
  mutations: Mutation[]
}

export declare function normalize(path: string | Readonly<Path>): Readonly<Path>

export declare type NumberOp = IncOp<number> | DecOp<number>

export declare type ObjectOp = AssignOp | UnassignOp

export declare type Ok<V> = Result<null, V>

export declare interface OmittedDocument {
  id: string
  reason: 'existence' | 'permission'
}

export declare type OnlyDigits<S> = S extends `${infer Head}${infer Tail}`
  ? Head extends Digit
    ? Tail extends ''
      ? true
      : OnlyDigits<Tail> extends true
        ? true
        : false
    : false
  : false

export declare type Operation = PrimitiveOp | ArrayOp | ObjectOp

export declare interface OptimisticDocumentEvent {
  type: 'optimistic'
  id: string
  before: SanityDocumentBase | undefined
  after: SanityDocumentBase | undefined
  mutations: Mutation[]
  stagedChanges: Mutation[]
}

export declare interface OptimisticStore {
  meta: {
    /**
     * A stream of events for anything that happens in the store
     */
    events: Observable<OptimisticDocumentEvent | RemoteDocumentEvent>
    /**
     * A stream of current staged changes
     */
    stage: Observable<MutationGroup[]>
    /**
     * A stream of current conflicts. TODO: Needs more work
     */
    conflicts: Observable<Conflict[]>
  }
  /**
   * Applies the given mutations. Mutations are not guaranteed to be submitted in the same transaction
   * Can this mutate both local and remote documents at the same time
   */
  mutate(mutation: Mutation[]): MutationResult
  /**
   * Makes sure the given mutations are posted in a single transaction
   */
  transaction(
    transaction:
      | {
          id?: string
          mutations: Mutation[]
        }
      | Mutation[],
  ): MutationResult
  /**
   * Checkout a document for editing. This is required to be able to see optimistic changes
   */
  listen(id: string): Observable<SanityDocumentBase | undefined>
  /**
   * Listen for events for a given document id
   */
  listenEvents(
    id: string,
  ): Observable<RemoteDocumentEvent | OptimisticDocumentEvent>
  /**
   * Optimize list of pending mutations
   */
  optimize(): void
  /**
   * Submit pending mutations
   */
  submit(): Promise<SubmitResult[]>
}

export declare interface OptimisticStoreBackend {
  /**
   * Sets up a subscription to a document
   * The first event should either be a sync event or an error event.
   * After that, it should emit mutation events, error events or sync events
   * @param id
   */
  listen: (id: string) => Observable<ListenerEvent>
  submit: (mutationGroups: Transaction) => Observable<SubmitResult>
}

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

export declare function parse<const T extends string>(path: T): StringToPath<T>

export declare type ParseAllProps<Props extends string[]> = Props extends [
  `${infer Head}`,
  ...infer Tail,
]
  ? Tail extends string[]
    ? ConcatInner<ParseProperty<Trim<Head>>, ParseAllProps<Tail>>
    : ParseProperty<Trim<Head>>
  : Ok<[]>

export declare type ParseError<T extends string = 'unknown'> = T & {
  error: true
}

export declare type ParseExpressions<S extends string> =
  S extends `[${infer Expr}]${infer Remainder}`
    ? Trim<Remainder> extends ''
      ? ToArray<ParseInnerExpression<Trim<Expr>>>
      : ConcatInner<
          ToArray<ParseInnerExpression<Trim<Expr>>>,
          ParseExpressions<Remainder>
        >
    : Err<ParseError<`Cannot parse object from "${S}"`>>

export declare type ParseInnerExpression<S extends string> = S extends ''
  ? Err<ParseError<'Saw an empty expression'>>
  : Try<ParseNumber<S>, ParseObject<S>>

export declare type ParseKVPair<S extends string> =
  Split<S, '=='> extends [`${infer LHS}`, `${infer RHS}`]
    ? ParseValue<Trim<RHS>> extends infer Res
      ? Res extends [null, infer Value]
        ? Ok<{
            [P in Trim<LHS>]: Value
          }>
        : Err<
            ParseError<`Can't parse right hand side as a value in "${S}" (Invalid value ${RHS})`>
          >
      : never
    : Err<ParseError<`Can't parse key value pair from ${S}`>>

export declare type ParseNumber<S extends string> =
  S extends `${infer Head}${infer Tail}`
    ? Head extends '-'
      ? OnlyDigits<Tail> extends true
        ? Ok<ToNumber<S>>
        : Err<ParseError<`Invalid integer value "${S}"`>>
      : OnlyDigits<S> extends true
        ? Ok<ToNumber<S>>
        : Err<ParseError<`Invalid integer value "${S}"`>>
    : Err<ParseError<`Invalid integer value "${S}"`>>

export declare type ParseObject<S extends string> =
  S extends `${infer Pair},${infer Remainder}`
    ? Trim<Remainder> extends ''
      ? Ok<Record<never, never>>
      : MergeInner<ParseKVPair<Pair>, ParseObject<Remainder>>
    : ParseKVPair<S>

export declare type ParseProperty<S extends string> =
  Trim<S> extends ''
    ? Err<ParseError<'Empty property'>>
    : Split<Trim<S>, '[', true> extends [`${infer Prop}`, `${infer Expression}`]
      ? Trim<Prop> extends ''
        ? ParseExpressions<Trim<Expression>>
        : ConcatInner<Ok<[Trim<Prop>]>, ParseExpressions<Trim<Expression>>>
      : Ok<[Trim<S>]>

export declare type ParseValue<S extends string> = string extends S
  ? Err<ParseError<'ParseValue got generic string type'>>
  : S extends 'null'
    ? Ok<null>
    : S extends 'true'
      ? Ok<true>
      : S extends 'false'
        ? Ok<false>
        : S extends `"${infer Value}"`
          ? Ok<Value>
          : Try<
              ParseNumber<S>,
              Err<
                ParseError<`ParseValue failed. Can't parse "${S}" as a value.`>
              >
            >

export declare type PatchMutation<
  Patches extends NodePatchList = NodePatchList,
> = {
  type: 'patch'
  id: string
  patches: Patches
  options?: PatchOptions
}

export declare type PatchOptions = {
  ifRevision?: string
}

export declare type Path = PathElement[] | readonly PathElement[]

export declare type PathElement = PropertyName | Index | KeyedPathElement

export declare type PrimitiveOp = AnyOp | StringOp | NumberOp

export declare type PropertyName = string

export declare type QueryParams = Record<
  string,
  string | number | boolean | (string | number | boolean)[]
>

export declare interface ReadOnlyDocumentStore {
  listenDocument: <Doc extends SanityDocumentBase>(
    id: string,
  ) => Observable<DocumentUpdate<Doc>>
  listenDocuments: <
    Doc extends SanityDocumentBase,
    const IdTuple extends string[],
  >(
    id: IdTuple,
  ) => Observable<MapTuple<IdTuple, DocumentUpdate<Doc>>>
}

export declare type RelativePosition = 'before' | 'after'

export declare type RemoteDocumentEvent = RemoteSyncEvent | RemoteMutationEvent

export declare interface RemoteMutationEvent {
  type: 'mutation'
  id: string
  before: {
    local: SanityDocumentBase | undefined
    remote: SanityDocumentBase | undefined
  }
  after: {
    local: SanityDocumentBase | undefined
    remote: SanityDocumentBase | undefined
  }
  effects?: {
    apply: RawPatch
  }
  previousRev?: string
  resultRev?: string
  mutations: Mutation[]
  rebasedStage: MutationGroup[]
}

export declare interface RemoteSyncEvent {
  type: 'sync'
  id: string
  before: {
    local: SanityDocumentBase | undefined
    remote: SanityDocumentBase | undefined
  }
  after: {
    local: SanityDocumentBase | undefined
    remote: SanityDocumentBase | undefined
  }
  rebasedStage: MutationGroup[]
}

export declare type RemoveOp<ReferenceItem extends Index | KeyedPathElement> = {
  type: 'remove'
  referenceItem: ReferenceItem
}

export declare type ReplaceOp<
  Items extends AnyArray,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'replace'
  referenceItem: ReferenceItem
  items: Items
}

/**
 * These are fixed, and it's up to the implementation of the listen function to turn them into request parameters
 */
export declare interface RequestOptions {
  events: ['welcome', 'mutation', 'reconnect']
  includeResult: false
  includePreviousRevision: false
  visibility: 'transaction'
  effectFormat: 'mendoza'
  includeMutations?: boolean
  tag?: string
}

export declare type Result<E, V> = [E, V]

export declare type SafePath<S extends string> = StripError<StringToPath<S>>

export declare type SanityCreateIfNotExistsMutation<
  Doc extends SanityDocumentBase,
> = {
  createIfNotExists: Doc
}

export declare type SanityCreateMutation<Doc extends SanityDocumentBase> = {
  create: Doc
}

export declare type SanityCreateOrReplaceMutation<
  Doc extends SanityDocumentBase,
> = {
  createOrReplace: Doc
}

export declare type SanityDecPatch = {
  id: string
  dec: {
    [path: string]: number
  }
}

export declare type SanityDeleteMutation = {
  delete: {
    id: string
  }
}

export declare type SanityDiffMatchPatch = {
  id: string
  diffMatchPatch: {
    [path: string]: string
  }
}

export declare interface SanityDocumentBase {
  _id?: string
  _type: string
  _createdAt?: string
  _updatedAt?: string
  _rev?: string
}

export declare type SanityIncPatch = {
  id: string
  inc: {
    [path: string]: number
  }
}

export declare type SanityInsertPatch = {
  id: string
  insert: Insert
}

export declare type SanityMutation<
  Doc extends SanityDocumentBase = SanityDocumentBase,
> =
  | SanityCreateMutation<Doc>
  | SanityCreateIfNotExistsMutation<Doc>
  | SanityCreateOrReplaceMutation<Doc>
  | SanityDeleteMutation
  | SanityPatchMutation

export declare type SanityPatch =
  | SanitySetPatch
  | SanityUnsetPatch
  | SanityInsertPatch
  | SanitySetIfMissingPatch
  | SanityDiffMatchPatch
  | SanityIncPatch
  | SanityDecPatch

export declare type SanityPatchMutation = {
  patch:
    | SanitySetPatch
    | SanitySetIfMissingPatch
    | SanityDiffMatchPatch
    | SanityInsertPatch
    | SanityUnsetPatch
}

export declare type SanitySetIfMissingPatch = {
  id: string
  setIfMissing: {
    [path: string]: any
  }
}

export declare type SanitySetPatch = {
  id: string
  set: {
    [path: string]: any
  }
}

export declare type SanityUnsetPatch = {
  id: string
  unset: string[]
}

export declare type SetIfMissingOp<T> = {
  type: 'setIfMissing'
  value: T
}

export declare type SetOp<T> = {
  type: 'set'
  value: T
}

export declare type SharedListenerListenFn = (
  query: string,
  queryParams: QueryParams,
  options: RequestOptions,
) => Observable<ListenerEndpointEvent>

export declare type Split<
  S extends string,
  Char extends string,
  IncludeSeparator extends boolean = false,
> = S extends `${infer First}${Char}${infer Remainder}`
  ? [First, `${IncludeSeparator extends true ? Char : ''}${Remainder}`]
  : [S]

export declare type SplitAll<
  S extends string,
  Char extends string,
> = S extends `${infer First}${Char}${infer Remainder}`
  ? [First, ...SplitAll<Remainder, Char>]
  : [S]

export declare function startsWith(parentPath: Path, path: Path): boolean

export declare function stringify(pathArray: Path): string

export declare type StringOp = DiffMatchPatchOp

export declare type StringToPath<S extends string> = Unwrap<
  ParseAllProps<SplitAll<Trim<S>, '.'>>
>

export declare type StripError<
  S extends StringToPath<string> | ParseError<string>,
> = S extends ParseError<string> ? never : S

export declare interface SubmitResult {}

export declare type ToArray<R extends Result<any, any>> = R extends [
  infer E,
  infer V,
]
  ? E extends null
    ? V extends any[]
      ? R
      : Ok<[R[1]]>
    : R
  : R

export declare type ToNumber<T extends string> =
  T extends `${infer N extends number}` ? N : never

/** Converts a stream of id set listener events into a state containing the list of document ids */
export declare function toState(options?: {
  insert?: InsertMethod
}): (input$: Observable<DocumentIdSetEvent>) => Observable<DocumentIdSetState>

export declare interface Transaction {
  id?: string
  mutations: Mutation[]
}

export declare interface TransactionalMutationGroup {
  transaction: true
  id?: string
  mutations: Mutation[]
}

export declare type Trim<
  S extends string,
  Char extends string = ' ',
> = TrimRight<TrimLeft<S, Char>, Char>

export declare type TrimLeft<
  Str extends string,
  Char extends string = ' ',
> = string extends Str
  ? Str
  : Str extends `${Char}${infer Trimmed}`
    ? TrimLeft<Trimmed, Char>
    : Str

export declare type TrimRight<
  Str extends string,
  Char extends string = ' ',
> = string extends Str
  ? Str
  : Str extends `${infer Trimmed}${Char}`
    ? TrimRight<Trimmed, Char>
    : Str

export declare type TruncateOp = {
  type: 'truncate'
  startIndex: number
  endIndex?: number
}

export declare type Try<R extends Result<any, any>, Handled> = R[1] extends null
  ? Handled
  : R

export declare type UnassignOp<
  K extends readonly string[] = readonly string[],
> = {
  type: 'unassign'
  keys: K
}

export declare type UnsetOp = {
  type: 'unset'
}

export declare type Unwrap<R extends Result<any, any>> = R extends [
  infer E,
  infer V,
]
  ? E extends null
    ? V
    : E
  : never

export declare type UpsertOp<
  Items extends AnyArray,
  Pos extends RelativePosition,
  ReferenceItem extends Index | KeyedPathElement,
> = {
  type: 'upsert'
  items: Items
  referenceItem: ReferenceItem
  position: Pos
}

export {}
