import {
  DrawCircleFromCenterMode,
  type ClickEvent,
  type FeatureCollection,
  type FeatureOf,
  type Geometry,
  type GuideFeatureCollection,
  type ModeProps,
  type PointerMoveEvent,
  type Polygon,
  type Position,
  type Tooltip,
} from '@deck.gl-community/editable-layers'
import type { ValueOf } from 'type-fest'
import { EditType } from '../../../editable/const'
import type { MapMeasurementUnitAuto } from '../const'
import type { EditModeConfig, MeasurementToolsTextProps } from '../types'
import { buildTooltip, getTextLayerFormattedDistance } from '../utils'

/**
 * Custom buffer measurement mode that extends `DrawCircleFromCenterMode` to draw circular buffers with area calculation and formatted tooltips.
 *
 * @remarks
 * Click once to set the center, click again to set the radius. Supports Enter to finalize
 * and Escape to cancel. Emits area and position data on completion.
 */
export class CustomMeasureBufferMode extends DrawCircleFromCenterMode {
  _isMeasuringSessionFinished = false
  _currentArea = 0
  resultPolygon: FeatureCollection = {
    type: 'FeatureCollection',
    features: [],
  }
  tooltips: Tooltip[] = []

  override handleClick(event: ClickEvent, props: ModeProps<FeatureCollection>) {
    // restart measuring session
    if (this._isMeasuringSessionFinished) {
      this._isMeasuringSessionFinished = false
      this.resetClickSequence()
      this._currentArea = 0
    }

    super.handleClick(event, props)
  }

  override handlePointerMove(
    event: PointerMoveEvent,
    props: ModeProps<FeatureCollection>,
  ): void {
    if (this._isMeasuringSessionFinished) {
      return
    }
    super.handlePointerMove(event, props)
    this.tooltips = buildTooltip(
      this.areaCircle!,
      this.position,
      props.modeConfig as EditModeConfig,
    )
  }

  override checkAndFinishPolygon(props: ModeProps<FeatureCollection>) {
    const clickSequence = this.getClickSequence()
    const tentativeFeature = this.getTentativeGuide(props)
    if (
      this.isFinalizedEdition(
        clickSequence,
        tentativeFeature as { geometry: Geometry },
      )
    ) {
      this.finalizeEdition(props)
    }
  }

  override handleKeyUp(
    event: KeyboardEvent,
    props: ModeProps<FeatureCollection>,
  ) {
    if (event.key === 'Enter') {
      this.finalizeEdition(props)
    } else if (event.key === 'Escape') {
      this.resetEdition(props)
    }
  }

  override getGuides(
    props: ModeProps<FeatureCollection>,
  ): GuideFeatureCollection {
    if (this._isMeasuringSessionFinished) {
      return this.resultPolygon as GuideFeatureCollection
    } else {
      return super.getGuides(props)
    }
  }

  isFinalizedEdition(
    clickSequence: Position[],
    tentativeFeature: { geometry: Geometry },
  ): boolean {
    return (
      clickSequence.length > 1 && tentativeFeature?.geometry.type === 'Polygon'
    )
  }

  finalizeEdition(props: ModeProps<FeatureCollection>) {
    const tentativeFeature = this.getTentativeGuide(props)
    props.selectedIndexes = []
    props.data = {
      type: 'FeatureCollection',
      features: [],
    }

    const editAction = this.getAddFeatureOrBooleanPolygonAction(
      tentativeFeature as FeatureOf<Polygon>,
      props,
    )
    this._isMeasuringSessionFinished = true
    props.onUpdateCursor('cell')
    if (editAction) {
      this.resultPolygon = editAction.updatedData
      props.onEdit({
        ...editAction,
        editType: EditType.addPosition,
        editContext: {
          ...(editAction.editContext as Record<string, unknown>),
          distance: this._currentArea,
          positions: this.getClickSequence(),
          finished: this._isMeasuringSessionFinished,
        },
      })
    }
  }

  resetEdition(props: ModeProps<FeatureCollection>) {
    this.resetClickSequence()
    props.onEdit({
      // Because the new drawing feature is dropped, so the data will keep as the same.
      updatedData: props.data,
      editType: 'cancelFeature',
      editContext: {},
    })
  }

  override resetClickSequence() {
    super.resetClickSequence()
    this.tooltips = []
    this._isMeasuringSessionFinished = false
    this._currentArea = 0
    this.radius = 0
    this.areaCircle = 0
  }

  override getTooltips(): Tooltip[] {
    return this.tooltips
  }

  getTooltipText({
    meters,
    locale,
    unit,
    options,
  }: {
    meters: number
    unit: ValueOf<typeof MapMeasurementUnitAuto>
    locale: string
    options?: MeasurementToolsTextProps
  }): string {
    return getTextLayerFormattedDistance({
      meters,
      locale,
      unit,
      options,
      dimension: 'area',
    })
  }
}
