// ==UserScript==
// @name         Runcity
// @namespace    http://tampermonkey.net/
// @version      2024-11-18
// @description  Prettify pages & store data on server
// @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'

  const links = {
    "Волонтеры": "suv_comp",
    "Контрольные пункты": "cp_mgmt",
    "Добавить новый КП": "cp_mgmt/?action=edit",
    "Маршруты": "route_mgmt",
  }

  const catLinks = {
    "Редактор маршрута": (catId) => `route_mgmt?action=edit&cat_id=${catId}`,
    "Конструктор маршрута": (catId) => `route_mgmt/?action=build2&cat_id=${catId}`,
    "Карта": (catId) => `route_mgmt?action=map&cat_id=${catId}`,
    "Этапы": (catId) => `route_mgmt/?action=stages&cat_id=${catId}`,
    "Легенда": (catId) => `route_mgmt?action=preview&cat_id=${catId}&locale_id=ru`,
  }

  const fileInputs = {
    "attachment1": {
      "l": "legend_photo",
      "h": "history_photo"
    },
    "attachment4": {
      "l": "legend_files",
      "h": "history_files"
    },
    "attachment2": "admin_photo",
    "attachment3": "admin_files"
  }

  const fileInpitVariants = {
    "l": "для легенды",
    "h": "для ист. справки"
  }

  const mapsCenterByCompetition = {
    "msk2025": {
      lat: 55.839808,
      ifSouthern: {
        lat: 55.798531,
        lon: 37.690380,
      },
      ifEverywhere: {
        lat: 55.839759,
        lon: 37.706577
      }
    }
  }

  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 {
    name
    content
    desc

    constructor(row = null) {
      if (row == null) return 

      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(`общие ("c")`, "")
          .replaceAll(`("l")`, "")
          .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 div
      if (options.index != null) {
        let prop = new Property(rows[options.index])
        div = prop.toDiv(options.name, options.desc)
      }
      else {
        let prop = new Property()
        div = prop.toDiv(options.name)
      }
      container.appendChild(div)
    }

    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)
  }

  function addStylesToHead(styles) {
    let styleSheet = document.createElement("style")
    styleSheet.textContent = styles
    document.head.appendChild(styleSheet)
  }

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

  function pathNameSplit() {
    return window.location.pathname.split('/')
  }

  function getCompetition() {
    return pathNameSplit()[1]
  }

  function getPageType() {
    return pathNameSplit()[2]
  }

  function urlParams() {
    return new URLSearchParams(location.search)
  }

  function getAction() {
    let params = urlParams()
    return params.get("action")
  }

  function isCpManagement() {
    return getPageType() === "cp_mgmt"
  }

  function isRouteManagement() {
    return getPageType() === "route_mgmt"
  }

  function isCpListPage() {
    return isCpManagement() && getAction() === null
  }

  function isRouteListPage() {
    return isRouteManagement() && getAction() === null
  }

  function isRouteBuildPage() {
    return isRouteManagement() && getAction() === "build2"
  }

  function isRouteMapPage() {
    return isRouteManagement() && getAction() === "map"
  }

  function isEditCpPage() {
    return isCpManagement() && getAction() === "edit"
  }

  function isDeleteCpPage() {
    return isCpManagement() && getAction() === "delete"
  }
  
  async function updatePoint(formData) {
    return await fetch(`https://runcity.geo.rictum.ru/api/competitions/${getCompetition()}/update`, {
      method: 'POST',
      body: formData
    })
  }

  async function deletePoint() {
    return await fetch(`https://runcity.geo.rictum.ru/api/points/${urlParams().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/?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?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, fileInputVariant = null) {
    let formData = new FormData()

    let normalInputName = fileInputVariant !== null ? fileInputs[inputName][fileInputVariant] : 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 parsePurpose(cell) {
    return cell?.querySelector("td:nth-child(2)")?.innerHTML.match(/для[^\|]+/g)?.[0].trim()
  }

  function parsefileInputVariant(cell) {
    return Object.keys(fileInpitVariants).find(key => fileInpitVariants[key] === parsePurpose(cell))
  }

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

    let fileListContainer
    if (variant !== null) {
      if (inputName == "attachment1" || inputName == "attachment4") {
        if (variant == "l") {
          let fileContainerClassName = fileInputs[inputName][variant].replace("_", "-")
          fileListContainer = document.querySelector(`.${fileContainerClassName}-container .file-list-container`)
        }
        else if (variant == "h") {
          let filesContainer = document.querySelector(inputName == "attachment1" ? `.history-photo-container` : `.history-files-container`)
          fileListContainer = filesContainer.querySelector(`.file-list-container`)
          if (fileListContainer == null) {
            fileListContainer = document.createElement("div")
            fileListContainer.classList.add("file-list-container")
            filesContainer.appendChild(fileListContainer)
          }
        }
      }
    }

    let input = document.querySelector(`input[name="${inputName}\[\]"]`)
    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, variant)
      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 getAttachmentIndex(input) {
    return input.name.replace(/\D/g, '')
  }

  function getContainersByVariant(variant, attachmentIndex) {
    let from = variant == "l" ? "history" : "legend"
    let to = variant == "l" ? "legend" : "history"
    let type = attachmentIndex == 1 ? "photo" : "files"
    
    return [document.querySelector(`.${from}-${type}-container`), document.querySelector(`.${to}-${type}-container`)]
  }

  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 variant = this.parentElement.querySelector('input[type="radio"]:checked')?.value
        let fileListContainer

        if (variant) {
          let [_, filesContainer] = getContainersByVariant(variant, getAttachmentIndex(this))
          fileListContainer = filesContainer.querySelector(".file-list-container")
        }
        else {
          fileListContainer = this.parentElement.parentElement.querySelector(".file-list-container")
        }

        let fileContainer = document.createElement("div")
        fileContainer.classList.add("file-container", "file-container-new")

        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 = [...fileContainer.parentElement.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
    
          fileContainer.parentElement.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
        }

        let variant = parsefileInputVariant(row)

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

  function moveNewFilesOnVariantChange() {
    document.querySelectorAll(`input[name^="new_file_type"]`).forEach(el => el.addEventListener("change", function() {
      let attachmentIndex = getAttachmentIndex(this)
      
      let [from, to] = getContainersByVariant(this.value, attachmentIndex)
      let newFiles = [...from.querySelectorAll(`.file-container-new`)]
      for (const newFile of newFiles) {
        to.querySelector(`.file-list-container`).appendChild(newFile)
      }
    }))
  }

  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)
    
    moveNewFilesOnVariantChange()
    $("#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)

    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 fileListContainers = [...document.querySelectorAll(".file-list-container")]
    let fileInputNames = ["legend_photo", "history_photo", "legend_files", "history_files", "admin_photo", "admin_files"]
    for (let [i, fileListContainer] of fileListContainers.entries()) {
      formData.set(`cp[${fileInputNames[i]}]`, [...fileListContainer.children].filter(el => !el.classList.contains("file-container-new")).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 redirectAfterNewCpIfNeeded() {
    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]", urlParams().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)
    }
  }

  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"]`)
      let fileInputVariant = parsefileInputVariant(document.querySelector(`tr:has(a[href$="${shortHref}"])`))

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

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

  function addStickyMenu() {
    let menuContainer = document.createElement("nav")
    menuContainer.classList.add("sticky-menu")
    let menu = document.createElement("menu")
    menuContainer.appendChild(menu)

    let catId = urlParams().get("cat_id")
    if (catId !== null) {
      for (const [label, href] of Object.entries(catLinks)) {
        links[label] = href(catId)
      }
    }

    for (const [label, href] of Object.entries(links)) {
      let menuItem = document.createElement("li")
      let link = document.createElement("a")
      link.href = `/${getCompetition()}/${href}`
      link.innerText = label

      menuItem.appendChild(link)
      menu.appendChild(menuItem)
    }

    document.querySelector("#header").after(menuContainer)
  }

  function addClearBoth() {
    let clearEl = document.createElement("div")
    clearEl.style.clear = "both"
    document.body.appendChild(clearEl)
  }

  function initMapbox() {
    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')
  }

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

  function addStageLink() {
    let linkCells = [...document.querySelectorAll("#content table td:nth-child(2)")]
    for (const linkCell of linkCells) {
      let href = linkCell.querySelector("a").href
      let catId = new URLSearchParams(href).get("cat_id")
      let constructorLink = linkCell.querySelector(`a[href="${catLinks["Конструктор маршрута"](catId).replace("route_mgmt/", "")}"]`)

      if (constructorLink) {
        let stagesLink = document.createElement("a")
        stagesLink.innerText = "Этапы"
        stagesLink.href = catLinks[stagesLink.innerText](catId)

        constructorLink.after(stagesLink)
        constructorLink.after(", ")
      }
    }
  }

  function getRows() {
    return [...document.querySelectorAll('#props > tbody > tr')]
  }

  function getInsertedFileRows(rows) {
    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++
      }
    }

    return insertedFileRows
  }

  function makeContainer() {
    let container = document.createElement('div')
    container.id = "new"
    
    return container
  }

  function makeHeader(rows) {
    let headerContainer = createFrom(rows, "header", [
      { index: 2, name: "№", desc: "" },
      { index: 8, name: "Название" }
    ])
  
    let copyLink = document.createElement("div")
    copyLink.innerHTML = new Property(rows[2]).desc
    headerContainer.append(copyLink)

    return headerContainer
  }

  function bindDeleteButton() {
    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)
  }

  function makeTopAndBottomButtons(rows, form, oldTable, container, insertedFileRows) {
    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))
    })

    return [topButtonsContainer, bottomButtonsContainer]
  }

  function makeTopOptions(rows) {
    return 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: "Знак" }
    ])
  }

  function makeComment(rows) {
    return createFrom(rows, "comment", [
      { index: 9, desc: "" }
    ])
  }

  function makeLegend(rows) {
    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)

    return legendContainer
  }

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

  function makeLegendFiles(rows) {
    let legendFilesContainer = document.createElement("div")
    legendFilesContainer.classList.add("legend-container__files")
  
    let imagesForLegendContainer = createFrom(rows, "files-container legend-photo-container", [
      { index: 23, name: "Фото в легенде" }
    ])
    legendFilesContainer.appendChild(imagesForLegendContainer)
  
    let imagesForHistoryContainer = createFrom(rows, "files-container history-photo-container", [
      { name: "Фото для ИС" }
    ])
    legendFilesContainer.appendChild(imagesForHistoryContainer)
  
    let audioForLegendContainer = createFrom(rows, "files-container legend-files-container", [
      { index: 27, name: "Файлы в легенде" }
    ])
    legendFilesContainer.appendChild(audioForLegendContainer)
  
    let audioForHistoryContainer = createFrom(rows, "files-container history-files-container", [
      { name: "Файлы для ИС" }
    ])
    legendFilesContainer.appendChild(audioForHistoryContainer)

    return legendFilesContainer
  }

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

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

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

    return adminFilesContainer
  }

  function makeBottomOptions(rows) {
    return createFromMulti(rows, "options bottom-options", {
      from: 59,
      to: rows.length - 3
    })
  }

  function makeDialog() {
    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 = ""
    })
  }

  function formatMap() {
    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)
    }
  }

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

  function prettifyEditCpPage() {
    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 */

    chooseLegendAsDefaultVariant()

    /* CONTAINER */

    let form = document.querySelector('form')
    let rows = getRows()
    let insertedFileRows = getInsertedFileRows(rows)

    let container = makeContainer()
    let oldTable = document.querySelector("#props")

    /* HEADER */

    let headerContainer = makeHeader(rows)

    /* TOP BUTTONS */

    let [topButtonsContainer, bottomButtonsContainer] = makeTopAndBottomButtons(rows, form, oldTable, container, insertedFileRows)

    addUglyDeleteListener()
    createSendButtons()

    /* LEGEND */

    let legendContainer = makeLegend(rows)
    legendContainer.appendChild(makeLegendFiles(rows))

    /* APPEND ALL */

    container.appendChild(topButtonsContainer)
    container.appendChild(headerContainer)
    container.appendChild(makeTopOptions(rows))
    container.appendChild(makeComment(rows))
    container.appendChild(legendContainer)
    container.appendChild(makeAdminFiles(rows))
    container.appendChild(makeBottomOptions(rows))
    container.appendChild(bottomButtonsContainer)

    /* MAP */

    formatMap()

    /* DIALOG */
    
    makeDialog()

    /* PRETTIFY CHECKBOX */
    
    checkIfAlwaysPrettify(form, oldTable, container, insertedFileRows)
  }

  function isAllPointsSouthOfLat(lat) {
    map.eachLayer(function(layer) {
      if (layer instanceof L.Marker) {   
        let latLng = layer.getLatLng()
        if (latLng.lat > lat) {
          return false
        }
      }
    })
  }

  function centerMap() {
    if (map !== undefined && L !== undefined) {
      let coords = mapsCenterByCompetition[getCompetition()]
      if (coords == null) return
      
      if (isAllPointsSouthOfLat(lat)) {
        map.setView(new L.LatLng(coords.ifSouthern.lat, coords.ifSouthern.lon), 13)
      }
      else {
        map.setView(new L.LatLng(coords.ifEverywhere.lat, coords.ifEverywhere.lon), 12)
      }
    }
  }

  let styles = `
    .sticky-menu {
      position: sticky;
      top: 0;
      z-index: 9999;
    
      menu {
        display: flex;
        gap: .3rem;
        background: white;
        font-size: 1.2rem;
        padding-left: 10px;

        li {
          list-style-type: none;

          &:not(:last-child)::after {
            content: " | "
          }
        }
      }
    }

    #content h1 {
      margin-bottom: 0;
    }

    #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=radio] {
          margin-right: 5px;
        }

        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][value="c"] {
      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 */

  redirectAfterNewCpIfNeeded()

  /* HEAD */

  addStylesToHead(styles)

  /* SWITCH FOR DIFFERENT PAGES */

  if (isDeleteCpPage()) {
    bindDeleteButton()
    return
  }

  addClearBoth()
  if (document.querySelector(".leaflet-container")) {
    initMapbox()
    addFullscreenButton()

    if (isRouteBuildPage() || isRouteMapPage()) {
      centerMap()
    }
  }

  addStickyMenu()

  if (isEditCpPage()) {
    prettifyEditCpPage()
  }

  if (isRouteListPage()) {
    addStageLink()
  }
})();