// ==UserScript==
// @name         Runcity
// @namespace    http://tampermonkey.net/
// @version      2024-11-18
// @desc  try to take over the world!
// @author       You
// @match        https://rst.runcity.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=runcity.org
// @grant        none
// @require https://code.jquery.com/jquery-3.7.1.min.js
// @require https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js
// @require https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
// ==/UserScript==

(function () {
  'use strict';

  let fileInputs = {
    "attachment1": "legend_photo",
    "attachment4": "legend_files",
    "attachment2": "admin_photo",
    "attachment3": "admin_files"
  }

  const localStorageItems = {
    NEED_UPDATE_ID: "needUpdateId",
    JUST_CREATED: "justCreated",
    PREV_CREATED: "prevCreated",
    LATTITUDE: "lattitude",
    LONGITUDE: "longitude",
    ALWAYS_PRETTIFY: "always",
    REDIRECT_EXIT: "redirectExit"
  }

  const ZOOM = 17
  const METERS = 510

  let removedFilesLinks = []

  class Property {
    id;
    name;
    content;
    desc;

    constructor(id, row) {
      this.id = id

      let columns = [...row.querySelectorAll('td, th')].map(el => el.cloneNode(true))
      if (columns.length === 1) {
        this.content = columns[0].innerHTML.trim()
      }
      else if (columns.length >= 2) {
        this.name = columns[0].innerHTML.trim()
        let elementsToRemove = columns[1].querySelector("input[name=\"read_gps2\"]")
        if (elementsToRemove) {
          columns[1].removeChild(elementsToRemove)
        }
        
        this.content = columns[1].innerHTML
          .replaceAll("<br>", "")
          .replaceAll(/\s*Перейти к легенде.*$/gs, "")
          .replaceAll("для легенды (\"l\")", "")
          .replaceAll("общие (\"c\")", "")
          .replaceAll("для ист. справки (\"h\")", "")
          .replaceAll("Получить координаты КП из картинки", "")
          .trim()
      }

      if (columns.length === 3) {
        this.desc = columns[2].innerHTML.replaceAll("<br>", "").replaceAll(/(\n)\s*/g, "$1").trim()
      }
    }

    toDiv(altName = null, altDesc = null) {
      let div = document.createElement('div')
      let desc = altDesc ?? this.desc
      desc = desc ? "<div class='desc-icon-container' title='" + desc + "'>" + 
        "<img class='desc-icon' src='data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnIGlkPSJzdmcyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjAwIiB3aWR0aD0iMjAwIiB2ZXJzaW9uPSIxLjAiPgogPHBhdGggaWQ9InBhdGgyMzgyIiBkPSJtMTY1LjMzIDExMy40NGExMDMuNjEgMTAzLjYxIDAgMSAxIC0yMDcuMjIgMCAxMDMuNjEgMTAzLjYxIDAgMSAxIDIwNy4yMiAweiIgdHJhbnNmb3JtPSJtYXRyaXgoLjkzNzM5IDAgMCAuOTM3MzkgNDIuMTQzIC02LjMzOTIpIiBzdHJva2Utd2lkdGg9IjAiIGZpbGw9IiNmZmYiLz4KIDxnIGlkPSJsYXllcjEiPgogIDxwYXRoIGlkPSJwYXRoMjQxMyIgZD0ibTEwMCAwYy01NS4yIDAtMTAwIDQ0LjgtMTAwIDEwMC01LjA0OTVlLTE1IDU1LjIgNDQuOCAxMDAgMTAwIDEwMHMxMDAtNDQuOCAxMDAtMTAwLTQ0LjgtMTAwLTEwMC0xMDB6bTAgMTIuODEyYzQ4LjEzIDAgODcuMTkgMzkuMDU4IDg3LjE5IDg3LjE4OHMtMzkuMDYgODcuMTktODcuMTkgODcuMTktODcuMTg4LTM5LjA2LTg3LjE4OC04Ny4xOSAzOS4wNTgtODcuMTg4IDg3LjE4OC04Ny4xODh6bTEuNDcgMjEuMjVjLTUuNDUgMC4wMy0xMC42NTMgMC43MzctMTUuMjgyIDIuMDYzLTQuNjk5IDEuMzQ2LTkuMTI2IDMuNDg0LTEyLjg3NiA2LjIxOS0zLjIzOCAyLjM2Mi02LjMzMyA1LjM5MS04LjY4NyA4LjUzMS00LjE1OSA1LjU0OS02LjQ2MSAxMS42NTEtNy4wNjMgMTguNjg3LTAuMDQgMC40NjgtMC4wNyAwLjg2OC0wLjA2MiAwLjg3NiAwLjAxNiAwLjAxNiAyMS43MDIgMi42ODcgMjEuODEyIDIuNjg3IDAuMDUzIDAgMC4xMTMtMC4yMzQgMC4yODItMC45MzcgMS45NDEtOC4wODUgNS40ODYtMTMuNTIxIDEwLjk2OC0xNi44MTMgNC4zMi0yLjU5NCA5LjgwOC0zLjYxMiAxNS43NzgtMi45NjkgMi43NCAwLjI5NSA1LjIxIDAuOTYgNy4zOCAyIDIuNzEgMS4zMDEgNS4xOCAzLjM2MSA2Ljk0IDUuODEzIDEuNTQgMi4xNTYgMi40NiA0LjU4NCAyLjc1IDcuMzEyIDAuMDggMC43NTkgMC4wNSAyLjQ4LTAuMDMgMy4yMTktMC4yMyAxLjgyNi0wLjcgMy4zNzgtMS41IDQuOTY5LTAuODEgMS41OTctMS40OCAyLjUxNC0yLjc2IDMuODEyLTIuMDMgMi4wNzctNS4xOCA0LjgyOS0xMC43OCA5LjQwNy0zLjYgMi45NDQtNi4wNCA1LjE1Ni04LjEyIDcuMzQzLTQuOTQzIDUuMTc5LTcuMTkxIDkuMDY5LTguNTY0IDE0LjcxOS0wLjkwNSAzLjcyLTEuMjU2IDcuNTUtMS4xNTYgMTMuMTkgMC4wMjUgMS40IDAuMDYyIDIuNzMgMC4wNjIgMi45N3YwLjQzaDIxLjU5OGwwLjAzLTIuNGMwLjAzLTMuMjcgMC4yMS01LjM3IDAuNTYtNy40MSAwLjU3LTMuMjcgMS40My01IDMuOTQtNy44MSAxLjYtMS44IDMuNy0zLjc2IDYuOTMtNi40NyA0Ljc3LTMuOTkxIDguMTEtNi45OSAxMS4yNi0xMC4xMjUgNC45MS00LjkwNyA3LjQ2LTguMjYgOS4yOC0xMi4xODcgMS40My0zLjA5MiAyLjIyLTYuMTY2IDIuNDYtOS41MzIgMC4wNi0wLjgxNiAwLjA3LTMuMDMgMC0zLjk2OC0wLjQ1LTcuMDQzLTMuMS0xMy4yNTMtOC4xNS0xOS4wMzItMC44LTAuOTA5LTIuNzgtMi44ODctMy43Mi0zLjcxOC00Ljk2LTQuMzk0LTEwLjY5LTcuMzUzLTE3LjU2LTkuMDk0LTQuMTktMS4wNjItOC4yMy0xLjYtMTMuMzUtMS43NS0wLjc4LTAuMDIzLTEuNTktMC4wMzYtMi4zNy0wLjAzMnptLTEwLjkwOCAxMDMuNnYyMmgyMS45OTh2LTIyaC0yMS45OTh6Ii8+CiA8L2c+Cjwvc3ZnPgo='/>" +
        "</div>" : ""
      div.innerHTML = "<label for=''>" + (altName ?? this.name ?? "") + "</label>" + "<div>" + this.content + "</div>" +
        desc
      return div
    }
  }

  function createFrom(rows, classList, data) {
    let container = document.createElement('div')
    container.classList.add(...classList.split(" "))
    for (const options of data) {
      let prop = new Property(options.index, rows[options.index])
      container.appendChild(prop.toDiv(options.name, options.desc))
    }

    return container
  }

  function createFromMulti(rows, classList, data) {
    let rowData = []
    let toIndex = data.to ?? rows.length - 1
    for (let i = data.from; i <= toIndex; i++) {
      rowData.push({ index: i })
    }

    return createFrom(rows, classList, rowData)
  }

  function addCss(link) {
    let css = document.createElement("link")
    css.href = link
    css.rel = "stylesheet"
    document.head.appendChild(css)
  }

  function addJs(link) {
    let script = document.createElement("script")
    script.src = link
    document.head.appendChild(script)
  }

  const sleep = ms => new Promise(res => setTimeout(res, ms));

  function isEditCpPage() {
    let params = new URLSearchParams(document.location.search)
    return params.get("action") === "edit"
  }

  function isDeleteCpPage() {
    let params = new URLSearchParams(document.location.search)
    return params.get("action") === "delete"
  }
  
  async function updatePoint(formData) {
    return await fetch(`https://runcity.geo.rictum.ru/api/competitions/${window.location.pathname.split('/')[1]}/update`, {
      method: 'POST',
      body: formData
    })
  }

  async function deletePoint() {
    return await fetch(`https://runcity.geo.rictum.ru/api/points/${new URLSearchParams(location.search).get("cp_id")}`, {
      method: 'DELETE'
    })
  }

  function copyCoordinates() {
    let copyButton = document.createElement("button")
    copyButton.addEventListener("click", async event => {
      event.preventDefault();
      let lat = document.querySelector(`input[name="cp[lattitude]"]`).value
      let lon = document.querySelector(`input[name="cp[longitude]"]`).value

      const text = new Blob([`${lat}, ${lon}`], { type: "text/plain" });
      const data = new ClipboardItem({ "text/plain": text });
      await navigator.clipboard.write([data]);
    })
    copyButton.classList.add("copy-button")

    let copyImage = document.createElement("img")
    copyImage.src = "https://upload.wikimedia.org/wikipedia/commons/a/aa/Bw_copy_icon_320x320.svg"
    copyButton.appendChild(copyImage)

    return copyButton
  }

  function yandexMaps(lat, lon, zoom) {
    return `https://yandex.ru/maps/213/moscow/?ll=${lon}%2C${lat}&z=${zoom}`
  }

  function googleMaps(lat, lon, meters) {
    return `https://www.google.ru/maps/@${lat},${lon},${meters}m`
  }

  function pastvu(lat, lon, zoom) {
    return `https://pastvu.com/?g=${lat},${lon}&z=${zoom}&s=yandex&t=scheme&type=1`
  }

  function twoGis(lat, lon, zoom) {
    return `https://2gis.ru/moscow?m=${lon}%2C${lat}%2F${zoom}`
  }

  function makeRef(linkCallback, iconSrc, zoom) {
    let ref = document.createElement("a")
    ref.href = linkCallback(lat, lon, zoom)
    ref.setAttribute('target', "_blank")
    ref.addEventListener("click", function (e) {
      let lat = document.querySelector(`input[name="cp[lattitude]"]`).value
      let lon = document.querySelector(`input[name="cp[longitude]"]`).value
      this.href = linkCallback(lat, lon, zoom)
    })

    let icon = document.createElement("img")
    icon.src = iconSrc
    icon.classList.add("map-icon")
    ref.appendChild(icon)

    return ref
  }

  let makeCoordinatesLinks = (function() {
    var executed = false
    return function () {
      if (executed) return
      executed = true
      
      let linksContainer = document.createElement("div")
      
      linksContainer.appendChild(copyCoordinates())
      linksContainer.appendChild(makeRef(yandexMaps, "https://upload.wikimedia.org/wikipedia/commons/7/72/Yandex_Maps_icon.svg", ZOOM))
      linksContainer.appendChild(makeRef(googleMaps, "https://upload.wikimedia.org/wikipedia/commons/a/aa/Google_Maps_icon_%282020%29.svg", METERS))
      linksContainer.appendChild(makeRef(pastvu, "https://pastvu.com/coast-icon.png", ZOOM))
      linksContainer.appendChild(makeRef(twoGis, "https://d-assets.2gis.ru/favicon.png", ZOOM))

      document.querySelector(`div:has(> div > input[name="cp[longitude]"])`).after(linksContainer)
    }
  })()

  function makeDownloadLink(name, href = null) {
    let downloadLink = document.createElement("a")
    downloadLink.setAttribute("download", name)
    downloadLink.textContent = "Скачать"
    downloadLink.setAttribute("target", "_blank")
    if (href)
      downloadLink.href = href

    return downloadLink
  }

  async function sendFileDeleted(inputName) {
    let formData = new FormData()

    let normalInputName = fileInputs[inputName]
    formData.set(`cp[id]`, document.querySelector("input[name='cp[id]']").value)
    formData.set(`cp[${normalInputName}]`, "-1")
    let result = await updatePoint(formData)
    try {
      console.log(result.ok)
    }
    catch (e) {
      console.log(e)
    }
  }

  function addDisplayedFile(inputName, type, file, preview, removeLink) {
    if (!inputName || !file || !removeLink) return

    let input = document.querySelector(`input[name="${inputName}\[\]"]`)
    let fileListContainer = input.parentElement.parentElement.querySelector(".file-list-container")
    let fileContainer = document.createElement("div")
    fileContainer.classList.add("file-container")

    const displayedFile = document.createElement(type ?? "a")
    displayedFile.classList.add("preview-small")

    if (type == "img") {
      displayedFile.src = preview
      displayedFile.dataset.origin = file
      displayedFile.addEventListener("click", () => {
        let dialog = document.querySelector("dialog")
        makeSwiper(dialog, fileListContainer, file)
        dialog.showModal()
      })
    }
    else if (type == "audio") {
      displayedFile.src = file
      displayedFile.setAttribute("controls", "")
    }
    else {
      displayedFile.href = file
      displayedFile.textContent = "Файл"
    }

    let fileButtonsContainer = document.createElement("div")
    fileButtonsContainer.classList.add("file-buttons-container")

    fileButtonsContainer.appendChild(makeDownloadLink(/[^/]*$/.exec(new URL(file).pathname)[0], file))

    let deleteButton = document.createElement("button")
    deleteButton.type = "button"
    deleteButton.classList.add("button-delete")
    deleteButton.textContent = "x"
    deleteButton.addEventListener("click", async e => {
      if (!confirm("Точно?")) return

      await fetch(removeLink)
      await sendFileDeleted(inputName)
      removedFilesLinks.push(removeLink)

      fileListContainer.removeChild(fileContainer)
    })
    fileButtonsContainer.appendChild(deleteButton)

    fileContainer.appendChild(displayedFile)
    fileContainer.appendChild(fileButtonsContainer)

    fileListContainer.appendChild(fileContainer)
  }

  function dataURLtoFile(dataurl, filename) {
    let arr = dataurl.split(',')
    let mime = arr[0].match(/:(.*?);/)[1]
    let bstr = atob(arr[arr.length - 1])
    let n = bstr.length
    let u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    return new File([u8arr], filename, {type:mime})
  }

  function getHtmlElementByFileType(file) {
    const htmlElementsForTypes = {
      "image/": "img",
      "audio/": "audio"
    }
    let res = "div"
    for (const [type, el] of Object.entries(htmlElementsForTypes)) {
      if (file.type.startsWith(type)) {
        res = el
        break
      }
    }
    let el = document.createElement(res)
    if (res == "audio") {
      el.setAttribute("controls", "")
    }

    return el
  }

  function makeHandleFilesFunc() {
    let storedFiles = []

    return function() {
      let dt = new DataTransfer()

      if (storedFiles.length) {
        for (const oldFile of storedFiles) {
          let oldFileObj = dataURLtoFile(oldFile.data, oldFile.name)
          dt.items.add(oldFileObj)
        }
      }

      for (let i = 0; i < this.files.length; i++) {
        const file = this.files[i]
        dt.items.add(file)

        let fileListContainer = this.parentElement.parentElement.querySelector(".file-list-container")
        let fileContainer = document.createElement("div")
        fileContainer.classList.add("file-container")

        const displayedFile = getHtmlElementByFileType(file)
        displayedFile.classList.add("preview")
        displayedFile.file = file
        displayedFile.addEventListener("click", () => {
          let dialog = document.querySelector("dialog")
          makeSwiper(dialog, fileListContainer, displayedFile.src)
          dialog.showModal()
        })

        let fileButtonsContainer = document.createElement("div")
        fileButtonsContainer.classList.add("file-buttons-container")

        let downloadLink = makeDownloadLink(file.name)
        fileButtonsContainer.appendChild(downloadLink)

        let deleteButton = document.createElement("button")
        deleteButton.classList.add("button-delete")
        deleteButton.textContent = "x"
        deleteButton.type = "button"
        deleteButton.addEventListener("click", e => {
          let index = [...fileListContainer.children].indexOf(fileContainer)

          if (!confirm("Точно?")) return
    
          storedFiles.splice(storedFiles.length - this.files.length + index, 1)
          
          let rdt = new DataTransfer()
          for (const file of this.files) {
            rdt.items.add(file)
          }
          rdt.items.remove(index)
          this.files = rdt.files
    
          fileListContainer.removeChild(fileContainer)
        })
        fileButtonsContainer.appendChild(deleteButton)

        fileContainer.appendChild(displayedFile)
        fileContainer.appendChild(fileButtonsContainer)

        fileListContainer.appendChild(fileContainer)

        const reader = new FileReader()
        reader.onload = (e) => {
          displayedFile.src = e.target.result
          downloadLink.setAttribute("href", displayedFile.src)

          storedFiles.push({data: displayedFile.src, name: file.name})
        }
        reader.readAsDataURL(file)
      }

      this.files = dt.files
    }
  }

  let prettifyFiles = (function() {
    var executed = false
    return function(insertedFileRows) {
      if (executed) return
      executed = true

      document.querySelector("#new").querySelectorAll("input[type=file]").forEach((element, index) => {
        element.id = `input-file-${index}`
        element.setAttribute("multiple", "multiple")
        element.dataset.index = index
        let pseudoInput = document.createElement("label")
        pseudoInput.classList.add("custom-file-upload")
        pseudoInput.setAttribute("for", element.id)
        pseudoInput.textContent = "+"
        element.parentElement.insertBefore(pseudoInput, element)
    
        let fileListContainer = document.createElement("div")
        fileListContainer.classList.add("file-list-container")
        element.parentElement.parentElement.appendChild(fileListContainer)
    
        element.addEventListener("change", makeHandleFilesFunc(), false)
      })

      moveFileInputValues(insertedFileRows)
    }
  })()

  function saveInputValues(from) {
    let fromInputs = [...from.querySelectorAll("input, textarea")]
    let result = new Map()

    for (const fromEl of fromInputs) {
      result.set(fromEl, fromEl.type == "radio" ? fromEl.checked : fromEl.value)
    }

    return result
  }

  function setInputValues(to, values) {
    let toInputs = [...to.querySelectorAll("input, textarea")]

    for (const [fromEl, value] of values) {
      let currentToInput = toInputs.find(toEl => 
        toEl.type !== 'file' &&
        toEl.type === fromEl.type && 
        toEl.name === fromEl.name && 
        toEl.id === fromEl.id && 
        (toEl.type !== "radio" || toEl.value === fromEl.value)
      )

      if (currentToInput) {
        currentToInput.checked = fromEl.checked
        currentToInput.value = fromEl.value
      }
    }
  }

  function moveFileInputValues(insertedFileRows) {
    for (const [inputRowIndex, rows] of insertedFileRows) {
      for (const row of rows) {
        let fileInput = null, type = null, fileUrl = null, deleteUrl = null, preview = null
        if (row.querySelector("img")) {
          type = "img"
          fileUrl = row.querySelector("td:first-child a").href
          preview = row.querySelector("img").src
        }
        else if (row.querySelector("audio")) {
          type = "audio"
          fileUrl = row.querySelector("audio").src
        }
        else {
          fileUrl = row.querySelector("td:first-child a").href
        }

        deleteUrl = row.querySelector("td:nth-child(2) a").href

        if (inputRowIndex == 23) {
          fileInput = "attachment1"
        }
        else if (inputRowIndex == 27) {
          fileInput = "attachment4"
        }
        else if (inputRowIndex == 31) {
          fileInput = "attachment2"
        }
        else if (inputRowIndex == 35) {
          fileInput = "attachment3"
        }
        else {
          console.log(inputRowIndex)
          return
        }

        addDisplayedFile(fileInput, type, fileUrl, preview, deleteUrl)
      }
    }
  }

  function removeDeletedFilesFromUglyVersion(removeLink) {
    let shortHref = removeLink.split("/").at(-1)
    document.querySelector(`tr:has(a[href="${shortHref}"])`).remove()
  }

  function moveInputValues(form, was, became) {
    let inputValues = saveInputValues(was)
    form.removeChild(was)
    form.appendChild(became)
    setInputValues(became, inputValues)
    createSendButtons()
  }

  function prettify(form, was, became, insertedFileRows) {
    moveInputValues(form, was, became)
    prettifyFiles(insertedFileRows)
      
    $("#cps_main").select2()
    makeCoordinatesLinks()
  }

  function uglify(form, was, became) {
    moveInputValues(form, was, became)
    for (const removedFileLink of removedFilesLinks) {
      removeDeletedFilesFromUglyVersion(removedFileLink)
    }
    removedFilesLinks = []
  }

  function createAlwaysPrettifyInput(index) {
    let alwaysPrettify = document.createElement("div")
    alwaysPrettify.classList.add("always-prettify-container")

    let alwaysPrettifyLabel = document.createElement("label")
    alwaysPrettifyLabel.setAttribute("for", "always-prettify-" + index)
    alwaysPrettifyLabel.textContent = "Всегда"
    alwaysPrettify.appendChild(alwaysPrettifyLabel)

    let alwaysPrettifyCheckbox = document.createElement("input")
    alwaysPrettifyCheckbox.type = "checkbox"
    alwaysPrettifyCheckbox.id = "always-prettify-" + index
    alwaysPrettifyCheckbox.name = "always-prettify-" + index
    alwaysPrettifyCheckbox.addEventListener("change", function() {
      let otherCheckboxes = document.querySelectorAll(`input[name^="always-prettify-"]`)
      for (let checkbox of otherCheckboxes) {
        if (checkbox.id !== this.id) {
          checkbox.checked = this.checked
        }
      }
      if (this.checked)
        localStorage.setItem(localStorageItems.ALWAYS_PRETTIFY, "+")
      else
        localStorage.removeItem(localStorageItems.ALWAYS_PRETTIFY)
    })

    alwaysPrettify.appendChild(alwaysPrettifyCheckbox)

    i++

    return alwaysPrettify
  }

  function hide(elList) {
    elList.forEach(el => el.classList.add("hidden"))
  }

  function show(elList) {
    elList.forEach(el => el.classList.remove("hidden"))
  }

  function makeSwiper(dialog, fileListContainer, src) {
    let files = [...fileListContainer.querySelectorAll(`:is(.preview, .preview-small)`)]

    let swiperDiv = document.createElement("div")
    swiperDiv.classList.add("swiper")

    let swiperWrapper = document.createElement("div")
    swiperWrapper.classList.add("swiper-wrapper")

    for (const file of files) {
      let swiperSlide = document.createElement("div")
      swiperSlide.classList.add("swiper-slide")

      let downloadLink = document.createElement("a")
      downloadLink.classList.add("swiper-download-link")
      downloadLink.href = file.dataset.origin
      downloadLink.text = "Скачать"
      downloadLink.setAttribute("target", "_blank")
      swiperSlide.appendChild(downloadLink)

      let swiperFile = file.cloneNode(true)
      if (swiperFile.dataset.origin)
        swiperFile.src = swiperFile.dataset.origin

      swiperSlide.appendChild(swiperFile)
      swiperWrapper.appendChild(swiperSlide)
    }

    swiperDiv.appendChild(swiperWrapper)

    let prevButton = document.createElement("div")
    prevButton.classList.add("swiper-button-prev")
    swiperDiv.appendChild(prevButton)

    let nextButton = document.createElement("div")
    nextButton.classList.add("swiper-button-next")
    swiperDiv.appendChild(nextButton)

    dialog.appendChild(swiperDiv)

    new Swiper('.swiper', {
      initialSlide: files.findIndex(el => el.dataset.origin && el.dataset.origin === src || el.src === src),
    
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    })

    bindArrowsForGallery('.swiper-button-prev', '.swiper-button-next')
  }

  function bindArrowsForGallery(leftButtonQuery, rightButtonQuery) {
    document.addEventListener("keydown", function(e) {
      if (!document.querySelector("dialog").open) return
      switch(e.key) {
        case "ArrowLeft":
          $(leftButtonQuery).click()
        break

        case "ArrowRight":
          $(rightButtonQuery).click()
        break

        default: return
      }
      e.preventDefault()
    })
  }

  async function sendForm(onSend) {
    let formData = new FormData(document.querySelector("form"))

    let fileContainers = [...document.querySelectorAll(".file-list-container")]
    let fileInputNames = ["legend_photo", "legend_files", "admin_photo", "admin_files"]
    for (let [i, fileContainer] of fileContainers.entries()) {
      formData.set(`cp[${fileInputNames[i]}]`, fileContainer.children.length)
    }

    if (formData.get("cp[id]") == '') {
      localStorage.setItem(localStorageItems.NEED_UPDATE_ID, true)
    }

    let props = document.querySelectorAll("input[name^=\"prop_\"]");
    for (let prop of props) {
      let parent = prop.parentElement
      let propName

      if (parent.tagName === "DIV") {
        propName = parent.parentElement.querySelector("label").textContent
      }
      else if (parent.tagName === "TD") {
        propName = parent.parentElement.querySelector("td:first-child").textContent
      }

      formData.set(`propname[${prop.name}]`, propName)
    }

    let result = await updatePoint(formData)

    try {
      console.log(result.ok)
    }
    catch (e) {
      console.log(e)
    }
    finally {
      onSend()
    }
  }

  function createSaveAndNewButton() {
    let saveAndNewButton = document.createElement("button")
    saveAndNewButton.textContent = "+"
    saveAndNewButton.type = "button"
    saveAndNewButton.classList.add("safe-action")
    saveAndNewButton.addEventListener("click", () => {
      sendForm(() => {
        let cpNumber = document.querySelector(`input[name="cp[number]"]`).value
        let lattitude = document.querySelector(`input[name="cp[lattitude]"]`).value
        let longitude = document.querySelector(`input[name="cp[longitude]"]`).value

        localStorage.setItem(localStorageItems.JUST_CREATED, cpNumber)
        localStorage.setItem(localStorageItems.LATTITUDE, lattitude)
        localStorage.setItem(localStorageItems.LONGITUDE, longitude)
        
        document.querySelector(`input[name="save_go"]`).click()
      })
    })

    return saveAndNewButton
  }
  
  function createSendButtons() {
    if (document.querySelector(".pseudo-save")) return

    let saveAndStayButtons = document.querySelectorAll(`input[name="save_go"]`)
    let saveAndExitButtons = document.querySelectorAll(`input[name="save_exit"]`)
    let saveButtons = [...saveAndStayButtons, ...saveAndExitButtons]

    for (let saveButton of saveButtons) {
      let pseudoSaveButton = document.createElement("button")
      pseudoSaveButton.type = "button"
      pseudoSaveButton.textContent = saveButton.value
      pseudoSaveButton.classList.add("safe-action", "pseudo-save")
      pseudoSaveButton.addEventListener("click", async () => await sendForm(() => {
        if (document.querySelector(`input[name="cp[id]"]`).value != '') {
          saveButton.click()
          return
        }

        if (saveButton.name == "save_exit") {
          localStorage.setItem(localStorageItems.REDIRECT_EXIT, true)
        }
        
        saveAndStayButtons[0].click()
      }))

      saveButton.style.display = "none"
      saveButton.parentElement.insertBefore(pseudoSaveButton, saveButton)
    }

    saveAndStayButtons.forEach(el => el.after(createSaveAndNewButton()))
  }

  function addUglyDeleteListener() {
    let links = [...document.querySelectorAll("a[href*='del_stored']")]

    for (const link of links) {
      let shortHref = link.href.split("/").at(-1)
      let fileInput = document.querySelector(`tr:has(a[href$="${shortHref}"]) ~ tr:has(input[type="file"]) input[type="file"]`)

      link.addEventListener("click", async e => {
        e.preventDefault()
        if (!confirm("Точно?")) return

        await sendFileDeleted(fileInput.name.split("[")[0])
        location.href = link.href
      })
    }
    
  }

  function addFullscreenButton() {
    $(async () => {
      if (document.querySelector("#map") !== null) {
        while (L.Control.Fullscreen === undefined) {
          await sleep(500)
        }
        map.addControl(new L.Control.Fullscreen())
      }
    })
  }

  let styles = `
    .large-menu {
      position: sticky;
      top: 0;
      z-index: 10;
      background: white;
    }

    #content-wrapper {
      display: flex;
      gap: 20px;
    
      #props {
        width: unset !important;
      }

      form {
        width: 50%;
      }

      #map-wrapper {
        width: 50%;
      }

      #map {
        width: unset !important;
      }

      #new {
        display: flex;
        flex-direction: column;

        & > :not(:first-child) {
          padding-top: 5px;
          border-top: 1px solid #ddd;
        }

        & > :not(:last-child) {
          padding-bottom: 5px;
        }

        .map-icon {
          width: 15px;
          height: 15px;
        }

        .copy-button {
          all: unset;
          cursor: pointer;
        }

        .copy-button img {
          width: 15px;
          heigth: 15px;
        }

        input[type=radio], input[type=checkbox] {
          margin: 0;
        }

        input[type="file"] {
          display: none;
        }

        div > input[type="checkbox"] {
          display: flex;
          align-items: center;
        }

        input#cp_number {
          width: 3em;
        }

        select#cps_main {
          width: 6em;
        }

        input:is(#lat, #lng) {
          width: 5.5em;
        }

        input[name="cp[name_int]"] {
          width: 34em;
        }
      }

      #cps_main {
        width: 150px;
      }

      input[type=submit] {
        width: fit-content;
      }

      .desc-icon-container {
        display: flex;
        align-items: center;
      }

      .desc-icon {
        width: 15px;
        height: 15px;
        cursor: help;
      }

      button, input[type=submit] {
        cursor: pointer;
      }
      
      .buttons-row__content-wrapper {
        display: flex;
        justify-content: center;
        gap: 10px;
        font-weight: normal;
      }

      .custom-file-upload {
        display: block;
        width: fit-content;
        border: 1px solid black;
        padding: 5px 14px;
        border-radius: 5px;
        box-shadow: 0 1px 3px #ddd;
        background: linear-gradient(white, #ddd);
      }

      .buttons > * > * {
        display: flex;
        gap: 10px;
      }

      .always-prettify-container {
        display: flex;
        align-items: center;
        gap: 5px;
      }

      .header > * {
        display: flex;
        gap: 5px;
      }

      :is(.header, .options) input {
        width: unset;
      }
        
      .header {
        display: flex;
        gap: 30px;
        align-items: start;
      }

      .options {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
      }

      .options > * {
        display: flex;
        gap: 5px;
        align-items: center;
      }

      .legend-container {
        display: flex;
      }

      .legend-container > :not(:last-child) {
        padding-right: 15px;
        border-right: 1px solid #ddd;
      }

      .legend-container > :not(:first-child) {
        padding-left: 15px;
      }

      .legend-container > * {
        display: flex;
        flex-direction: column;
        flex: 0 1 48%;
        gap: 5px;
      }

      .legend-container__desc-header {
        display: flex;
        gap: 10px;
        align-items: center;
        justify-content: end;
      }

      .legend-desc {
        display: flex;
        flex-direction: column;
        gap: 5px;
      }

      .collapsed, .hidden {
        display: none;
      }

      .collapse-button::after {
        content: "Свернуть"
      }

      .collapsed + * > .collapse-button::after {
        content: "Развернуть"
      }
      
      :is(.comment, .legend-desc) > * {
        display: flex;
        gap: 5px;
      }

      :is(.comment, .legend-desc) label {
        display: block;
        width: 10em;
      }

      :is(.comment, .legend-desc) > * > :nth-child(2) {
        flex: 1 1 auto;
      }

      textarea {
        width: 100%;
      }
    }

    .files-container input[type=radio] {
      display: none;
    }

    .file-list-container {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 5px;
    }

    .file-container {
      display: flex;
      flex-direction: column;
      gap: 5px;
    }

    .file-container img.preview {
      width: 120px;
    }

    .file-container img.preview-small {
      width: 60px;
    }

    .file-container img:is(.preview, .preview-small) {
      cursor: zoom-in;
    }

    dialog .preview,
    .swiper {
      max-width: 1000px;
      width: 100%;
      max-height: 500px;
      height: 100%;
    }

    .swiper-button-next,
    .swiper-button-prev {
      user-select: none;
      -webkit-user-select: none;
    }

    .swiper-wrapper {
      align-items: center;
    }

    .swiper-wrapper > .swiper-slide {
      width: 100%;
      max-height: 500px;
      height: 100%;
    }

    .swiper-slide img {
      width: 100%;
      max-height: 500px;
      height: 100%;
      object-fit: contain;
      object-position: center;
    }

    .swiper-download-link {
      display: block;
      position: absolute;
      top: 0;
      right: 0;
      background: white;
      padding: 5px 10px;
      margin: 5px;
      border: 1px solid #ccc;
      border-radius: 7.5px;
    }

    button.button-delete {
      padding: 0;
      border: 0;
      margin: 0;
      background: none;
      font-size: 18px;
    }

    .admin-files-container {
      display: flex;
    }

    .admin-files-container > :not(:last-child) {
      padding-right: 10px;
      border-right: 1px solid #ddd;
    }

    .admin-files-container > :not(:first-child) {
      padding-left: 10px;
    }

    dialog img.preview {
      width: auto;
    }

    .file-buttons-container {
      display: flex;
      align-items: center;
      gap: 5px;
    }
  `

  /* REDIRECTS */

  let justCreated = localStorage.getItem(localStorageItems.JUST_CREATED)
  let prevCreated = localStorage.getItem(localStorageItems.PREV_CREATED)
  let needUpdateId = localStorage.getItem(localStorageItems.NEED_UPDATE_ID)
  let exit = localStorage.getItem(localStorageItems.REDIRECT_EXIT)

  if (needUpdateId) {
    let formData = new FormData()
    formData.set("cp[id]", new URLSearchParams(document.location.search).get("cp_id"))
    formData.set("cp[number]", document.querySelector(`input[name="cp[number]"]`).value)
    formData.set("update_id", true)
    updatePoint(formData).then(() => localStorage.removeItem(localStorageItems.NEED_UPDATE_ID))
  }

  if (exit) {
    localStorage.removeItem(localStorageItems.REDIRECT_EXIT)

    location.href = location.href.replace(location.search, '')
    return
  }

  if (justCreated !== null) {
    localStorage.setItem(localStorageItems.PREV_CREATED, justCreated)
    localStorage.removeItem(localStorageItems.JUST_CREATED)
    
    location.href = location.href.replace(location.search, "?action=edit")
    return
  }
  if (prevCreated !== null) {
    document.querySelector(`input[name="cp[number]"]`).value = parseInt(localStorage.getItem(localStorageItems.PREV_CREATED)) + 1
    document.querySelector(`input[name="cp[lattitude]"]`).value = localStorage.getItem(localStorageItems.LATTITUDE)
    document.querySelector(`input[name="cp[longitude]"]`).value = localStorage.getItem(localStorageItems.LONGITUDE)

    localStorage.removeItem(localStorageItems.PREV_CREATED)
    localStorage.removeItem(localStorageItems.LATTITUDE)
    localStorage.removeItem(localStorageItems.LONGITUDE)
  }

  /* HEAD */

  let styleSheet = document.createElement("style")
  styleSheet.textContent = styles
  document.head.appendChild(styleSheet)

  /* SWITCH FOR DIFFERENT PAGES */

  if (isDeleteCpPage()) {
    let deleteButton = document.querySelector(`input[name="delete_go"]`)
    let pseudoDeleteButton = document.createElement("button")
    pseudoDeleteButton.type = "button"
    pseudoDeleteButton.textContent = deleteButton.value
    pseudoDeleteButton.classList.add("unsafe-action", "pseudo-save")
    pseudoDeleteButton.addEventListener("click", async () => {
      await deletePoint()
      deleteButton.click()
    })

    deleteButton.style.display = "none"
    deleteButton.parentElement.insertBefore(pseudoDeleteButton, deleteButton)
    
    return
  }

  addJs('https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js')
  addCss('https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/leaflet.fullscreen.css')

  addFullscreenButton()

  if (!isEditCpPage()) {
    return
  }

  /* action=edit HEAD */

  addCss("https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css")
  addCss("https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css")

  /* NEW DEFALUT VALUES */

  document.querySelector(`#props input[name="new_file_type1"][value="l"]`).click()
  document.querySelector(`#props input[name="new_file_type4"][value="l"]`).click()

  /* CONTAINER */

  let form = document.querySelector('form')
  let rows = [...document.querySelectorAll('#props > tbody > tr')]
  let insertedFileRows = new Map()
  let i = 0
  while (i < rows.length) {
    if ([23, 27, 31, 35].includes(i) && rows[i].querySelector(`input[type="file"]`) == null) {
      if (!insertedFileRows.has(i)) {
        insertedFileRows.set(i, [rows[i]])
      }
      else {
        let savedRows = insertedFileRows.get(i)
        savedRows.push(rows[i])
        insertedFileRows.set(i, savedRows)
      }
      rows.splice(i, 1)
    }
    else {
      i++
    }
  }

  let container = document.createElement('div')
  container.id = "new"
  let oldTable = document.querySelector("#props")

  /* HEADER */

  let headerContainer = createFrom(rows, "header", [
    { index: 2, name: "№", desc: "" },
    { index: 8, name: "Название" }
  ])

  let copyLink = document.createElement("div")
  copyLink.innerHTML = new Property(2, rows[2]).desc
  headerContainer.append(copyLink)

  /* TOP BUTTONS */

  let topButtonsContainer = createFrom(rows, "buttons", [
    { index: 0 }
  ])

  let bottomButtonsContainer = topButtonsContainer.cloneNode(true)

  document.querySelectorAll("#props tr:is(:first-child, :last-child) th").forEach((el, index) => {
    let rowContentWrapper = document.createElement("div")
    rowContentWrapper.classList.add("buttons-row__content-wrapper")
    while ([...el.children].length > 0) {
      let child = el.firstChild
      rowContentWrapper.appendChild(child)
    }
    
    let prettifyButton = document.createElement("button")
    prettifyButton.type = "button"
    prettifyButton.textContent = "Сделать красиво"
    prettifyButton.addEventListener("click", () => {
      prettify(form, oldTable, container, insertedFileRows)
    })

    rowContentWrapper.appendChild(prettifyButton)
    rowContentWrapper.appendChild(createAlwaysPrettifyInput(index))

    el.appendChild(rowContentWrapper)
  })

  ;[topButtonsContainer, bottomButtonsContainer].forEach((el, index) => {
    let unglifyButton = document.createElement("button")
    unglifyButton.type = "button"
    unglifyButton.textContent = "Сделать некрасиво"
    unglifyButton.addEventListener("click", () => {
      uglify(form, container, oldTable)
    })

    let topRowContentWrapper = el.querySelector("div > div > div")
    topRowContentWrapper.appendChild(unglifyButton)
    topRowContentWrapper.appendChild(createAlwaysPrettifyInput(index))
  })

  addUglyDeleteListener()
  createSendButtons()

  /* OPTIONS */

  let firstContainer = createFrom(rows, "options", [
    { index: 3 },
    { index: 4, name: "КП-загадка" },
    { index: 16, name: "Этапник" },
    { index: 7, name: "Пиктограмма" },
    { index: 12, name: "Нужна ИС" },
    { index: 17, name: "Спрятать ИС" },
    { index: 14, name: "Основной КП" },
    { index: 10, name: "Широта" },
    { index: 11, name: "Долгота" },
    { index: 5, name: "Старт" },
    { index: 6, name: "Финиш" },
    { index: 15, name: "Знак" }
  ])

  let commentContainer = createFrom(rows, "comment", [
    { index: 9, desc: "" }
  ])

  /* LEGEND */

  let legendContainer = document.createElement("div")
  legendContainer.classList.add("legend-container")

  /* LEGEND DESC */

  let legendDescContainer = document.createElement("div")
  legendDescContainer.classList.add("legend-container__desc")

  let legendDescHeader = document.createElement("div")
  legendDescHeader.classList.add("legend-container__desc-header")


  const LEGEND_RU_LABEL = "Русский"
  const LEGEND_EN_LABEL = "Английский"
  let legendLang = document.createElement("div")
  legendLang.classList.add("legend-desc__lang")
  legendLang.textContent = LEGEND_RU_LABEL
  legendDescHeader.appendChild(legendLang)

  let legendEnSwitchContainer = createFrom(rows, "legend-switch-container", [
    { index: 48 }
  ])
  legendEnSwitchContainer.addEventListener("click", event => {
    hide([legendRuDescContainer, legendRuHiddenDescContainer, legendEnSwitchContainer])
    show([legendEnDescContainer, legendEnHiddenDescContainer, legendRuSwitchContainer])
    legendLang.textContent = LEGEND_EN_LABEL
  })
  legendDescHeader.appendChild(legendEnSwitchContainer)
  let copyDescButton = document.createElement("button")
  copyDescButton.type = "button"
  copyDescButton.textContent = "Копировать"
  copyDescButton.addEventListener("click", () => {
    let ruInputs = [...container.querySelectorAll(":is(input, textarea)[name^=\"cp_strings\[ru\]\"]")]
    let enInputs = [...container.querySelectorAll(":is(input, textarea)[name^=\"cp_strings\[en\]\"]")]
    for (const [i, enInput] of enInputs.entries()) {
      enInput.value = ruInputs[i].value
    }
  })

  let legendRuSwitchContainer = createFrom(rows, "legend-switch-container hidden", [
    { index: 39 }
  ])
  legendRuSwitchContainer.addEventListener("click", event => {
    hide([legendEnDescContainer, legendEnHiddenDescContainer, legendRuSwitchContainer])
    show([legendRuDescContainer, legendRuHiddenDescContainer, legendEnSwitchContainer])
    legendLang.textContent = LEGEND_RU_LABEL
  })
  legendDescHeader.appendChild(legendRuSwitchContainer)

  legendDescHeader.appendChild(copyDescButton)

  let legendRuDescContainer = createFrom(rows, "legend-desc", [
    { index: 40, desc: "" },
    { index: 41, desc: "" },
    { index: 42, desc: "" },
    { index: 43, desc: "" }
  ])

  let legendRuHiddenDescContainer = createFrom(rows, "legend-desc collapsible collapsed", [
    { index: 44, desc: "" },
    { index: 45, desc: "" },
    { index: 46, desc: "" },
    { index: 47, desc: "" },
  ])

  let legendEnDescContainer = createFrom(rows, "legend-desc hidden", [
    { index: 49, desc: "" },
    { index: 50, desc: "" },
    { index: 51, desc: "" },
    { index: 52, desc: "" }
  ])

  let legendEnHiddenDescContainer = createFrom(rows, "legend-desc collapsible collapsed hidden", [
    { index: 53, desc: "" },
    { index: 54, desc: "" },
    { index: 55, desc: "" },
    { index: 56, desc: "" }
  ])

  let hider = document.createElement("div")
  let hiderButton = document.createElement("button")
  hiderButton.classList.add("collapse-button")
  hiderButton.setAttribute("type", "button")
  hiderButton.addEventListener("click", event => {
    container.querySelectorAll(".legend-desc.collapsible").forEach(element => {
      element.classList.toggle("collapsed")
    })
  })
  hider.appendChild(hiderButton)

  legendDescContainer.appendChild(legendDescHeader)
  legendDescContainer.appendChild(legendRuDescContainer)
  legendDescContainer.appendChild(legendRuHiddenDescContainer)
  legendDescContainer.appendChild(legendEnDescContainer)
  legendDescContainer.appendChild(legendEnHiddenDescContainer)
  legendDescContainer.appendChild(hider)
  legendContainer.appendChild(legendDescContainer)

  /* LEGEND FILES */
  let legendFilesContainer = document.createElement("div")
  legendFilesContainer.classList.add("legend-container__files")

  let imagesForLegendContainer = createFrom(rows, "files-container", [
    { index: 23, name: "Фото в легенде" }
  ])
  legendFilesContainer.appendChild(imagesForLegendContainer)

  let audioForLegendContainer = createFrom(rows, "files-container", [
    { index: 27, name: "Файлы в легенде" }
  ])
  legendFilesContainer.appendChild(audioForLegendContainer)

  legendContainer.appendChild(legendFilesContainer)

  let adminFilesContainer = document.createElement('div')
  adminFilesContainer.classList.add('admin-files-container')

  let imagesForAdminContainer = createFrom(rows, "files-container", [
    { index: 31, name: "Фото в админке" }
  ])
  adminFilesContainer.appendChild(imagesForAdminContainer)

  let audioForAdminContainer = createFrom(rows, "files-container", [
    { index: 35, name: "Файлы в админке" }
  ])
  adminFilesContainer.appendChild(audioForAdminContainer)

  /* BOTTOM OPTIONS */

  let bottomOptionsContainer = createFromMulti(rows, "options bottom-options", {
    from: 59,
    to: rows.length - 3
  })

  /* APPEND ALL */

  container.appendChild(topButtonsContainer)
  container.appendChild(headerContainer)
  container.appendChild(firstContainer)
  container.appendChild(commentContainer)
  container.appendChild(legendContainer)
  container.appendChild(adminFilesContainer)
  container.appendChild(bottomOptionsContainer)
  container.appendChild(bottomButtonsContainer)

  /* MAP */
  if (map !== undefined && L !== undefined) {
    let content = document.querySelector("#content")
    let contentWrapper = document.createElement("div")
    contentWrapper.id = "content-wrapper"
    contentWrapper.appendChild(document.querySelector("form"))
    contentWrapper.appendChild(document.querySelector("#map-wrapper"))
    content.appendChild(contentWrapper)
  
    let panToCenter = document.createElement("button")
    panToCenter.type = "button"
    panToCenter.textContent = "В центр"
    panToCenter.addEventListener("click", () => {
      let lat = document.querySelector("input[name=\"cp\[lattitude\]\"").value
      let lon = document.querySelector("input[name=\"cp\[longitude\]\"").value
      map.setView(new L.LatLng(parseFloat(lat), parseFloat(lon)), 16)
    })
    document.querySelector("#map_controls").appendChild(panToCenter)
  }

  /* DIALOG */
  let dialog = document.createElement("dialog")
  dialog.id = "dialog"
  document.body.appendChild(dialog)

  dialog.addEventListener("click", e => {
    if (e.target == dialog) {
      e.target.close()
    }
  })

  dialog.addEventListener("close", e => {
    e.target.innerHTML = ""
  })

  /* PRETTIFY CHECKBOX */

  if (localStorage.getItem(localStorageItems.ALWAYS_PRETTIFY)) {
    document.querySelector(`input[name^="always-prettify-0"]`).click()
    prettify(form, oldTable, container, insertedFileRows)
  }

})();