import {CreateIfNotExistsMutation} from '@sanity/types'
import {CreateMutation} from '@sanity/types'
import {CreateOrReplaceMutation} from '@sanity/types'
import {DeleteMutation} from '@sanity/types'
import {PatchMutation} from '@sanity/types'
import {Path} from '@sanity/types'

/**
 * Converts a path in array form to a JSONPath string
 *
 * @param pathArray - Array of path segments
 * @returns String representation of the path
 * @internal
 */
export declare function arrayToJSONMatchPath(pathArray: Path): string

/**
 * @internal
 */
export declare class BufferedDocument {
  private mutations
  /**
   * The Document we are wrapping
   */
  document: Document_2
  /**
   * The Document with local changes applied
   */
  LOCAL: Doc | null
  /**
   * Commits that are waiting to be delivered to the server
   */
  private commits
  /**
   * Local mutations that are not scheduled to be committed yet
   */
  buffer: SquashingBuffer
  /**
   * Assignable event handler for when the buffered document applies a mutation
   */
  onMutation?: (message: {mutation: Mutation; document: Doc | null; remote: boolean}) => void
  /**
   * Assignable event handler for when a remote mutation happened
   */
  onRemoteMutation?: Document_2['onRemoteMutation']
  /**
   * Assignable event handler for when the buffered document rebased
   */
  onRebase?: (localDoc: Doc | null, remoteMutations: Mut[], localMutations: Mut[]) => void
  /**
   * Assignable event handler for when the document is deleted
   */
  onDelete?: (doc: Doc | null) => void
  /**
   * Assignable event handler for when the state of consistency changed
   */
  onConsistencyChanged?: (isConsistent: boolean) => void
  /**
   * Assignable event handler for when the buffered document should commit changes
   */
  commitHandler?: (msg: CommitHandlerMessage) => void
  /**
   * Whether or not we are currently commiting
   */
  committerRunning: boolean
  constructor(doc: Doc | null)
  reset(doc: Doc | null): void
  add(mutation: Mutation): void
  arrive(mutation: Mutation): void
  commit(): Promise<void>
  performCommits(): void
  _cycleCommitter(): void
  handleDocRebase(edge: Doc | null, remoteMutations: Mutation[], localMutations: Mutation[]): void
  handleDocumentDeleted(): void
  handleDocMutation(msg: {mutation: Mutation; document: Doc | null; remote: boolean}): void
  rebase(remoteMutations: Mutation[], localMutations: Mutation[]): void
  handleDocConsistencyChanged(isConsistent: boolean): void
}

/**
 * @internal
 */
export declare interface CommitHandlerMessage {
  mutation: Mutation
  success: () => void
  failure: () => void
  cancel: (error: Error) => void
}

/**
 * Sanity document with a guaranteed `_id` and `_type`
 *
 * @internal
 */
export declare interface Doc {
  _id: string
  _type: string
  _rev?: string
  _updatedAt?: string
  _createdAt?: string
  [attribute: string]: unknown
}

/**
 * Models a document as it is changed by our own local patches and remote patches coming in from
 * the server. Consolidates incoming patches with our own submitted patches and maintains two
 * versions of the document. EDGE is the optimistic document that the user sees that will always
 * immediately reflect whatever she is doing to it, and HEAD which is the confirmed version of the
 * document consistent with the mutations we have received from the server. As long as nothing out of
 * the ordinary happens, we can track all changes by hooking into the onMutation callback, but we
 * must also respect onRebase events that fire when we have to backtrack because one of our optimistically
 * applied patches were rejected, or some bastard was able to slip a mutation in between ours own.
 *
 * @internal
 */
declare class Document_2 {
  /**
   * Incoming patches from the server waiting to be applied to HEAD
   */
  incoming: Mutation[]
  /**
   * Patches we know has been subitted to the server, but has not been seen yet in the return channel
   * so we can't be sure about the ordering yet (someone else might have slipped something between them)
   */
  submitted: Mutation[]
  /**
   * Pending mutations
   */
  pending: Mutation[]
  /**
   * Our model of the document according to the incoming patches from the server
   */
  HEAD: Doc | null
  /**
   * Our optimistic model of what the document will probably look like as soon as all our patches
   * have been processed. Updated every time we stage a new mutation, but also might revert back
   * to previous states if our mutations fail, or could change if unexpected mutations arrive
   * between our own. The `onRebase` callback will be called when EDGE changes in this manner.
   */
  EDGE: Doc | null
  /**
   * Called with the EDGE document when that document changes for a reason other than us staging
   * a new patch or receiving a mutation from the server while our EDGE is in sync with HEAD:
   * I.e. when EDGE changes because the order of mutations has changed in relation to our
   * optimistic predictions.
   */
  onRebase?: (edge: Doc | null, incomingMutations: Mutation[], pendingMutations: Mutation[]) => void
  /**
   * Called when we receive a patch in the normal order of things, but the mutation is not ours
   */
  onMutation?: (msg: {mutation: Mutation; document: Doc | null; remote: boolean}) => void
  /**
   * Called when consistency state changes with the boolean value of the current consistency state
   */
  onConsistencyChanged?: (isConsistent: boolean) => void
  /**
   * Called whenever a new incoming mutation comes in. These are always ordered correctly.
   */
  onRemoteMutation?: (mut: Mutation) => void
  /**
   * We are consistent when there are no unresolved mutations of our own, and no un-applicable
   * incoming mutations. When this has been going on for too long, and there has been a while
   * since we staged a new mutation, it is time to reset your state.
   */
  inconsistentAt: Date | null
  /**
   * The last time we staged a patch of our own. If we have been inconsistent for a while, but it
   * hasn't been long since we staged a new mutation, the reason is probably just because the user
   * is typing or something.
   *
   * Should be used as a guard against resetting state for inconsistency reasons.
   */
  lastStagedAt: Date | null
  constructor(doc: Doc | null)
  reset(doc: Doc | null): void
  arrive(mutation: Mutation): void
  stage(mutation: Mutation, silent?: boolean): SubmissionResponder
  isConsistent(): boolean
  considerIncoming(): void
  updateConsistencyFlag(): void
  applyIncoming(mut: Mutation | undefined): boolean
  /**
   * Returns true if there are unresolved mutations between HEAD and EDGE, meaning we have
   * mutations that are still waiting to be either submitted, or to be confirmed by the server.
   *
   * @returns true if there are unresolved mutations between HEAD and EDGE, false otherwise
   */
  hasUnresolvedMutations(): boolean
  /**
   * When an incoming mutation is applied to HEAD, this is called to remove the mutation from
   * the unresolved state. If the newly applied patch is the next upcoming unresolved mutation,
   * no rebase is needed, but we might have the wrong idea about the ordering of mutations, so in
   * that case we are given the flag `needRebase` to tell us that this mutation arrived out of
   * order in terms of our optimistic version, so a rebase is needed.
   *
   * @param txnId - Transaction ID of the remote mutation
   * @returns true if rebase is needed, false otherwise
   */
  consumeUnresolved(txnId: string): boolean
  pendingSuccessfullySubmitted(pendingTxnId: string): void
  pendingFailed(pendingTxnId: string): void
  rebase(incomingMutations: Mutation[]): void
}
export {Document_2 as Document}

/**
 * Extracts values matching the given JsonPath
 *
 * @param path - Path to extract
 * @param value - Value to extract from
 * @returns An array of values matching the given path
 * @public
 */
export declare function extract(path: string, value: unknown): unknown[]

/**
 * Extracts a value for the given JsonPath, and includes the specific path of where it was found
 *
 * @param path - Path to extract
 * @param value - Value to extract from
 * @returns An array of objects with `path` and `value` keys
 * @internal
 */
export declare function extractWithPath(
  path: string,
  value: unknown,
): {
  path: (string | number)[]
  value: unknown
}[]

/**
 * Internal mutation body representation - note that theoretically a
 * mutation can only hold one of these operations each, but for sake
 * of simpler code it is bundled together as one here
 *
 * @internal
 */
export declare interface Mut {
  create?: CreateMutation['create']
  createIfNotExists?: CreateIfNotExistsMutation['createIfNotExists']
  createOrReplace?: CreateOrReplaceMutation['createOrReplace']
  delete?: DeleteMutation['delete']
  patch?: PatchMutation['patch']
}

/**
 * A mutation describing a number of operations on a single document.
 * This should be considered an immutable structure. Mutations are compiled
 * on first application, and any changes in properties will not effectively
 * change its behavior after that.
 *
 * @internal
 */
export declare class Mutation {
  params: MutationParams
  compiled?: (doc: Doc | null) => Doc | null
  _appliesToMissingDocument: boolean | undefined
  constructor(options: MutationParams)
  get transactionId(): string | undefined
  get transition(): string | undefined
  get identity(): string | undefined
  get previousRev(): string | undefined
  get resultRev(): string | undefined
  get mutations(): Mut[]
  get timestamp(): Date | undefined
  get effects():
    | {
        apply: unknown
        revert: unknown
      }
    | undefined
  assignRandomTransactionId(): void
  appliesToMissingDocument(): boolean
  compile(): void
  apply(document: Doc | null): Doc | null
  static applyAll(document: Doc | null, mutations: Mutation[]): Doc | null
  static squash(document: Doc | null, mutations: Mutation[]): Mutation
}

/**
 * Parameters attached to the mutation
 *
 * @internal
 */
export declare interface MutationParams {
  transactionId?: string
  transition?: string
  identity?: string
  previousRev?: string
  resultRev?: string
  mutations: Mut[]
  timestamp?: string
  effects?: {
    apply: unknown
    revert: unknown
  }
}

/**
 * Implements a buffer for mutations that incrementally optimises the mutations by
 * eliminating set-operations that overwrite earlier set-operations, and rewrite
 * set-operations that change strings into other strings into diffMatchPatch operations.
 *
 * @internal
 */
export declare class SquashingBuffer {
  /**
   * The document forming the basis of this squash
   */
  BASIS: Doc | null
  /**
   * The document after the out-Mutation has been applied, but before the staged
   * operations are committed.
   */
  PRESTAGE: Doc | null
  /**
   * setOperations contain the latest set operation by path. If the set-operations are
   * updating strings to new strings, they are rewritten as diffMatchPatch operations,
   * any new set operations on the same paths overwrites any older set operations.
   * Only set-operations assigning plain values to plain values gets optimized like this.
   */
  setOperations: Record<string, Mut | undefined>
  /**
   * `documentPresent` is true whenever we know that the document must be present due
   * to preceeding mutations. `false` implies that it may or may not already exist.
   */
  documentPresent: boolean
  /**
   * The operations in the out-Mutation are not able to be optimized any further
   */
  out: Mut[]
  /**
   * Staged mutation operations
   */
  staged: Mut[]
  constructor(doc: Doc | null)
  add(mut: Mutation): void
  hasChanges(): boolean
  /**
   * Extracts the mutations in this buffer.
   * After this is done, the buffer lifecycle is over and the client should
   * create an new one with the new, updated BASIS.
   *
   * @param txnId - Transaction ID
   * @returns A `Mutation` instance if we had outgoing mutations pending, null otherwise
   */
  purge(txnId?: string): Mutation | null
  addOperation(op: Mut): void
  /**
     * Attempt to perform one single set operation in an optimised manner, return value
     * reflects whether or not the operation could be performed.

     * @param path - The JSONPath to the set operation in question
     * @param nextValue - The value to be set
     * @returns True of optimized, false otherwise
     */
  optimiseSetOperation(path: string, nextValue: unknown): boolean
  stashStagedOperations(): void
  /**
   * Rebases given the new base-document
   *
   * @param newBasis - New base document to rebase on
   * @returns New "edge" document with buffered changes integrated
   */
  rebase(newBasis: Doc | null): Doc | null
}

/**
 * @internal
 */
export declare interface SubmissionResponder {
  success: () => void
  failure: () => void
}

export {}
