import {
  getFileName,
  isImage,
  isVideo,
  SEARCH_TYPE,
  keyPressed,
  updateActorImage,
  updateTokenImage,
  decodeURISafely,
} from '../scripts/utils.js';
import TokenCustomConfig from './tokenCustomConfig.js';
import EffectMappingForm from './effectMappingForm.js';
import { TVA_CONFIG } from '../scripts/settings.js';
import UserList from './userList.js';
import FlagsConfig from './flagsConfig.js';
import RandomizerConfig from './randomizerConfig.js';
import { doImageSearch, findImagesFuzzy } from '../scripts/search.js';

export const TOKEN_HUD_VARIANTS = { variants: null, actor: null };

export async function renderTokenHUD(hud, html, token, searchText = '', fp_files = null) {
  activateStatusEffectListeners(token);

  const hudSettings = TVA_CONFIG.hud;
  const FULL_ACCESS = TVA_CONFIG.permissions.hudFullAccess[game.user.role];
  const PARTIAL_ACCESS = TVA_CONFIG.permissions.hud[game.user.role];

  // Check if the HUD button should be displayed
  if (
    !hudSettings.enableSideMenu ||
    (!PARTIAL_ACCESS && !FULL_ACCESS) ||
    token.flags['token-variants']?.disableHUDButton
  )
    return;

  const tokenActor = game.actors.get(token.actorId);

  // Disable button if Token HUD Wildcard is enabled and appropriate setting is set
  if (TVA_CONFIG.worldHud.disableIfTHWEnabled && game.modules.get('token-hud-wildcard')?.active) {
    if (tokenActor && tokenActor.prototypeToken.randomImg) return;
  }

  const button = $(`
  <div class="control-icon" data-action="token-variants-side-selector">
    <img
      id="token-variants-side-button"
      src="modules/token-variants/img/token-images.svg"
      width="36"
      height="36"
      title="Left-click: Image Menu&#013;Right-click: Search & Additional settings"
    />
  </div>
`);

  html.find('div.right').last().append(button);
  html.find('div.right').click(_deactivateTokenVariantsSideSelector);

  button.click((event) => _onButtonClick(event, token));
  if (FULL_ACCESS) {
    button.contextmenu((event) => _onButtonRightClick(event, hud, html, token));
  }
}

async function _onButtonClick(event, token) {
  const button = $(event.target).closest('.control-icon');

  // De-activate 'Status Effects'
  button.closest('div.right').find('div.control-icon.effects').removeClass('active');
  button.closest('div.right').find('.status-effects').removeClass('active');

  // Remove contextmenu
  button.find('.contextmenu').remove();

  // Toggle variants side menu
  button.toggleClass('active');
  let variantsWrap = button.find('.token-variants-wrap');
  if (button.hasClass('active')) {
    if (!variantsWrap.length) {
      variantsWrap = await renderSideSelect(token);
      if (variantsWrap) button.find('img').after(variantsWrap);
      else return;
    }
    variantsWrap.addClass('active');
  } else {
    variantsWrap.removeClass('active');
  }
}

function _onButtonRightClick(event, hud, html, token) {
  // Display side menu if button is not active yet
  const button = $(event.target).closest('.control-icon');
  if (!button.hasClass('active')) {
    // button.trigger('click');
    button.addClass('active');
  }

  if (button.find('.contextmenu').length) {
    // Contextmenu already displayed. Remove and activate images
    button.find('.contextmenu').remove();
    button.removeClass('active').trigger('click');
    //button.find('.token-variants-wrap.images').addClass('active');
  } else {
    // Contextmenu is not displayed. Hide images, create it and add it
    button.find('.token-variants-wrap.images').removeClass('active');
    const contextMenu = $(`
    <div class="token-variants-wrap contextmenu active">
      <div class="token-variants-context-menu active">
        <input class="token-variants-side-search" type="text" />
        <button class="flags" type="button"><i class="fab fa-font-awesome-flag"></i><label>Flags</label></button>
        <button class="file-picker" type="button"><i class="fas fa-file-import fa-fw"></i><label>Browse Folders</label></button>
        <button class="effectConfig" type="button"><i class="fas fa-sun"></i><label>Mappings</label></button>
        <button class="randomizerConfig" type="button"><i class="fas fa-dice"></i><label>Randomizer</label></button>
      </div>
    </div>
      `);
    button.append(contextMenu);

    // Register contextmenu listeners
    contextMenu
      .find('.token-variants-side-search')
      .on('keyup', (event) => _onImageSearchKeyUp(event, token))
      .on('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
      });
    contextMenu.find('.flags').click((event) => {
      const tkn = canvas.tokens.get(token._id);
      if (tkn) {
        event.preventDefault();
        event.stopPropagation();
        new FlagsConfig(tkn).render(true);
      }
    });
    contextMenu.find('.file-picker').click(async (event) => {
      event.preventDefault();
      event.stopPropagation();
      new FilePicker({
        type: 'imagevideo',
        callback: async (path, fp) => {
          const content = await FilePicker.browse(fp.activeSource, fp.result.target);
          let files = content.files.filter((f) => isImage(f) || isVideo(f));
          if (files.length) {
            button.find('.token-variants-wrap').remove();
            const sideSelect = await renderSideSelect(token, '', files);
            if (sideSelect) {
              sideSelect.addClass('active');
              button.append(sideSelect);
            }
          }
        },
      }).render(true);
    });
    contextMenu.find('.effectConfig').click((event) => {
      new EffectMappingForm(token).render(true);
    });
    contextMenu.find('.randomizerConfig').click((event) => {
      new RandomizerConfig(token).render(true);
    });
  }
}

function _deactivateTokenVariantsSideSelector(event) {
  const controlIcon = $(event.target).closest('.control-icon');
  const dataAction = controlIcon.attr('data-action');

  switch (dataAction) {
    case 'effects':
      break; // Effects button
    case 'thwildcard-selector':
      break; // Token HUD Wildcard module button
    default:
      return;
  }

  $(event.target)
    .closest('div.right')
    .find('.control-icon[data-action="token-variants-side-selector"]')
    .removeClass('active');
  $(event.target).closest('div.right').find('.token-variants-wrap').removeClass('active');
}

async function renderSideSelect(token, searchText = '', fp_files = null) {
  const hudSettings = TVA_CONFIG.hud;
  const worldHudSettings = TVA_CONFIG.worldHud;
  const FULL_ACCESS = TVA_CONFIG.permissions.hudFullAccess[game.user.role];
  const PARTIAL_ACCESS = TVA_CONFIG.permissions.hud[game.user.role];

  const tokenActor = game.actors.get(token.actorId);

  let images = [];
  let actorVariants = [];
  let imageDuplicates = new Set();
  const pushImage = (img) => {
    if (imageDuplicates.has(img.path)) {
      if (!images.find((obj) => obj.path === img.path && obj.name === img.name)) {
        images.push(img);
      }
    } else {
      images.push(img);
      imageDuplicates.add(img.path);
    }
  };

  actorVariants = getVariants(tokenActor);

  if (!fp_files) {
    if (!searchText) {
      // Insert current token image
      if (token.texture?.src && token.texture?.src !== CONST.DEFAULT_TOKEN) {
        pushImage({
          path: decodeURISafely(token.texture.src),
          name: token.flags?.['token-variants']?.name ?? getFileName(token.texture.src),
        });
      }

      if (tokenActor) {
        // Insert default token image
        const defaultImg =
          tokenActor.prototypeToken?.flags['token-variants']?.['randomImgDefault'] ||
          tokenActor.prototypeToken?.flags['token-hud-wildcard']?.['default'] ||
          '';
        if (defaultImg) {
          pushImage({ path: decodeURISafely(defaultImg), name: getFileName(defaultImg) });
        }

        if (FULL_ACCESS || PARTIAL_ACCESS) {
          actorVariants.forEach((variant) => {
            for (const name of variant.names) {
              pushImage({ path: decodeURISafely(variant.imgSrc), name: name });
            }
          });
        }

        // Parse directory flag and include the images
        if (FULL_ACCESS || PARTIAL_ACCESS) {
          const directoryFlag = tokenActor.getFlag('token-variants', 'directory');
          if (directoryFlag) {
            let dirFlagImages;
            try {
              let path = directoryFlag.path;
              let source = directoryFlag.source;
              let bucket = '';
              if (source.startsWith('s3:')) {
                bucket = source.substring(3, source.length);
                source = 's3';
              }
              const content = await FilePicker.browse(source, path, {
                type: 'imagevideo',
                bucket,
              });
              dirFlagImages = content.files;
            } catch (err) {
              dirFlagImages = [];
            }
            dirFlagImages = dirFlagImages.forEach((f) => {
              if (isImage(f) || isVideo(f)) pushImage({ path: decodeURISafely(f), name: getFileName(f) });
            });
          }
        }

        if (
          (FULL_ACCESS || PARTIAL_ACCESS) &&
          worldHudSettings.includeWildcard &&
          !worldHudSettings.displayOnlySharedImages
        ) {
          // Merge wildcard images
          const protoImg = tokenActor.prototypeToken.texture.src;
          if (tokenActor.prototypeToken.randomImg) {
            (await tokenActor.getTokenImages())
              .filter((img) => !img.includes('*'))
              .forEach((img) => {
                pushImage({ path: decodeURISafely(img), name: getFileName(img) });
              });
          } else if (protoImg.includes('*') || protoImg.includes('{') || protoImg.includes('}')) {
            // Modified version of Actor.getTokenImages()
            const getTokenImages = async () => {
              if (tokenActor._tokenImages) return tokenActor._tokenImages;

              let source = 'data';
              let pattern = tokenActor.prototypeToken.texture.src;
              const browseOptions = { wildcard: true };

              // Support non-user sources
              if (/\.s3\./.test(pattern)) {
                source = 's3';
                const { bucket, keyPrefix } = FilePicker.parseS3URL(pattern);
                if (bucket) {
                  browseOptions.bucket = bucket;
                  pattern = keyPrefix;
                }
              } else if (pattern.startsWith('icons/')) source = 'public';

              // Retrieve wildcard content
              try {
                const content = await FilePicker.browse(source, pattern, browseOptions);
                tokenActor._tokenImages = content.files;
              } catch (err) {
                tokenActor._tokenImages = [];
              }
              return tokenActor._tokenImages;
            };

            (await getTokenImages())
              .filter((img) => !img.includes('*') && (isImage(img) || isVideo(img)))
              .forEach((variant) => {
                pushImage({ path: decodeURISafely(variant), name: getFileName(variant) });
              });
          }
        }
      }
    }

    // Perform image search if needed
    if (FULL_ACCESS) {
      let search;
      if (searchText) {
        search = searchText.length > 2 ? searchText : null;
      } else {
        if (worldHudSettings.displayOnlySharedImages || tokenActor?.getFlag('token-variants', 'disableNameSearch')) {
          // No search
        } else if (token.name.length > 2) {
          search = token.name;
        }
      }

      if (search) {
        let artSearch = await doImageSearch(search, {
          searchType: SEARCH_TYPE.TOKEN,
          searchOptions: { keywordSearch: worldHudSettings.includeKeywords },
        });

        // Merge full search, and keywords into a single array
        if (artSearch) {
          artSearch.forEach((results) => {
            results.forEach((img) => pushImage(img));
          });
        }
      }
    }
  } else {
    images = fp_files.map((f) => {
      return { path: decodeURISafely(f), name: getFileName(f) };
    });
  }

  // Retrieving the possibly custom name attached as a flag to the token
  let tokenImageName = '';
  if (token.flags['token-variants'] && token.flags['token-variants']['name']) {
    tokenImageName = token.flags['token-variants']['name'];
  } else {
    tokenImageName = getFileName(token.texture.src);
  }

  let imagesParsed = [];
  const tokenConfigs = (TVA_CONFIG.tokenConfigs || []).flat();
  const tkn = canvas.tokens.get(token._id);
  const userMappings = tkn.document.getFlag('token-variants', 'userMappings') || {};

  for (const imageObj of images) {
    const img = isImage(imageObj.path);
    const vid = isVideo(imageObj.path);

    const hasConfig = Boolean(
      tokenConfigs.find((config) => config.tvImgSrc === imageObj.path && config.tvImgName === imageObj.name)
    );
    let shared = false;
    if (TVA_CONFIG.permissions.hudFullAccess[game.user.role]) {
      actorVariants.forEach((variant) => {
        if (variant.imgSrc === imageObj.path && variant.names.includes(imageObj.name)) {
          shared = true;
        }
      });
    }

    const [title, style] = genTitleAndStyle(userMappings, imageObj.path, imageObj.name);

    imagesParsed.push({
      route: imageObj.path,
      name: imageObj.name,
      used: imageObj.path === token.texture.src && imageObj.name === tokenImageName,
      img,
      vid,
      unknownType: !img && !vid,
      shared: shared,
      hasConfig: hasConfig,
      title: title,
      style: game.user.isGM && style ? 'box-shadow: ' + style + ';' : null,
    });
  }

  //
  // Render
  //
  const imageDisplay = hudSettings.displayAsImage;
  const imageOpacity = hudSettings.imageOpacity / 100;

  const sideSelect = $(
    await renderTemplate('modules/token-variants/templates/sideSelect.html', {
      imagesParsed,
      imageDisplay,
      imageOpacity,
      tokenHud: true,
    })
  );

  // Activate listeners
  sideSelect.find('video').hover(
    function () {
      if (TVA_CONFIG.playVideoOnHover) {
        this.play();
        $(this).siblings('.fa-play').hide();
      }
    },
    function () {
      if (TVA_CONFIG.pauseVideoOnHoverOut) {
        this.pause();
        this.currentTime = 0;
        $(this).siblings('.fa-play').show();
      }
    }
  );
  sideSelect.find('.token-variants-button-select').click((event) => _onImageClick(event, token._id));

  if (FULL_ACCESS) {
    sideSelect.find('.token-variants-button-select').on('contextmenu', (event) => _onImageRightClick(event, token._id));
  }

  return sideSelect;
}

async function _onImageClick(event, tokenId) {
  event.preventDefault();
  event.stopPropagation();

  const token = canvas.tokens.controlled.find((t) => t.document.id === tokenId);
  if (!token) return;

  const worldHudSettings = TVA_CONFIG.worldHud;

  const imgButton = $(event.target).closest('.token-variants-button-select');
  const imgSrc = imgButton.attr('data-name');
  const name = imgButton.attr('data-filename');

  if (!imgSrc || !name) return;

  if (keyPressed('config') && game.user.isGM) {
    const toggleCog = (saved) => {
      const cog = imgButton.find('.fa-cog');
      if (saved) {
        cog.addClass('active');
      } else {
        cog.removeClass('active');
      }
    };
    new TokenCustomConfig(token, {}, imgSrc, name, toggleCog).render(true);
  } else if (token.document.texture.src === imgSrc) {
    let tokenImageName = token.document.getFlag('token-variants', 'name');
    if (!tokenImageName) tokenImageName = getFileName(token.document.texture.src);
    if (tokenImageName !== name) {
      await updateTokenImage(imgSrc, {
        token: token,
        imgName: name,
        animate: worldHudSettings.animate,
      });
      if (token.actor && worldHudSettings.updateActorImage) {
        if (worldHudSettings.useNameSimilarity) {
          updateActorWithSimilarName(imgSrc, name, token.actor);
        } else {
          updateActorImage(token.actor, imgSrc, { imgName: name });
        }
      }
    }
  } else {
    await updateTokenImage(imgSrc, {
      token: token,
      imgName: name,
      animate: worldHudSettings.animate,
    });
    if (token.actor && worldHudSettings.updateActorImage) {
      if (worldHudSettings.useNameSimilarity) {
        updateActorWithSimilarName(imgSrc, name, token.actor);
      } else {
        updateActorImage(token.actor, imgSrc, { imgName: name });
      }
    }
  }
}

async function _onImageRightClick(event, tokenId) {
  event.preventDefault();
  event.stopPropagation();
  let token = canvas.tokens.controlled.find((t) => t.document.id === tokenId);
  if (!token) return;

  const imgButton = $(event.target).closest('.token-variants-button-select');
  const imgSrc = imgButton.attr('data-name');
  const name = imgButton.attr('data-filename');

  if (!imgSrc || !name) return;

  if (keyPressed('config') && game.user.isGM) {
    const regenStyle = (token, img) => {
      const mappings = token.document.getFlag('token-variants', 'userMappings') || {};
      const name = imgButton.attr('data-filename');
      const [title, style] = genTitleAndStyle(mappings, img, name);
      imgButton
        .closest('.token-variants-wrap')
        .find(`.token-variants-button-select[data-name='${img}']`)
        .css('box-shadow', style)
        .prop('title', title);
    };
    new UserList(token, imgSrc, regenStyle).render(true);
  } else if (token.actor) {
    let tokenActor = game.actors.get(token.actor.id);
    let variants = getVariants(tokenActor);

    // Remove selected variant if present in the flag, add otherwise
    let del = false;
    let updated = false;
    for (let variant of variants) {
      if (variant.imgSrc === imgSrc) {
        let fNames = variant.names.filter((name) => name !== name);
        if (fNames.length === 0) {
          del = true;
        } else if (fNames.length === variant.names.length) {
          fNames.push(name);
        }
        variant.names = fNames;
        updated = true;
        break;
      }
    }
    if (del) variants = variants.filter((variant) => variant.imgSrc !== imgSrc);
    else if (!updated) variants.push({ imgSrc: imgSrc, names: [name] });

    // Set shared variants as an actor flag
    setVariants(tokenActor, variants);
    imgButton.find('.fa-share').toggleClass('active'); // Display green arrow
  }
}

async function _onImageSearchKeyUp(event, token) {
  event.preventDefault();
  event.stopPropagation();
  if (event.key === 'Enter' || event.keyCode === 13) {
    if (event.target.value.length >= 3) {
      const button = $(event.target).closest('.control-icon');
      button.find('.token-variants-wrap').remove();
      const sideSelect = await renderSideSelect(token, event.target.value);
      if (sideSelect) {
        sideSelect.addClass('active');
        button.append(sideSelect);
      }
    }
  }
}

function genTitleAndStyle(mappings, imgSrc, name) {
  let title = TVA_CONFIG.worldHud.showFullPath ? imgSrc : name;
  let style = '';
  let offset = 2;
  for (const [userId, img] of Object.entries(mappings)) {
    if (img === imgSrc) {
      const user = game.users.get(userId);
      if (!user) continue;
      if (style.length === 0) {
        style = `inset 0 0 0 ${offset}px ${user.color}`;
      } else {
        style += `, inset 0 0 0 ${offset}px ${user.color}`;
      }
      offset += 2;
      title += `\nDisplayed to: ${user.name}`;
    }
  }
  return [title, style];
}

async function updateActorWithSimilarName(imgSrc, imgName, actor) {
  const results = await findImagesFuzzy(
    imgName,
    SEARCH_TYPE.PORTRAIT,
    {
      algorithm: {
        fuzzyThreshold: 0.4,
        fuzzyLimit: 50,
      },
    },
    true
  );

  if (results && results.length !== 0) {
    updateActorImage(actor, results[0].path, { imgName: results[0].name });
  } else {
    updateActorImage(actor, imgSrc, { imgName: imgName });
  }
}

function activateStatusEffectListeners(token) {
  if (TVA_CONFIG.permissions.statusConfig[game.user.role] && token.actorId && game.actors.get(token.actorId)) {
    $('.control-icon[data-action="effects"]')
      .find('img:first')
      .click((event) => {
        event.preventDefault();
        if (keyPressed('config')) {
          event.stopPropagation();
          new EffectMappingForm(token).render(true);
        }
      });

    $('.control-icon[data-action="visibility"]')
      .find('img')
      .click((event) => {
        event.preventDefault();
        if (keyPressed('config')) {
          event.stopPropagation();
          new EffectMappingForm(token, {
            createMapping: { label: 'In Combat', expression: 'token-variants-visibility' },
          }).render(true);
        }
      });

    $('.control-icon[data-action="combat"]')
      .find('img')
      .click((event) => {
        event.preventDefault();
        if (keyPressed('config')) {
          event.stopPropagation();
          new EffectMappingForm(token, {
            createMapping: { label: 'In Combat', expression: 'token-variants-combat' },
          }).render(true);
        }
      });

    $('.status-effects')
      .find('img')
      .click((event) => {
        event.preventDefault();
        if (keyPressed('config')) {
          event.stopPropagation();

          let effectName = event.target.getAttribute('title');
          if (game.system.id === 'pf2e') {
            effectName = $(event.target).closest('picture').attr('title');
          }
          new EffectMappingForm(token, {
            createMapping: { label: effectName, expression: effectName },
          }).render(true);
        }
      });
  }
}

function getVariants(actor) {
  if (TOKEN_HUD_VARIANTS.variants) return TOKEN_HUD_VARIANTS.variants;
  else return actor?.getFlag('token-variants', 'variants') || [];
}

function setVariants(actor, variants) {
  TOKEN_HUD_VARIANTS.variants = variants;
  TOKEN_HUD_VARIANTS.actor = actor;
}
