import {
  Control,
  LayerGroup,
  latLng,
  Icon,
  Marker,
} from '../../../vendors/leaflet/leaflet-src.esm.js'
import {
  PhotonSearch,
  PhotonReverse,
} from '../../../vendors/photon/leaflet.photon.esm.js'
import * as Utils from '../utils.js'
import { translate } from '../i18n.js'
import { uMapAlert as Alert } from '../../components/alerts/alert.js'

export const HomeControl = Control.extend({
  options: {
    position: 'topleft',
  },

  onAdd: (map) => {
    const path = map._umap.getStaticPathFor('home.svg')
    const homeURL = map._umap.urls.get('home')
    const container = Utils.loadTemplate(
      `<a href="${homeURL}" class="home-button" title="${translate('Back to home')}"><img src="${path}" alt="${translate('Home logo')}" width="38px" height="38px" /></a>`
    )
    return container
  },
})

export const EditControl = Control.extend({
  options: {
    position: 'topright',
  },

  onAdd: (map) => {
    const template = `
      <div class="edit-enable dark">
        <button type="button" data-ref="button" class="round"><i class="icon icon-16 icon-edit"></i> ${translate('Edit')}</button>
      </div>
    `
    const [container, { button }] = Utils.loadTemplateWithRefs(template)
    button.addEventListener('click', () => map._umap.enableEdit())
    button.addEventListener('mouseover', () => {
      map._umap.tooltip.open({
        content: map._umap.help.displayLabel('TOGGLE_EDIT'),
        anchor: button,
        position: 'bottom',
        delay: 750,
        duration: 5000,
      })
    })
    return container
  },
})

export const LoadTemplateControl = Control.extend({
  options: {
    position: 'topright',
  },

  onAdd: (map) => {
    const template = `
      <div class="load-template dark hide-on-edit">
        <button type="button" data-ref="button" class="round"><i class="icon icon-16 icon-template"></i>&nbsp;${translate('Reuse this template')}</button>
      </div>
    `
    const [container, { button }] = Utils.loadTemplateWithRefs(template)
    button.addEventListener('click', () => {
      const downloadUrl = map._umap.urls.get('map_download', {
        map_id: map._umap.id,
      })
      const targetUrl = `${map._umap.urls.get('map_new')}?templateUrl=${downloadUrl}`
      window.open(targetUrl)
    })
    button.addEventListener('mouseover', () => {
      map._umap.tooltip.open({
        content: translate('Create a new map using this template'),
        anchor: button,
        position: 'bottom',
        delay: 750,
        duration: 5000,
      })
    })
    return container
  },
})

export const MoreControl = Control.extend({
  options: {
    position: 'topleft',
  },

  onAdd: function (map) {
    const pos = this.getPosition()
    const corner = map._controlCorners[pos]
    const className = 'umap-more-controls'
    const template = `
      <div class="umap-control-text">
        <button class="umap-control-more" type="button" data-ref="button"></button>
      </div>
    `
    const [container, { button }] = Utils.loadTemplateWithRefs(template)
    button.addEventListener('click', () => corner.classList.toggle(className))
    button.addEventListener('mouseover', () => {
      const extended = corner.classList.contains(className)
      map._umap.tooltip.open({
        content: extended ? translate('Hide controls') : translate('More controls'),
        anchor: button,
        position: 'right',
        delay: 750,
      })
    })
    return container
  },
})

export const PermanentCreditsControl = Control.extend({
  options: {
    position: 'bottomleft',
  },

  onAdd: (map) => {
    const container = Utils.loadTemplate(
      `<div class="umap-permanent-credits-container text">${Utils.toHTML(map.options.permanentCredit)}</div>`
    )
    const background = map._umap.getProperty('permanentCreditBackground')
      ? '#FFFFFFB0'
      : ''
    container.style.backgroundColor = background
    return container
  },
})

const BaseButton = Control.extend({
  initialize: function (umap, options) {
    this._umap = umap
    Control.prototype.initialize.call(this, options)
    this.afterInit()
  },

  onAdd: function (map) {
    const template = `
      <div class="${this.options.className} umap-control">
        <button type="button" title="${this.options.title}" data-ref="button"></button>
      </div>
    `
    const [container, { button }] = Utils.loadTemplateWithRefs(template)
    button.addEventListener('click', (event) => {
      event.stopPropagation()
      this.onClick()
    })
    button.addEventListener('dblclick', (event) => {
      event.stopPropagation()
    })
    if (this.options.icon) {
      button.appendChild(
        Utils.loadTemplate(`<i class="icon icon-24 ${this.options.icon}"></i>`)
      )
    }
    this.afterAdd(container, map)
    return container
  },

  onRemove(map) {
    this.afterRemove(map)
  },

  afterInit: () => {},
  afterAdd: (container, map) => {},
  afterRemove: (map) => {},
})

export const DataLayersControl = BaseButton.extend({
  options: {
    position: 'topleft',
    className: 'umap-control-browse',
    title: translate('Open browser'),
    icon: 'icon-layers',
  },

  afterAdd: function (container) {
    Utils.toggleBadge(container, this._umap.browser?.hasActiveFilters())
  },

  onClick: function () {
    this._umap.openBrowser()
  },
})

export const CaptionControl = BaseButton.extend({
  options: {
    position: 'topleft',
    className: 'umap-control-caption',
    title: translate('About'),
    icon: 'icon-info',
  },

  onClick: function () {
    this._umap.openCaption()
  },
})

export const EmbedControl = BaseButton.extend({
  options: {
    position: 'topleft',
    title: translate('Share and download'),
    className: 'leaflet-control-embed',
    icon: 'icon-share',
  },

  onClick: function () {
    this._umap.share.open()
  },
})

export const PrintControl = BaseButton.extend({
  options: {
    position: 'topleft',
    title: translate('Print'),
    icon: 'icon-print',
  },

  onClick: function () {
    this._umap.openPrinter('print')
  },
})

export const SearchControl = BaseButton.extend({
  options: {
    position: 'topleft',
    title: translate('Search location'),
    className: 'leaflet-control-search',
    icon: 'icon-search',
  },

  afterAdd(container, map) {
    this.layer = new LayerGroup().addTo(map)
    this.photonOptions = {
      limit: 10,
      noResultLabel: translate('No results'),
    }
    if (map.options.photonUrl) this.photonOptions.url = map.options.photonUrl
  },

  afterRemove: function (map) {
    this.layer.remove()
  },

  onClick: function () {
    const template = `
      <div>
        <h3><i class="icon icon-16 icon-search"></i>${translate('Search location')}</h3>
        <input class="photon-input" data-ref=input />
        <div class="photon-autocomplete" data-ref=resultsContainer></div>
      </div>
    `
    const [container, { input, resultsContainer }] =
      Utils.loadTemplateWithRefs(template)
    const id = Math.random()
    this.search = new Search(
      this._umap._leafletMap,
      input,
      this.layer,
      this.photonOptions
    )
    this._umap.panel.open({ content: container }).then(() => {
      this.search.on('ajax:send', () => {
        this._umap.loader.start(id)
      })
      this.search.on('ajax:return', () => {
        this._umap.loader.stop(id)
      })
      this.search.resultsContainer = resultsContainer
      input.focus()
    })
  },
})

export const AttributionControl = Control.Attribution.extend({
  options: {
    prefix: '',
  },

  _update: function () {
    // Layer is no more on the map
    if (!this._map) return
    Control.Attribution.prototype._update.call(this)
    const shortCredit = this._map._umap.getProperty('shortCredit')
    const captionMenus = this._map._umap.getProperty('captionMenus')
    // Use our own container, so we can hide/show on small screens
    const originalCredits = this._container.innerHTML
    this._container.innerHTML = ''
    const template = Utils.sanitizeVars`
      <div class="attribution-container">
        ${originalCredits}
        <span data-ref="short"> — ${Utils.toHTML(shortCredit)}</span>
        <a  href="#" data-ref="caption"> — ${translate('Open caption')}</a>
        <a href="https://umap-project.org/" data-ref="site"> — ${translate('Powered by uMap')}</a>
        <a href="#" class="attribution-toggle"></a>
      </div>
    `
    const [container, { short, caption, site }] = Utils.loadTemplateWithRefs(template)
    caption.addEventListener('click', () => this._map._umap.openCaption())
    this._container.appendChild(container)
    short.hidden = !shortCredit
    caption.hidden = !captionMenus
    site.hidden = !captionMenus
  },
})

/* Used in edit mode to define the default tilelayer */
export const TileLayerChooser = BaseButton.extend({
  options: {
    position: 'topleft',
  },

  onClick: function () {
    this.openSwitcher({ edit: true })
  },

  openSwitcher: function (options = {}) {
    const template = `
      <div class="umap-edit-tilelayers">
        <h3><i class="icon icon-16 icon-tilelayer" title=""></i><span class="">${translate('Change tilelayers')}</span></h3>
        <ul data-ref="tileContainer"></ul>
      </div>
    `
    const [container, { tileContainer }] = Utils.loadTemplateWithRefs(template)
    this.buildList(tileContainer, options)
    const panel = options.edit ? this._umap.editPanel : this._umap.panel
    panel.open({ content: container, highlight: 'tilelayers' })
  },

  buildList: function (container, options) {
    this._umap._leafletMap.eachTileLayer((tilelayer) => {
      const browserIsHttps = window.location.protocol === 'https:'
      const tileLayerIsHttp = tilelayer.options.url_template.indexOf('http:') === 0
      if (browserIsHttps && tileLayerIsHttp) return
      container.appendChild(this.addTileLayerElement(tilelayer, options))
    })
  },

  addTileLayerElement: function (tilelayer, options) {
    const selectedClass = this._umap._leafletMap.hasLayer(tilelayer) ? 'selected' : ''
    const src = Utils.template(
      tilelayer.options.url_template,
      this._umap._leafletMap.options.demoTileInfos
    )
    const template = Utils.sanitizeVars`
      <li>
        <img src="${src}" loading="lazy" />
        <div>${tilelayer.options.name}</div>
      </li>
    `
    const li = Utils.loadTemplate(template)
    li.addEventListener('click', () => {
      const oldTileLayer = this._umap.properties.tilelayer
      this._umap._leafletMap.selectTileLayer(tilelayer)
      this._umap._leafletMap._controls.tilelayers.setLayers()
      if (options?.edit) {
        this._umap.properties.tilelayer = tilelayer.toJSON()
        this._umap.sync.update(
          'properties.tilelayer',
          this._umap.properties.tilelayer,
          oldTileLayer
        )
      }
    })
    return li
  },
})

export const LocateControl = BaseButton.extend({
  options: {
    position: 'topleft',
    title: translate('Center map on your location'),
    icon: 'icon-locate',
  },

  async start() {
    await this.loadPlugin()
    this._locate.start()
  },

  stop() {
    this._locate?.stop()
  },

  async loadPlugin() {
    if (this._locate) return
    const { LocateControl } = await import(
      '../../../vendors/locatecontrol/L.Control.Locate.esm.js'
    )
    this._locate = new LocateControl({
      strings: {
        title: translate('Center map on your location'),
      },
      showPopup: false,
      flyTo: this.options.easing,
      onLocationError: (err) => Alert.error(err.message),
    })
    this._locate._map = this._umap._leafletMap
    this._locate.onAdd(this._umap._leafletMap)
  },

  async onClick() {
    if (this._locate?._active) {
      this.stop()
    } else {
      this.start()
    }
  },

  afterRemove() {
    this.stop()
  },

  afterInit() {
    this._umap._leafletMap.on('locateactivate', () => {
      this._container.classList.add('active')
    })
    this._umap._leafletMap.on('locatedeactivate', () => {
      this._container.classList.remove('active')
    })
  },
})

export const Search = PhotonSearch.extend({
  initialize: function (map, input, layer, options) {
    this.options.placeholder = translate('Type a place name or coordinates')
    this.options.location_bias_scale = 0.5
    PhotonSearch.prototype.initialize.call(this, map, input, options)
    this.options.url = map.options.urls.search
    if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString()
    this.reverse = new PhotonReverse({
      handleResults: (geojson) => {
        this.handleResultsWithReverse(geojson)
      },
    })
    this.layer = layer
  },

  handleResultsWithReverse: function (geojson) {
    const latlng = this.reverse.latlng
    geojson.features.unshift({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
      properties: {
        name: translate('Go to "{coords}"', { coords: `${latlng.lat} ${latlng.lng}` }),
      },
    })

    this.handleResults(geojson)
  },

  search: function () {
    this.layer.clearLayers()
    const pattern = /^(?<lat>[-+]?\d{1,2}[.,]\d+)\s*[ ,]\s*(?<lng>[-+]?\d{1,3}[.,]\d+)$/
    if (pattern.test(this.input.value)) {
      this.hide()
      const { lat, lng } = pattern.exec(this.input.value).groups
      const latlng = latLng(lat, lng)
      if (Utils.LatLngIsValid(latlng)) {
        this.reverse.doReverse(latlng)
      } else {
        Alert.error(translate('Invalid latitude or longitude'))
      }
      return
    }
    // Only numbers, abort.
    if (/^[\d .,]*$/.test(this.input.value)) return
    // Do normal search
    this.options.includePosition = this.map.getZoom() > 10
    PhotonSearch.prototype.search.call(this)
  },

  onBlur: function (e) {
    // Overrided because we don't want to hide the results on blur.
    this.fire('blur')
  },

  formatResult: function (feature, el) {
    const [tools, { point, geom }] = Utils.loadTemplateWithRefs(`
      <span class="search-result-tools">
        <button type="button" title="${translate('Add this geometry to my map')}" data-ref=geom><i class="icon icon-16 icon-polygon-plus"></i></button>
        <button type="button" title="${translate('Add this place to my map')}" data-ref=point><i class="icon icon-16 icon-marker-plus"></i></button>
      </span>
    `)
    geom.hidden = !['R', 'W'].includes(feature.properties.osm_type)
    point.addEventListener('mousedown', (event) => {
      event.stopPropagation()
      const datalayer = this.map._umap.defaultEditDataLayer()
      const marker = datalayer.makeFeature(feature)
      marker.edit()
    })
    geom.addEventListener('mousedown', async (event) => {
      event.stopPropagation()
      const osm_id = feature.properties.osm_id
      const types = {
        R: 'relation',
        W: 'way',
        N: 'node',
      }
      const osm_type = types[feature.properties.osm_type]
      if (!osm_type || !osm_id) return
      const importer = this.map._umap.importer
      importer.build()
      importer.format = 'geojson'
      importer.raw = await this.getOSMObject(osm_type, osm_id)
      importer.submit()
    })
    el.appendChild(tools)
    this._formatResult(feature, el)
    const path = this.map._umap.getStaticPathFor('target.svg')
    const icon = new Icon({
      iconUrl: path,
      iconSize: [24, 24],
      iconAnchor: [12, 12],
    })
    const coords = feature.geometry.coordinates
    const target = new Marker([coords[1], coords[0]], { icon })
    el.addEventListener('mouseover', (event) => {
      target.addTo(this.layer)
    })
    el.addEventListener('mouseout', (event) => {
      target.removeFrom(this.layer)
    })
  },

  async getOSMObject(osm_type, osm_id) {
    const url = `https://www.openstreetmap.org/api/0.6/${osm_type}/${osm_id}/full`
    const response = await this.map._umap.request.get(url)
    if (response?.ok) {
      const data = await this.map._umap.formatter.fromOSM(await response.text())
      data.features = data.features.filter(
        (feature) => feature.properties.id === `${osm_type}/${osm_id}`
      )
      return JSON.stringify(data)
    }
  },

  setChoice: function (choice) {
    choice = choice || this.RESULTS[this.CURRENT]
    if (choice) {
      const feature = choice.feature
      const zoom = Math.max(this.map.getZoom(), 14) // Never unzoom.
      this.map.setView(
        [feature.geometry.coordinates[1], feature.geometry.coordinates[0]],
        zoom
      )
    }
  },
})
