class CorpusAnalysisConcordance {
  name = 'Concordance';

  constructor(app) {
    this.app = app;

    this.data = {};

    this.elements = {
      // TODO: Prefix elements with "corpus-analysis-app-"
      container: document.querySelector('#concordance-extension-container'),
      error: document.querySelector('#concordance-extension-error'),
      form: document.querySelector('#concordance-extension-form'),
      progress: document.querySelector('#concordance-extension-progress'),
      subcorpusInfo: document.querySelector('#concordance-extension-subcorpus-info'),
      subcorpusActions: document.querySelector('#concordance-extension-subcorpus-actions'),
      subcorpusItems: document.querySelector('#concordance-extension-subcorpus-items'),
      subcorpusList: document.querySelector('#concordance-extension-subcorpus-list'),
      subcorpusPagination: document.querySelector('#concordance-extension-subcorpus-pagination')
    };

    this.settings = {
      context: parseInt(this.elements.form['context'].value),
      perPage: parseInt(this.elements.form['per-page'].value),
      selectedSubcorpus: undefined,
      textStyle: parseInt(this.elements.form['text-style'].value),
      tokenRepresentation: this.elements.form['token-representation'].value
    };

    this.app.registerExtension(this);
  }

  init() {
    // Init data
    this.data.corpus = this.app.data.corpus;
    this.data.subcorpora = {};
    // Add event listeners
    this.elements.form.addEventListener('submit', event => {
      event.preventDefault();
      this.app.disableActionElements();
      let query = this.elements.form.query.value.trim();
      let subcorpusName = this.elements.form['subcorpus-name'].value;
      this.elements.error.innerText = '';
      this.elements.error.classList.add('hide');
      this.elements.progress.classList.remove('hide');
      let subcorpus = {};
      this.data.corpus.o.query(subcorpusName, query)
        .then(cQiStatus => {
          subcorpus.q = query;
          subcorpus.selectedItems = new Set();
          if (subcorpusName !== 'Last') {this.data.subcorpora.Last = subcorpus;}
          return this.data.corpus.o.subcorpora.get(subcorpusName);
        })
        .then(cQiSubcorpus => {
          subcorpus.o = cQiSubcorpus;
          return cQiSubcorpus.paginate(1, this.settings.perPage, this.settings.context);
        })
        .then(
          paginatedSubcorpus => {
            subcorpus.p = paginatedSubcorpus;
            this.data.subcorpora[subcorpusName] = subcorpus;
            this.settings.selectedSubcorpus = subcorpusName;
            this.renderSubcorpusList();
            this.renderSubcorpusInfo();
            this.renderSubcorpusActions();
            this.renderSubcorpusItems();
            this.renderSubcorpusPagination();
            this.elements.progress.classList.add('hide');
            this.app.enableActionElements();
          },
          cQiError => {
            this.elements.error.innerText = JSON.stringify(cQiError);
            this.elements.error.classList.remove('hide');
            if ('payload' in cQiError && 'code' in cQiError.payload && 'msg' in cQiError.payload) {
              app.flash(`${cQiError.payload.code}: ${cQiError.payload.msg}`, 'error');
            }
            this.elements.progress.classList.add('hide');
            this.app.enableActionElements();
          }
        );
    });
    this.elements.form.addEventListener('change', event => {
      if (event.target === this.elements.form['context']) {
        this.settings.context = parseInt(this.elements.form['context'].value);
        this.elements.form.submit.click();
      }
      if (event.target === this.elements.form['per-page']) {
        this.settings.perPage = parseInt(this.elements.form['per-page'].value);
        this.elements.form.submit.click();
      }
      if (event.target === this.elements.form['text-style']) {
        this.settings.textStyle = parseInt(this.elements.form['text-style'].value);
        this.setTextStyle();
      }
      if (event.target === this.elements.form['token-representation']) {
        this.settings.tokenRepresentation = this.elements.form['token-representation'].value;
        this.setTokenRepresentation();
      }
    });
  }

  clearSubcorpusList() {
    this.elements.subcorpusList.innerHTML = '';
    this.elements.subcorpusList.classList.add('hide');
  }

  renderSubcorpusList() {
    this.clearSubcorpusList();
    for (let subcorpusName in this.data.subcorpora) {
      this.elements.subcorpusList.innerHTML += `
        <a class="btn waves-effect waves-light subcorpus-selector" data-target="${subcorpusName}"><i class="material-icons left">bookmark</i>${subcorpusName}</a>
      `.trim();
    }
    for (let subcorpusSelectorElement of this.elements.subcorpusList.querySelectorAll('.subcorpus-selector')) {
      let subcorpusName = subcorpusSelectorElement.dataset.target;
      if (subcorpusName === this.settings.selectedSubcorpus) {
        subcorpusSelectorElement.classList.add('disabled');
        continue;
      }
      subcorpusSelectorElement.addEventListener('click', () => {
        this.settings.selectedSubcorpus = subcorpusName;
        this.elements.progress.classList.remove('hide');
        this.renderSubcorpusList();
        this.renderSubcorpusInfo();
        this.renderSubcorpusActions();
        this.renderSubcorpusActions();
        this.renderSubcorpusItems();
        this.renderSubcorpusPagination();
        this.elements.progress.classList.add('hide');
      });
    }
    this.elements.subcorpusList.classList.remove('hide');
  }

  clearSubcorpusInfo() {
    this.elements.subcorpusInfo.innerHTML = '';
    this.elements.subcorpusInfo.classList.add('hide');
  }

  renderSubcorpusInfo() {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    this.clearSubcorpusInfo();
    this.elements.subcorpusInfo.innerHTML = `${subcorpus.p.total} matches found for <code>${subcorpus.q.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</code>`;
    this.elements.subcorpusInfo.classList.remove('hide');
  }

  clearSubcorpusActions() {
    for (let tooltippedElement of this.elements.subcorpusActions.querySelectorAll('.tooltipped')) {
      M.Tooltip.getInstance(tooltippedElement).destroy();
    }
    this.elements.subcorpusActions.innerHTML = '';
  }

  renderSubcorpusActions() {
    this.clearSubcorpusActions();
    this.elements.subcorpusActions.innerHTML += `
      <a class="btn-floating btn-small tooltipped waves-effect waves-light corpus-analysis-action subcorpus-export-trigger" data-tooltip="Export subcorpus">
        <i class="material-icons">download</i>
      </a>
      <a class="btn-floating btn-small red tooltipped waves-effect waves-light corpus-analysis-action subcorpus-delete-trigger" data-tooltip="Delete subcorpus">
        <i class="material-icons">delete</i>
      </a>
    `.trim();
    M.Tooltip.init(this.elements.subcorpusActions.querySelectorAll('.tooltipped'));
    this.elements.subcorpusActions.querySelector('.subcorpus-export-trigger').addEventListener('click', event => {
      event.preventDefault();
      let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
      let modalElementId = Utils.generateElementId('export-subcorpus-modal-');
      let exportFormatSelectElementId = Utils.generateElementId('export-format-select-');
      let exportSelectedMatchesOnlyCheckboxElementId = Utils.generateElementId('export-selected-matches-only-checkbox-');
      let exportFileNameInputElementId = Utils.generateElementId('export-file-name-input-');
      let modalElement = Utils.HTMLToElement(
        `
          <div class="modal" id="${modalElementId}">
            <div class="modal-content">
              <h4>Export subcorpus "${subcorpus.o.name}"</h4>
              <br>
              <div class="row">
                <div class="input-field col s3">
                  <select id="${exportFormatSelectElementId}">
                    <option value="csv" selected>CSV</option>
                    <option value="json">JSON</option>
                  </select>
                  <label>Export format</label>
                </div>
                <div class="input-field col s9">
                  <input id="${exportFileNameInputElementId}" type="text" class="validate" value="export">
                  <label class="active" for="${exportFileNameInputElementId}">Export filename without filename extension (.csv/.json/...)</label>
                </div>
                <p class="col s12">
                  <label>
                    <input id="${exportSelectedMatchesOnlyCheckboxElementId}" type="checkbox" ${subcorpus.selectedItems.size === 0 ? '' : 'checked'}>
                    <span>Export selected matches only</span>
                  </label>
                </p>
              </div>
            </div>
            <div class="modal-footer">
              <a class="btn-flat modal-close waves-effect waves-light">Cancel</a>
              <a class="action-button btn modal-close waves-effect waves-light" data-action="export">Export</a>
            </div>
          </div>
        `
      );
      document.querySelector('#modals').appendChild(modalElement);
      let exportFormatSelectElement = modalElement.querySelector(`#${exportFormatSelectElementId}`);
      let exportFormatSelect = M.FormSelect.init(exportFormatSelectElement);
      let exportSelectedMatchesOnlyCheckboxElement = modalElement.querySelector(`#${exportSelectedMatchesOnlyCheckboxElementId}`);
      let exportFileNameInputElement = modalElement.querySelector(`#${exportFileNameInputElementId}`);
      let exportButton = modalElement.querySelector('.action-button[data-action="export"]');
      let modal = M.Modal.init(
        modalElement,
        {
          dismissible: false,
          onCloseEnd: () => {
            exportFormatSelect.destroy();
            modal.destroy();
            modalElement.remove();
          }
        }
      );
      exportButton.addEventListener('click', event => {
        event.preventDefault();
        this.app.disableActionElements();
        this.elements.progress.classList.remove('hide');
        let exportFormat = exportFormatSelectElement.value;
        let exportFileName = exportFileNameInputElement.value;
        let exportFileNameExtension = exportFormat === 'csv' ? 'csv' : 'json';
        let exportFileNameWithExtension = `${exportFileName}.${exportFileNameExtension}`;
        let exportSelectedMatchesOnly = exportSelectedMatchesOnlyCheckboxElement.checked;
        let promise;
        if (exportSelectedMatchesOnly) {
          if (subcorpus.selectedItems.size === 0) {
            this.elements.progress.classList.add('hide');
            this.app.enableActionElements();
            app.flash('No matches selected', 'error');
            return;
          }
          promise = subcorpus.o.partial_export([...subcorpus.selectedItems], 50);
        } else {
          promise = subcorpus.o.export(50);
        }
        promise.then(
          data => {
            let blob;
            if (exportFormat === 'csv') {
              let csvContent = 'sep=,\r\n';
              csvContent += '"#Match","Text title","Left context","KWIC","Right context"';
              for (let match of data.matches) {
                csvContent += '\r\n';
                csvContent += `"${match.num}"`;
                csvContent += ',';
                let textIds = new Set();
                for (let cpos = match.c[0]; cpos <= match.c[1]; cpos++) {
                  textIds.add(data.cpos_lookup[cpos].text);
                }
                csvContent += '"' + [...textIds].map(x => data.text_lookup[x].title.replace('"', '""')).join(', ') + '"';
                csvContent += ',';
                if (match.lc !== null) {
                  let lc_cpos_list = [];
                  for (let cpos = match.lc[0]; cpos <= match.lc[1]; cpos++) {lc_cpos_list.push(cpos);}
                  csvContent += '"' + lc_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
                }
                csvContent += ',';
                let c_cpos_list = [];
                for (let cpos = match.c[0]; cpos <= match.c[1]; cpos++) {c_cpos_list.push(cpos);}
                csvContent += '"' + c_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
                csvContent += ',';
                let rc_cpos_list = [];
                for (let cpos = match.rc[0]; cpos <= match.rc[1]; cpos++) {rc_cpos_list.push(cpos);}
                if (match.rc !== null) {
                  csvContent += '"' + rc_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
                }
              }
              blob = new Blob([csvContent], {type: 'text/csv;charset=utf-8;'});
            } else {
              blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json;charset=utf-8;'});
            }
            let url = URL.createObjectURL(blob);
            let pom = document.createElement('a');
            pom.href = url;
            pom.setAttribute('download', exportFileNameWithExtension);
            pom.click();
            this.elements.progress.classList.add('hide');
            this.app.enableActionElements();
        });
      });
      modal.open();
    });
    this.elements.subcorpusActions.querySelector('.subcorpus-delete-trigger').addEventListener('click', event => {
      event.preventDefault();
      let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
      subcorpus.o.drop().then(
        cQiStatus => {
          app.flash(`${subcorpus.o.name} deleted`, 'corpus');
          delete this.data.subcorpora[subcorpus.o.name];
          this.settings.selectedSubcorpus = undefined;
          for (let subcorpusName in this.data.subcorpora) {
            this.settings.selectedSubcorpus = subcorpusName;
            break;
          }
          this.renderSubcorpusList();
          if (this.settings.selectedSubcorpus) {
            this.renderSubcorpusInfo();
            this.renderSubcorpusActions();
            this.renderSubcorpusItems();
            this.renderSubcorpusPagination();
          } else {
            this.clearSubcorpusInfo();
            this.clearSubcorpusActions();
            this.clearSubcorpusItems();
            this.clearSubcorpusPagination();
          }
        },
        cQiError => {
          app.flash(`${cQiError.payload.code}: ${cQiError.payload.msg}`, 'error');
        }
      );
    });
  }

  clearSubcorpusItems() {
    // Destroy with .p-attr elements associated Materialize tooltips
    for (let pAttrElement of this.elements.subcorpusItems.querySelectorAll('.p-attr.tooltipped')) {
      M.Tooltip.getInstance(pAttrElement)?.destroy();
    }
    this.elements.subcorpusItems.innerHTML = `
      <tr class="show-if-only-child">
        <td colspan="100%">
          <p>
            <span class="card-title"><i class="left material-icons" style="font-size: inherit;">search</i>Nothing here...</span><br>
            No matches available.
          </p>
        </td>
      </tr>
    `.trim();
  }

  renderSubcorpusItems() {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    this.clearSubcorpusItems();
    for (let item of subcorpus.p.items) {
      let itemIsSelected = item.num in subcorpus.selectedItems;
      this.elements.subcorpusItems.innerHTML += `
        <tr class="item" data-id="${item.num}">
          <td class="num">${item.num}</td>
          <td class="text-title">${this.foo(...item.c)}</td>
          <td class="left-context">${item.lc ? this.cposRange2HTML(...item.lc) : ''}</td>
          <td class="kwic">${this.cposRange2HTML(...item.c)}</td>
          <td class="right-context">${item.rc ? this.cposRange2HTML(...item.rc) : ''}</td>
          <td class="actions right-align">
            <a class="btn-floating btn-small waves-effect waves-light corpus-analysis-action goto-reader-trigger">
              <i class="material-icons prefix">search</i>
            </a>
            <a class="btn-floating btn-small waves-effect waves-light corpus-analysis-action select-trigger ${itemIsSelected ? 'green' : ''}">
              <i class="material-icons prefix">${itemIsSelected ? 'check' : 'add'}</i>
            </a>
          </td>
        </tr>
      `.trim();
    }
    this.setTextStyle();
    this.setTokenRepresentation();
    for (let gotoReaderTriggerElement of this.elements.subcorpusItems.querySelectorAll('.goto-reader-trigger')) {
      gotoReaderTriggerElement.addEventListener('click', event => {
        event.preventDefault();
        let corpusAnalysisReader = this.app.extensions.Reader;
        let itemId = parseInt(gotoReaderTriggerElement.closest('.item').dataset.id);
        let item = undefined;
        for (let x of subcorpus.p.items) {if (x.num === itemId) {item = x;}}
        let page = Math.max(1, Math.ceil(item.c[0] / corpusAnalysisReader.settings.perPage));
        corpusAnalysisReader.page(page, () => {
          let range = new Range();
          let leftCpos = corpusAnalysisReader.data.corpus.p.items[0].includes(item.c[0]) ? item.c[0] : corpusAnalysisReader.data.corpus.p.items[0][0];
          let rightCpos = corpusAnalysisReader.data.corpus.p.items[0].includes(item.c[1]) ? item.c[1] : corpusAnalysisReader.data.corpus.p.items[0].at(-1);
          let leftElement = corpusAnalysisReader.elements.corpus.querySelector(`.p-attr[data-cpos="${leftCpos}"]`);
          let rightElement = corpusAnalysisReader.elements.corpus.querySelector(`.p-attr[data-cpos="${rightCpos}"]`);
          range.setStartBefore(leftElement);
          range.setEndAfter(rightElement);
          document.getSelection().removeAllRanges();
          document.getSelection().addRange(range);
        });
        this.app.elements.m.extensionTabs.select('reader-extension-container');
      });
    }
    for (let selectTriggerElement of this.elements.subcorpusItems.querySelectorAll('.select-trigger')) {
      selectTriggerElement.addEventListener('click', event => {
        event.preventDefault();
        let itemElement = selectTriggerElement.closest('.item');
        let itemId = parseInt(itemElement.dataset.id);
        if (subcorpus.selectedItems.has(itemId)) {
          subcorpus.selectedItems.delete(itemId);
          selectTriggerElement.classList.remove('green');
          selectTriggerElement.querySelector('i').textContent = 'add';
        } else {
          subcorpus.selectedItems.add(itemId);
          selectTriggerElement.classList.add('green');
          selectTriggerElement.querySelector('i').textContent = 'check';
        }
      });
    }
  }

  clearSubcorpusPagination() {
    this.elements.subcorpusPagination.innerHTML = '';
    this.elements.subcorpusPagination.classList.add('hide');
  }

  renderSubcorpusPagination() {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    this.clearSubcorpusPagination();
    if (subcorpus.p.pages === 0) {return;}
    this.elements.subcorpusPagination.innerHTML += `
      <li class="${subcorpus.p.page === 1 ? 'disabled' : 'waves-effect'}">
        <a class="corpus-analysis-action pagination-trigger" ${subcorpus.p.page === 1 ? '' : 'data-target="1"'}>
          <i class="material-icons">first_page</i>
        </a>
      </li>
    `.trim();
    this.elements.subcorpusPagination.innerHTML += `
      <li class="${subcorpus.p.has_prev ? 'waves-effect' : 'disabled'}">
        <a class="corpus-analysis-action pagination-trigger" ${subcorpus.p.has_prev ? 'data-target="' + subcorpus.p.prev_num + '"' : ''}>
          <i class="material-icons">chevron_left</i>
        </a>
      </li>
    `.trim();
    for (let i = 1; i <= subcorpus.p.pages; i++) {
      this.elements.subcorpusPagination.innerHTML += `
        <li class="${i === subcorpus.p.page ? 'active' : 'waves-effect'}">
          <a class="corpus-analysis-action pagination-trigger" ${i === subcorpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a>
        </li>
      `.trim();
    }
    this.elements.subcorpusPagination.innerHTML += `
      <li class="${subcorpus.p.has_next ? 'waves-effect' : 'disabled'}">
        <a class="corpus-analysis-action pagination-trigger" ${subcorpus.p.has_next ? 'data-target="' + subcorpus.p.next_num + '"' : ''}>
          <i class="material-icons">chevron_right</i>
        </a>
      </li>
    `.trim();
    this.elements.subcorpusPagination.innerHTML += `
      <li class="${subcorpus.p.page === subcorpus.p.pages ? 'disabled' : 'waves-effect'}">
        <a class="corpus-analysis-action pagination-trigger" ${subcorpus.p.page === subcorpus.p.pages ? '' : 'data-target="' + subcorpus.p.pages + '"'}>
          <i class="material-icons">last_page</i>
        </a>
      </li>
    `.trim();
    for (let paginationTriggerElement of this.elements.subcorpusPagination.querySelectorAll('.pagination-trigger[data-target]')) {
      paginationTriggerElement.addEventListener('click', event => {
        event.preventDefault();
        this.app.disableActionElements();
        this.elements.progress.classList.remove('hide');
        let page = parseInt(paginationTriggerElement.dataset.target);
        subcorpus.o.paginate(page, this.settings.perPage, this.settings.context)
          .then(
            paginatedSubcorpus => {
              subcorpus.p = paginatedSubcorpus;
              this.renderSubcorpusItems();
              this.renderSubcorpusPagination();
              this.elements.progress.classList.add('hide');
              this.app.enableActionElements();
            }
          )
      });
    }
    this.elements.subcorpusPagination.classList.remove('hide');
  }

  foo(firstCpos, lastCpos) {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    /* Returns a list of texts occuring in this cpos range */
    let textIds = new Set();
    for (let cpos = firstCpos; cpos <= lastCpos; cpos++) {
      textIds.add(subcorpus.p.lookups.cpos_lookup[cpos].text);
    }
    return [...textIds].map(x => subcorpus.p.lookups.text_lookup[x].title).join(', ');
  }

  cposRange2HTML(firstCpos, lastCpos) {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    let html = '';
    for (let cpos = firstCpos; cpos <= lastCpos; cpos++) {
      let prevPAttr = cpos > firstCpos ? subcorpus.p.lookups.cpos_lookup[cpos - 1] : null;
      let pAttr = subcorpus.p.lookups.cpos_lookup[cpos];
      let nextPAttr = cpos < lastCpos ? subcorpus.p.lookups.cpos_lookup[cpos + 1] : null;
      let isEntityStart = 'ent' in pAttr && pAttr.ent !== prevPAttr?.ent;
      let isEntityEnd = 'ent' in pAttr && pAttr.ent !== nextPAttr?.ent;
      // Add a space before pAttr
      if (cpos !== firstCpos || pAttr.simple_pos !== 'PUNCT') {html += ' ';}
      // Add entity start
      if (isEntityStart) {
        html += `<span class="s-attr" data-cpos="${cpos}" data-id="${pAttr.ent}" data-s-attr-type="ent" data-s-attr-ent-type="${subcorpus.p.lookups.ent_lookup[pAttr.ent].type}">`;
      }
      // Add pAttr
      html += `<span class="p-attr" data-cpos="${cpos}"></span>`;
      // Add entity end
      if (isEntityEnd) {
        html += ` <span class="black-text hide new white ent-indicator" data-badge-caption="">${subcorpus.p.lookups.ent_lookup[pAttr.ent].type}</span>`;
        html += '</span>';
      }
    }
    return html;
  }

  setTextStyle() {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    if (this.settings.textStyle >= 0) {
      // Destroy with .p-attr elements associated Materialize tooltips
      for (let pAttrElement of this.elements.subcorpusItems.querySelectorAll('.p-attr.tooltipped')) {
        M.Tooltip.getInstance(pAttrElement)?.destroy();
      }
      // Set basic styling on .p-attr elements
      for (let pAttrElement of this.elements.subcorpusItems.querySelectorAll('.p-attr')) {
        pAttrElement.setAttribute('class', 'p-attr');
      }
      // Set basic styling on .s-attr[data-type="ent"] elements
      for (let entElement of this.elements.subcorpusItems.querySelectorAll('.s-attr[data-s-attr-type="ent"]')) {
        entElement.querySelector('.ent-indicator').classList.add('hide');
        entElement.removeAttribute('style');
        entElement.setAttribute('class', 's-attr');
      }
    }
    if (this.settings.textStyle >= 1) {
      // Set advanced styling on .s-attr[data-type="ent"] elements
      for (let entElement of this.elements.subcorpusItems.querySelectorAll('.s-attr[data-s-attr-type="ent"]')) {
        entElement.classList.add('chip');
        entElement.querySelector('.ent-indicator').classList.remove('hide');
      }
    }
    if (this.settings.textStyle >= 2) {
      // Set advanced styling on .p-attr elements
      for (let pAttrElement of this.elements.subcorpusItems.querySelectorAll('.p-attr')) {
        pAttrElement.classList.add('chip', 'hoverable', 'tooltipped');
        let cpos = pAttrElement.dataset.cpos;
        let pAttr = subcorpus.p.lookups.cpos_lookup[cpos];
        let positionalPropertiesHTML = `
          <p class="left-align">
            <b>Positional properties</b><br>
            <span>Token: ${cpos}</span>
        `.trim();
        let structuralPropertiesHTML = `
          <p class="left-align">
            <b>Structural properties</b>
        `.trim();
        for (let [property, propertyValue] of Object.entries(pAttr)) {
          if (['lemma', 'ner', 'pos', 'simple_pos', 'word'].includes(property)) {
            if (propertyValue === 'None') {continue;}
            positionalPropertiesHTML += `<br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i>${property}: ${propertyValue}`;
          } else {
            structuralPropertiesHTML += `<br><span>${property}: ${propertyValue}</span>`;
            if (!(`${property}_lookup` in subcorpus.p.lookups)) {continue;}
            for (let [subproperty, subpropertyValue] of Object.entries(subcorpus.p.lookups[`${property}_lookup`][propertyValue])) {
              if (subpropertyValue === 'NULL') {continue;}
              structuralPropertiesHTML += `<br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i>${subproperty}: ${subpropertyValue}`
            }
          }
        }
        positionalPropertiesHTML += '</p>';
        structuralPropertiesHTML += '</p>';
        M.Tooltip.init(
          pAttrElement,
          {html: positionalPropertiesHTML + structuralPropertiesHTML}
        );
      }
    }
  }

  setTokenRepresentation() {
    let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
    for (let pAttrElement of this.elements.subcorpusItems.querySelectorAll('.p-attr')) {
      let pAttr = subcorpus.p.lookups.cpos_lookup[pAttrElement.dataset.cpos];
      pAttrElement.innerText = pAttr[this.settings.tokenRepresentation];
    }
  }
}
