import {SanityClient} from '@sanity/client'
import type {SanityDocument} from '@sanity/client'
import type {StudioPathLike} from '@sanity/client/csm'
import type {InsertMenuOptions} from '@sanity/insert-menu'
import {Mutation, NodePatchList} from '@sanity/mutate'
import {createSharedListener, DocumentMutatorMachineInput} from '@sanity/mutate/_unstable_machine'
import type {ArrayOptions, SanityDocument as SanityDocument_2} from '@sanity/types'
import type {
  ComponentType,
  FunctionComponent,
  HTMLAttributes,
  PropsWithChildren,
  ReactElement,
} from 'react'

/**
 * @public
 */
export declare type CreateDataAttribute<T extends CreateDataAttributeProps> =
  (T extends WithRequired<CreateDataAttributeProps, 'id' | 'type' | 'path'>
    ? {
        /**
         * Returns a string representation of the data attribute
         * @param path - An optional path to concatenate with any existing path
         * @public
         */
        (path?: StudioPathLike): string
        /**
         * Returns a string representation of the data attribute
         * @public
         */
        toString(): string
      }
    : T extends WithRequired<CreateDataAttributeProps, 'id' | 'type'>
      ? (path: StudioPathLike) => string
      : object) & {
    /**
     * Concatenate the current path with a new path
     * @param path - A path to concatenate with any existing path
     * @public
     */
    scope(path: StudioPathLike): CreateDataAttribute<
      T & {
        path: StudioPathLike
      }
    >
    /**
     * Combines the current props with additional props
     * @param props - New props to merge with any existing props
     * @public
     */
    combine: <U extends CreateDataAttributeProps>(props: U) => CreateDataAttribute<T & U>
  }

/**
 * A helper function for creating `data-sanity` attributes by explicitly providing metadata.
 * @returns An object with methods for incrementally adding and scoping metadata, and for generating a data attribute string.
 * @public
 */
export declare function createDataAttribute<T extends CreateDataAttributeProps>(
  props: T,
): CreateDataAttribute<T>

/**
 * The metadata that can be embedded in a data attribute.
 * All values are marked optional in the base type as they can be provided incrementally using the `createDataAttribute` function.
 * @public
 */
export declare interface CreateDataAttributeProps {
  /** The studio base URL, optional */
  baseUrl?: string
  /** The dataset, optional */
  dataset?: string
  /** The document ID, required */
  id?: string
  /** The field path, required */
  path?: StudioPathLike
  /** The project ID, optional */
  projectId?: string
  /** The studio tool name, optional */
  tool?: string
  /** The document type, required */
  type?: string
  /** The studio workspace, optional */
  workspace?: string
}

/**
 * Creates a controller which dispatches overlay related events
 *
 * @param handler - Dispatched event handler
 * @param overlayElement - Parent element containing rendered overlay elements
 * @public
 */
export declare function createOverlayController({
  handler,
  overlayElement,
  inFrame,
  optimisticActorReady,
}: OverlayOptions): OverlayController

/**
 * @public
 * @deprecated Use `import type {DatasetMutatorMachineInput} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type DatasetMutatorMachineInput = DatasetMutatorMachineInputDeprecated

export declare interface DatasetMutatorMachineInputDeprecated
  extends Omit<DocumentMutatorMachineInput, 'id'> {
  client: SanityClient
  /** A shared listener can be provided, if not it'll be created using `client.listen()` */
  sharedListener?: ReturnType<typeof createSharedListener>
}

/**
 * Cleanup function used when e.g. unmounting
 * @public
 */
export declare type DisableVisualEditing = () => void

export declare interface DocumentSchema {
  type: 'document'
  name: string
  title?: string
  icon?: string
  fields: Record<string, SchemaObjectField>
}

/**
 * @public
 * @deprecated Use `import type {DocumentsGet} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type DocumentsGet = DocumentsGetDeprecated

export declare type DocumentsGetDeprecated = <T extends Record<string, any>>(
  documentId: string,
) => OptimisticDocumentDeprecated<T>

/**
 * @public
 * @deprecated Use `import type {DocumentsMutate} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type DocumentsMutate = DocumentsMutateDeprecated

export declare type DocumentsMutateDeprecated = (
  documentId: string,
  mutations: Mutation[],
  options?: {
    commit?:
      | boolean
      | {
          debounce: number
        }
  },
) => void

/** @public */
export declare interface DragEndEvent {
  insertPosition: DragInsertPosition
  target: SanityNode
  dragGroup: string | null
  flow: string
  preventInsertDefault: boolean
}

/** @public */
export declare type DragInsertPosition = {
  top?: {
    rect: OverlayRect
    sanity: SanityNode
  } | null
  left?: {
    rect: OverlayRect
    sanity: SanityNode
  } | null
  bottom?: {
    rect: OverlayRect
    sanity: SanityNode
  } | null
  right?: {
    rect: OverlayRect
    sanity: SanityNode
  } | null
} | null

/** @public */
export declare type DragSkeleton = {
  w: number
  h: number
  offsetX: number
  offsetY: number
  childRects: {
    x: number
    y: number
    w: number
    h: number
    tagName: string
  }[]
  maxWidth: number
}

/**
 * Element focus state
 * @public
 */
export declare type ElementFocusedState = 'clicked' | 'duplicate' | boolean

/**
 * An element that is safe to parse
 * @internal
 */
export declare type ElementNode = HTMLElement | SVGElement

/**
 * Element state for consuming applications
 * @public
 */
export declare interface ElementState {
  id: string
  activated: boolean
  element: ElementNode
  focused: ElementFocusedState
  hovered: boolean
  rect: OverlayRect
  sanity: SanityNode | SanityStegaNode
  dragDisabled: boolean
}

/**
 * Enables Visual Editing overlay in a page with sourcemap encoding.
 *
 * This will overlay UI on hovered elements that deep-links to Sanity Studio.
 * @public
 */
export declare function enableVisualEditing(options?: VisualEditingOptions): DisableVisualEditing

export declare function getArrayItemKeyAndParentPath(pathOrNode: string | SanityNode): {
  path: string
  key: string
  hasExplicitKey: boolean
}

/**
 *
 * @public
 */
export declare interface HistoryAdapter {
  subscribe: (navigate: HistoryAdapterNavigate) => () => void
  update: (update: HistoryUpdate) => void
}

/**
 *
 * @public
 */
export declare type HistoryAdapterNavigate = (update: HistoryUpdate) => void

/**
 * Preview frame history refresh event, emitted by Presentation Tool
 * @public
 */
export declare type HistoryRefresh =
  | {
      /**
       * source 'manual' means the refresh button were clicked by the user
       */
      source: 'manual'
      /**
       * If true then there's either preview-kit or a loader connected on the page
       */
      livePreviewEnabled: boolean
    }
  | {
      /**
       * source 'mutation' means a document were mutated and the preview might need to refresh
       */
      source: 'mutation'
      /**
       * If true then there's either preview-kit or a loader connected on the page
       */
      livePreviewEnabled: boolean
      /**
       * Select metadata about the document that were mutated
       * If it's prefixed with `drafts.` then it's a draft document, otherwise it's a published document.
       */
      document: {
        /**
         * If it's prefixed with `drafts.` then it's a draft document, otherwise it's a published document.
         */
        _id: string
        /**
         * The document type is frequently used in `revalidateTag` scenarios with Next.js App Router
         */
        _type: string
        /**
         * The document revision, can be used to dedupe requests, as we always send two due to debouncing and handling Content Lake eventual consistency
         */
        _rev: string
        /**
         * If the document has a top level slug field named `slug` with the type `slug`, then it'll be included here
         */
        slug?: {
          current?: string | null
        }
      }
    }

/**
 * Preview frame history update
 * @public
 */
export declare type HistoryUpdate = {
  type: 'push' | 'pop' | 'replace'
  title?: string
  url: string
}

/**
 * Base controller dispatched message
 * @typeParam T - Type of message
 * @public
 */
export declare interface Msg<T extends string> {
  type: T
}

/**
 * @public
 * @deprecated Use `import type {OptimisticDocument} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type OptimisticDocument = OptimisticDocumentDeprecated

export declare type OptimisticDocumentDeprecated<
  T extends Record<string, any> = Record<string, any>,
> = {
  /**
   * The document ID
   */
  id: string
  /**
   * Commits any locally applied mutations to the remote document
   */
  commit: () => void
  /**
   * @deprecated - use `getSnapshot` instead
   */
  get: {
    (): SanityDocument<T> | undefined
    <P extends PathDeprecated<T, keyof T>>(path: P): PathValueDeprecated<T, P> | undefined
  }
  /**
   * Returns a promise that resolves to the current document snapshot
   */
  getSnapshot: () => Promise<SanityDocument<T> | null>
  /**
   * Applies the given patches to the document
   */
  patch: (
    patches: OptimisticDocumentPatchesDeprecated<T>,
    options?: {
      commit?:
        | boolean
        | {
            debounce: number
          }
    },
  ) => void
}

/**
 * @public
 * @deprecated Use `import type {OptimisticDocumentPatches} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type OptimisticDocumentPatches = OptimisticDocumentPatchesDeprecated

export declare type OptimisticDocumentPatchesDeprecated<
  T extends Record<string, any> = Record<string, any>,
> =
  | ((context: {
      draftId: string
      publishedId: string
      /**
       * @deprecated - use `getSnapshot` instead
       */
      snapshot: SanityDocument<T> | undefined
      getSnapshot: () => Promise<SanityDocument<T> | null>
    }) => Promise<NodePatchList> | NodePatchList)
  | NodePatchList

/**
 * @public
 * @deprecated Use `import type {OptimisticReducer} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type OptimisticReducer<T, U> = OptimisticReducerDeprecated<T, U>

/**
 * @public
 * @deprecated Use `import type {OptimisticReducerAction} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type OptimisticReducerAction<T> = OptimisticReducerActionDeprecated<T>

export declare type OptimisticReducerActionDeprecated<T> = {
  document: T
  id: string
  originalId: string
  type: 'appear' | 'mutate' | 'disappear'
}

export declare type OptimisticReducerDeprecated<T, U> = (
  state: T,
  action: OptimisticReducerActionDeprecated<U>,
) => T

/**
 * @public
 */
export declare type OverlayComponent<
  T extends Record<string, unknown> = Record<string, unknown>,
  P extends OverlayElementParent = OverlayElementParent,
> = ComponentType<OverlayComponentProps<P | undefined> & T>

/**
 * @public
 */
export declare interface OverlayComponentProps<
  P extends OverlayElementParent = OverlayElementParent,
> extends OverlayComponentResolverContext<P> {
  PointerEvents: FunctionComponent<PropsWithChildren<HTMLAttributes<HTMLDivElement>>>
}

/**
 * @public
 */
export declare type OverlayComponentResolver<
  T extends OverlayComponent = OverlayComponent<Record<string, unknown>, any>,
> = (context: OverlayComponentResolverContext) =>
  | T
  | {
      component: T
      props?: Record<string, unknown>
    }
  | Array<
      | T
      | {
          component: T
          props?: Record<string, unknown>
        }
    >
  | ReactElement
  | undefined
  | void

/**
 * @public
 */
export declare interface OverlayComponentResolverContext<
  P extends OverlayElementParent = OverlayElementParent,
> {
  /**
   * The resolved field's document schema type
   */
  document: DocumentSchema
  /**
   * The element node that the overlay is attached to
   */
  element: ElementNode
  /**
   * The resolved field schema type
   */
  field: OverlayElementField
  /**
   * Whether the overlay is focused or not
   */
  focused: boolean
  /**
   * The Sanity node data that triggered the overlay
   */
  node: SanityNode
  /**
   * The resolved field's parent schema type
   */
  parent: P
  /**
   * A convience property, equal to `field.value.type`
   */
  type: string
}

/**
 * Object returned by a controller instantiation
 * @public
 */
export declare interface OverlayController {
  activate: () => void
  deactivate: () => void
  destroy: () => void
}

export declare type OverlayElementField =
  | SchemaArrayItem
  | SchemaObjectField
  | SchemaUnionOption
  | undefined

export declare type OverlayElementParent =
  | DocumentSchema
  | SchemaNode
  | SchemaArrayItem
  | SchemaUnionOption
  | SchemaUnionNode
  | undefined

/**
 * Callback function used for handling dispatched controller messages
 * @public
 */
export declare type OverlayEventHandler = (message: OverlayMsg) => void

/**
 * Controller dispatched messages
 * @public
 */
export declare type OverlayMsg =
  | OverlayMsgActivate
  | OverlayMsgBlur
  | OverlayMsgDeactivate
  | OverlayMsgDragEnd
  | OverlayMsgDragEndMinimapTransition
  | OverlayMsgDragStart
  | OverlayMsgDragStartMinimapTransition
  | OverlayMsgDragToggleMinimap
  | OverlayMsgDragToggleMinimapPrompt
  | OverlayMsgDragUpdateCursorPosition
  | OverlayMsgDragUpdateGroupRect
  | OverlayMsgDragUpdateInsertPosition
  | OverlayMsgDragUpdateSkeleton
  | OverlayMsgElementActivate
  | OverlayMsgElementClick
  | OverlayMsgElementContextMenu
  | OverlayMsgElementDeactivate
  | OverlayMsgElementMouseEnter
  | OverlayMsgElementMouseLeave
  | OverlayMsgElementRegister
  | OverlayMsgElementUnregister
  | OverlayMsgElementUpdate
  | OverlayMsgElementUpdateRect
  | OverlayMsgSetCursor

/** @public */
export declare type OverlayMsgActivate = Msg<'overlay/activate'>

/** @public */
export declare type OverlayMsgBlur = Msg<'overlay/blur'>

/** @public */
export declare type OverlayMsgDeactivate = Msg<'overlay/deactivate'>

/** @public */
export declare type OverlayMsgDragEnd = Msg<'overlay/dragEnd'> & DragEndEvent

/** @public */
export declare type OverlayMsgDragEndMinimapTransition = Msg<'overlay/dragEndMinimapTransition'>

/** @public */
export declare type OverlayMsgDragStart = Msg<'overlay/dragStart'> & {
  flow: 'horizontal' | 'vertical'
}

/** @public */
export declare type OverlayMsgDragStartMinimapTransition = Msg<'overlay/dragStartMinimapTransition'>

/** @public */
export declare type OverlayMsgDragToggleMinimap = Msg<'overlay/dragToggleMinimap'> & {
  display: boolean
}

/** @public */
export declare type OverlayMsgDragToggleMinimapPrompt = Msg<'overlay/dragToggleMinimapPrompt'> & {
  display: boolean
}

/** @public */
export declare type OverlayMsgDragUpdateCursorPosition = Msg<'overlay/dragUpdateCursorPosition'> & {
  x: number
  y: number
}

/** @public */
export declare type OverlayMsgDragUpdateGroupRect = Msg<'overlay/dragUpdateGroupRect'> & {
  groupRect: OverlayRect | null
}

/** @public */
export declare type OverlayMsgDragUpdateInsertPosition = Msg<'overlay/dragUpdateInsertPosition'> & {
  insertPosition: DragInsertPosition | null
}

/** @public */
export declare type OverlayMsgDragUpdateSkeleton = Msg<'overlay/dragUpdateSkeleton'> & {
  skeleton: DragSkeleton
}

/** @public */
export declare interface OverlayMsgElement<T extends string> extends Msg<`element/${T}`> {
  id: string
}

/** @public */
export declare type OverlayMsgElementActivate = OverlayMsgElement<'activate'>

/** @public */
export declare type OverlayMsgElementClick = OverlayMsgElement<'click'> & {
  sanity: SanityNode | SanityStegaNode
}

/** @public */
export declare type OverlayMsgElementContextMenu =
  | OverlayMsgElement<'contextmenu'>
  | (OverlayMsgElement<'contextmenu'> & {
      position: {
        x: number
        y: number
      }
      sanity: SanityNode
    })

/** @public */
export declare type OverlayMsgElementDeactivate = OverlayMsgElement<'deactivate'>

/** @public */
export declare type OverlayMsgElementMouseEnter = OverlayMsgElement<'mouseenter'> & {
  rect: OverlayRect
}

/** @public */
export declare type OverlayMsgElementMouseLeave = OverlayMsgElement<'mouseleave'>

/** @public */
export declare type OverlayMsgElementRegister = OverlayMsgElement<'register'> & {
  element: ElementNode
  sanity: SanityNode | SanityStegaNode
  rect: OverlayRect
  dragDisabled: boolean
}

/** @public */
export declare type OverlayMsgElementUnregister = OverlayMsgElement<'unregister'>

/** @public */
export declare type OverlayMsgElementUpdate = OverlayMsgElement<'update'> & {
  sanity: SanityNode | SanityStegaNode
  rect: OverlayRect
}

/** @public */
export declare type OverlayMsgElementUpdateRect = OverlayMsgElement<'updateRect'> & {
  rect: OverlayRect
}

/** @public */
export declare type OverlayMsgSetCursor = Msg<'overlay/setCursor'> & {
  element: ElementNode
  cursor: string | undefined
}

/**
 * Options passed when instantiating an overlay controller
 * @public
 */
export declare interface OverlayOptions {
  handler: OverlayEventHandler
  overlayElement: HTMLElement
  inFrame: boolean
  optimisticActorReady: boolean
}

/**
 * @public
 */
export declare interface OverlayRect {
  x: number
  y: number
  w: number
  h: number
}

/**
 * @public
 * @deprecated Use `import type {Path} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type Path<T, K extends keyof T> = PathDeprecated<T, K>

export declare type PathDeprecated<T, K extends keyof T> = K extends string
  ? T[K] extends Record<string, any>
    ? `${K}.${PathDeprecated<T[K], keyof T[K]>}` | K
    : K
  : never

/**
 * @public
 * @deprecated Use `import type {PathValue} from '@sanity/visual-editing/optimistic'` instead
 */
export declare type PathValue<T, P extends string> = PathValueDeprecated<T, P>

export declare type PathValueDeprecated<T, P extends string> = P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? PathValueDeprecated<T[K], Rest>
    : never
  : P extends keyof T
    ? T[P]
    : never

/**
 * Data resolved from a Sanity node
 * @public
 */
export declare type SanityNode = {
  baseUrl: string
  dataset?: string
  id: string
  isDraft?: string
  path: string
  projectId?: string
  tool?: string
  type?: string
  workspace?: string
}

/**
 * Data resolved from a Sanity Stega node
 * @public
 */
export declare type SanityStegaNode = {
  origin: string
  href: string
  data?: unknown
}

export declare interface SchemaArrayItem<T extends SchemaNode = SchemaNode> {
  type: 'arrayItem'
  name: string
  title?: string
  value: T
}

export declare interface SchemaArrayNode<T extends SchemaNode = SchemaNode> {
  type: 'array'
  of: SchemaArrayItem<T>
}

export declare interface SchemaBooleanNode {
  type: 'boolean'
  value?: boolean
}

export declare interface SchemaInlineNode {
  type: 'inline'
  /** the name of the referenced type */
  name: string
}

export declare type SchemaNode =
  | SchemaArrayNode
  | SchemaBooleanNode
  | SchemaInlineNode
  | SchemaNullNode
  | SchemaNumberNode
  | SchemaObjectNode
  | SchemaStringNode
  | SchemaUnionNode
  | SchemaUnknownNode

export declare interface SchemaNullNode {
  type: 'null'
}

export declare interface SchemaNumberNode {
  type: 'number'
  value?: number
}

export declare interface SchemaObjectField<T extends SchemaNode = SchemaNode> {
  type: 'objectField'
  name: string
  title?: string
  value: T
  optional?: boolean
}

export declare interface SchemaObjectNode<T extends SchemaNode = SchemaNode> {
  type: 'object'
  fields: Record<string, SchemaObjectField<T>>
  rest?: SchemaObjectNode | SchemaUnknownNode | SchemaInlineNode
  dereferencesTo?: string
}

export declare interface SchemaStringNode {
  type: 'string'
  value?: string
}

export declare interface SchemaUnionNode<T extends SchemaNode = SchemaNode> {
  type: 'union'
  of: SchemaUnionOption<T>[] | SchemaStringNode[] | SchemaNumberNode[]
  options?: SchemaUnionNodeOptions
}

export declare type SchemaUnionNodeOptions = Omit<ArrayOptions, 'insertMenu'> & {
  insertMenu?: Omit<InsertMenuOptions, 'views'> & {
    views?: Array<
      | {
          name: 'list'
        }
      | {
          name: 'grid'
          previewImageUrls?: Record<string, string | undefined>
        }
    >
  }
}

export declare interface SchemaUnionOption<T extends SchemaNode = SchemaNode> {
  type: 'unionOption'
  name: string
  title?: string
  icon?: string
  value: T
}

export declare interface SchemaUnknownNode {
  type: 'unknown'
}

/**
 * @public
 * @deprecated Use `import {useDocuments} from '@sanity/visual-editing/react'` instead
 */
export declare const useDocuments: typeof useDocumentsDeprecated

export declare function useDocumentsDeprecated(): {
  getDocument: DocumentsGetDeprecated
  mutateDocument: DocumentsMutateDeprecated
}

/**
 * @public
 * @deprecated Use `import {useOptimistic} from '@sanity/visual-editing/react'` instead
 */
export declare const useOptimistic: typeof useOptimisticDeprecated

export declare function useOptimisticDeprecated<T, U = SanityDocument_2>(
  passthrough: T,
  reducer: OptimisticReducerDeprecated<T, U> | Array<OptimisticReducerDeprecated<T, U>>,
): T

export declare function useSharedState<
  T extends boolean | null | number | object | string | undefined | unknown = unknown,
>(key: string): T

/**
 * @public
 */
export declare interface VisualEditingOptions {
  /**
   * @alpha
   * This API is unstable and could change at any time.
   */
  components?: OverlayComponentResolver
  /**
   * The history adapter is used for Sanity Presentation to navigate URLs in the preview frame.
   */
  history?: HistoryAdapter
  /**
   * The refresh API allows smarter refresh logic than the default `location.reload()` behavior.
   */
  refresh?: (payload: HistoryRefresh) => false | Promise<void>
  /**
   * The CSS z-index on the root node that renders overlays, tweak it accordingly to what layout you have.
   */
  zIndex?: string | number
}

/**
 * Helper
 * @internal
 */
export declare type WithRequired<T, K extends keyof T> = T & {
  [P in K]-?: T[P]
}

export {}
