/* global google */
/* eslint-disable filenames/match-regex */
import type { Cluster } from './Cluster'

import type { ClusterIconStyle, ClusterIconInfo } from './types'

export class ClusterIcon {
  cluster: Cluster
  className: string
  clusterClassName: string
  styles: ClusterIconStyle[]
  center: google.maps.LatLng | undefined
  div: HTMLDivElement | null
  sums: ClusterIconInfo | null
  visible: boolean
  url: string
  height: number
  width: number
  anchorText: [number, number]
  anchorIcon: [number, number]
  textColor: string
  textSize: number
  textDecoration: string
  fontWeight: string
  fontStyle: string
  fontFamily: string
  backgroundPosition: string
  cMouseDownInCluster: boolean | null
  cDraggingMapByCluster: boolean | null
  timeOut: number | null

  boundsChangedListener: google.maps.MapsEventListener | null

  constructor(cluster: Cluster, styles: ClusterIconStyle[]) {
    cluster.getClusterer().extend(ClusterIcon, google.maps.OverlayView)

    this.cluster = cluster

    this.clusterClassName = this.cluster.getClusterer().getClusterClass()

    this.className = this.clusterClassName

    this.styles = styles

    this.center = undefined

    this.div = null

    this.sums = null

    this.visible = false

    this.boundsChangedListener = null

    this.url = ''

    this.height = 0
    this.width = 0

    this.anchorText = [0, 0]
    this.anchorIcon = [0, 0]

    this.textColor = 'black'
    this.textSize = 11
    this.textDecoration = 'none'
    this.fontWeight = 'bold'
    this.fontStyle = 'normal'
    this.fontFamily = 'Arial,sans-serif'

    this.backgroundPosition = '0 0'

    this.cMouseDownInCluster = null
    this.cDraggingMapByCluster = null
    this.timeOut = null;

    (this as unknown as google.maps.OverlayView).setMap(cluster.getMap()) // Note: this causes onAdd to be called

    this.onBoundsChanged = this.onBoundsChanged.bind(this)
    this.onMouseDown = this.onMouseDown.bind(this)
    this.onClick = this.onClick.bind(this)
    this.onMouseOver = this.onMouseOver.bind(this)
    this.onMouseOut = this.onMouseOut.bind(this)
    this.onAdd = this.onAdd.bind(this)
    this.onRemove = this.onRemove.bind(this)
    this.draw = this.draw.bind(this)
    this.hide = this.hide.bind(this)
    this.show = this.show.bind(this)
    this.useStyle = this.useStyle.bind(this)
    this.setCenter = this.setCenter.bind(this)
    this.getPosFromLatLng = this.getPosFromLatLng.bind(this)
  }

  onBoundsChanged() {
    this.cDraggingMapByCluster = this.cMouseDownInCluster
  }

  onMouseDown() {
    this.cMouseDownInCluster = true

    this.cDraggingMapByCluster = false
  }

  onClick(event: Event) {
    this.cMouseDownInCluster = false

    if (!this.cDraggingMapByCluster) {
      const markerClusterer = this.cluster.getClusterer()

      /**
       * This event is fired when a cluster marker is clicked.
       * @name MarkerClusterer#click
       * @param {Cluster} c The cluster that was clicked.
       * @event
       */
      google.maps.event.trigger(markerClusterer, 'click', this.cluster)
      google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster) // deprecated name

      // The default click handler follows. Disable it by setting
      // the zoomOnClick property to false.
      if (markerClusterer.getZoomOnClick()) {
        // Zoom into the cluster.
        const maxZoom = markerClusterer.getMaxZoom()

        const bounds = this.cluster.getBounds()

        const map = (markerClusterer as unknown as google.maps.OverlayView).getMap()

        if (map !== null && 'fitBounds' in map) {
          map.fitBounds(bounds)
        }


        // There is a fix for Issue 170 here:
        this.timeOut = window.setTimeout(() => {
          const map = (markerClusterer as unknown as google.maps.OverlayView).getMap()

          if (map !== null) {
            if ('fitBounds' in map) {
              map.fitBounds(bounds)
            }

            const zoom = map.getZoom() || 0

            // Don't zoom beyond the max zoom level
            if (
              maxZoom !== null &&
              zoom > maxZoom
            ) {
              map.setZoom(maxZoom + 1)
            }
          }
        }, 100)
      }

      // Prevent event propagation to the map:
      event.cancelBubble = true

      if (event.stopPropagation) {
        event.stopPropagation()
      }
    }
  }

  onMouseOver() {
    /**
     * This event is fired when the mouse moves over a cluster marker.
     * @name MarkerClusterer#mouseover
     * @param {Cluster} c The cluster that the mouse moved over.
     * @event
     */
    google.maps.event.trigger(
      this.cluster.getClusterer(),
      'mouseover',
      this.cluster
    )
  }

  onMouseOut() {
    /**
     * This event is fired when the mouse moves out of a cluster marker.
     * @name MarkerClusterer#mouseout
     * @param {Cluster} c The cluster that the mouse moved out of.
     * @event
     */
    google.maps.event.trigger(
      this.cluster.getClusterer(),
      'mouseout',
      this.cluster
    )
  }

  onAdd() {
    this.div = document.createElement('div')

    this.div.className = this.className

    if (this.visible) {
      this.show()
    }

    ;(this as unknown as google.maps.OverlayView).getPanes()?.overlayMouseTarget.appendChild(this.div)

    const map = (this as unknown as google.maps.OverlayView).getMap()

    if (map !== null) {
      // Fix for Issue 157
      this.boundsChangedListener = google.maps.event.addListener(
        map,
        'bounds_changed',
        this.onBoundsChanged
      )

      this.div.addEventListener('mousedown', this.onMouseDown)

      this.div.addEventListener('click', this.onClick)

      this.div.addEventListener('mouseover', this.onMouseOver)

      this.div.addEventListener('mouseout', this.onMouseOut)
    }
  }

  onRemove() {
    if (this.div && this.div.parentNode) {
      this.hide()

      if (this.boundsChangedListener !== null) {
        google.maps.event.removeListener(this.boundsChangedListener)
      }

      this.div.removeEventListener('mousedown', this.onMouseDown)

      this.div.removeEventListener('click', this.onClick)

      this.div.removeEventListener('mouseover', this.onMouseOver)

      this.div.removeEventListener('mouseout', this.onMouseOut)

      this.div.parentNode.removeChild(this.div)

      if (this.timeOut !== null) {
        window.clearTimeout(this.timeOut)

        this.timeOut = null
      }

      this.div = null
    }
  }

  draw() {
    if (this.visible && this.div !== null && this.center) {
      const pos = this.getPosFromLatLng(this.center)

      this.div.style.top = pos !== null ? `${pos.y}px` : '0'
      this.div.style.left = pos !== null ? `${pos.x}px` : '0'
    }
  }

  hide() {
    if (this.div) {
      this.div.style.display = 'none'
    }

    this.visible = false
  }

  show() {
    if (this.div && this.center) {
      const divTitle = this.sums === null ||
      typeof this.sums.title === 'undefined' ||
      this.sums.title === '' ? this.cluster.getClusterer().getTitle() :  this.sums.title

      // NOTE: values must be specified in px units
      const bp = this.backgroundPosition.split(' ')

      const spriteH = parseInt(bp[0]?.replace(/^\s+|\s+$/g, '') || '0', 10)
      const spriteV = parseInt(bp[1]?.replace(/^\s+|\s+$/g, '') || '0', 10)

      const pos = this.getPosFromLatLng(this.center)

      this.div.className = this.className
      this.div .setAttribute('style', `cursor: pointer; position: absolute; top: ${pos !== null ? `${pos.y}px` : '0'}; left: ${pos !== null ? `${pos.x}px` : '0'}; width: ${this.width}px; height: ${this.height}px; `)

      const img = document.createElement('img')

      img.alt = divTitle
      img.src = this.url
      img.width = this.width
      img.height = this.height
      img.setAttribute('style', `position: absolute; top: ${spriteV}px; left: ${spriteH}px`)

      if (!this.cluster.getClusterer().enableRetinaIcons) {
        img.style.clip = `rect(-${spriteV}px, -${spriteH + this.width}px, -${
          spriteV + this.height
        }, -${spriteH})`
      }

      const textElm = document.createElement('div')

      textElm .setAttribute('style', `position: absolute; top: ${this.anchorText[0]}px; left: ${this.anchorText[1]}px; color: ${this.textColor}; font-size: ${this.textSize}px; font-family: ${this.fontFamily}; font-weight: ${this.fontWeight}; fontStyle: ${this.fontStyle}; text-decoration: ${this.textDecoration}; text-align: center; width: ${this.width}px; line-height: ${this.height}px`)

      if (this.sums?.text) textElm.innerText = `${this.sums?.text}`
      if (this.sums?.html) textElm.innerHTML = `${this.sums?.html}`

      this.div.innerHTML = ''

      this.div.appendChild(img)
      this.div.appendChild(textElm)

      this.div.title = divTitle

      this.div.style.display = ''
    }

    this.visible = true
  }

  useStyle(sums: ClusterIconInfo) {
    this.sums = sums

    const styles = this.cluster.getClusterer().getStyles()

    const style =
      styles[Math.min(styles.length - 1, Math.max(0, sums.index - 1))]

    if (style) {
      this.url = style.url
      this.height = style.height
      this.width = style.width

      if (style.className) {
        this.className = `${this.clusterClassName} ${style.className}`
      }

      this.anchorText = style.anchorText || [0, 0]
      this.anchorIcon = style.anchorIcon || [this.height / 2, this.width / 2]

      this.textColor = style.textColor || 'black'

      this.textSize = style.textSize || 11

      this.textDecoration = style.textDecoration || 'none'

      this.fontWeight = style.fontWeight || 'bold'

      this.fontStyle = style.fontStyle || 'normal'

      this.fontFamily = style.fontFamily || 'Arial,sans-serif'

      this.backgroundPosition = style.backgroundPosition || '0 0'
    }
  }

  setCenter(center: google.maps.LatLng) {
    this.center = center
  }

  getPosFromLatLng(latlng: google.maps.LatLng): google.maps.Point | null {
    const pos = (this as unknown as google.maps.OverlayView).getProjection().fromLatLngToDivPixel(latlng)

    if (pos !== null) {
      pos.x -= this.anchorIcon[1]

      pos.y -= this.anchorIcon[0]
    }

    return pos
  }
}
