const Template = {
  popup: document.querySelector('#tpl___popup'),

  tabs: document.querySelector('#tpl___tabs'),
  tabHeading: document.querySelector('#tpl___tabs__tab-heading'),
  tabPanels: document.querySelector('#tpl___tabs__panels'),
  tabPanel: document.querySelector('#tpl___tabs__tab-panel'),

  battleMonster: document.querySelector('#tpl___battle__monster'),
  battleHpBar: document.querySelector('#tpl___battle__hp-bar'),
  battleExpBar: document.querySelector('#tpl___battle__exp-bar'),
  battleActionFeedback: document.querySelector('#tpl___battle__action-feedback'),

  techniques: document.querySelector('#tpl___techniques'),
  technique: document.querySelector('#tpl___technique'),

  map: document.querySelector('#tpl___map'),

  healingCenter: document.querySelector('#tpl___healing-center'),
  shop: document.querySelector('#tpl___shop'),
  shopItem: document.querySelector('#tpl___shop__item'),

  party: document.querySelector('#tpl___party'),
  partyMonster: document.querySelector('#tpl___party__monster'),

  monsterStats: document.querySelector('#tpl___monster-stats'),

  movesetList: document.querySelector('#tpl___moveset__list'),
  movesetItem: document.querySelector('#tpl___moveset__item'),

  areaSelection: document.querySelector('#tpl___area-selection'),
  areaSelectionItem: document.querySelector('#tpl___area-selection__item'),

  inventory: document.querySelector('#tpl___inventory'),
  inventoryItem: document.querySelector('#tpl___inventory__item'),

  menuJournal:  document.querySelector('#tpl___menu__journal'),
  dialogSave:  document.querySelector('#tpl___dialog__save'),
  dialogLoad:  document.querySelector('#tpl___dialog__load'),

  menuSettings: document.querySelector('#tpl___menu__settings'),
};

const UI = {
  elements: {
    sceneBattle: document.querySelector('#scene__battle'),
    sceneTown: document.querySelector('#scene__town'),

    battle: document.querySelector('#battle'),
    battleOpponent: document.querySelector('#battle__opponent'),
    battleOpponentSprite: null,
    battleOpponentAnimation: document.querySelector('.battle__technique-animation'),
    battlePlayer: document.querySelector('#battle__player'),

    techniques: document.querySelector('#techniques'),

    log: document.querySelector('#log'),

    status: document.querySelector('#status'),
    showMap: document.querySelector('#status [data-template-slot="showMap"]'),
    nextTrainer: document.querySelector('#status [data-template-slot="nextTrainer"]'),
    changeArea: document.querySelector('#status [data-template-slot="changeArea"]'),

    menuParty: document.querySelector('#menu__party'),
    menuInventory: document.querySelector('#menu__inventory'),
    menuCatch: document.querySelector('#menu__catch'),
    menuLog:  document.querySelector('#menu__log'),
    menuJournal: document.querySelector('#menu__journal'),
    menuSettings: document.querySelector('#menu__settings'),
  },

  events: document.createElement('div'),


  /**
   * @param {HTMLElement} template
   */
  createTemplate (template) {
    const templateBase = document.createElement('div');
    templateBase.innerHTML = template.innerHTML.trim();

    const templateNode = templateBase.firstChild;

    /**
     * @param {HTMLElement} targetElement
     */
    templateNode.appendTo = function (targetElement) {
      if (templateNode.dataset.templateType && templateNode.dataset.templateType === 'multiple') {
        for (const child of [...this.children]) {
          targetElement.appendChild(child);
        }
      } else {
        targetElement.appendChild(this);
      }
    };

    return templateNode;
  },

  /**
   * @returns {HTMLElement}
   */
  createPopup () {
    const popup = UI.createTemplate(Template.popup);

    popup.addEventListener('click', ({ target }) => {
      if (target === popup) {
        popup.dispatchEvent(new Event('close'));
        popup.remove();
      }
    });

    return popup;
  },

  /**
   * @typedef {Object} Tab
   * @property {HTMLElement} heading
   * @property {HTMLElement} content
   * @inner
   *
   * @param {Tab[]} tabs
   *
   * @returns {HTMLElement}
   */
  createTabs (tabs) {
    const wrap = UI.createTemplate(Template.tabs);
    const panelsNode = UI.createTemplate(Template.tabPanels);

    wrap.style.gridTemplateColumns = '1fr '.repeat(tabs.length);

    const name = randomString();

    for (const idx in tabs) {
      const tab = tabs[idx];
      const tabHeading = UI.createTemplate(Template.tabHeading);
      const tabPanel = UI.createTemplate(Template.tabPanel);

      const inputId = `${name}_${idx}`;
      const panelId = randomString();

      const tabHeadingInput = tabHeading.querySelector('[data-template-slot="input"]');
      tabHeadingInput.name = name;
      tabHeadingInput.id = inputId;
      tabHeadingInput.setAttribute('aria-controls', panelId);
      if (idx == 0) {
        tabHeadingInput.checked = true;
      }

      const tabHeadingLabel = tabHeading.querySelector('[data-template-slot="label"]');
      tabHeadingLabel.setAttribute('for', inputId);
      tabHeadingLabel.appendChild(tab.heading);

      tabPanel.id = panelId;
      tabPanel.appendChild(tab.content);

      tabHeading.appendTo(wrap);
      panelsNode.appendChild(tabPanel);
    }

    wrap.appendChild(panelsNode);

    return wrap;
  },

  /**
   * @param {HTMLElement} slotNode
   * @param {HTMLElement} replacingNode
   *
   * @returns {HTMLElement}
   */
  replaceTemplateSlot (slotNode, replacingNode) {
    replacingNode.dataset.templateSlot = slotNode.dataset.templateSlot;
    slotNode.replaceWith(replacingNode);

    return replacingNode;
  },

  /**
   * @param {HTMLElement} popup
   */
  drawPopup (popup) {
    const otherPopupExists = document.querySelector('.popup__overlay');
    if (otherPopupExists) {
      popup.classList.add('popup--is-multiple');
    }

    document.body.appendChild(popup);
  },


  /* Battle */

  techniqueAnimationIsRunning: false,
  techniqueAnimationNumber: 0,
  techniqueAnimationFps: 20,


  /**
   * @param {Monster} monster
   *
   * @returns {HTMLElement}
   */
  createHpBar (monster) {
    const template = UI.createTemplate(Template.battleHpBar);
    const bar = template.querySelector('[data-template-slot="bar"]');
    const text = template.querySelector('[data-template-slot="text"]');

    let barColor;
    const percentHp = (monster.hp / monster.stats.hp) * 100;
    if (percentHp > 60) {
      barColor = 'green';
    } else if (percentHp > 15) {
      barColor = 'rgb(240, 240, 100)';
    } else {
      barColor = 'red';
    }

    bar.style.backgroundColor = barColor;
    bar.style.width = `${percentHp}%`;
    text.textContent = `${monster.hp} / ${monster.stats.hp}`;

    return template;
  },

  /**
   * @param {Monster} monster
   *
   * @returns {HTMLElement}
   */
  createExpBar (monster) {
    const template = UI.createTemplate(Template.battleExpBar);
    const bar = template.querySelector('[data-template-slot="bar"]');
    const text = template.querySelector('[data-template-slot="text"]');

    const expToNextLevel = monster.getExperienceRequired() - monster.getExperienceRequired(-1);
    const currentRelativeExp = monster.exp - monster.getExperienceRequired(-1);
    const expPercent = (currentRelativeExp / expToNextLevel) * 100;

    bar.style.width = `${expPercent}%`;
    text.textContent = `${monster.exp} / ${monster.getExperienceRequired()}`;

    return template;
  },

  /**
   * @param {string} gender
   *
   * @returns {HTMLElement}
   */
  createGenderIcon (gender) {
    const icon = document.createElement('span');
    icon.textContent = gender === 'male' ? '♂' : gender === 'female' ? '♀' : '⚲';
    icon.title = slugToName(gender);

    icon.classList.add('gender-icon');

    return icon;
  },

  /**
   * @param {string} type
   *
   * @returns {HTMLElement}
   */
  createElementTypeIcon (type) {
    const img = document.createElement('img');
    img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/element/${type}_type.png`;
    img.title = translate(type) || slugToName(type);

    return img;
  },

  /**
   * @param {StatusEffect} statusEffect
   *
   * @returns {HTMLElement}
   */
  createStatusEffectIcon (statusEffect) {
    if (!statusEffect) {
      return document.createElement('i');
    }

    if (statusEffect.slug === 'faint') {
      const node = document.createElement('b');
      node.innerHTML = 'X';
      node.title = statusEffect.name;

      return node;
    }

    const img = document.createElement('img');
    img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/status/icon_${statusEffect.slug}.png`;
    img.title = statusEffect.name;

    return img;
  },

  /**
   * @param {Monster} monster
   */
  createBattleMonster (monster) {
    const template = UI.createTemplate(Template.battleMonster);

    template.querySelector('[data-template-slot="name"]').textContent = monster.name;
    template.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
    template.querySelector('[data-template-slot="level"]').textContent = monster.level;
    template.querySelector('[data-template-slot="statusEffect"]').innerHTML = UI.createStatusEffectIcon(monster.statusEffect).outerHTML;
    template.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;

    UI.replaceTemplateSlot(template.querySelector('[data-template-slot="hpBar"]'), UI.createHpBar(monster));

    return template;
  },

  /**
   * @returns {HTMLElement}
   */
  createOpponentMonster () {
    const battleMonsterNode = UI.createBattleMonster(Memory.state.opponent.activeMonster);

    battleMonsterNode.classList.add('battle__monster--opponent');

    if (Game.isBattleType('trainer')) {
      battleMonsterNode.classList.add('battle__monster--is-trainer');
      battleMonsterNode.querySelector('[data-template-slot="trainerName"]').textContent = Memory.state.opponent.name;

      if (Memory.state.opponent.sprite) {
        battleMonsterNode.classList.add('battle__monster--has-trainer-sprite');
        battleMonsterNode.querySelector('[data-template-slot="trainerSprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/player/${Memory.state.opponent.sprite}`;
        battleMonsterNode.querySelector('[data-template-slot="trainerSprite"]').title = Memory.state.opponent.name;
      }
    }

    return battleMonsterNode;
  },

  /**
   * @returns {HTMLElement}
   */
  createActiveMonster () {
    const battleMonsterNode = UI.createBattleMonster(Memory.state.player.activeMonster);

    UI.replaceTemplateSlot(
      battleMonsterNode.querySelector('[data-template-slot="expBar"]'),
      UI.createExpBar(Memory.state.player.activeMonster)
    );

    battleMonsterNode.classList.add('battle__monster--player');

    battleMonsterNode.querySelector('[data-template-slot="sprite"]').addEventListener('click', () => {
      UI.openStatsMenu(Memory.state.player.activeMonster);
    });
    if (UI.isHighlighting) {
      battleMonsterNode.querySelector('[data-template-slot="sprite"]').classList.add(UI.highlightClassName);
    }

    return battleMonsterNode;
  },

  /**
   * @param {Monster} monster
   *
   * @returns {HTMLElement}
   */
  createActiveTechniques (monster) {
    const template = UI.createTemplate(Template.techniques);

    for (const technique of monster.activeTechniques) {
      const techniqueNode = UI.createTemplate(Template.technique);

      techniqueNode.querySelector('[data-template-slot="name"]').textContent = technique.name;
      techniqueNode.querySelector('[data-template-slot="recharge"]').textContent = technique.rechargeLength;
      techniqueNode.querySelector('[data-template-slot="types"]').innerHTML = technique.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');
      techniqueNode.querySelector('[data-template-slot="range"]').textContent = translate(technique.range) || slugToName(technique.range);
      techniqueNode.querySelector('[data-template-slot="power"]').textContent = technique.power;
      techniqueNode.querySelector('[data-template-slot="accuracy"]').textContent = technique.accuracy;

      if (technique.isRecharging()) {
        techniqueNode.setAttribute('disabled', true);
      }

      template.appendChild(techniqueNode);
    }

    return template;
  },

  /**
   * @returns {HTMLElement}
   */
  createPlayerDefeatedMonsterSelection () {
    const monsterSelectionNode = UI.createMonsterSelection(Memory.state.player.monsters.filter((monster) => monster.hp > 0));

    monsterSelectionNode.addEventListener('monster:selected', (event) => {
      Game.setActivePlayerMonster(event.detail.monster);
      Game.playerIsChoosingNextMonster = false;
    });

    return monsterSelectionNode;
  },

  /**
   * @param {HTMLElement} battleMonsterNode
   */
  drawOpponentMonster () {
    const battleMonsterNode = UI.createOpponentMonster();

    UI.elements.battleOpponentSprite = battleMonsterNode.querySelector('[data-template-slot="sprite"]');
    UI.elements.battleOpponentSprite.style.transitionDuration = `${UI.damageHighlightClickDuration}s`;

    // en/disable catch
    UI.drawActiveBall();

    const previousBattleMonsterNode = UI.elements.battleOpponent.querySelector('.battle__monster');
    if (previousBattleMonsterNode) {
      UI.elements.battleOpponentSprite.classList = previousBattleMonsterNode.querySelector('[data-template-slot="sprite"]').classList;

      UI.elements.battleOpponent.removeChild(previousBattleMonsterNode);
    }
    UI.elements.battleOpponent.appendChild(battleMonsterNode);
  },

  /**
   * @returns {void}
   */
  drawActiveMonster () {
    const battleMonsterNode = UI.createActiveMonster();

    const previousBattleMonsterNode = UI.elements.battlePlayer.querySelector('.battle__monster');
    if (previousBattleMonsterNode) {
      UI.elements.battlePlayer.removeChild(previousBattleMonsterNode);
    }
    UI.elements.battlePlayer.appendChild(battleMonsterNode);
  },

  /**
   * @returns {void}
   */
  drawActiveTechniques () {
    if (!Memory.state.player) { // on starter selection screen only
      return;
    }

    const activeTechniques = UI.createActiveTechniques(Memory.state.player.activeMonster);
    activeTechniques.id = 'techniques';

    document.querySelector('#techniques').innerHTML = activeTechniques.innerHTML;
  },

  /**
   * @param {Technique} technique
   *
   * @returns {void}
   */
  drawTechniqueAnimation (technique) {
    const animation = technique.animation;

    if (!(animation && DB.allAnimations[animation])) {
      return;
    }

    if (UI.techniqueAnimationIsRunning) {
      return;
    }
    UI.techniqueAnimationIsRunning = true;

    const x = UI.battleClickEvent.clientX;
    const y = UI.battleClickEvent.clientY;

    const techniqueAnimationLoop = () => {
      UI.elements.battleOpponentAnimation.src = `/modules/tuxemon/mods/tuxemon/animations/technique/${animation}_${("00" + UI.techniqueAnimationNumber).slice(-2)}.png`;
      UI.elements.battleOpponentAnimation.style.top = y - (UI.elements.battleOpponentAnimation.clientHeight / 2) + 'px';
      UI.elements.battleOpponentAnimation.style.left = x - (UI.elements.battleOpponentAnimation.clientWidth / 2) + 'px';
      // console.log(UI.elements.battleOpponentAnimation.src);

      UI.techniqueAnimationNumber++;

      if (UI.techniqueAnimationNumber >= DB.allAnimations[animation].length) {
        UI.techniqueAnimationIsRunning = false;
        UI.techniqueAnimationNumber = 0;
        UI.elements.battleOpponentAnimation.src = '';
        return;
      }

      setTimeout(() => requestAnimationFrame(techniqueAnimationLoop), 1000 / UI.techniqueAnimationFps);
    };

    requestAnimationFrame(techniqueAnimationLoop);
  },

  drawLog () {
    if (UI.elements.log.children.length > Memory.state.Settings.logMaxLength) {
      UI.elements.log.innerHTML = '';
    }

    for (const message of Game.logMessages) {
      const textNode = document.createElement('div');

      textNode.innerHTML = '> '.repeat(message.indentation) + message.message;

      if (message.style) {
        for (const property of Object.keys(message.style)) {
          const value = message.style[property];
          textNode.style[property] = value;
        }
      }

      UI.elements.log.appendChild(textNode);
      UI.elements.log.scrollTop = UI.elements.log.scrollHeight;
    }

    Game.logMessages = [];
  },

  toggleLog () {
    UI.elements.log.classList.toggle('log--is-hidden');
  },

  openLog () {
    UI.elements.log.classList.remove('log--is-hidden');
  },

  closeLog () {
    UI.elements.log.classList.add('log--is-hidden');
  },

  drawArea () {
    if (Game.isTown(Memory.state.currentArea)) {
      UI.elements.sceneTown.querySelector('[data-template-slot="map"]').replaceChildren(UI.createMap());

      UI.closeLog();

      UI.elements.sceneBattle.classList.add('hidden');
      UI.elements.sceneTown.classList.remove('hidden');
    } else {
      UI.elements.battle.style.backgroundImage = `url(/modules/tuxemon/mods/tuxemon/gfx/ui/combat/${Memory.state.currentArea.environment.battle_graphics.background})`;

      UI.elements.sceneTown.classList.add('hidden');
      UI.elements.sceneBattle.classList.remove('hidden');

      UI.drawOpponentMonster();
      UI.drawActiveMonster();
      UI.drawActiveTechniques();
    }

    UI.drawStatus();
    UI.drawActiveBall();
  },

  progressTurn () {
    UI.drawOpponentMonster();
    UI.drawActiveMonster();
    UI.drawActiveTechniques();
    UI.drawStatus();
  },

  /**
   * @param {HTMLElement} monsterSelectionNode
   */
  openPlayerDefeatedMonsterSelection (monsterSelectionNode) {
    const popup = UI.createPopup().cloneNode(true); // remove event listeners

    monsterSelectionNode.addEventListener('monster:selected', () => {
      popup.remove();
    });

    popup.querySelector('[data-template-slot="content"]').appendChild(monsterSelectionNode);
    UI.drawPopup(popup);
  },


  /* Battle - Action Feedback */

  /**
   * @type {MouseEvent}
   */
  battleClickEvent: null,

  damageHighlightClickDuration: 0.1,
  damageHighlightClickTimeout: null,


  /**
   * @param {any} feedback
   *
   * @returns {HTMLElement}
   */
  createActionFeedback (feedback) {
    const feedbackNode = UI.createTemplate(Template.battleActionFeedback);
    feedbackNode.innerHTML = feedback;

    feedbackNode.style.top = UI.battleClickEvent.pageY - UI.elements.battleOpponent.offsetTop + (Math.random() * 40 - 20) + 'px';
    feedbackNode.style.left = UI.battleClickEvent.pageX - UI.elements.battleOpponent.offsetLeft + (Math.random() * 40 - 20) + 'px';

    feedbackNode.dataset.duration = 2;
    feedbackNode.style.animationDuration = `${feedbackNode.dataset.duration}s`;

    return feedbackNode;
  },

  /**
   * @param {number|string} damage
   *
   * @returns {HTMLElement}
   */
  createDamage (damage) {
    return UI.createActionFeedback(damage);
  },

  /**
   * @returns {HTMLElement}
   */
  createDamageMiss () {
    return UI.createActionFeedback('MISS!');
  },

  /**
   * @param {HTMLElement} damageNode
   * @param {number} multiplier
   *
   * @returns {HTMLElement}
   */
  applyMultiplierToDamage (damageNode, multiplier) {
    damageNode.style.fontSize = `${multiplier * 2}rem`;

    return damageNode;
  },

  /**
   * @param {HTMLElement} damageNode
   * @param {Technique} technique
   *
   * @returns {HTMLElement}
   */
  applyTechniqueToDamage (damageNode, technique) {
    damageNode.style.color = mixColors(
      ...technique.types.map((type) => standardizeColor(ElementTypeColor[type]))
    );

    return damageNode;
  },

  /**
   * @param {HTMLElement} damageNode
   * @param {StatusEffect} statusEffect
   *
   * @returns {HTMLElement}
   */
  applyStatusEffectToDamage (damageNode, statusEffect) {
    damageNode.style.color = StatusEffectTypeColor[statusEffect.slug];

    return damageNode;
  },

  /**
   * @param {HTMLElement} damageNode
   */
  drawActionFeedback (node) {
    UI.elements.battleOpponent.appendChild(node);
    setTimeout(() => node.remove(), (node.dataset.duration * 1000) - 500);
  },

  /**
   * @param {HTMLElement} damageNode
   */
  drawDamage (damageNode) {
    UI.drawActionFeedback(damageNode);

    UI.elements.battleOpponentSprite.classList.add('damaged');
    clearTimeout(UI.damageHighlightClickTimeout);
    UI.damageHighlightClickTimeout = setTimeout(() => UI.elements.battleOpponentSprite.classList.remove('damaged'), UI.damageHighlightClickDuration * 1000);
  },

  /**
   * @param {HTMLElement} damageNode
   */
  drawDamageMiss (damageNode) {
    UI.drawActionFeedback(damageNode);
  },


  /* Town */

  drawTown () {},


  /* Map */

  /**
   * @returns {HTMLElement}
   */
  createMap () {
    const template = UI.createTemplate(Template.map);
    const currentArea = Memory.state.currentArea;

    template.innerHTML = currentArea.map;

    if (currentArea.locations) {
      for (const locationId of Object.keys(currentArea.locations)) {
        const location = currentArea.locations[locationId];

        template.querySelector(`[data-location="${locationId}"]`).addEventListener('click', () => {
          if (location.type === 'healingCenter') {
            UI.openHealingCenter(location);
          }

          else if (location.type === 'shop') {
            UI.openShop(location);
          }
        });
      }
    }

    return template;
  },

  /**
   * @param {Object} healingCenter
   */
  openHealingCenter (healingCenter) {
    const popup = UI.createPopup();
    const template = UI.createTemplate(Template.healingCenter);

    const price = convertToCurrencyBase(healingCenter.price);
    template.querySelector('[data-template-slot="price"]').innerHTML = formatPrice(price);

    template.querySelector('[data-template-slot="heal"]').addEventListener('click', () => {
      const applicableMonsters = Memory.state.player.monsters.filter((monster) => {
        return monster.hp < monster.stats.hp || (monster.statusEffect && monster.statusEffect.category === 'negative')
      });

      if (applicableMonsters.length === 0) {
        alert(translate('ui:no_applicable_monsters', true));
        return;
      }

      const monsterSelectionPopup = UI.createPopup();
      const monsterSelection = UI.createMonsterSelection(applicableMonsters);

      monsterSelection.addEventListener('monster:selected', (event) => {
        if (Memory.state.money < price) {
          alert(`Not enough ${DB.currencies.map[Memory.state.Settings.currency].symbol}.`);
          return;
        }

        Memory.state.money -= price;
        event.detail.monster.hp = event.detail.monster.stats.hp;
        event.detail.monster.statusEffect = null;
        event.detail.node.remove();

        if (monsterSelection.children.length === 0) {
          monsterSelectionPopup.remove();
        }

        UI.drawStatus();
      });

      monsterSelectionPopup.querySelector('.popup').appendChild(monsterSelection);
      UI.drawPopup(monsterSelectionPopup);
    });

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },

  /**
   * @param {Object} shop
   */
  async openShop (shop) {
    const popup = UI.createPopup();
    const template = UI.createTemplate(Template.shop);

    for (const itemData of shop.items) {
      const price = convertToCurrencyBase(itemData.price);
      const item = await fetchItem(itemData.item_name);
      const itemNode = UI.createTemplate(Template.shopItem);

      itemNode.querySelector('[data-template-slot="sprite"]').src = item.sprite;
      itemNode.querySelector('[data-template-slot="name"]').innerHTML = item.name;
      itemNode.querySelector('[data-template-slot="price"]').innerHTML = formatPrice(price);

      itemNode.addEventListener('click', () => {
        if (Memory.state.money < price) {
          alert(`Not enough ${DB.currencies.map[Memory.state.Settings.currency].symbol}.`);
          return;
        }

        Memory.state.money -= price;

        const itemInInventory = Memory.state.player.inventory.find((inventoryItem) => inventoryItem.slug === item.slug);
        if (itemInInventory) {
          itemInInventory.quantity++;
        } else {
          Memory.state.player.inventory.push(new InventoryItem(item, 1));
        }

        UI.drawStatus();
      });

      template.appendChild(itemNode);
    }

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },


  /* Menu */

  partySelectionMode: 'select',
  inventorySelectionMode: 'use',
  isHighlighting: false,
  highlightClassName: 'setting-highlight',


  /**
   * @param {Monster[]} monsters
   *
   * @returns {HTMLElement}
   */
  createMonsterSelection (monsters) {
    const template = document.createElement('div');
    template.classList.add('monster-selection');

    for (const monster of monsters) {
      const monsterNode = UI.createMonsterSelectionMonster(monster);

      monsterNode.addEventListener('monster:selected', (event) => {
        template.dispatchEvent(new CustomEvent('monster:selected', {
          detail: {
            node: monsterNode,
            monster: event.detail.monster,
          },
        }));
      });

      template.appendChild(monsterNode);
    }

    return template;
  },

  /**
   * @param {Monster} monster
   *
   * @returns {HTMLElement}
   */
  createMonsterSelectionMonster (monster) {
    const template = UI.createPartyMonster(monster);

    template.addEventListener('click', () => {
      template.dispatchEvent(new CustomEvent('monster:selected', {
        detail: {
          monster: monster,
        },
      }));
    });

    return template;
  },

  /**
   * @param {Monster[]} monsters
   *
   * @returns {HTMLElement}
   */
  createPartySelection (monsters) {
    const party = UI.createTemplate(Template.party);
    party.id = 'party';
    for (const monsterIdx in monsters) {
      const monster = monsters[monsterIdx];
      const partyMonster = UI.createPartyMonster(monster);

      partyMonster.addEventListener('click', async (event) => {
        // bubble up to partyNode
        let target = event.target;
        while (target.parentNode.id !== party.id) {
          target = target.parentNode;
        }

        party.dispatchEvent(new CustomEvent('party:monster:selected', {
          detail: {
            monster: monster,
            mode: UI.partySelectionMode,
            node: partyMonster,
          },
        }));
      });

      party.querySelector('[data-template-slot="monsters"]').appendChild(partyMonster);
    }

    const selectionModesNode = party.querySelector('[data-template-slot="modes"]');
    const selectionModeNodes = selectionModesNode.querySelectorAll('[data-party-selection-mode]');
    selectionModeNodes.forEach((node) => {
      if (node.dataset.partySelectionMode === UI.partySelectionMode) {
        node.setAttribute('selected', true);
      }

      node.addEventListener('click', () => {
        selectionModesNode.querySelector(`[data-party-selection-mode="${UI.partySelectionMode}"]`).removeAttribute('selected');

        UI.partySelectionMode = node.dataset.partySelectionMode;

        node.setAttribute('selected', true);
      });
    });

    return party;
  },

  drawStatus () {
    const currentArea = Memory.state.currentArea;

    UI.elements.status.querySelector('[data-template-slot="money"]').textContent =
      `${Memory.state.money.toFixed(DB.currencies.map[Memory.state.Settings.currency].decimals)}` +
      ' ' +
      `${DB.currencies.map[Memory.state.Settings.currency].symbol}`;
    UI.elements.status.querySelector('[data-template-slot="area"]').textContent = currentArea.name;
    UI.elements.status.querySelector('[data-template-slot="monsterProgress"]').textContent = `${currentArea.monsterProgress} / ${currentArea.requiredEncounters}`;
    UI.elements.status.querySelector('[data-template-slot="trainerProgress"]').textContent = `${currentArea.trainerProgress} / ${currentArea.trainers.length}`;

    const nextTrainerButton = UI.elements.nextTrainer;
    if (!Game.isTown(currentArea)) {
      if (
        Memory.state.opponent.type === 'monster' &&
        currentArea.monsterProgress >= currentArea.requiredEncounters &&
        currentArea.trainerProgress < currentArea.trainers.length &&
        !Game.isInBattle
      ) {
        nextTrainerButton.disabled = false;
      } else {
        nextTrainerButton.disabled = true;
      }
    } else {
      nextTrainerButton.disabled = true;
    }

    const changeAreaButton = UI.elements.changeArea;
    if (!Game.isTown(currentArea)) {
      if (
        Game.isInBattle ||
          (Memory.state.opponent && Memory.state.opponent.type === 'trainer')
      ) {
        changeAreaButton.disabled = true;
      } else {
        changeAreaButton.disabled = false;
      }
    } else {
      changeAreaButton.disabled = false;
    }
  },

  /**
   * @param {Monster[]} monsters
   */
  openStarterMonsterSelection (monsters) {
    const popup = UI.createPopup().cloneNode(true); // remove close event
    const template = UI.createPartySelection(monsters);

    const title = document.createElement('h1');
    title.textContent = 'Select your Tuxemon!';
    title.style.textAlign = 'center';
    template.prepend(title);

    template.addEventListener('party:monster:selected', (event) => {
      const monster = event.detail.monster;

      if (UI.partySelectionMode === 'select') {
        template.dispatchEvent(new CustomEvent('starter:monster:selected', {
          detail: {
            monster: monster,
            node: event.detail.node,
            popup: popup,
          },
        }));
      }
      else if (UI.partySelectionMode === 'stats') {
        UI.openStatsMenu(monster);
      }
      else if (UI.partySelectionMode === 'techniques') {
        UI.openMovesetSelection(monster);
      }
    });

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);

    return template;
  },

  openMap () {
    if (Game.isInBattle || Game.isTown(Memory.state.currentArea)) {
      return;
    }

    const popup = UI.createPopup();
    const template = UI.createMap();

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },

  openAreaSelection () {
    const popup = UI.createPopup();
    const template = UI.createTemplate(Template.areaSelection);
    const currentArea = Memory.state.currentArea;

    for (const connectionSlug in currentArea.connections) {
      const connection = currentArea.connections[connectionSlug];
      const connectionNode = UI.createTemplate(Template.areaSelectionItem);

      connectionNode.querySelector('[data-template-slot="text"]').textContent =
        translate(connection['modules/tuxemon.slug']) || slugToName(connection['modules/tuxemon.slug']);

      let canGo = true;
      for (const condition of connection.conditions) {
        if (condition === 'encounters') {
          canGo = canGo && currentArea.monsterProgress >= currentArea.requiredEncounters;
        }

        else if (condition === 'trainers') {
          canGo = canGo && currentArea.trainerProgress >= currentArea.trainers.length;
        }

        else if (condition.startsWith('area.')) {
          canGo = Memory.state.areaProgress.hasOwnProperty(condition.replace('area.', ''));
        }

        else if (condition.startsWith('event.')) {
          canGo = false;
        }
      }

      if (!canGo) {
        connectionNode.setAttribute('disabled', true);
      }

      connectionNode.addEventListener('click', () => {
        if (canGo) {
          Game.goToArea(connectionSlug);
          popup.remove();
        } else {
          alert("Can\'t go there yet!");
        }
      });

      template.appendChild(connectionNode);
    }

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },

  openPartyMenu () {
    const popup = UI.createPopup();
    const template = UI.createPartySelection(Memory.state.player.monsters);

    template.addEventListener('party:monster:selected', (event) => {
      const monster = event.detail.monster;

      if (UI.partySelectionMode === 'select') {
        Game.setActivePlayerMonster(monster);

        popup.remove();
      }
      else if (UI.partySelectionMode === 'stats') {
        UI.openStatsMenu(monster);
      }
      else if (UI.partySelectionMode === 'techniques') {
        UI.openMovesetSelection(monster);
      }
    });

    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },

  drawActiveBall () {
    if (Game.canCatchMonster()) {
      UI.elements.menuCatch.removeAttribute('disabled');
    } else {
      UI.elements.menuCatch.setAttribute('disabled', true);
    }

    if (Memory.state.activeBall) {
      UI.elements.menuCatch.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/items/${Memory.state.activeBall.slug}.png`;
    } else {
      UI.elements.menuCatch.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/items/tuxeball.png`;
    }
  },

  openInventoryMenu () {
    const popup = UI.createPopup();
    const inventory = UI.createTemplate(Template.inventory);
    inventory.id = 'inventory';

    const tabs = {
      heal: {
        heading: translate('ui:inventory:tab:heal'),
        items: [],
      },
      stats: {
        heading: translate('ui:inventory:tab:stats'),
        items: [],
      },
      balls: {
        heading: translate('ui:inventory:tab:balls'),
        items: [],
      },
      techniques: {
        heading: translate('ui:inventory:tab:techniques'),
        items: [],
      },
      other: {
        heading: translate('ui:inventory:tab:other'),
        items: [],
      },
      keyItems: {
        heading: translate('ui:inventory:tab:key_items'),
        items: [],
      },
    };

    for (const item of Memory.state.player.inventory) {
      const inventoryItemNode = UI.createInventoryItem(item);

      if (['potion', 'revive'].includes(item.category)) {
        tabs['heal'].items.push(inventoryItemNode);
      }
      else if (['stats'].includes(item.category)) {
        tabs['stats'].items.push(inventoryItemNode);
      }
      else if (['capture'].includes(item.category)) {
        tabs['balls'].items.push(inventoryItemNode);
      }
      else if (['technique'].includes(item.category)) {
        tabs['techniques'].items.push(inventoryItemNode);
      }
      else if (['KeyItem'].includes(item.type)) {
        tabs['keyItems'].items.push(inventoryItemNode);
      }
      else {
        tabs['other'].items.push(inventoryItemNode);
      }
    }

    const tabsNode = UI.createTabs(Object.values(tabs).map((tab) => {
      const content = document.createElement('div');
      content.classList.add('inventory__tab');
      for (const item of tab.items) {
        content.appendChild(item);
      }

      return {
        heading: document.createTextNode(tab.heading),
        content: content,
      };
    }));

    tabsNode.style.gridTemplateColumns = '1fr 1fr 1fr';
    inventory.querySelector('[data-template-slot="items"]').appendChild(tabsNode);

    const selectionModesNode = inventory.querySelector('[data-template-slot="modes"]');
    const selectionModeNodes = selectionModesNode.querySelectorAll('[data-selection-mode]');
    selectionModeNodes.forEach((node) => {
      if (node.dataset.selectionMode === UI.inventorySelectionMode) {
        node.setAttribute('selected', true);
      }

      node.addEventListener('click', () => {
        selectionModesNode.querySelector(`[data-selection-mode="${UI.inventorySelectionMode}"]`).removeAttribute('selected');

        UI.inventorySelectionMode = node.dataset.selectionMode;

        node.setAttribute('selected', true);
      });
    });

    popup.querySelector('.popup').appendChild(inventory);
    popup.classList.add('inventory__popup');

    UI.drawPopup(popup);
  },

  openJournalMenu () {
    const popup = UI.createPopup();
    const journal = UI.createTemplate(Template.menuJournal);

    journal.querySelector('[data-template-slot="save"]').addEventListener('click', UI.createEventListener(() => {
      UI.openSaveDialog();
    }));

    journal.querySelector('[data-template-slot="load"]').addEventListener('click', UI.createEventListener(() => {
      UI.openLoadDialog();
    }));

    popup.querySelector('.popup').appendChild(journal);
    UI.drawPopup(popup);
  },

  openSettingsMenu () {
    const popup = UI.createPopup();
    const template = UI.createTemplate(Template.menuSettings);


    /* Language */

    const languageSelectNode = template.querySelector('[data-template-slot="language"]');

    const languages = {
      'cs_CZ': 'Czech (Czech Republic)',
      'de_DE': 'German (Germany)',
      'en_US': 'English (United States)',
      'eo': 'Esperanto',
      'es_ES': 'Spanish (Spain)',
      'es_MX': 'Spanish (Mexico)',
      'fi': 'Finnish',
      'fr_FR': 'French (France)',
      'it_IT': 'Italian (Italy)',
      'ja': 'Japanese',
      'nb_NO': 'Norwegian Bokmål (Norway)',
      'pl': 'Polish',
      'pt_BR': 'Portuguese (Brazil)',
      'zh_CN': 'Chinese (China)',
    };

    for (const languageCode of Object.keys(languages)) {
      const languageName = languages[languageCode];
      const languageOptionNode = document.createElement('option');

      languageOptionNode.value = languageCode;
      languageOptionNode.textContent = languageName;

      if (languageCode === Memory.state.Settings.language) {
        languageOptionNode.selected = true;
      }

      languageSelectNode.appendChild(languageOptionNode);
    }

    languageSelectNode.addEventListener('change', async () => {
      const selected = [...languageSelectNode.children].find((node) => node.selected === true);
      Memory.state.Settings.language = selected.value;

      await fetchTranslation(Memory.state.Settings.language);
      applyTranslation();

      UI.drawArea();
    });


    /* Currency */

    const currencySelectNode = template.querySelector('[data-template-slot="currency"]');
    const currencyCodeMap = Object.keys(DB.currencies.map).sort((a, b) => {
      const nameA = DB.currencies.map[a].name;
      const nameB = DB.currencies.map[b].name;

      return nameA > nameB ? 1 : -1;
    });

    for (const currencyCode of currencyCodeMap) {
      const currency = DB.currencies.map[currencyCode];
      const currencyOptionNode = document.createElement('option');

      currencyOptionNode.value = currencyCode,
      currencyOptionNode.textContent = `${currency.symbol} - ${currency.name}`;

      if (currencyCode === Memory.state.Settings.currency) {
        currencyOptionNode.selected = true;
      }

      currencySelectNode.appendChild(currencyOptionNode);
    }

    currencySelectNode.addEventListener('change', async () => {
      const selected = [...currencySelectNode.children].find((node) => node.selected === true);
      const previousCurrencyCode = Memory.state.Settings.currency;
      const newCurrencyCode = selected.value;

      Memory.state.Settings.currency = newCurrencyCode;

      // re-calculate money
      const previousCurrency = DB.currencies.map[previousCurrencyCode];
      const newCurrency = DB.currencies.map[newCurrencyCode];
      const baseRateMoney = Memory.state.money / previousCurrency.rate;
      const exchangedMoney = baseRateMoney * newCurrency.rate;
      Memory.state.money = Number(exchangedMoney.toFixed(newCurrency.decimals));

      UI.drawArea();
      UI.drawStatus();
    });

    template.querySelector('[data-template-slot="currency.lastUpdated"]').textContent = DB.currencies.last_updated;


    // Highlight

    template.querySelector('[data-template-slot="highlight"]').addEventListener('click', () => {
      UI.isHighlighting = !UI.isHighlighting;

      const elements = [
        UI.elements.battleOpponent,
        UI.elements.battlePlayer.querySelector('[data-template-slot="sprite"]'),
        UI.elements.techniques,
        UI.elements.showMap,
        UI.elements.nextTrainer,
        UI.elements.changeArea,
        UI.elements.menuParty,
        UI.elements.menuCatch,
        UI.elements.menuInventory,
        UI.elements.menuLog,
        UI.elements.menuJournal,
        UI.elements.menuSettings,

        Template.technique.content.firstElementChild,
        Template.healingCenter.content.querySelector('[data-template-slot="heal"]'),
        Template.shopItem.content.firstElementChild,
        ...Template.monsterStats.content.querySelectorAll('button'),
        Template.movesetItem.content.firstElementChild,
        Template.tabHeading.content.querySelector('[data-template-slot="label"]'),
        ...Template.party.content.querySelectorAll('[data-template-slot="modes"] button'),
        Template.partyMonster.content.firstElementChild,
        Template.inventoryItem.content.firstElementChild,
        ...Template.inventory.content.querySelectorAll('[data-template-slot="modes"] button'),
        ...Template.menuSettings.content.querySelectorAll('select, button'),

        ...document.querySelector('.menu__settings').querySelectorAll('select, button'),
      ];

      for (const element of elements) {
        if (UI.isHighlighting) {
          element.classList.add(UI.highlightClassName);
        } else {
          element.classList.remove(UI.highlightClassName);
        }
      }

      // map
      const mapElements = [
        UI.elements.sceneTown.querySelector('[data-template-slot="map"]').firstElementChild,
        Template.map.content.firstElementChild,
      ];
      for (const element of mapElements) {
        if (UI.isHighlighting) {
          element.classList.add(`${UI.highlightClassName}--map`);
        } else {
          element.classList.remove(`${UI.highlightClassName}--map`);
        }
      }
    });


    popup.querySelector('.popup').appendChild(template);
    UI.drawPopup(popup);
  },


  /* Menu - Party */

  /**
   * @param {Monster}
   *
   * @returns {HTMLElement}
   */
  createPartyMonster (monster) {
    const partyMonster = UI.createTemplate(Template.partyMonster);

    partyMonster.querySelector('[data-template-slot="sprite"]').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;
    partyMonster.querySelector('[data-template-slot="name"]').textContent = monster.name;
    partyMonster.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
    partyMonster.querySelector('[data-template-slot="level"]').textContent = monster.level;
    partyMonster.querySelector('[data-template-slot="statusEffect"]').innerHTML = UI.createStatusEffectIcon(monster.statusEffect).outerHTML;
    partyMonster.querySelector('[data-template-slot="hpText"]').textContent = `${monster.hp} / ${monster.stats.hp}`;

    return partyMonster;
  },


  /* Menu - Monster */

  /**
   * @param {Monster} monster
   *
   * @returns {HTMLElement}
   */
  createStatsMenu (monster) { // TODO
    const template = UI.createTemplate(Template.monsterStats);

    template.querySelector('[data-template-slot="name"]').textContent = monster.name;
    template.querySelector('[data-template-slot="gender"]').innerHTML = UI.createGenderIcon(monster.gender).outerHTML;
    template.querySelector('[data-template-slot="level"]').textContent = monster.level;
    template.querySelector('[data-template-slot="types"]').innerHTML = monster.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');

    template.querySelector('[data-template-slot="stats.melee.name"]').textContent = translate(StatType.melee) || slugToName(StatType.melee);
    template.querySelector('[data-template-slot="stats.armour.name"]').textContent = translate(StatType.armour) || slugToName(StatType.armour);
    template.querySelector('[data-template-slot="stats.ranged.name"]').textContent = translate(StatType.ranged) || slugToName(StatType.ranged);
    template.querySelector('[data-template-slot="stats.dodge.name"]').textContent = translate(StatType.dodge) || slugToName(StatType.dodge);
    template.querySelector('[data-template-slot="stats.speed.name"]').textContent = translate(StatType.speed) || slugToName(StatType.speed);

    template.querySelector('[data-template-slot="stats.melee.value"]').textContent = monster.stats.melee;
    template.querySelector('[data-template-slot="stats.armour.value"]').textContent = monster.stats.armour;
    template.querySelector('[data-template-slot="stats.ranged.value"]').textContent = monster.stats.ranged;
    template.querySelector('[data-template-slot="stats.dodge.value"]').textContent = monster.stats.dodge;
    template.querySelector('[data-template-slot="stats.speed.value"]').textContent = monster.stats.speed;

    template.querySelector('[data-template-slot="techniques"]').addEventListener('click', () => UI.openMovesetSelection(monster));

    return template;
  },

  /**
   * @param {Monster} monster
   *
   * @returns {Promise<HTMLElement>}
   */
  async createMovesetSelection (monster) {
    const movesetListNode = UI.createTemplate(Template.movesetList);
    for (const move of monster.moveset) {
      const technique = await fetchTechnique(move.technique);
      const movesetItemNode = UI.createTemplate(Template.movesetItem);

      movesetItemNode.title = technique.description;

      movesetItemNode.querySelector('[data-template-slot="name"]').textContent = technique.name;
      movesetItemNode.querySelector('[data-template-slot="types"]').innerHTML = technique.types.map((type) => UI.createElementTypeIcon(type).outerHTML).join('');
      movesetItemNode.querySelector('[data-template-slot="power"]').textContent = technique.power;
      movesetItemNode.querySelector('[data-template-slot="level"]').textContent = move.level_learned;

      // disabled?
      if (monster.level < move.level_learned) {
        movesetItemNode.setAttribute('disabled', true);
      }

      // selected?
      if (monster.activeTechniques.find((item) => item.slug == technique.slug)) {
        movesetItemNode.toggleAttribute('selected');
      }

      // clicked
      movesetItemNode.addEventListener('click', () => {
        if (movesetItemNode.getAttribute('disabled')) {
          return false;
        }

        if (monster.activeTechniques.length === 4 && !movesetItemNode.hasAttribute('selected')) {
          return;
        }

        // un/select
        movesetItemNode.toggleAttribute('selected');

        const isSelected = movesetItemNode.hasAttribute('selected');
        if (isSelected) {
          monster.activeTechniques.push(technique);
        } else {
          const idxTechniqueToRemove = monster.activeTechniques.findIndex((item) => item.slug == technique.slug);
          if (idxTechniqueToRemove > -1) {
            monster.activeTechniques.splice(idxTechniqueToRemove, 1);
          }
        }

        const event = new CustomEvent('movesetSelection:moveSelected', {
          detail: {
            isSelected: movesetItemNode.hasAttribute('selected'),
            technique: technique,
          },
        });
        UI.events.dispatchEvent(event);
      });

      movesetListNode.appendChild(movesetItemNode);
    }

    return movesetListNode;
  },

  /**
   * @param {Monster} monster
   */
  openStatsMenu (monster) {
    const popup = UI.createPopup();
    const statusMenu = UI.createStatsMenu(monster);

    popup.querySelector('.popup').appendChild(statusMenu);
    UI.drawPopup(popup);
  },

  /**
   * @param {Monster} monster
   */
  async openMovesetSelection (monster) {
    const popup = UI.createPopup();
    const movesetSelection = await UI.createMovesetSelection(monster);

    popup.querySelector('.popup').appendChild(movesetSelection);
    popup.addEventListener('close', () => UI.drawActiveTechniques());

    UI.drawPopup(popup);
  },


  /* Menu - Inventory */

  /**
   * @param {InventoryItem} item
   *
   * @returns {HTMLElement}
   */
  createInventoryItem (item) {
    const inventoryItemNode = UI.createTemplate(Template.inventoryItem);

    inventoryItemNode.title = item.description;
    inventoryItemNode.dataset.inventoryItem = item.slug;

    inventoryItemNode.querySelector('[data-template-slot="sprite"]').src = item.sprite;
    inventoryItemNode.querySelector('[data-template-slot="name"]').textContent = item.name;
    inventoryItemNode.querySelector('[data-template-slot="quantity"]').textContent = item.quantity;

    inventoryItemNode.addEventListener('click', async () => {
      if (UI.inventorySelectionMode === 'use') {
        if (item.category === 'potion') {
          UI.openItemMonsterSelection(item);
        }

        else if (item.category === 'revive') {
          UI.openItemMonsterSelection(item);
        }

        else if (item.category === 'capture') {
          Game.useItem(item);
        }
      }
      else if (UI.inventorySelectionMode === 'info') {
        UI.openItemInfo(item);
      }
    });

    return inventoryItemNode;
  },

  /**
   * @param {InventoryItem} item
   */
  redrawInventoryItem (item) {
    const itemNode = document.querySelector(`#inventory *[data-inventory-item="${item.slug}"]`);
    if (item.quantity === 0) {
      itemNode.remove();
    }

    const newNode = UI.createInventoryItem(item);
    itemNode.replaceWith(newNode);
  },

  /**
   * @param {InventoryItem} item
   * @param {Monster[]} monsters
   *
   * @returns {HTMLElement}
   */
  createItemMonsterSelection (item, monsters) {
    const template = UI.createMonsterSelection(
      monsters.filter((monster) => Game.canUseItem(item, monster))
    );

    const onMonsterSelectd = async (event) => {
      const monster = event.detail.monster;
      const monsterNode = event.detail.node || event.target;

      await Game.useItem(item, monster);

      if (item.quantity === 0) {
        Game.removeItemFromInventory(Memory.state.player.inventory, item);
        template.dispatchEvent(new Event('item:isExhausted'));
      }

      else {
        const canStillUseItem = Game.canUseItem(item, monster);
        if (canStillUseItem) {
          const replacingMonsterNode = UI.createMonsterSelectionMonster(monster);
          replacingMonsterNode.addEventListener('monster:selected', onMonsterSelectd);

          monsterNode.replaceWith(replacingMonsterNode);
        } else {
          monsterNode.remove();
          template.dispatchEvent(new Event('monster:lostCondition'));
        }
      }

      UI.redrawInventoryItem(item);
    };

    template.addEventListener('monster:selected', onMonsterSelectd);

    return template;
  },

  /**
   * @param {InventoryItem} item
   */
  openItemMonsterSelection (item) {
    const popup = UI.createPopup();
    const template = UI.createItemMonsterSelection(item, Memory.state.player.monsters);
    template.classList.add('inventory__monster-selection');

    if (template.children.length === 0) {
      alert(translate('ui:no_applicable_monsters', true));
      return;
    }

    template.addEventListener('item:isExhausted', () => popup.remove());
    template.addEventListener('monster:lostCondition', () => popup.remove());

    popup.querySelector('.popup').appendChild(template);

    UI.drawPopup(popup);
  },

  /**
   * @param {InventoryItem} item
   */
  openItemInfo (item) {
    const popup = UI.createPopup();
    const template = document.createElement('div');

    template.textContent = item.conditions + ' -- ' + item.effects + ' -- ' + item.description;

    popup.querySelector('.popup').appendChild(template);

    UI.drawPopup(popup);
  },


  /* Menu - Journal */

  openSaveDialog () {
    const popup = UI.createPopup();
    const dialog = UI.createTemplate(Template.dialogSave);

    const saveData = Memory.save();

    dialog.querySelector('[data-template-slot="saveData"]').value = saveData;
    dialog.querySelector('[data-template-slot="saveClipboard"]').addEventListener('click', UI.createEventListenr(async () => {
      if (navigator.clipboard) {
        await navigator.clipboard.writeText(saveData);
        alert('Saved to clipboard!');
      } else {
        alert('ERROR: Browser can\'t copy to clipboard! You have to do it manually.');
      }
    }));

    popup.querySelector('.popup').appendChild(dialog);
    UI.drawPopup(popup);
  },

  openLoadDialog () {
    const popup = UI.createPopup();
    const dialog = UI.createTemplate(Template.dialogLoad);

    dialog.querySelector('[data-template-slot="load"]').addEventListener('click', UI.createEventListener(() => {
      Memory.load(
        dialog.querySelector('[data-template-slot="saveData"]').value.trim()
      );

      document.querySelectorAll('.popup__overlay').forEach((element) => element.remove())
    }));

    popup.querySelector('.popup').appendChild(dialog);
    UI.drawPopup(popup);
  },


  // Error

  /**
   * @param {Function} listener
   *
   * @returns {Function}
   */
  createEventListener (listener) {
    return (event) => {
      try {
        listener(event);
      } catch (exception) {
        console.log(exception);
        UI.showErrorMessage(exception);
      }
    };
  },

  /**
   * @param {string} message
   */
  showErrorMessage (message) {
    alert(message);
  },
};

// UI element click bindings
UI.elements.showMap.addEventListener('click', UI.createEventListener(UI.openMap));
UI.elements.changeArea.addEventListener('click', UI.createEventListener(UI.createEventListener(UI.openAreaSelection)));
UI.elements.menuParty.addEventListener('click', UI.createEventListener(UI.openPartyMenu));
UI.elements.menuInventory.addEventListener('click', UI.createEventListener(UI.openInventoryMenu));
UI.elements.menuLog.addEventListener('click', UI.createEventListener(UI.toggleLog));
UI.elements.menuJournal.addEventListener('click', UI.createEventListener(UI.openJournalMenu));
UI.elements.menuSettings.addEventListener('click', UI.createEventListener(UI.openSettingsMenu));