/**
 * Fetch and display preview images for various image upload forms.
 */

import { assertNotNull } from './utils/assert';
import { fetchJson, handleError } from './utils/requests';
import { $, $$, clearEl, hideEl, makeEl, showEl } from './utils/dom';
import { addTag } from './tagsinput';
import { oncePersistedPageShown } from './utils/events';

const MATROSKA_MAGIC = 0x1a45dfa3;

function scrapeUrl(url) {
  return fetchJson('POST', '/images/scrape', { url })
    .then(handleError)
    .then(response => response.json());
}

function elementForEmbeddedImage({ camo_url, type }) {
  // The upload was fetched from the scraper and is a path name
  if (typeof camo_url === 'string') {
    return makeEl('img', { className: 'scraper-preview--image', src: camo_url });
  }

  // The upload was fetched from a file input and is an ArrayBuffer
  const objectUrl = URL.createObjectURL(new Blob([camo_url], { type }));
  const tagName = new DataView(camo_url).getUint32(0) === MATROSKA_MAGIC ? 'video' : 'img';
  return makeEl(tagName, { className: 'scraper-preview--image', src: objectUrl });
}

function setupImageUpload() {
  const imgPreviews = $('#js-image-upload-previews');
  if (!imgPreviews) return;

  const form = imgPreviews.closest('form');
  const [fileField, remoteUrl, scraperError] = $$('.js-scraper', form);
  const descrEl = $('.js-image-descr-input', form);
  const tagsEl = $('.js-image-tags-input', form);
  const sourceEl = $$('.js-source-url', form).find(input => input.value === '');
  const fetchButton = $('#js-scraper-preview');
  if (!fetchButton) return;

  function showImages(images) {
    clearEl(imgPreviews);

    images.forEach((image, index) => {
      const img = elementForEmbeddedImage(image);
      const imgWrap = makeEl('span', { className: 'scraper-preview--image-wrapper' });
      imgWrap.appendChild(img);

      const label = makeEl('label', { className: 'scraper-preview--label' });
      const radio = makeEl('input', {
        type: 'radio',
        className: 'scraper-preview--input',
      });
      if (image.url) {
        radio.name = 'scraper_cache';
        radio.value = image.url;
      }
      if (index === 0) {
        radio.checked = true;
      }
      label.appendChild(radio);
      label.appendChild(imgWrap);
      imgPreviews.appendChild(label);
    });
  }

  function showError() {
    clearEl(imgPreviews);
    showEl(scraperError);
    enableFetch();
  }

  function hideError() {
    hideEl(scraperError);
  }

  function disableFetch() {
    fetchButton.setAttribute('disabled', '');
  }

  function enableFetch() {
    fetchButton.removeAttribute('disabled');
  }

  const reader = new FileReader();

  reader.addEventListener('load', event => {
    showImages([
      {
        camo_url: event.target.result,
        type: fileField.files[0].type,
      },
    ]);

    // Clear any currently cached data, because the file field
    // has higher priority than the scraper:
    remoteUrl.value = '';
    disableFetch();
    hideError();
  });

  // Watch for files added to the form
  fileField.addEventListener('change', () => {
    fileField.files.length && reader.readAsArrayBuffer(fileField.files[0]);
  });

  // Watch for [Fetch] clicks
  fetchButton.addEventListener('click', () => {
    if (!remoteUrl.value) return;

    disableFetch();

    scrapeUrl(remoteUrl.value)
      .then(data => {
        if (data === null) {
          scraperError.innerText = 'No image found at that address.';
          showError();
          return;
        } else if (data.errors && data.errors.length > 0) {
          scraperError.innerText = data.errors.join(' ');
          showError();
          return;
        }

        hideError();

        // Set source
        if (sourceEl) sourceEl.value = sourceEl.value || data.source_url || '';
        // Set description
        if (descrEl) descrEl.value = descrEl.value || data.description || '';
        // Add author
        if (tagsEl && data.author_name) addTag(tagsEl, `artist:${data.author_name.toLowerCase()}`);
        // Clear selected file, if any
        fileField.value = '';
        showImages(data.images);

        enableFetch();
      })
      .catch(showError);
  });

  // Fetch on "enter" in url field
  remoteUrl.addEventListener('keydown', event => {
    if (event.keyCode === 13) {
      // Hit enter
      fetchButton.click();
    }
  });

  // Enable/disable the fetch button based on content in the image scraper. Fetching with no URL makes no sense.
  function setFetchEnabled() {
    if (remoteUrl.value.length > 0) {
      enableFetch();
    } else {
      disableFetch();
    }
  }

  remoteUrl.addEventListener('input', () => setFetchEnabled());
  setFetchEnabled();

  // Catch unintentional navigation away from the page

  function beforeUnload(event) {
    // Chrome requires returnValue to be set
    event.preventDefault();
    event.returnValue = '';
  }

  function registerBeforeUnload() {
    window.addEventListener('beforeunload', beforeUnload);
  }

  function unregisterBeforeUnload() {
    window.removeEventListener('beforeunload', beforeUnload);
  }

  function createTagError(message) {
    const buttonAfter = $('#tagsinput-save');
    const errorElement = makeEl('span', { className: 'help-block tag-error', innerText: message });

    buttonAfter.insertAdjacentElement('beforebegin', errorElement);
  }

  function clearTagErrors() {
    $$('.tag-error').forEach(el => el.remove());
  }

  const ratingsTags = ['safe', 'suggestive', 'questionable', 'explicit', 'semi-grimdark', 'grimdark', 'grotesque'];

  // populate tag error helper bars as necessary
  // return true if all checks pass
  // return false if any check fails
  function validateTags() {
    const tagInput = $('textarea.js-taginput');

    if (!tagInput) {
      return true;
    }

    const tagsArr = tagInput.value.split(',').map(t => t.trim());

    const errors = [];

    let hasRating = false;
    let hasSafe = false;
    let hasOtherRating = false;

    tagsArr.forEach(tag => {
      if (ratingsTags.includes(tag)) {
        hasRating = true;
        if (tag === 'safe') {
          hasSafe = true;
        } else {
          hasOtherRating = true;
        }
      }
    });

    if (!hasRating) {
      errors.push('Tag input must contain at least one rating tag');
    } else if (hasSafe && hasOtherRating) {
      errors.push('Tag input may not contain any other rating if safe');
    }

    if (tagsArr.length < 3) {
      errors.push('Tag input must contain at least 3 tags');
    }

    errors.forEach(msg => createTagError(msg));

    return errors.length === 0; // true: valid if no errors
  }

  function disableUploadButton() {
    const submitButton = $('.button.input--separate-top');

    if (!submitButton) {
      return;
    }

    const originalButtonText = submitButton.innerText;

    submitButton.disabled = true;
    submitButton.innerText = 'Please wait...';

    // delay is needed because Safari stops the submit if the button is immediately disabled
    requestAnimationFrame(() => submitButton.setAttribute('disabled', 'disabled'));

    // Rolling back the disabled state when user navigated back to the form.
    oncePersistedPageShown(() => {
      if (!submitButton.disabled) {
        return;
      }

      submitButton.disabled = false;
      submitButton.innerText = originalButtonText;

      submitButton.removeAttribute('disabled');
    });
  }

  function submitHandler(event) {
    // Remove any existing tag error elements
    clearTagErrors();

    if (validateTags()) {
      // Disable navigation check
      unregisterBeforeUnload();

      // Prevent duplicate attempts to submit the form
      disableUploadButton();

      // Let the form submission complete
    } else {
      // Scroll to view validation errors
      assertNotNull($('.fancy-tag-upload')).scrollIntoView();

      // Prevent the form from being submitted
      event.preventDefault();
    }
  }

  fileField.addEventListener('change', registerBeforeUnload);
  fetchButton.addEventListener('click', registerBeforeUnload);
  form.addEventListener('submit', submitHandler);
}

export { setupImageUpload };