import type {InsertMenuOptions} from '@sanity/insert-menu'
import type {ArrayOptions} from '@sanity/types'
import type {
  ComponentProps,
  ComponentType,
  FunctionComponent,
  HTMLAttributes,
  PropsWithChildren,
  ReactElement,
} from 'react'
import {HTMLProps} from 'react'

export declare function defineOverlayComponent<
  T extends OverlayComponent<Record<string, unknown>, any>,
>(
  component: T,
  props?: Omit<ComponentProps<T>, keyof OverlayComponentProps>,
): {
  component: T
  props: typeof props
}

export declare function defineOverlayComponents<T extends OverlayComponent>(
  resolver: OverlayComponentResolver<T>,
): typeof resolver

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

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

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

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

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

export declare const PointerEvents: FunctionComponent<
  PropsWithChildren<HTMLAttributes<HTMLDivElement>>
>

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

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'
}

export declare const UnionInsertMenuOverlay: OverlayComponent<
  {
    direction?: 'horizontal' | 'vertical'
    hoverAreaExtent?: HTMLProps<HTMLDivElement>['height' | 'width']
  },
  SchemaUnionNode<SchemaNode>
>

export {}
