import {SanityClient} from '@sanity/client'
import type {SanityDocument} from '@sanity/client'
import {Mutation, NodePatchList} from '@sanity/mutate'
import {createSharedListener, DocumentMutatorMachineInput} from '@sanity/mutate/_unstable_machine'
import {
  DocumentSchema,
  HistoryRefresh,
  HistoryUpdate,
  SanityNode,
  SanityStegaNode,
  SchemaArrayItem,
  SchemaArrayNode,
  SchemaBooleanNode,
  SchemaInlineNode,
  SchemaNode,
  SchemaNullNode,
  SchemaNumberNode,
  SchemaObjectField,
  SchemaObjectNode,
  SchemaStringNode,
  SchemaUnionNode,
  SchemaUnionNodeOptions,
  SchemaUnionOption,
  SchemaUnknownNode,
} from '@sanity/presentation-comlink'
import type {SanityDocument as SanityDocument_2} from '@sanity/types'
import {
  CreateDataAttribute,
  createDataAttribute,
  CreateDataAttributeProps,
  WithRequired,
} from '@sanity/visual-editing-csm'
import type {
  ComponentType,
  FunctionComponent,
  HTMLAttributes,
  PropsWithChildren,
  ReactElement,
} from 'react'

export {CreateDataAttribute}

export {createDataAttribute}

export {CreateDataAttributeProps}

/**
 * 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,
  inPopUp,
  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 {DocumentSchema}

/**
 * @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
}

/**
 * @public
 */
export declare interface ElementChildTarget {
  sanity: SanityNode | SanityStegaNode
  element: ElementNode
}

/**
 * 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
  targets: ElementChildTarget[]
  elementType: 'element' | 'group'
}

/**
 * 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

export {HistoryRefresh}

export {HistoryUpdate}

/**
 * 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 element node that the Sanity node data is detected on
   */
  targetElement: 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
  | OverlayMsgResetMouseState

/** @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
  targets: ElementChildTarget[]
  elementType: 'element' | 'group'
}

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

/** @public */
export declare type OverlayMsgElementUpdate = OverlayMsgElement<'update'> & {
  sanity: SanityNode | SanityStegaNode
  rect: OverlayRect
  targets: ElementChildTarget[]
  elementType: 'element' | 'group'
}

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

/** @public */
export declare type OverlayMsgResetMouseState = Msg<'overlay/reset-mouse-state'>

/** @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
  inPopUp: boolean
  optimisticActorReady: boolean
}

/** @public  */
export declare type OverlayPluginDefinition =
  | OverlayPluginExclusiveDefinition
  | OverlayPluginHudDefinition

/** @public  */
export declare interface OverlayPluginDefinitionBase {
  name: string
  title?: string
  icon?: ComponentType
  guard?: (context: OverlayComponentResolverContext | undefined) => boolean
}

/** @public  */
export declare interface OverlayPluginExclusiveDefinition extends OverlayPluginDefinitionBase {
  type: 'exclusive'
  component?: OverlayComponent<
    Record<string, unknown> & {
      closeExclusiveView: () => void
    },
    any
  >
}

/** @public  */
export declare interface OverlayPluginHudDefinition extends OverlayPluginDefinitionBase {
  type: 'hud'
  component?: OverlayComponent<Record<string, unknown>, any>
}

/**
 * @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

export {SanityNode}

export {SanityStegaNode}

export {SchemaArrayItem}

export {SchemaArrayNode}

export {SchemaBooleanNode}

export {SchemaInlineNode}

export {SchemaNode}

export {SchemaNullNode}

export {SchemaNumberNode}

export {SchemaObjectField}

export {SchemaObjectNode}

export {SchemaStringNode}

export {SchemaUnionNode}

export {SchemaUnionNodeOptions}

export {SchemaUnionOption}

export {SchemaUnknownNode}

/**
 * @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.
   */
  plugins?: OverlayPluginDefinition[]
  /**
   * @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
}

export {WithRequired}

export {}
