import {
  getPickedEditHandle,
  MeasureAreaMode,
  type ClickEvent,
  type FeatureCollection,
  type FeatureOf,
  type GuideFeatureCollection,
  type LineString,
  type ModeProps,
  type Point,
  type PointerMoveEvent,
  type Polygon,
  type Position,
  type Tooltip,
} from '@deck.gl-community/editable-layers'
import { area, centroid, lineIntersect } from '@turf/turf'
import { EditType } from '../../../editable/const'
import type { EditModeConfig, MeasurementToolsTextProps } from '../types'
import { buildTooltip, getTextLayerFormattedDistance } from '../utils'
import type { ValueOf } from 'type-fest'
import type { MapMeasurementUnitAuto } from '../const'
import type { TentativeFeature } from 'node_modules/@deck.gl-community/editable-layers/dist/edit-modes/types'

/**
 * Custom area measurement mode that extends `MeasureAreaMode` with polygon finalization, keyboard controls, and formatted tooltip support.
 *
 * @remarks
 * Calculates area using Turf.js. Supports Enter to finalize (3+ points), Escape to cancel,
 * and click on first/last point to close the polygon. Prevents overlapping lines when configured.
 */
export class CustomMeasureAreaMode extends MeasureAreaMode {
  _isMeasuringSessionFinished = false
  area = 0
  tooltips: Tooltip[] = []
  resultPolygon: FeatureCollection = {
    type: 'FeatureCollection',
    features: [],
  }

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

    const { picks } = event
    const clickedEditHandle = getPickedEditHandle(picks)
    const clickSequence = this.getClickSequence()

    let positionAdded = false
    if (!clickedEditHandle && !this.allowOverlapping(props)) {
      // Don't add another point right next to an existing one
      this.addClickSequence(event)
      positionAdded = true
    }

    const latestClickSequence = this.getClickSequence()
    const polygonCoordinates = [...latestClickSequence, latestClickSequence[0]!]
    this.area = area({
      type: 'Polygon',
      coordinates: [polygonCoordinates],
    })

    if (
      this.isFinalizedEdition(
        clickSequence,
        clickedEditHandle as unknown as {
          properties: { positionIndexes: number[] }
        },
      )
    ) {
      this.finalizeEdition(props)
    } else if (positionAdded) {
      props.onEdit({
        // data is the same
        updatedData: props.data,
        editType: 'addTentativePosition',
        editContext: {
          position: event.mapCoords,
        },
      })
    }
  }

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

  updateTooltip(
    polygon: Polygon | TentativeFeature,
    props: ModeProps<FeatureCollection>,
  ): void {
    const position = centroid(polygon) as FeatureOf<Point>
    this.area = area(polygon)
    this.tooltips = buildTooltip(
      this.area,
      position.geometry.coordinates as unknown as Position,
      props.modeConfig as EditModeConfig,
    )
  }

  override handlePointerMove(
    event: PointerMoveEvent,
    props: ModeProps<FeatureCollection>,
  ): void {
    if (this._isMeasuringSessionFinished) {
      return
    }
    super.handlePointerMove(event, props)
    const tentativeGuide = this.getTentativeGuide(props)
    if (tentativeGuide?.geometry.type === 'Polygon') {
      this.updateTooltip(tentativeGuide, props)
    }
  }

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

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

  override getTooltips() {
    return this.tooltips
  }

  isFinalizedEdition(
    clickSequence: Position[],
    clickedEditHandle:
      | { properties: { positionIndexes: number[] } }
      | null
      | undefined,
  ): boolean {
    const { positionIndexes } = clickedEditHandle?.properties ?? {
      positionIndexes: [],
    }
    return (
      clickSequence.length > 2 &&
      clickedEditHandle !== undefined &&
      (positionIndexes[0] === 0 ||
        positionIndexes[0] === clickSequence.length - 1)
    )
  }

  allowOverlapping(props: ModeProps<FeatureCollection>) {
    let overlappingLines = false
    const clickSequence = this.getClickSequence()
    if (
      clickSequence.length > 2 &&
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      props.modeConfig?.preventOverlappingLines
    ) {
      const currentLine = {
        type: 'LineString',
        coordinates: [...clickSequence, clickSequence[0]],
      } as LineString
      const otherLines = {
        type: 'LineString',
        coordinates: [...clickSequence.slice(0, clickSequence.length - 1)],
      } as LineString
      const intersectingPoints = lineIntersect(currentLine, otherLines)
      if ((intersectingPoints as FeatureCollection).features.length > 0) {
        overlappingLines = true
      }
    }
    return overlappingLines
  }

  finalizeEdition(props: ModeProps<FeatureCollection>) {
    const clickSequence = this.getClickSequence()
    // They clicked the first or last point (or double-clicked), so complete the polygon
    // Remove the hovered position
    const polygonToAdd: Polygon = {
      type: 'Polygon',
      coordinates: [[...clickSequence, clickSequence[0]!]],
    }
    props.selectedIndexes = []
    props.data = {
      type: 'FeatureCollection',
      features: [],
    }
    const editAction = this.getAddFeatureOrBooleanPolygonAction(
      polygonToAdd,
      props,
    )
    this._isMeasuringSessionFinished = true
    if (editAction) {
      this.resultPolygon = editAction.updatedData
      props.onEdit({
        ...editAction,
        editType: EditType.addPosition,
        editContext: {
          ...(editAction.editContext as Record<string, unknown>),
          distance: this.area,
          positions: this.getClickSequence(),
          finished: this._isMeasuringSessionFinished,
        },
      })
    }
    // Update the tooltip with the final area
    this.updateTooltip(polygonToAdd, props)
  }

  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: {},
    })
  }

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