(function () {
  if (!window.L || L.Icon.OptimisticCanvasMarker) {
    return
  }

  function drawRoundRect(ctx, x, y, width, height, radius) {
    radius = Math.min(radius, width / 2, height / 2)
    ctx.beginPath()
    ctx.moveTo(x + radius, y)
    ctx.lineTo(x + width - radius, y)
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
    ctx.lineTo(x + width, y + height - radius)
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
    ctx.lineTo(x + radius, y + height)
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
    ctx.lineTo(x, y + radius)
    ctx.quadraticCurveTo(x, y, x + radius, y)
    ctx.closePath()
  }

  function getTextColor(bgColor, lightColor, darkColor) {
    if (typeof window.textColor === 'function') {
      return window.textColor(bgColor, lightColor, darkColor)
    }
    lightColor = lightColor || '#fff'
    darkColor = darkColor || '#000'
    if (typeof bgColor !== 'string') {
      return lightColor
    }
    const color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor
    if (!/^[0-9a-f]{6}$/i.test(color)) {
      return lightColor
    }
    const r = parseInt(color.substring(0, 2), 16)
    const g = parseInt(color.substring(2, 4), 16)
    const b = parseInt(color.substring(4, 6), 16)
    return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ? darkColor : lightColor
  }

  function setCanvasSize(canvas, width, height) {
    const ratio = window.devicePixelRatio || 1
    canvas.width = Math.round(width * ratio)
    canvas.height = Math.round(height * ratio)
    canvas.style.width = `${width}px`
    canvas.style.height = `${height}px`
    const ctx = canvas.getContext('2d')
    ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
    return ctx
  }

  L.Icon.OptimisticCanvasMarker = L.Icon.extend({
    options: {
      iconSize: [24, 24],
      iconAnchor: [12, 12],
      tooltipAnchor: [0, 48],
      popupAnchor: [0, 0],
      markerColor: '#333333',
      iconColor: '#ffffff',
      strokeColor: '#ffffff',
      iconText: '',
      shape: 'small',
      opacity: 0.8
    },

    createIcon: function (oldIcon) {
      const canvas = oldIcon && oldIcon.tagName === 'CANVAS' ? oldIcon : document.createElement('canvas')
      const options = this.options
      const size = L.point(options.iconSize)
      canvas.className = `optimistic-canvas-marker optimistic-canvas-marker-${options.shape || 'small'}`
      this._setIconStyles(canvas, 'icon')

      const ctx = setCanvasSize(canvas, size.x, size.y)
      ctx.clearRect(0, 0, size.x, size.y)
      ctx.globalAlpha = options.opacity || 0.8

      const markerSize = options.markerSize || (options.shape === 'small' ? 'small' : 'normal')

      if (markerSize === 'small' && options.shape === 'flag') {
        this._drawSmallFlag(ctx, options, size)
      } else if (markerSize === 'small' && options.shape === 'alert') {
        this._drawSmallAlert(ctx, options, size)
      } else if (markerSize === 'small') {
        this._drawSmall(ctx, options, size)
      } else if (options.shape === 'pin') {
        this._drawPin(ctx, options, size)
      } else if (options.shape === 'flag') {
        this._drawFlag(ctx, options, size)
      } else if (options.shape === 'alert') {
        this._drawAlert(ctx, options, size)
      } else {
        this._drawSmall(ctx, options, size)
      }

      return canvas
    },

    createShadow: function () {
      return null
    },

    _drawSmall: function (ctx, options, size) {
      const text = String(options.iconText || '')
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const width = Math.max(18, Math.min(size.x, 10 + text.length * 7))
      const height = 18
      const x = (size.x - width) / 2
      const y = (size.y - height) / 2

      ctx.shadowColor = 'rgba(0,0,0,0.3)'
      ctx.shadowBlur = 1
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = '#ffffff'
      ctx.lineWidth = 1
      ctx.fillRect(x, y, width, height)
      ctx.strokeRect(x, y, width, height)

      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, size.x / 2, size.y / 2 + 0.5)
    },

    _drawSmallFlag: function (ctx, options, size) {
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const stroke = options.strokeColor || color
      const text = String(options.iconText || '')
      const width = Math.max(18, Math.min(size.x - 2, 10 + text.length * 7))
      const height = 15
      const x = (size.x - width) / 2
      const y = 2

      ctx.shadowColor = 'rgba(0,0,0,0.3)'
      ctx.shadowBlur = 1
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1
      ctx.fillRect(x, y, width, height)
      ctx.strokeRect(x, y, width, height)
      ctx.beginPath()
      ctx.moveTo(x, y + height)
      ctx.lineTo(x, size.y - 2)
      ctx.stroke()

      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, size.x / 2, y + height / 2 + 0.5)
    },

    _drawSmallAlert: function (ctx, options, size) {
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const stroke = options.strokeColor || color
      const text = String(options.iconText || '')
      const x = size.x / 2

      ctx.shadowColor = 'rgba(0,0,0,0.3)'
      ctx.shadowBlur = 1
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1
      ctx.beginPath()
      ctx.moveTo(x, 2)
      ctx.lineTo(size.x - 1, 18)
      ctx.lineTo(1, 18)
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
      ctx.beginPath()
      ctx.moveTo(x, 16)
      ctx.lineTo(x, size.y - 2)
      ctx.stroke()

      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, x, 12.5)
    },

    _drawPin: function (ctx, options, size) {
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const stroke = options.strokeColor || color
      const x = size.x / 2
      const y = 16

      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1.5
      ctx.beginPath()
      ctx.moveTo(x, 1)
      ctx.bezierCurveTo(7.7, 1, 1, 7.7, 1, 15.9)
      ctx.bezierCurveTo(1, 24.1, x, 51, x, 51)
      ctx.bezierCurveTo(x, 51, 31, 24.1, 31, 15.9)
      ctx.bezierCurveTo(31, 7.7, 24.3, 1, x, 1)
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
      this._drawText(ctx, options, x, y)
    },

    _drawFlag: function (ctx, options, size) {
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const stroke = options.strokeColor || color

      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1.5
      ctx.beginPath()
      ctx.rect(4, 4, 25, 25)
      ctx.fill()
      ctx.stroke()
      ctx.beginPath()
      ctx.moveTo(4, 29)
      ctx.lineTo(4, 49)
      ctx.stroke()
      this._drawText(ctx, options, 16, 16)
    },

    _drawAlert: function (ctx, options, size) {
      const color = options.iconColor || '#ffffff'
      const fill = options.markerColor || '#333333'
      const stroke = options.strokeColor || color

      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1.5
      ctx.beginPath()
      ctx.moveTo(16, 4)
      ctx.lineTo(31, 33)
      ctx.lineTo(1, 33)
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
      ctx.beginPath()
      ctx.moveTo(16, 30)
      ctx.lineTo(16, 51)
      ctx.stroke()
      this._drawText(ctx, options, 16, 22)
    },

    _drawText: function (ctx, options, x, y) {
      ctx.fillStyle = options.iconColor || '#ffffff'
      ctx.font = '700 13px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(String(options.iconText || ''), x, y)
    }
  })

  L.icon.optimisticCanvasMarker = function (options) {
    return new L.Icon.OptimisticCanvasMarker(options)
  }

  L.OptimisticCanvasFeatureGroup = L.Layer.extend({
    options: {
      pane: 'markerClusterGroup',
      padding: 256,
      clickTolerance: 4,
      contextMenuTolerance: 10,
      dragClickTolerance: 4
    },

    initialize: function (options) {
      L.setOptions(this, options)
      this._layers = {}
      this._hitLayers = []
      this._hoverLayer = null
      this._resetFrame = null
      this._markerClusterGroup = null
      this._mouseDownPoint = null
      this._draggedSinceMouseDown = false
      this._domEventTypes = ['click', 'contextmenu', 'mousedown', 'mousemove', 'mouseup']
      this._boundHandleDomEvent = this._handleDomEvent.bind(this)
    },

    onAdd: function (map) {
      this._map = map
      this._canvas = L.DomUtil.create('canvas', 'optimistic-markercluster-canvas leaflet-layer leaflet-zoom-animated')
      this._canvas.style.pointerEvents = 'none'
      ;(this.getPane() || map.getPanes().overlayPane).appendChild(this._canvas)
      this._ctx = this._canvas.getContext('2d')

      map.on('moveend zoomend resize viewreset', this._reset, this)
      map.on('movestart zoomstart', this._clearHoverLayer, this)
      this._domEventTypes.forEach(type => {
        map._container.addEventListener(type, this._boundHandleDomEvent, true)
      })
      if (map.options.zoomAnimation && L.Browser.any3d) {
        map.on('zoomanim', this._animateZoom, this)
      }

      Object.keys(this._layers).forEach(id => this._attachLayer(this._layers[id]))
      this._reset()
    },

    onRemove: function (map) {
      map.off('moveend zoomend resize viewreset', this._reset, this)
      map.off('movestart zoomstart', this._clearHoverLayer, this)
      this._domEventTypes.forEach(type => {
        map._container.removeEventListener(type, this._boundHandleDomEvent, true)
      })
      map.off('zoomanim', this._animateZoom, this)
      if (this._canvas?.parentNode) {
        this._canvas.parentNode.removeChild(this._canvas)
      }
      if (this._resetFrame) {
        cancelAnimationFrame(this._resetFrame)
        this._resetFrame = null
      }
      Object.keys(this._layers).forEach(id => {
        const layer = this._layers[id]
        layer._icon = null
        layer._map = null
        layer._optimisticCanvasFeatureGroup = null
        layer.removeEventParent?.(this)
      })
      this._canvas = null
      this._ctx = null
      this._map = null
      this._hitLayers = []
    },

    addLayer: function (layer) {
      if (!layer?.getLatLng) {
        return this
      }
      this._layers[L.stamp(layer)] = layer
      this._patchLayer(layer)
      this._attachLayer(layer)
      this._scheduleReset()
      return this
    },

    removeLayer: function (layer) {
      const id = layer && L.stamp(layer)
      if (id && this._layers[id]) {
        if (this._hoverLayer === layer) {
          this._fireLayerEvent(layer, 'mouseout')
          this._hoverLayer = null
        }
        delete this._layers[id]
        layer._icon = null
        layer._map = null
        layer._optimisticCanvasFeatureGroup = null
        layer.removeEventParent?.(this)
        this._scheduleReset()
      }
      return this
    },

    clearLayers: function () {
      Object.keys(this._layers).forEach(id => {
        this._layers[id]._icon = null
        this._layers[id]._map = null
        this._layers[id]._optimisticCanvasFeatureGroup = null
        this._layers[id].removeEventParent?.(this)
      })
      this._layers = {}
      this._hitLayers = []
      this._hoverLayer = null
      this._scheduleReset()
      return this
    },

    eachLayer: function (callback, context) {
      Object.keys(this._layers).forEach(id => callback.call(context || this, this._layers[id]))
      return this
    },

    getLayers: function () {
      return Object.keys(this._layers).map(id => this._layers[id])
    },

    hasLayer: function (layer) {
      return !!(layer && this._layers[L.stamp(layer)])
    },

    _attachLayer: function (layer) {
      if (!this._map) return
      layer._map = this._map
      layer._icon = this._canvas
      layer._optimisticCanvasFeatureGroup = this
      layer.addEventParent?.(this)
    },

    _patchLayer: function (layer) {
      if (layer._optimisticCanvasPatched) return
      layer._optimisticCanvasPatched = true
      const redraw = function () {
        this._optimisticCanvasFeatureGroup?._scheduleReset()
      }
      const originalSetOpacity = layer.setOpacity
      layer.setOpacity = function (opacity) {
        this.options.opacity = opacity
        this.opa = opacity
        redraw.call(this)
        return this
      }
      layer._optimisticOriginalSetOpacity = originalSetOpacity

      const originalSetIcon = layer.setIcon
      layer.setIcon = function (icon) {
        this.options.icon = icon
        redraw.call(this)
        if (this._popup) {
          this.bindPopup(this._popup, this._popup.options)
        }
        return this
      }
      layer._optimisticOriginalSetIcon = originalSetIcon

      const originalSetLatLng = layer.setLatLng
      layer.setLatLng = function () {
        const result = originalSetLatLng.apply(this, arguments)
        redraw.call(this)
        return result
      }
    },

    _reset: function () {
      this._resetFrame = null
      if (!this._map || !this._canvas || !this._ctx) return
      const size = this._map.getSize()
      const padding = this.options.padding
      const topLeft = this._map.containerPointToLayerPoint([-padding, -padding])
      this._canvas.width = size.x + padding * 2
      this._canvas.height = size.y + padding * 2
      this._canvas.style.width = `${this._canvas.width}px`
      this._canvas.style.height = `${this._canvas.height}px`
      L.DomUtil.setPosition(this._canvas, topLeft)
      this._origin = topLeft.add(this._map.getPixelOrigin())
      this._center = this._map.getCenter()
      this._zoom = this._map.getZoom()
      this._draw(topLeft)
    },

    _scheduleReset: function () {
      if (!this._map || this._resetFrame) return
      this._resetFrame = requestAnimationFrame(() => this._reset())
    },

    _clearHoverLayer: function () {
      if (!this._hoverLayer) return
      this._hoverLayer.closeTooltip?.()
      this._fireLayerEvent(this._hoverLayer, 'mouseout')
      this._hoverLayer = null
      if (this._map?._container) {
        this._map._container.style.cursor = ''
      }
    },

    _closeOpenTooltips: function (exceptLayer) {
      Object.keys(this._layers).forEach(id => {
        const layer = this._layers[id]
        if (layer !== exceptLayer && layer._tooltip?._map) {
          layer.closeTooltip?.()
        }
      })
    },

    _animateZoom: function (event) {
      if (!this._map || !this._canvas || !this._origin) return
      const scale = this._map.getZoomScale(event.zoom, this._zoom)
      const offset = this._origin
        .multiplyBy(scale)
        .subtract(this._map._getNewPixelOrigin(event.center, event.zoom))
      L.DomUtil.setTransform(this._canvas, offset, scale)
    },

    _draw: function (topLeft) {
      const ctx = this._ctx
      const bounds = this._map.getBounds().pad(0.4)
      ctx.clearRect(0, 0, this._canvas.width, this._canvas.height)
      this._hitLayers = []
      const drawItems = []

      Object.keys(this._layers).forEach(id => {
        const layer = this._layers[id]
        const latlng = layer.getLatLng()
        if (!bounds.contains(latlng)) return
        const layerPoint = this._map.latLngToLayerPoint(latlng)
        drawItems.push({
          layer,
          latlng,
          layerPoint,
          zIndex: this._getLayerZIndex(layer, layerPoint)
        })
      })

      drawItems
        .sort((a, b) => a.zIndex - b.zIndex)
        .forEach(item => {
        const layer = item.layer
        const x = item.layerPoint.x - topLeft.x
        const y = item.layerPoint.y - topLeft.y
        const hitArea = layer.getChildCount ? this._drawCluster(ctx, layer, x, y) : this._drawMarker(ctx, layer, x, y)
        const hitRadius = typeof hitArea === 'number' ? hitArea : hitArea?.r
        if (hitRadius) {
          const containerPoint = this._map.latLngToContainerPoint(item.latlng)
          this._hitLayers.push({
            layer,
            x: containerPoint.x + (hitArea.dx || 0),
            y: containerPoint.y + (hitArea.dy || 0),
            r: hitRadius,
            zIndex: item.zIndex
          })
        }
      })
    },

    _getLayerZIndex: function (layer, layerPoint) {
      const iconOptions = layer.options?.icon?.options || {}
      const isSelectedUnused = (iconOptions.iconUrl || '').indexOf('dot_blue') !== -1
        || iconOptions.className === 'unused-marker-top'
      const selectedBoost = isSelectedUnused ? 100000 : 0
      if (Number.isFinite(layer._zIndex)) {
        return layer._zIndex + selectedBoost
      }
      return layerPoint.y + (layer.options?.zIndexOffset || 0) + selectedBoost
    },

    _getIconImage: function (iconUrl) {
      if (!iconUrl || typeof Image === 'undefined') return null
      this._iconImageCache = this._iconImageCache || {}
      let image = this._iconImageCache[iconUrl]
      if (!image) {
        image = new Image()
        image.onload = () => this._scheduleReset()
        image.onerror = () => {
          image._optimisticLoadError = true
        }
        image.src = iconUrl
        this._iconImageCache[iconUrl] = image
      }
      if (image._optimisticLoadError || !image.complete || !image.naturalWidth) return null
      return image
    },

    _drawMarker: function (ctx, marker, x, y) {
      const point = marker.point || {}
      const iconOptions = marker.options?.icon?.options || {}
      const opacity = marker.opa ?? marker.options.opacity ?? iconOptions.opacity ?? 0.8
      if (opacity <= 0) return 0

      ctx.save()
      ctx.globalAlpha = Math.max(0.1, Math.min(1, opacity))
      if (point.isUnused || iconOptions.iconUrl) {
        const iconUrl = iconOptions.iconUrl || ''
        const fill = iconUrl.indexOf('dot_blue') !== -1 ? '#2d67d8' : '#e34b4b'
        const iconSize = Array.isArray(iconOptions.iconSize) ? Math.max(iconOptions.iconSize[0], iconOptions.iconSize[1]) : 16
        const isDotIcon = point.isUnused || iconUrl.indexOf('dot_red') !== -1 || iconUrl.indexOf('dot_blue') !== -1
        if (isDotIcon) {
          const iconImage = this._getIconImage(iconUrl)
          const iconWidth = Array.isArray(iconOptions.iconSize) ? iconOptions.iconSize[0] : iconSize
          const iconHeight = Array.isArray(iconOptions.iconSize) ? iconOptions.iconSize[1] : iconSize
          const iconAnchor = Array.isArray(iconOptions.iconAnchor) ? iconOptions.iconAnchor : [iconWidth / 2, iconHeight / 2]
          if (iconImage) {
            ctx.drawImage(iconImage, x - iconAnchor[0], y - iconAnchor[1], iconWidth, iconHeight)
            ctx.restore()
            return Math.ceil(Math.max(iconWidth, iconHeight) / 2) + 2
          }
          const outerRadius = Math.max(4, Math.min(15, (iconSize - 1) / 2))
          const innerRadius = Math.max(2, Math.round(iconSize / 4))
          ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
          ctx.beginPath()
          ctx.arc(x, y, outerRadius, 0, Math.PI * 2)
          ctx.fill()
          ctx.fillStyle = iconUrl.indexOf('dot_blue') !== -1 ? '#2d67c6' : '#c64444'
          ctx.beginPath()
          ctx.arc(x, y, innerRadius, 0, Math.PI * 2)
          ctx.fill()
          ctx.restore()
          return Math.ceil(outerRadius) + 2
        }
        const radius = Math.max(7, Math.min(15, Math.round(iconSize / 2) - 1))
        ctx.fillStyle = fill
        ctx.strokeStyle = '#ffffff'
        ctx.lineWidth = 2
        ctx.beginPath()
        ctx.arc(x, y, radius, 0, Math.PI * 2)
        ctx.fill()
        ctx.stroke()
        ctx.restore()
        return radius + 3
      }
      const options = marker.options.icon?.options || {}
      const shape = options.shape || 'pin'
      const markerSize = options.markerSize || (shape === 'small' ? 'small' : 'normal')
      const markerColor = options.markerColor || point.route?.color || '#333333'
      const iconColor = options.iconColor || '#ffffff'
      const iconText = options.iconText || point.p + 1
      if (shape === 'small') {
        this._drawSmallMarker(ctx, x, y, markerColor, iconColor, iconText)
        ctx.restore()
        return 14
      }
      if (markerSize === 'small') {
        if (shape === 'flag' || shape === 'alert') {
          this._drawSmallStatusMarker(ctx, x, y, markerColor, iconColor, iconText, shape)
        } else {
          this._drawSmallMarker(ctx, x, y, markerColor, iconColor, iconText)
        }
        ctx.restore()
        return 16
      }
      this._drawPinMarker(ctx, x, y, markerColor, iconColor, iconText, shape)
      ctx.restore()
      return {r: 26, dx: 0, dy: -25}
    },

    _drawCluster: function (ctx, cluster, x, y) {
      const markers = cluster.getAllChildMarkers()
      let colors = []
      let hasUnused = false
      markers.forEach(marker => {
        const point = marker.point
        if (point?.isUnused) {
          hasUnused = true
        } else if (point?.polyline?.options?.color && colors.indexOf(point.polyline.options.color) === -1) {
          colors.push(point.polyline.options.color)
        }
      })

      let markerColor = '#000000'
      if (colors.length === 1) markerColor = colors[0]
      if (hasUnused) markerColor = '#ffffff'
      const text = `x${markers.length}`
      ctx.save()
      ctx.globalAlpha = 0.8
      if (localStorage.getItem('small_markers')) {
        let fill = markerColor
        if (colors.length > 1 && !hasUnused) {
          fill = ctx.createLinearGradient(x - 13, y, x + 13, y)
          colors.forEach((color, index) => {
            fill.addColorStop(index / colors.length, color)
            fill.addColorStop((index + 1) / colors.length, color)
          })
        }
        this._drawClusterBubble(ctx, x, y, fill, getTextColor(markerColor), text)
      } else {
        this._drawPinMarker(ctx, x, y + 25, markerColor, getTextColor(markerColor, '#ffffff', '#000000'), text, 'pin')
      }
      ctx.restore()
      return localStorage.getItem('small_markers') ? 18 : 24
    },

    _drawSmallMarker: function (ctx, x, y, fill, color, text) {
      text = String(text || '')
      const width = Math.max(18, Math.min(30, 10 + text.length * 7))
      const height = 18
      ctx.shadowColor = 'rgba(0,0,0,0.3)'
      ctx.shadowBlur = 1
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = '#ffffff'
      ctx.lineWidth = 1
      ctx.fillRect(x - width / 2, y - height / 2, width, height)
      ctx.strokeRect(x - width / 2, y - height / 2, width, height)
      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, x, y + 0.5)
    },

    _drawSmallStatusMarker: function (ctx, x, y, fill, color, text, shape) {
      text = String(text || '')
      const stroke = color || '#ffffff'

      ctx.shadowColor = 'rgba(0,0,0,0.3)'
      ctx.shadowBlur = 1
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = stroke
      ctx.lineWidth = 1
      ctx.beginPath()
      if (shape === 'alert') {
        ctx.moveTo(x, y - 12)
        ctx.lineTo(x + 12, y + 6)
        ctx.lineTo(x - 12, y + 6)
        ctx.closePath()
        ctx.fill()
        ctx.stroke()
        ctx.beginPath()
        ctx.moveTo(x, y + 4)
        ctx.lineTo(x, y + 12)
        ctx.stroke()
        y -= 1
      } else {
        const width = Math.max(18, Math.min(30, 10 + text.length * 7))
        const height = 15
        const left = x - width / 2
        const top = y - 11
        ctx.fillRect(left, top, width, height)
        ctx.strokeRect(left, top, width, height)
        ctx.beginPath()
        ctx.moveTo(left, top + height)
        ctx.lineTo(left, y + 12)
        ctx.stroke()
        y = top + height / 2
      }

      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, x, y + 0.5)
    },

    _drawClusterBubble: function (ctx, x, y, fill, color, text) {
      const width = Math.max(22, 10 + String(text).length * 7)
      const height = 20
      drawRoundRect(ctx, x - width / 2, y - height / 2, width, height, 10)
      ctx.shadowColor = 'rgba(0,0,0,0.4)'
      ctx.shadowBlur = 2
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = -1
      ctx.fillStyle = fill
      ctx.strokeStyle = '#ffffff'
      ctx.lineWidth = 1
      ctx.fill()
      ctx.stroke()
      ctx.shadowColor = 'transparent'
      ctx.fillStyle = color
      ctx.font = '700 11px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, x, y + 0.5)
    },

    _drawPinMarker: function (ctx, x, y, fill, color, text, shape) {
      ctx.fillStyle = fill
      ctx.strokeStyle = color
      ctx.lineWidth = 1.5
      ctx.beginPath()
      if (shape === 'alert') {
        ctx.moveTo(x, y - 46)
        ctx.lineTo(x + 15, y - 17)
        ctx.lineTo(x - 15, y - 17)
        ctx.closePath()
        ctx.fill()
        ctx.stroke()
        ctx.beginPath()
        ctx.moveTo(x, y - 20)
        ctx.lineTo(x, y + 1)
        ctx.stroke()
        y -= 28
      } else if (shape === 'flag') {
        ctx.rect(x - 12, y - 46, 25, 25)
        ctx.fill()
        ctx.stroke()
        ctx.beginPath()
        ctx.moveTo(x - 12, y - 21)
        ctx.lineTo(x - 12, y - 1)
        ctx.stroke()
        y -= 34
      } else {
        ctx.moveTo(x, y - 49)
        ctx.bezierCurveTo(x - 8.3, y - 49, x - 15, y - 42.3, x - 15, y - 34.1)
        ctx.bezierCurveTo(x - 15, y - 25.9, x, y + 1, x, y + 1)
        ctx.bezierCurveTo(x, y + 1, x + 15, y - 25.9, x + 15, y - 34.1)
        ctx.bezierCurveTo(x + 15, y - 42.3, x + 8.3, y - 49, x, y - 49)
        ctx.closePath()
        ctx.fill()
        ctx.stroke()
        y -= 34
      }
      ctx.fillStyle = color
      ctx.font = '700 13px Arial, sans-serif'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText(String(text || ''), x, y)
    },

    _handleDomEvent: function (originalEvent) {
      if (!this._map) return
      const leafletUiElement = originalEvent.target?.closest?.('.leaflet-popup, .leaflet-tooltip, .leaflet-control')
      if (leafletUiElement) {
        if (originalEvent.type === 'mousemove') {
          this._clearHoverLayer()
          this._closeOpenTooltips()
        }
        leafletUiElement.addEventListener(originalEvent.type, L.DomEvent.stopPropagation, { once: true })
        return
      }
      const event = {
        type: originalEvent.type,
        originalEvent,
        containerPoint: this._map.mouseEventToContainerPoint(originalEvent),
        layerPoint: this._map.mouseEventToLayerPoint(originalEvent),
        latlng: this._map.mouseEventToLatLng(originalEvent)
      }
      if (event.type === 'mousedown') {
        this._mouseDownPoint = event.containerPoint
        this._draggedSinceMouseDown = false
      }
      if (event.type === 'mousemove') {
        if (originalEvent.buttons || this._map._animatingZoom) {
          this._updateDraggedState(event.containerPoint)
          if (!this._map._optimisticChangingPoly) {
            this._clearHoverLayer()
            return
          }
        }
      }
      if (event.type === 'mouseup') {
        this._updateDraggedState(event.containerPoint)
        return
      }
      if ((event.type === 'click' || event.type === 'contextmenu') && this._shouldSuppressClick(event.containerPoint)) {
        this._draggedSinceMouseDown = false
        this._mouseDownPoint = null
        return
      }
      const layer = this._findLayer(
        event.containerPoint,
        event.type === 'mousedown'
          ? 0
          : event.type === 'contextmenu'
            ? this.options.contextMenuTolerance
            : this.options.clickTolerance
      )
      if (event.type === 'mousemove') {
        if (layer !== this._hoverLayer) {
          if (this._hoverLayer) {
            this._hoverLayer.closeTooltip?.()
            this._fireLayerEvent(this._hoverLayer, 'mouseout', event)
          }
          this._hoverLayer = layer
          if (layer) {
            this._closeOpenTooltips(layer)
            this._fireLayerEvent(layer, 'mouseover', event)
          }
        }
        this._map._container.style.cursor = layer ? 'pointer' : ''
        return
      }
      if (!layer) return
      this._fireLayerEvent(layer, event.type, event)
      if (event.type !== 'mousedown') {
        L.DomEvent.stop(originalEvent)
      }
    },

    _updateDraggedState: function (point) {
      if (!this._mouseDownPoint || this._draggedSinceMouseDown) {
        return
      }
      if (this._mouseDownPoint.distanceTo(point) > this.options.dragClickTolerance) {
        this._draggedSinceMouseDown = true
      }
    },

    _shouldSuppressClick: function (point) {
      this._updateDraggedState(point)
      return this._draggedSinceMouseDown
    },

    _findLayer: function (point, tolerance) {
      tolerance = tolerance ?? this.options.clickTolerance
      let bestLayer = null
      let bestDistance = Infinity
      let bestZIndex = -Infinity
      for (let i = 0; i < this._hitLayers.length; i++) {
        const hit = this._hitLayers[i]
        const dx = hit.x - point.x
        const dy = hit.y - point.y
        const distance = Math.sqrt(dx * dx + dy * dy)
        if (distance <= hit.r + tolerance && (hit.zIndex > bestZIndex || (hit.zIndex === bestZIndex && distance < bestDistance))) {
          bestLayer = hit.layer
          bestDistance = distance
          bestZIndex = hit.zIndex
        }
      }
      return bestLayer
    },

    _fireLayerEvent: function (layer, type, mapEvent) {
      const latlng = layer.getLatLng()
      const event = {
        type,
        target: layer,
        sourceTarget: layer,
        layer,
        latlng,
        layerPoint: mapEvent?.layerPoint || this._map.latLngToLayerPoint(latlng),
        containerPoint: mapEvent?.containerPoint || this._map.latLngToContainerPoint(latlng),
        originalEvent: mapEvent?.originalEvent
      }
      if (L.MarkerCluster && layer instanceof L.MarkerCluster && this._markerClusterGroup) {
        layer.fire(type, event)
        this._markerClusterGroup.fire(type, event, true)
        return
      }

      layer.fire(type, event, true)
      if (type === 'click' && layer.getPopup?.()) {
        layer.openPopup?.()
      }
    }
  })

  L.optimisticCanvasFeatureGroup = function (options) {
    return new L.OptimisticCanvasFeatureGroup(options)
  }

  if (L.MarkerClusterGroup) {
    L.MarkerClusterGroup.OptimisticCanvas = L.MarkerClusterGroup.extend({
      initialize: function (options) {
        L.MarkerClusterGroup.prototype.initialize.call(this, options)
        this._featureGroup = L.optimisticCanvasFeatureGroup({
          pane: options?.clusterPane || L.Marker.prototype.options.pane
        })
        this._featureGroup._markerClusterGroup = this
        this._featureGroup.addEventParent(this)
      },

      refreshClusters: function (layers) {
        if (!this._topClusterLevel) {
          return this
        }
        if (layers instanceof L.MarkerClusterGroup && !layers._topClusterLevel) {
          return this
        }
        return L.MarkerClusterGroup.prototype.refreshClusters.call(this, layers)
      }
    })

    L.optimisticCanvasMarkerClusterGroup = function (options) {
      return new L.MarkerClusterGroup.OptimisticCanvas(options)
    }
  }
})()
