// ==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 adminSite = "runcity.geo.rictum.ru"

  const links = {
    "Волонтеры": "suv_comp",
    "Контрольные пункты": "cp_mgmt",
    "+ КП": "cp_mgmt/?action=edit",
    "Маршруты": "route_mgmt",
    "Тесты": "route_mgmt?test=1",
    "+ Тест": "suv_comp?action=test_online",
    "Склеить": "suv_comp?action=test_merge",
    "Прохождение": "callcenter?action=online_log",
    "КП в трассах": "route_mgmt?action=cp_list",
    "Связанные КП": relatedPointsHref(),
  }

  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",
    NOT_PRETTIFY_EDIT_CP: "notPrettifyEditCp",
    REDIRECT_EXIT: "redirectExit",
    DISABLED_PAGES: "disabledPages"
  }

  const ZOOM = 17
  const METERS = 510

  const stageColors = ["#ff8080", "#ffc680", "#ffff80", "#80ff80", "#80ffff", "#8080ff", "#ff80ff"]

  class Tag {
    static make(name, params = {}) {
      let element = document.createElement(name)
      for (const [name, value] of Object.entries(params)) {
        if (name == "on") {
          for (const [event, listener] of Object.entries(value)) {
            element.addEventListener(event, listener)
          }
        }
        else if (name == "classes") {
          element.classList.add(...value.split(" "))
        }
        else if (name == "children") {
          element.append(...value)
        }
        else if (name.startsWith("_")) {
          element.setAttribute(name.substring(1), value)
        }
        else
          element[name] = value
      }

      return element
    }

    static div(params) {
      return this.make("div", params)
    }

    static span(params) {
      return this.make("span", params)
    }

    static button(params) {
      return this.make("button", params)
    }

    static input(params) {
      return this.make("input", params)
    }

    static a(params) {
      return this.make("a", params)
    }

    static img(params) {
      return this.make("img", params)
    }
  }

  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 desc = altDesc ?? this.desc
      let descTooltip = desc ? Tag.div({
        classes: 'desc-icon-container',
        title: desc,
        children: [
          Tag.img({
            classes: 'desc-icon',
            src: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnIGlkPSJzdmcyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjAwIiB3aWR0aD0iMjAwIiB2ZXJzaW9uPSIxLjAiPgogPHBhdGggaWQ9InBhdGgyMzgyIiBkPSJtMTY1LjMzIDExMy40NGExMDMuNjEgMTAzLjYxIDAgMSAxIC0yMDcuMjIgMCAxMDMuNjEgMTAzLjYxIDAgMSAxIDIwNy4yMiAweiIgdHJhbnNmb3JtPSJtYXRyaXgoLjkzNzM5IDAgMCAuOTM3MzkgNDIuMTQzIC02LjMzOTIpIiBzdHJva2Utd2lkdGg9IjAiIGZpbGw9IiNmZmYiLz4KIDxnIGlkPSJsYXllcjEiPgogIDxwYXRoIGlkPSJwYXRoMjQxMyIgZD0ibTEwMCAwYy01NS4yIDAtMTAwIDQ0LjgtMTAwIDEwMC01LjA0OTVlLTE1IDU1LjIgNDQuOCAxMDAgMTAwIDEwMHMxMDAtNDQuOCAxMDAtMTAwLTQ0LjgtMTAwLTEwMC0xMDB6bTAgMTIuODEyYzQ4LjEzIDAgODcuMTkgMzkuMDU4IDg3LjE5IDg3LjE4OHMtMzkuMDYgODcuMTktODcuMTkgODcuMTktODcuMTg4LTM5LjA2LTg3LjE4OC04Ny4xOSAzOS4wNTgtODcuMTg4IDg3LjE4OC04Ny4xODh6bTEuNDcgMjEuMjVjLTUuNDUgMC4wMy0xMC42NTMgMC43MzctMTUuMjgyIDIuMDYzLTQuNjk5IDEuMzQ2LTkuMTI2IDMuNDg0LTEyLjg3NiA2LjIxOS0zLjIzOCAyLjM2Mi02LjMzMyA1LjM5MS04LjY4NyA4LjUzMS00LjE1OSA1LjU0OS02LjQ2MSAxMS42NTEtNy4wNjMgMTguNjg3LTAuMDQgMC40NjgtMC4wNyAwLjg2OC0wLjA2MiAwLjg3NiAwLjAxNiAwLjAxNiAyMS43MDIgMi42ODcgMjEuODEyIDIuNjg3IDAuMDUzIDAgMC4xMTMtMC4yMzQgMC4yODItMC45MzcgMS45NDEtOC4wODUgNS40ODYtMTMuNTIxIDEwLjk2OC0xNi44MTMgNC4zMi0yLjU5NCA5LjgwOC0zLjYxMiAxNS43NzgtMi45NjkgMi43NCAwLjI5NSA1LjIxIDAuOTYgNy4zOCAyIDIuNzEgMS4zMDEgNS4xOCAzLjM2MSA2Ljk0IDUuODEzIDEuNTQgMi4xNTYgMi40NiA0LjU4NCAyLjc1IDcuMzEyIDAuMDggMC43NTkgMC4wNSAyLjQ4LTAuMDMgMy4yMTktMC4yMyAxLjgyNi0wLjcgMy4zNzgtMS41IDQuOTY5LTAuODEgMS41OTctMS40OCAyLjUxNC0yLjc2IDMuODEyLTIuMDMgMi4wNzctNS4xOCA0LjgyOS0xMC43OCA5LjQwNy0zLjYgMi45NDQtNi4wNCA1LjE1Ni04LjEyIDcuMzQzLTQuOTQzIDUuMTc5LTcuMTkxIDkuMDY5LTguNTY0IDE0LjcxOS0wLjkwNSAzLjcyLTEuMjU2IDcuNTUtMS4xNTYgMTMuMTkgMC4wMjUgMS40IDAuMDYyIDIuNzMgMC4wNjIgMi45N3YwLjQzaDIxLjU5OGwwLjAzLTIuNGMwLjAzLTMuMjcgMC4yMS01LjM3IDAuNTYtNy40MSAwLjU3LTMuMjcgMS40My01IDMuOTQtNy44MSAxLjYtMS44IDMuNy0zLjc2IDYuOTMtNi40NyA0Ljc3LTMuOTkxIDguMTEtNi45OSAxMS4yNi0xMC4xMjUgNC45MS00LjkwNyA3LjQ2LTguMjYgOS4yOC0xMi4xODcgMS40My0zLjA5MiAyLjIyLTYuMTY2IDIuNDYtOS41MzIgMC4wNi0wLjgxNiAwLjA3LTMuMDMgMC0zLjk2OC0wLjQ1LTcuMDQzLTMuMS0xMy4yNTMtOC4xNS0xOS4wMzItMC44LTAuOTA5LTIuNzgtMi44ODctMy43Mi0zLjcxOC00Ljk2LTQuMzk0LTEwLjY5LTcuMzUzLTE3LjU2LTkuMDk0LTQuMTktMS4wNjItOC4yMy0xLjYtMTMuMzUtMS43NS0wLjc4LTAuMDIzLTEuNTktMC4wMzYtMi4zNy0wLjAzMnptLTEwLjkwOCAxMDMuNnYyMmgyMS45OTh2LTIyaC0yMS45OTh6Ii8+CiA8L2c+Cjwvc3ZnPgo='
          })
        ]
      }) : ""

      return Tag.div({
        children: [
          Tag.make("label", {
            _for: '',
            innerHTML: altName ?? this.name ?? ""
          }),
          Tag.div({
            innerHTML: this.content ?? "",
          }),
          descTooltip
        ]
      })
    }
  }

  function createFrom(rows, classList, data) {
    let container = Tag.div({
      classes: classList
    })
    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.append(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) {
    document.head.append(
      Tag.make("link", {
        href: link,
        rel: "stylesheet"
      })
    )
  }

  function addJs(link) {
    document.head.append(
      Tag.make("script", {
        src: link
      })
    )
  }

  function addStylesToHead(styles) {
    document.head.append(
      Tag.make("style", {
        textContent: styles
      })
    )
  }

  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 isCpEditPage() {
    return isCpManagement() && getAction() === "edit"
  }

  function isCpDeletePage() {
    return isCpManagement() && getAction() === "delete"
  }

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

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

  function isRouteEditPage() {
    return isRouteManagement() && getAction() === "edit"
  }

  function isRouteStagesPage() {
    return isRouteManagement() && getAction() === "stages"
  }

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

  function cpLink(cpId) {
    return `/${getCompetition()}/cp_mgmt/?action=edit&cp_id=${cpId}`
  }

  async function updatePoint(formData) {
    return await fetch(`https://${adminSite}/api/competitions/${getCompetition()}/update`, {
      method: 'POST',
      body: formData
    })
  }

  async function deletePoint() {
    return await fetch(`https://${adminSite}/api/points/${urlParams().get("cp_id")}`, {
      method: 'DELETE'
    })
  }

  async function getPointsByField(field, values) {
    let params = new URLSearchParams()
    for (const value of values) {
      params.append(`${field}[]`, value)
    }
    let response = await fetch(`https://${adminSite}/api/competitions/${getCompetition()}/points?${params.toString()}`, {
      method: 'GET'
    })
    return await response.json()
  }

  async function updateRoute(formData) {
    return await fetch(`https://${adminSite}/api/competitions/${getCompetition()}/route`, {
      method: 'POST',
      body: formData
    })
  }

  async function getRoutes() {
    let response = await fetch(`https://${adminSite}/api/points/${urlParams().get("cp_id")}/routes`, {
      method: 'GET'
    })
    return await response.json()
  }

  async function getUnknownPoints() {
    let response = await fetch(`https://${adminSite}/api/competitions/${getCompetition()}/routes/${urlParams().get("cat_id")}/unknown`, {
      method: 'GET'
    })
    return await response.json()
  }

  function relatedPointsHref() {
    return `https://${adminSite}/competitions/${getCompetition()}/relations`
  }

  function createPseudoButton(button, callback, clickAfter = true) {
    let pseudoButton = Tag.button({
      type: "button",
      textContent: button.value,
      classes: [...button.classList].join(" ") + " pseudo-save",
      on: {
        click: async () => {
          await callback()
          if (clickAfter) {
            button.click()
          }
        }
      }
    })

    button.style.display = "none"
    button.parentElement.insertBefore(pseudoButton, button)
  }

  function copyCoordinates() {
    return Tag.button({
      classes: "copy-button",
      on: {
        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])
        }
      },
      children: [
        Tag.img({
          src: "https://upload.wikimedia.org/wikipedia/commons/a/aa/Bw_copy_icon_320x320.svg"
        })
      ]
    })
  }

  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) {
    return Tag.a({
      href: linkCallback(lat, lon, zoom),
      target: "_blank",
      on: {
        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)
        }
      },
      children: [
        Tag.img({
          src: iconSrc,
          classes: "map-icon"
        })
      ]
    })
  }

  let makeCoordinatesLinks = (function () {
    let executed = false
    return function () {
      if (executed) return
      executed = true

      let linksContainer = Tag.div({
        children: [
          copyCoordinates(),
          makeRef(yandexMaps, "https://upload.wikimedia.org/wikipedia/commons/7/72/Yandex_Maps_icon.svg", ZOOM),
          makeRef(googleMaps, "https://upload.wikimedia.org/wikipedia/commons/a/aa/Google_Maps_icon_%282020%29.svg", METERS),
          makeRef(pastvu, "https://pastvu.com/coast-icon.png", ZOOM),
          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) {
    return Tag.a({
      download: name,
      textContent: "Скачать",
      target: "_blank",
      href: href
    })
  }

  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, removedFilesLinks, variant = null) {
    if (!inputName || !file || !removeLink) return

    let fileListContainer
    if (variant !== null) {
      if (inputName == "attachment1" || inputName == "attachment4") {
        if (variant == "l") {
          fileListContainer = document.querySelector(`[data-container="${fileInputs[inputName][variant]}"] .file-list-container`)
        }
        else if (variant == "h") {
          let filesContainer = document.querySelector(`[data-container="${fileInputs[inputName].h}"]`)
          fileListContainer = getFileListContainer(filesContainer)
        }
      }
    }

    let input = document.querySelector(`input[name="${inputName}\[\]"]`)
    fileListContainer ??= input.parentElement.parentElement.querySelector(".file-list-container")
    let fileContainer = Tag.div({
      classes: "file-container"
    })

    const displayedFile = Tag.make(type ?? "a", {
      classes: "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 = Tag.div({
      classes: "file-buttons-container"
    })

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

    let deleteButton = Tag.button({
      type: "button",
      classes: "button-delete",
      textContent: "x",
      on: {
        click: async e => {
          if (!confirm("Точно?")) return

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

          fileListContainer.removeChild(fileContainer)
        }
      }
    })

    fileButtonsContainer.append(deleteButton)

    fileContainer.append(displayedFile)
    fileContainer.append(fileButtonsContainer)

    fileListContainer.append(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 = Tag.make(res)
    if (res == "audio") {
      el.setAttribute("controls", "")
    }

    return el
  }

  function getAttachmentIndex(input) {
    return input.name.replace(/\D/g, '')
  }

  function getContainersByVariant(variant, attachmentIndex) {
    let invertedVariant = variant == "l" ? "h" : "l"
    let variants = fileInputs[`attachment${attachmentIndex}`]

    return [document.querySelector(`[data-container="${variants[invertedVariant]}"]`), document.querySelector(`[data-container="${variants[variant]}"]`)]
  }

  function makeFileListContainer() {
    return Tag.div({
      classes: "file-list-container"
    })
  }
  
  function getFileListContainer(filesContainer) {
    let fileListContainer = filesContainer.querySelector(".file-list-container")
    if (fileListContainer == null) {
      fileListContainer = makeFileListContainer()
      filesContainer.append(fileListContainer)
    }

    return fileListContainer
  }

  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 = getFileListContainer(filesContainer)
        }
        else {
          fileListContainer = this.parentElement.parentElement.querySelector(".file-list-container")
        }

        let fileContainer = Tag.div({
          classes: "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 = Tag.div({
          classes: "file-buttons-container"
        })

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

        let deleteButton = Tag.button({
          classes: "button-delete",
          textContent: "x",
          type: "button",
          on: {
            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.append(deleteButton)

        fileContainer.append(displayedFile)
        fileContainer.append(fileButtonsContainer)

        fileListContainer.append(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 () {
    let executed = false
    return function (insertedFileRows, removedFilesLinks) {
      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 = Tag.make("label", {
          classes: "custom-file-upload",
          _for: element.id,
          textContent: "+"
        })
        element.parentElement.insertBefore(pseudoInput, element)

        element.parentElement.parentElement.append(makeFileListContainer())

        element.addEventListener("change", makeHandleFilesFunc(), false)
      })

      moveFileInputValues(insertedFileRows, removedFilesLinks)
    }
  })()

  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, removedFilesLinks) {
    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, removedFilesLinks, 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) {
        getFileListContainer(to).append(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.append(became)
    setInputValues(became, inputValues)
    createSendButtons()
  }

  function prettifyEditCpForm(form, was, became, insertedFileRows, removedFilesLinks) {
    moveInputValues(form, was, became)
    prettifyFiles(insertedFileRows, removedFilesLinks)

    moveNewFilesOnVariantChange()
    $("#cps_main").select2()
    makeCoordinatesLinks()
  }

  function uglifyEditCpForm(form, was, became, removedFilesLinks) {
    moveInputValues(form, was, became)
    for (const removedFileLink of removedFilesLinks) {
      removeDeletedFilesFromUglyVersion(removedFileLink)
    }
    removedFilesLinks.length = 0
  }

  function createAlwaysPrettifyInput(index) {
    let alwaysPrettify = Tag.div({
      classes: "always-prettify-container"
    })

    let alwaysPrettifyLabel = Tag.make("label", {
      _for: "always-prettify-" + index,
      textContent: "Всегда"
    })

    alwaysPrettify.append(alwaysPrettifyLabel)

    let alwaysPrettifyCheckbox = Tag.input({
      type: "checkbox",
      id: "always-prettify-" + index,
      name: "always-prettify-" + index,
      on: {
        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.removeItem(localStorageItems.NOT_PRETTIFY_EDIT_CP)
          else
            localStorage.setItem(localStorageItems.NOT_PRETTIFY_EDIT_CP, "+")
        }
      }
    })

    alwaysPrettify.append(alwaysPrettifyCheckbox)

    return alwaysPrettify
  }

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

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

  function toggleRows(rows, from, to, cycle, showIndex) {
    for (let i = from; i < to; i += cycle) {
      for (let j = 0; j < cycle; j++) {
        if (!showIndex.includes(j)) {
          rows[i + j]?.classList.toggle("collapsed")
        }
      }
    }
  }

  function toggleText(current, ...variants) {
    let index = variants.indexOf(current)
    if (index == -1) return null

    return variants[index + 1 < variants.length ? index + 1 : 0]
  }

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

    let swiperWrapper = Tag.div({
      classes: "swiper-wrapper"
    })

    for (const file of files) {
      let swiperSlide = Tag.div({
        classes: "swiper-slide",
        children: [
          Tag.a({
            classes: "swiper-download-link",
            href: file.dataset.origin,
            text: "Скачать",
            target: "_blank"
          }),
          Tag.img({
            src: file.dataset.origin
          })
        ]
      })

      swiperWrapper.append(swiperSlide)
    }

    let swiperDiv = Tag.div({
      classes: "swiper",
      children: [
        swiperWrapper,
        Tag.div({
          classes: "swiper-button-prev"
        }),
        Tag.div({
          classes: "swiper-button-next"
        })
      ]
    })

    dialog.append(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')
  }

  const bindArrowsForGallery = (function () {
    let executed = false
    return function(leftButtonQuery, rightButtonQuery) {
      if (executed) return
      executed = true

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

          case "ArrowRight":
            document.querySelector(rightButtonQuery).click()
            break

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

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

    let fileInputNames = Object.values(fileInputs).reduce((res, el) => typeof el == "string" ? [...res, el] : [...res, ...Object.values(el)], [])
    for (const fileInput of fileInputNames) {
      let fileListContainer = document.querySelector(`[data-container=${fileInput}] .file-list-container`)
      if (fileListContainer) {
        let count = [...fileListContainer.children].filter(el => !el.classList.contains("file-container-new")).length
        formData.set(`cp[${fileInput}]`, count)
      }
    }

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

  function createSaveAndNewButton() {
    let saveAndNewButton = Tag.button({
      textContent: "+",
      type: "button",
      classes: "safe-action",
      on: {
        click: async () => {
          await 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]
    let cpIsKnown = document.querySelector(`input[name="cp[id]"]`).value != ''

    for (let saveButton of saveButtons) {
      createPseudoButton(saveButton, async () => {
        await sendForm()

        if (cpIsKnown) return

        if (saveButton.name == "save_exit") {
          localStorage.setItem(localStorageItems.REDIRECT_EXIT, true)
        }

        saveAndStayButtons[0].click()
      }, cpIsKnown)
    }

    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 createMenuFromLinks(links, blanks = []) {
    let menu = Tag.make("menu")

    for (const [label, href] of Object.entries(links)) {
      let menuItem = Tag.make("li")
      let link = Tag.a({
        href: href,
        innerText: label
      })

      if (blanks.includes(label)) {
        link.target = "_blank"
      }

      menuItem.append(link)
      menu.append(menuItem)
    }

    return menu
  }

  function addStickyMenu() {
    let competition = getCompetition()
    if (!competition) return

    let menuContainer = Tag.make("nav", {
      classes: "sticky-menu"
    })

    let mainLinksFormatted = {}
    for (const [label, href] of Object.entries(links)) {
      mainLinksFormatted[label] = href.startsWith("http") ? href : `/${competition}/${href}`
    }
    menuContainer.append(createMenuFromLinks(mainLinksFormatted, ["Легенда"]))

    let goToContainer = Tag.make("li", {
      classes: "go-to-container",
      children: [
        Tag.input({
          type: "text",
          id: "go-to-cp"
        }),
        Tag.span({
          textContent: "✏️ КП",
          on: {
            click: async function () {
              let number = document.querySelector("#go-to-cp").value
              let json = await getPointsByField("number", [number])
              let id = json[number].cp_id

              if (id == null) {
                alert("Нет КП с таким номером")
                return
              }

              location.href = cpLink(id)
            }
          }
        })
      ]
    })

    ;[...menuContainer.querySelectorAll(`li`)].find(el => el.querySelector(`a`).textContent.trim() == "Контрольные пункты")?.after(goToContainer)

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

      menuContainer.append(createMenuFromLinks(catLinksFormatted))
    }

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

    addStylesToHead(`
      .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: " | "
            }
          }
        }

        .go-to-container {
          input {
            width: 5em;
          }

          span {
            margin-left: .5em;
            cursor: pointer;
          }
        }
      }
    `)
  }

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

  function hasMap() {
    return document.querySelector(".leaflet-container") !== null
  }

  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 innerText = "Этапы"
        let stagesLink = Tag.a({
          innerText: innerText,
          href: catLinks[innerText](catId)
        })

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

  function hideNonTesters() {
    if (urlParams().get("test") == null) return

    document.querySelectorAll(`td:nth-child(2)`).forEach(el => {
      let innerHTML = el.innerHTML
      let from = innerHTML.indexOf("Отчёты тестировщиков")
      if (from == -1)
        from = innerHTML.length

      el.innerHTML = innerHTML.substring(from)
    })
  }

  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() {
    return Tag.div({
      id: "new"
    })
  }

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

    let copyLink = Tag.div({
      innerHTML: new Property(rows[2]).desc
    })
    headerContainer.append(copyLink)

    return headerContainer
  }

  function bindDeleteButton() {
    createPseudoButton(document.querySelector(`input[name="delete_go"]`), deletePoint)
  }

  function makeTopAndBottomButtons(rows, form, oldTable, container, insertedFileRows, removedFilesLinks) {
    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 = Tag.div({
        classes: "buttons-row__content-wrapper"
      })
      while ([...el.children].length > 0) {
        let child = el.firstChild
        rowContentWrapper.append(child)
      }

      let prettifyButton = Tag.button({
        type: "button",
        textContent: "Сделать красиво",
        on: {
          click: () => {
            prettifyEditCpForm(form, oldTable, container, insertedFileRows, removedFilesLinks)
          }
        }
      })

      rowContentWrapper.append(prettifyButton)
      rowContentWrapper.append(createAlwaysPrettifyInput(index))

      el.append(rowContentWrapper)
    })

    ;[topButtonsContainer, bottomButtonsContainer].forEach((el, index) => {
      let unglifyButton = Tag.button({
        type: "button",
        textContent: "Сделать как было",
        on: {
          "click": () => {
            uglifyEditCpForm(form, container, oldTable, removedFilesLinks)
          }
        }
      })

      let topRowContentWrapper = el.querySelector("div > div > div")
      topRowContentWrapper.append(unglifyButton)
      topRowContentWrapper.append(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(container, rows) {
    let legendContainer = Tag.div({
      classes: "legend-container"
    })

    /* LEGEND DESC */

    const LEGEND_RU_LABEL = "Русский"
    const LEGEND_EN_LABEL = "Английский"

    let legendDescHeader = Tag.div({
      classes: "legend-container__desc-header"
    })

    let legendLang = Tag.div({
      classes: "legend-desc__lang",
      textContent: LEGEND_RU_LABEL
    })
    legendDescHeader.append(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.append(legendEnSwitchContainer)

    let copyDescButton = Tag.button({
      type: "button",
      textContent: "Копировать",
      on: {
        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.append(legendRuSwitchContainer)

    legendDescHeader.append(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 = Tag.div({
      children: [
        Tag.button({
          classes: "collapse-button",
          type: "button",
          on: {
            click: event => {
              let container = document.querySelector('.legend-container')
              container.querySelectorAll(".legend-desc.collapsible").forEach(element => {
                element.classList.toggle("collapsed")
              })
            }
          }
        })
      ]
    })

    let legendDescContainer = Tag.div({
      classes: "legend-container__desc",
      children: [
        legendDescHeader,
        legendRuDescContainer,
        legendRuHiddenDescContainer,
        legendEnDescContainer,
        legendEnHiddenDescContainer,
        hider
      ]
    })

    legendContainer.append(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 = Tag.div({
      classes: "legend-container__files"
    })

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

    let imagesForHistoryContainer = createFrom(rows, "files-container", [
      { name: "Фото для ИС" }
    ])
    imagesForHistoryContainer.dataset.container = fileInputs.attachment1.h
    legendFilesContainer.append(imagesForHistoryContainer)

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

    let audioForHistoryContainer = createFrom(rows, "files-container", [
      { name: "Файлы для ИС" }
    ])
    audioForHistoryContainer.dataset.container = fileInputs.attachment4.h
    legendFilesContainer.append(audioForHistoryContainer)

    return legendFilesContainer
  }

  function makeAdminFiles(rows) {
    let adminFilesContainer = Tag.div({
      classes: 'admin-files-container'
    })

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

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

    return adminFilesContainer
  }

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

  function makeDialog() {
    let dialog = Tag.make("dialog", {
      id: "dialog",
      on: {
        click: e => {
          if (e.target == dialog) {
            e.target.close()
          }
        },
        close: e => {
          e.target.innerHTML = ""
        }
      }
    })
    document.body.append(dialog)
  }

  function formatMap() {
    if (map !== undefined && L !== undefined) {
      let content = document.querySelector("#content")
      let contentWrapper = Tag.div({
        id: "content-wrapper",
        children: [
          document.querySelector("form"),
          document.querySelector("#map-wrapper")
        ]
      })
      content.append(contentWrapper)

      let panToCenter = Tag.button({
        type: "button",
        textContent: "В центр",
        on: {
          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").append(panToCenter)
    }
  }

  function makeCurrentPointHigherOnMap() {
    map.eachLayer(function (layer) {
      if (layer instanceof L.Marker) {
        if (layer.getElement().classList.contains("leaflet-marker-draggable")) {
          layer.setZIndexOffset(9999)
        }
      }
    })
  }

  function addRoutesList() {
    (async () => {
      let routes = await getRoutes()

      if (routes.error || routes.length == 0) return

      let result = Tag.div()
      result.append("Маршруты: ")
      for (const [i, route] of routes.entries()) {
        if (i !== 0) {
          result.append(", ")
        }

        result.append(Tag.a({
          href: `/${getCompetition()}/` + catLinks["Редактор маршрута"](route.cat_id),
          textContent: route.name
        }))
      }

      document.querySelector(`#map_controls`).after(result)
    })()
  }

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

  function addPrettifyEditPageCss() {
    addStylesToHead(`
      #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;
      }
    `)
  }

  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")
    addPrettifyEditPageCss()

    /* NEW DEFALUT VALUES */

    chooseLegendAsDefaultVariant()

    /* CONTAINER */

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

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

    /* HEADER */

    let headerContainer = makeHeader(rows)

    /* TOP BUTTONS */

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

    addUglyDeleteListener()
    createSendButtons()

    /* LEGEND */

    let legendContainer = makeLegend(container, rows)
    legendContainer.append(makeLegendFiles(rows))

    /* APPEND ALL */

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

    /* MAP */

    formatMap()
    makeCurrentPointHigherOnMap()
    addRoutesList()

    /* DIALOG */

    makeDialog()

    /* PRETTIFY CHECKBOX */

    checkIfAlwaysPrettify(form, oldTable, container, insertedFileRows, removedFilesLinks)
  }

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

  function prettifyRouteBuildPage() {
    function changeColumnWidth() {
      let styles = `
        #content table table {
          td:nth-child(2) {
            width: 10%;

            select {
              width: 5em !important;
            }
          }

          td:nth-child(3) {
            width: 12%;
          }

          td:nth-child(4) {
            width: 12%;

            input {
              width: 4em;
            }
          }
        }
      `
      addStylesToHead(styles)
    }

    function hideDescription() {
      document.querySelectorAll(`#content > form > table > tbody > tr:is(:nth-child(3), :nth-child(6)) `).forEach(el => el.remove())
    }

    function hideStartRow() {
      document.querySelector(`#content table table tr:nth-child(2) `).remove()
    }

    function getRows() {
      return [...document.querySelectorAll(`#content > form > table > tbody > tr:nth-child(3) tr`)]
    }

    function useColspanForFinishWarning() {
      let rows = getRows()
      let finishRow = rows.find(el => el.querySelector(`td`)?.textContent.trim() == "Финиш маршрута")

      finishRow.querySelectorAll(`td:not(:nth-child(2))`).forEach(el => el.remove())
      finishRow.querySelector(`td`).colSpan = 4
    }

    function makeStatsSpan(text) {
      let statsSpan = Tag.span({
        textContent: text,
        classes: "stats"
      })

      return statsSpan
    }

    function getPointCells() {
      return [...document.querySelectorAll(`#props table td:first-child`)]
    }

    function getPointNumberFromCell(cell) {
      return cell.textContent.trim().match(/^\d+/)?.[0]
    }

    function getPointNumbers() {
      let pointNumbers = {}
      for (const [rowIndex, pointCell] of getPointCells().entries()) {
        let pointNumber = getPointNumberFromCell(pointCell)
        if (pointNumber != null)
          pointNumbers[rowIndex] = pointNumber
      }

      return pointNumbers
    }

    async function getPointsFromRows() {
      return await getPointsByField("number", Object.values(getPointNumbers()))
    }

    function countCp(points) {
      let rows = getRows()

      let fullCount = 0
      let stageCount = 0
      let fullPuzzleCount = 0
      let stagePuzzleCount = 0
      let fullNeedHistoryCount = 0
      let stageNeedHistoryCount = 0
      let fullHistoryCount = 0
      let stageHistoryCount = 0

      let stageRow = null
      for (const [i, row] of rows.entries()) {
        if (i < 2) continue
        let isFinish = row.querySelector(".attention") !== null

        if (isFinish || row.querySelector(`th`)) {
          if (stageRow) {
            let stageHeader = stageRow.querySelector("th")
            stageHeader.append(makeStatsSpan(`КП в этапе: ${stageCount}`))
            stageHeader.append(makeStatsSpan(`Загадок: ${stagePuzzleCount}`))
            stageHeader.append(makeStatsSpan(`Нужно ИС: ${stageNeedHistoryCount}`))
            stageHeader.append(makeStatsSpan(`ИС: ${stageHistoryCount}`))
          }

          if (isFinish) break

          stageRow = row
          stageCount = 0
          stagePuzzleCount = 0
          stageNeedHistoryCount = 0
          stageHistoryCount = 0
          continue
        }

        let pointNumber = getPointNumberFromCell(row.querySelector("td"))
        if (!pointNumber) continue

        fullCount++
        stageCount++

        let point = points[pointNumber]
        if (!point) continue

        if (point.is_puzzle) {
          fullPuzzleCount++
          stagePuzzleCount++
        }

        if (point.history_needed) {
          fullNeedHistoryCount++
          stageNeedHistoryCount++
        }

        if (point.history_ru) {
          fullHistoryCount++
          stageHistoryCount++
        }
      }

      let tableHeader = document.querySelector("table tr:nth-child(2) th")
      tableHeader.append(makeStatsSpan(`Всего КП: ${fullCount}`))
      tableHeader.append(makeStatsSpan(`Загадок: ${fullPuzzleCount}`))
      tableHeader.append(makeStatsSpan(`Нужно ИС: ${fullNeedHistoryCount}`))
      tableHeader.append(makeStatsSpan(`ИС: ${fullHistoryCount}`))

      addStylesToHead(`
        .stats {
          color: red;
          margin-left: .5em;
        }  
      `)
    }

    function addLinksToCp(points) {
      let pointCells = getPointCells()

      for (const [i, pointCell] of pointCells.entries()) {
        let pointNumber = getPointNumberFromCell(pointCell)
        if (pointNumber == null) continue

        let cellContent = pointCell.textContent
        pointCell.innerHTML = ''
        pointCell.append(Tag.a({
          href: cpLink(points[pointNumber].cp_id),
          target: "_blank",
          textContent: cellContent
        }))
      }
    }

    function showHistoryData(points) {
      let pointCells = getPointCells()

      for (const [i, pointCell] of pointCells.entries()) {
        let pointNumber = getPointNumberFromCell(pointCell)
        if (pointNumber == null) continue

        let point = points[pointNumber]

        if (point.history_needed) {
          pointCell.append(Tag.span({ classes: "point-info", textContent: "Нужна ИС" }))
        }

        if (point.history_ru) {
          pointCell.append(Tag.span({ classes: "point-info", textContent: "Есть ИС" }))
        }

        if (point.photo_answer) {
          pointCell.append(Tag.span({ classes: "point-info", textContent: "Селфи" }))
        }
      }

      addStylesToHead(`
        .point-info {
          &:first-of-type {
            margin-left: 5px;
          }

          & + &::before {
            content: " | "
          }
        }
      `)
    }

    function getRouteName() {
      return document.querySelector(`#content h1`).textContent.match(/(?<=\")[^\"]+/)
    }

    function sendRouteData() {
      createPseudoButton(document.querySelector(`input[name="save_sort"]`), async () => {
        let formData = new FormData(document.querySelector(`form[name="main_form"]`))

        let catId = urlParams().get("cat_id")
        formData.set("cat_id", catId)
        formData.set("cat_name", getRouteName())
        await updateRoute(formData)
      })
    }

    function showUnknownPoints() {
      (async () => {
        let unknownPoints = await getUnknownPoints()
        if (unknownPoints.error || !unknownPoints.length) return

        let unknownPointsContainer = Tag.div()
        unknownPointsContainer.append("Несохраненные точки: ")
        for (const [i, cpId] of unknownPoints.entries()) {
          if (i !== 0) {
            unknownPointsContainer.append(", ")
          }
  
          unknownPointsContainer.append(Tag.a({
            href: cpLink(cpId),
            textContent: cpId
          }))
        }
        document.querySelector(`#content h1`).after(unknownPointsContainer)
      })()
    }

    addJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js")
    changeColumnWidth()
    hideDescription()
    hideStartRow()
    useColspanForFinishWarning()
    ;(async () => {
      let points = await getPointsFromRows()
      countCp(points)
      addLinksToCp(points)
      showHistoryData(points)
    })()
    sendRouteData()
    showUnknownPoints()
  }

  function getCpNumberFromOption(option) {
    return option.textContent.match(/(?<=^\s*#)\d+/g)?.[0]
  }

  function matchNumberFromSelect(select) {
    return [...document.querySelector(select).options].reduce(
      (res, el) => ({ ...res, [getCpNumberFromOption(el)]: el.value }),
      {}
    )
  }

  function unselectAll(select) {
    [...document.querySelector(select).selectedOptions].forEach(el => el.selected = false)
  }

  function scrollSelectToBottom(select) {
    document.querySelector(`${select} option:last-child`).scrollIntoView()
  }

  function prettifyRouteEditPage() {
    function createPointsInpit() {
      let pointInput = Tag.input({
        id: "add-point",
        type: "text",
        on: {
          focus: () => {
            pointInput.classList.remove("success", "error")
          }
        }
      })

      let addButton = Tag.button({
        type: "button",
        textContent: "Добавить",
        id: "add-button",
        on: {
          click: () => {
            let point = pointInput.value
            if (point == "") return

            let availablePoints = matchNumberFromSelect("#cps_avail")
            let addedPoints = matchNumberFromSelect("#cps_in")

            if (point in addedPoints) {
              alert("Точка уже добавлена")
              return
            }

            if (!(point in availablePoints)) {
              alert("Точка не найдена")
              return
            }

            unselectAll("#cps_avail")
            let option = document.querySelector(`#cps_avail option[value="${availablePoints[point]}"]`)
            option.selected = true
            document.querySelector(`#cps_add`).click()
            pointInput.classList.add("success")
            pointInput.value = ""
            option.selected = false
            scrollSelectToBottom("#cps_in")
          }
        }
      })

      let removeButton = Tag.button({
        type: "button",
        textContent: "Убрать",
        on: {
          click: () => {
            let point = pointInput.value
            if (point == "") return

            let addedPoints = matchNumberFromSelect("#cps_in")

            if (!(point in addedPoints)) return

            unselectAll("#cps_in")
            let option = document.querySelector(`#cps_in option[value="${addedPoints[point]}"]`)
            option.selected = true
            document.querySelector(`#cps_delete`).click()
            pointInput.classList.add("error")
            pointInput.value = ""
            option.selected = false
            scrollSelectToBottom("#cps_in")
          }
        }
      })

      let addPointsContainer = Tag.div({
        children: [
          addButton,
          pointInput,
          removeButton
        ]
      })
      document.querySelector("table tr:nth-child(16) td").prepend(addPointsContainer)

      addStylesToHead(`
        #cps_in, #cps_avail {
          height: 15em;
        }

        .success {
          border-color: green;
        }

        .error {
          border-color: red;
        }

        #add-point {
          width: 5em;
        }

        #add-button {
          margin-left: 420px;
        }
      `)
    }

    function makeTextareasOneRow() {
      document.querySelectorAll(`textarea:is([name="track[comment_int]"], [name="track[comment_ext]"])`).forEach(el => {
        el.rows = 1
      })
    }

    function hideRedundantRows() {
      document.querySelectorAll(`table tr:is(:nth-child(3), :nth-child(4), :nth-child(5))`).forEach(el => el.classList.add("hidden"))
    }

    async function getPointsFromSelect() {
      let options = [...document.querySelectorAll(`#cps_in option`)]
      return await getPointsByField("number", options.map(option => getCpNumberFromOption(option)))
    }

    function paintCpList(points) {
      let options = [...document.querySelectorAll(`#cps_in option`)]
      let catId = urlParams().get("cat_id")

      for (const option of options) {
        let category = points[getCpNumberFromOption(option)].categories.find(category => category.cat_id === catId)
        if (category && category.pivot.bonus_time == "0") {
          let stage = parseInt(category.pivot.stage)
          option.style['background-color'] = stageColors[(stage - 1) % stageColors.length]
        } 
      }
    }

    function hideAlreadyAddedPoints(points) {
      let options = [...document.querySelectorAll(`#cps_avail option`)]

      for (const option of options) {
        if (getCpNumberFromOption(option) in points) {
          option.style.display = "none"
        }
      }
    }

    makeTextareasOneRow()
    hideRedundantRows()
    createPointsInpit()
    ;(async () => {
      let points = await getPointsFromSelect()
      paintCpList(points)
      hideAlreadyAddedPoints(points)
    })()
  }

  function toggleStagePageRows(rows) {
    let showIndex = [0, 3]
    let to = rows.findIndex(el => el.querySelector("th")?.textContent.trim() == "Бонусы")
    if (to == -1)
      to = rows.length

    toggleRows(rows, 3, to, 8, showIndex)
    toggleRows(rows, to, rows.length, 1, [])
  }

  function hideUselessRowsFromRouteStagesPage() {
    let styles = `
      tr.collapsed {
        display: block;
        width: 0;
        height: 0;
        overflow: hidden;
      }
    `

    let rows = [...document.querySelectorAll(`#content tbody tr`)]
    let collapseButton = Tag.button({
      type: "button",
      textContent: "Показать",
      on: {
        click: () => {
          toggleStagePageRows(rows)
          collapseButton.textContent = toggleText(collapseButton.textContent, "Показать", "Скрыть")
        }
      }
    })

    toggleStagePageRows(rows)

    document.querySelector(`table tr:first-child th`).append(collapseButton)

    addStylesToHead(styles)
  }

  function addCpNameToOptions() {
    let options = [...document.querySelectorAll(`option`)]
    let cpIds = [...new Set(
      options
        .map($option => $option.value)
        .filter($value => $value != null)
      )
    ]

    ;(async () => {
      let points = await getPointsByField("cp_id", cpIds)

      for (const option of options) {
        if (option.value != null && points[option.value] != null) {
          option.innerText = option.innerText + " - " + points[option.value].name_int
        }
      }
    })()
  }

  function addCommonStyles() {
    addStylesToHead(`
      #content h1 {
        margin-bottom: 0;
      }
    `)
  }

  function getDisabledPages() {
    return JSON.parse(localStorage.getItem(localStorageItems.DISABLED_PAGES) ?? "{}")
  }

  function saveDisabledPages(pages) {
    localStorage.setItem(localStorageItems.DISABLED_PAGES, JSON.stringify(pages))
  }

  function isPageDisabled() {
    let disabledPages = getDisabledPages()
    let pageType = getPageType()

    return disabledPages[pageType]?.[getAction()]
  }

  function addDisabledPage() {
    let disabledPages = getDisabledPages()
    let pageType = getPageType()
    if (!(pageType in disabledPages)) {
      disabledPages[pageType] = {}
    }
    disabledPages[pageType][getAction()] = true

    saveDisabledPages(disabledPages)
  }

  function removeDisabledPage() {
    let disabledPages = getDisabledPages()
    let pageType = getPageType()
    if (!(pageType in disabledPages)) {
      return
    }
    delete disabledPages[pageType][getAction()]

    saveDisabledPages(disabledPages)
  }

  function addEnableButtons() {
    let enableButton = Tag.button({
      type: "button",
      id: "enable-button",
      textContent: "Сделать красиво",
      on: {
        click: () => {
          if (pretty) return

          hide([enableButton])
          prettify()
        }
      }
    })

    if (!isPageDisabled()) {
      hide([enableButton])
    }

    let header = document.querySelector("#header")
    header.insertBefore(enableButton, header.querySelector("#globalmenu"))
    header.insertBefore(createAlwaysEnable(), header.querySelector("#globalmenu"))

    addStylesToHead(`
      #always-enable-container {
        display: inline-block;
      }

      .hidden {
        display: none;
      }
    `)
  }

  function addDisableButton() {
    let disableButton = Tag.button({
      id: "disable-button",
      type: "button",
      textContent: "Сделать как было",
      on: {
        click: () => {
          if (!pretty) return

          if (!confirm("Это действие отключит скрипт на этой странице и удалит все несохраненные данные")) return

          addDisabledPage()
          pretty = false
          location.reload()
        }
      }
    })

    header.querySelector("#enable-button").after(disableButton)
  }

  function createAlwaysEnable() {
    let alwaysEnableContainer = Tag.div({
      id: "always-enable-container",
      children: [
        Tag.make("label", {
          _for: "always-enable",
          textContent: "Всегда красиво"
        })
      ]
    })

    let alwaysEnableCheckbox = Tag.input({
      type: "checkbox",
      id: "always-enable",
      name: "always-enable",
      on: {
        change: function () {
          if (this.checked)
            removeDisabledPage()
          else
            addDisabledPage()
        }
      }
    })


    if (!isPageDisabled()) {
      alwaysEnableCheckbox.checked = true
    }

    alwaysEnableContainer.append(alwaysEnableCheckbox)

    return alwaysEnableContainer
  }

  function prettify() {
    if (pretty) return

    pretty = true

    addDisableButton()

    addCommonStyles()

    /* SWITCH FOR DIFFERENT PAGES */

    addStickyMenu()

    if (isCpEditPage()) {
      prettifyEditCpPage()
    }

    if (isRouteListPage()) {
      addStageLink()
      hideNonTesters()
    }

    if (isRouteBuildPage()) {
      prettifyRouteBuildPage()
    }

    if (isRouteEditPage()) {
      prettifyRouteEditPage()
    }

    if (isRouteStagesPage()) {
      hideUselessRowsFromRouteStagesPage()
      addCpNameToOptions()
    }
  }

  function prettifyIfEnabled() {
    if (isPageDisabled()) return

    prettify()
  }

  /* REDIRECTS */

  redirectAfterNewCpIfNeeded()

  /* PRETTIFY */

  let pretty = false

  addEnableButtons()

  if (isCpDeletePage()) {
    bindDeleteButton()
    return
  }

  prettifyIfEnabled()

  /* MAPS */

  addClearBoth()

  if (hasMap()) {
    initMapbox()
    addFullscreenButton()

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