import TokenCustomConfig from './tokenCustomConfig.js';
import { isVideo, isImage, keyPressed, SEARCH_TYPE, BASE_IMAGE_CATEGORIES, getFileName } from '../scripts/utils.js';
import { showArtSelect } from '../token-variants.mjs';
import { TVA_CONFIG, getSearchOptions } from '../scripts/settings.js';

export function addToArtSelectQueue(search, options) {
  ArtSelect.queue.push({
    search: search,
    options: options,
  });
  $('button#token-variant-art-clear-queue').html(`Clear Queue (${ArtSelect.queue.length})`).show();
}

export function addToQueue(search, options) {
  ArtSelect.queue.push({
    search: search,
    options: options,
  });
}

export function renderFromQueue(force = false) {
  if (!force) {
    const artSelects = Object.values(ui.windows).filter((app) => app instanceof ArtSelect);
    if (artSelects.length !== 0) {
      if (ArtSelect.queue.length !== 0)
        $('button#token-variant-art-clear-queue').html(`Clear Queue (${ArtSelect.queue.length})`).show();
      return;
    }
  }

  let callData = ArtSelect.queue.shift();
  if (callData?.options.execute) {
    callData.options.execute();
    callData = ArtSelect.queue.shift();
  }
  if (callData) {
    showArtSelect(callData.search, callData.options);
  }
}

function delay(fn, ms) {
  let timer = 0;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(fn.bind(this, ...args), ms || 0);
  };
}

export class ArtSelect extends FormApplication {
  static queue = [];

  static instance = null;

  // showArtSelect(...) can take a while to fully execute and it is possible for it to be called
  // multiple times in very quick succession especially if copy pasting tokens or importing actors.
  // This variable set early in the function execution is used to queue additional requests rather
  // than continue execution
  static executing = false;

  static IMAGE_DISPLAY = {
    NONE: 0,
    PORTRAIT: 1,
    TOKEN: 2,
    PORTRAIT_TOKEN: 3,
    IMAGE: 4,
  };

  constructor(
    search,
    {
      preventClose = false,
      object = null,
      callback = null,
      searchType = null,
      allImages = null,
      image1 = '',
      image2 = '',
      displayMode = ArtSelect.IMAGE_DISPLAY.NONE,
      multipleSelection = false,
      searchOptions = {},
    } = {}
  ) {
    let title = game.i18n.localize('token-variants.windows.art-select.select-variant');
    if (searchType === SEARCH_TYPE.TOKEN)
      title = game.i18n.localize('token-variants.windows.art-select.select-token-art');
    else if (searchType === SEARCH_TYPE.PORTRAIT)
      title = game.i18n.localize('token-variants.windows.art-select.select-portrait-art');

    super(
      {},
      {
        closeOnSubmit: false,
        width: ArtSelect.WIDTH || 500,
        height: ArtSelect.HEIGHT || 500,
        left: ArtSelect.LEFT,
        top: ArtSelect.TOP,
        title: title,
      }
    );
    this.search = search;
    this.allImages = allImages;
    this.callback = callback;
    this.doc = object;
    this.preventClose = preventClose;
    this.image1 = image1;
    this.image2 = image2;
    this.displayMode = displayMode;
    this.multipleSelection = multipleSelection;
    this.searchType = searchType;
    this.searchOptions = mergeObject(searchOptions, getSearchOptions(), {
      overwrite: false,
    });
    ArtSelect.instance = this;

    const constructorName = `ArtSelect`;
    Object.defineProperty(ArtSelect.prototype.constructor, 'name', { value: constructorName });
  }

  static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
      id: 'token-variants-art-select',
      classes: ['sheet'],
      template: 'modules/token-variants/templates/artSelect.html',
      resizable: true,
      minimizable: false,
    });
  }

  _getHeaderButtons() {
    const buttons = super._getHeaderButtons();
    buttons.unshift({
      label: 'FilePicker',
      class: 'file-picker',
      icon: 'fas fa-file-import fa-fw',
      onclick: () => {
        new FilePicker({
          type: 'imagevideo',
          callback: (path) => {
            if (!this.preventClose) {
              this.close();
            }
            if (this.callback) {
              this.callback(path, getFileName(path));
            }
          },
        }).render();
      },
    });
    buttons.unshift({
      label: 'Image Category',
      class: 'type',
      icon: 'fas fa-swatchbook',
      onclick: () => {
        if (ArtSelect.instance) ArtSelect.instance._typeSelect();
      },
    });
    return buttons;
  }

  _typeSelect() {
    const categories = BASE_IMAGE_CATEGORIES.concat(TVA_CONFIG.customImageCategories);

    const buttons = {};
    for (const c of categories) {
      let label = c;
      if (c === this.searchType) {
        label = '<b>>>> ' + label + ' <<<</b>';
      }
      buttons[c] = {
        label: label,
        callback: () => {
          if (this.searchType !== c) {
            this.searchType = c;
            this._performSearch(this.search, true);
          }
        },
      };
    }

    new Dialog({
      title: `Select Image Category and Filter`,
      content: `<style>.dialog .dialog-button {flex: 0 0 auto;}</style>`,
      buttons: buttons,
    }).render(true);
  }

  async getData(options) {
    const data = super.getData(options);
    if (this.doc instanceof Item) {
      data.item = true;
      data.description = this.doc.system?.description?.value ?? '';
    }
    const searchOptions = this.searchOptions;
    const algorithm = searchOptions.algorithm;

    //
    // Create buttons
    //
    const tokenConfigs = (TVA_CONFIG.tokenConfigs || []).flat();
    const fuzzySearch = algorithm.fuzzy;

    let allButtons = new Map();
    let artFound = false;

    const genLabel = function (str, indices, start = '<mark>', end = '</mark>', fillChar = null) {
      if (!indices) return str;
      let fillStr = fillChar ? fillChar.repeat(str.length) : str;
      let label = '';
      let lastIndex = 0;
      for (const index of indices) {
        label += fillStr.slice(lastIndex, index[0]);
        label += start + str.slice(index[0], index[1] + 1) + end;
        lastIndex = index[1] + 1;
      }
      label += fillStr.slice(lastIndex, fillStr.length);

      return label;
    };

    const genTitle = function (obj) {
      if (!fuzzySearch) return obj.path;

      let percent = Math.ceil((1 - obj.score) * 100) + '%';
      if (searchOptions.runSearchOnPath) {
        return percent + '\n' + genLabel(obj.path, obj.indices, '', '', '-') + '\n' + obj.path;
      }
      return percent;
    };

    this.allImages.forEach((images, search) => {
      const buttons = [];
      images.forEach((imageObj) => {
        artFound = true;
        const vid = isVideo(imageObj.path);
        const img = isImage(imageObj.path);
        buttons.push({
          path: imageObj.path,
          img: img,
          vid: vid,
          type: vid || img,
          name: imageObj.name,
          label:
            fuzzySearch && !searchOptions.runSearchOnPath ? genLabel(imageObj.name, imageObj.indices) : imageObj.name,
          title: genTitle(imageObj),
          hasConfig:
            this.searchType === SEARCH_TYPE.TOKEN || this.searchType === SEARCH_TYPE.PORTRAIT_AND_TOKEN
              ? Boolean(
                  tokenConfigs.find((config) => config.tvImgSrc == imageObj.path && config.tvImgName == imageObj.name)
                )
              : false,
        });
      });
      allButtons.set(search, buttons);
    });

    if (artFound) data.allImages = allButtons;

    data.search = this.search;
    data.queue = ArtSelect.queue.length;
    data.image1 = this.image1;
    data.image2 = this.image2;
    data.displayMode = this.displayMode;
    data.multipleSelection = this.multipleSelection;
    data.displaySlider = algorithm.fuzzy && algorithm.fuzzyArtSelectPercentSlider;
    data.fuzzyThreshold = algorithm.fuzzyThreshold;
    if (data.displaySlider) {
      data.fuzzyThreshold = 100 - data.fuzzyThreshold * 100;
      data.fuzzyThreshold = data.fuzzyThreshold.toFixed(0);
    }
    data.autoplay = !TVA_CONFIG.playVideoOnHover;
    return data;
  }

  /**
   * @param {JQuery} html
   */
  activateListeners(html) {
    super.activateListeners(html);
    const callback = this.callback;
    const close = () => this.close();
    const object = this.doc;
    const preventClose = this.preventClose;
    const multipleSelection = this.multipleSelection;

    const boxes = html.find(`.token-variants-grid-box`);
    boxes.hover(
      function () {
        if (TVA_CONFIG.playVideoOnHover) {
          const vid = $(this).siblings('video');
          if (vid.length) {
            vid[0].play();
            $(this).siblings('.fa-play').hide();
          }
        }
      },
      function () {
        if (TVA_CONFIG.pauseVideoOnHoverOut) {
          const vid = $(this).siblings('video');
          if (vid.length) {
            vid[0].pause();
            vid[0].currentTime = 0;
            $(this).siblings('.fa-play').show();
          }
        }
      }
    );
    boxes.map((box) => {
      boxes[box].addEventListener('click', async function (event) {
        if (keyPressed('config')) {
          if (object)
            new TokenCustomConfig(object, {}, event.target.dataset.name, event.target.dataset.filename).render(true);
        } else {
          if (!preventClose) {
            close();
          }
          if (callback) {
            callback(event.target.dataset.name, event.target.dataset.filename);
          }
        }
      });
      if (multipleSelection) {
        boxes[box].addEventListener('contextmenu', async function (event) {
          $(event.target).toggleClass('selected');
        });
      }
    });

    let searchInput = html.find('#custom-art-search');
    searchInput.focus();
    searchInput[0].selectionStart = searchInput[0].selectionEnd = 10000;

    searchInput.on(
      'input',
      delay((event) => {
        this._performSearch(event.target.value);
      }, 350)
    );

    html.find(`button#token-variant-art-clear-queue`).on('click', (event) => {
      ArtSelect.queue = ArtSelect.queue.filter((callData) => callData.options.execute);
      $(event.target).hide();
    });

    $(html)
      .find('[name="fuzzyThreshold"]')
      .change((e) => {
        $(e.target)
          .siblings('.token-variants-range-value')
          .html(`${parseFloat(e.target.value).toFixed(0)}%`);
        this.searchOptions.algorithm.fuzzyThreshold = (100 - e.target.value) / 100;
      })
      .change(
        delay((event) => {
          this._performSearch(this.search, true);
        }, 350)
      );

    if (multipleSelection) {
      html.find(`button#token-variant-art-return-selected`).on('click', () => {
        if (callback) {
          const images = [];
          html
            .find(`.token-variants-grid-box.selected`)
            .siblings('.token-variants-grid-image')
            .each(function () {
              images.push(this.getAttribute('src'));
            });
          callback(images);
        }
        close();
      });
      html.find(`button#token-variant-art-return-all`).on('click', () => {
        if (callback) {
          const images = [];
          html.find(`.token-variants-grid-image`).each(function () {
            images.push(this.getAttribute('src'));
          });
          callback(images);
        }
        close();
      });
    }
  }

  _performSearch(search, force = false) {
    if (!force && this.search.trim() === search.trim()) return;
    showArtSelect(search, {
      callback: this.callback,
      searchType: this.searchType,
      object: this.doc,
      force: true,
      image1: this.image1,
      image2: this.image2,
      displayMode: this.displayMode,
      multipleSelection: this.multipleSelection,
      searchOptions: this.searchOptions,
      preventClose: this.preventClose,
    });
  }

  /**
   * @param {Event} event
   * @param {Object} formData
   */
  async _updateObject(event, formData) {
    if (formData && formData.search != this.search) {
      this._performSearch(formData.search);
    } else {
      this.close();
    }
  }

  setPosition(options = {}) {
    if (options.width) ArtSelect.WIDTH = options.width;
    if (options.height) ArtSelect.HEIGHT = options.height;
    if (options.top) ArtSelect.TOP = options.top;
    if (options.left) ArtSelect.LEFT = options.left;
    super.setPosition(options);
  }

  async close(options = {}) {
    let callData = ArtSelect.queue.shift();
    if (callData?.options.execute) {
      callData.options.execute();
      callData = ArtSelect.queue.shift();
    }
    if (callData) {
      callData.options.force = true;
      showArtSelect(callData.search, callData.options);
    } else {
      // For some reason there might be app instances that have not closed themselves by this point
      // If there are, close them now
      const artSelects = Object.values(ui.windows)
        .filter((app) => app instanceof ArtSelect)
        .filter((app) => app.appId !== this.appId);
      for (const app of artSelects) {
        app.close();
      }

      return super.close(options);
    }
  }
}

export function insertArtSelectButton(html, target, { search = '', searchType = SEARCH_TYPE.TOKEN } = {}) {
  const button = $(`<button 
      class="token-variants-image-select-button" 
      type="button"
      data-type="imagevideo"
      data-target="${target}"
      title="${game.i18n.localize('token-variants.windows.art-select.select-variant')}">
        <i class="fas fa-images"></i>
      </button>`);
  button.on('click', () => {
    showArtSelect(search, {
      callback: (imgSrc, name) => {
        button.siblings(`[name="${target}"]`).val(imgSrc);
      },
      searchType,
    });
  });
  const input = html.find(`[name="${target}"]`);
  input.after(button);
  return Boolean(input.length);
}
