const socket = io();

var priorResultsLoadState = {
  page: 0,
  pages: 1,
  per_page: 10,
  total: 20,
  offset: 0, // number of items generated since last load
  loading: false,
  initialized: false,
};

function loadPriorResults() {
  // Fix next page by offset
  let offsetPages =
    priorResultsLoadState.offset / priorResultsLoadState.per_page;
  priorResultsLoadState.page += offsetPages;
  priorResultsLoadState.pages += offsetPages;
  priorResultsLoadState.total += priorResultsLoadState.offset;
  priorResultsLoadState.offset = 0;

  if (priorResultsLoadState.loading) {
    return;
  }

  if (priorResultsLoadState.page >= priorResultsLoadState.pages) {
    return; // Nothing more to load
  }

  // Load
  priorResultsLoadState.loading = true;
  let url = new URL('/api/images', document.baseURI);
  url.searchParams.append(
    'page',
    priorResultsLoadState.initialized
      ? priorResultsLoadState.page + 1
      : priorResultsLoadState.page
  );
  url.searchParams.append('per_page', priorResultsLoadState.per_page);
  fetch(url.href, {
    method: 'GET',
    headers: new Headers({ 'content-type': 'application/json' }),
  })
    .then((response) => response.json())
    .then((data) => {
      priorResultsLoadState.page = data.page;
      priorResultsLoadState.pages = data.pages;
      priorResultsLoadState.per_page = data.per_page;
      priorResultsLoadState.total = data.total;

      data.items.forEach(function (dreamId, index) {
        let src = 'api/images/' + dreamId;
        fetch('/api/images/' + dreamId + '/metadata', {
          method: 'GET',
          headers: new Headers({ 'content-type': 'application/json' }),
        })
          .then((response) => response.json())
          .then((metadata) => {
            let seed = metadata.seed || 0; // TODO: Parse old metadata
            appendOutput(src, seed, metadata, true);
          });
      });

      // Load until page is full
      if (!priorResultsLoadState.initialized) {
        if (document.body.scrollHeight <= window.innerHeight) {
          loadPriorResults();
        }
      }
    })
    .finally(() => {
      priorResultsLoadState.loading = false;
      priorResultsLoadState.initialized = true;
    });
}

function resetForm() {
  var form = document.getElementById('generate-form');
  form.querySelector('fieldset').removeAttribute('disabled');
}

function initProgress(totalSteps, showProgressImages) {
  // TODO: Progress could theoretically come from multiple jobs at the same time (in the future)
  let progressSectionEle = document.querySelector('#progress-section');
  progressSectionEle.style.display = 'initial';
  let progressEle = document.querySelector('#progress-bar');
  progressEle.setAttribute('max', totalSteps);

  let progressImageEle = document.querySelector('#progress-image');
  progressImageEle.src = BLANK_IMAGE_URL;
  progressImageEle.style.display = showProgressImages ? 'initial' : 'none';
}

function setProgress(step, totalSteps, src) {
  let progressEle = document.querySelector('#progress-bar');
  progressEle.setAttribute('value', step);

  if (src) {
    let progressImageEle = document.querySelector('#progress-image');
    progressImageEle.src = src;
  }
}

function resetProgress(hide = true) {
  if (hide) {
    let progressSectionEle = document.querySelector('#progress-section');
    progressSectionEle.style.display = 'none';
  }
  let progressEle = document.querySelector('#progress-bar');
  progressEle.setAttribute('value', 0);
}

function toBase64(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.readAsDataURL(file);
    r.onload = () => resolve(r.result);
    r.onerror = (error) => reject(error);
  });
}

function ondragdream(event) {
  let dream = event.target.dataset.dream;
  event.dataTransfer.setData('dream', dream);
}

function seedClick(event) {
  // Get element
  var image = event.target.closest('figure').querySelector('img');
  var dream = JSON.parse(decodeURIComponent(image.dataset.dream));

  let form = document.querySelector('#generate-form');
  for (const [k, v] of new FormData(form)) {
    if (k == 'initimg') {
      continue;
    }
    let formElem = form.querySelector(`*[name=${k}]`);
    formElem.value = dream[k] !== undefined ? dream[k] : formElem.defaultValue;
  }

  document.querySelector('#seed').value = dream.seed;
  document.querySelector('#iterations').value = 1; // Reset to 1 iteration since we clicked a single image (not a full job)

  // NOTE: leaving this manual for the user for now - it was very confusing with this behavior
  // document.querySelector("#with_variations").value = variations || '';
  // if (document.querySelector("#variation_amount").value <= 0) {
  //     document.querySelector("#variation_amount").value = 0.2;
  // }

  saveFields(document.querySelector('#generate-form'));
}

function appendOutput(src, seed, config, toEnd = false) {
  let outputNode = document.createElement('figure');
  let altText = seed.toString() + ' | ' + config.prompt;

  // img needs width and height for lazy loading to work
  // TODO: store the full config in a data attribute on the image?
  const figureContents = `
        <a href="${src}" target="_blank">
            <img src="${src}"
                 alt="${altText}"
                 title="${altText}"
                 loading="lazy"
                 width="256"
                 height="256"
                 draggable="true"
                 ondragstart="ondragdream(event, this)"
                 data-dream="${encodeURIComponent(JSON.stringify(config))}"
                 data-dreamId="${encodeURIComponent(config.dreamId)}">
        </a>
        <figcaption onclick="seedClick(event, this)">${seed}</figcaption>
    `;

  outputNode.innerHTML = figureContents;

  if (toEnd) {
    document.querySelector('#results').append(outputNode);
  } else {
    document.querySelector('#results').prepend(outputNode);
  }
  document.querySelector('#no-results-message')?.remove();
}

function saveFields(form) {
  for (const [k, v] of new FormData(form)) {
    if (typeof v !== 'object') {
      // Don't save 'file' type
      localStorage.setItem(k, v);
    }
  }
}

function loadFields(form) {
  for (const [k, v] of new FormData(form)) {
    const item = localStorage.getItem(k);
    if (item != null) {
      form.querySelector(`*[name=${k}]`).value = item;
    }
  }
}

function clearFields(form) {
  localStorage.clear();
  let prompt = form.prompt.value;
  form.reset();
  form.prompt.value = prompt;
}

const BLANK_IMAGE_URL =
  'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"/>';
async function generateSubmit(form) {
  // Convert file data to base64
  // TODO: Should probably uplaod files with formdata or something, and store them in the backend?
  let formData = Object.fromEntries(new FormData(form));
  if (!formData.enable_generate && !formData.enable_init_image) {
    gen_label = document.querySelector('label[for=enable_generate]').innerHTML;
    initimg_label = document.querySelector(
      'label[for=enable_init_image]'
    ).innerHTML;
    alert(`Error: one of "${gen_label}" or "${initimg_label}" must be set`);
  }

  formData.initimg_name = formData.initimg.name;
  formData.initimg =
    formData.initimg.name !== '' ? await toBase64(formData.initimg) : null;

  // Evaluate all checkboxes
  let checkboxes = form.querySelectorAll('input[type=checkbox]');
  checkboxes.forEach(function (checkbox) {
    if (checkbox.checked) {
      formData[checkbox.name] = 'true';
    }
  });

  let strength = formData.strength;
  let totalSteps = formData.initimg
    ? Math.floor(strength * formData.steps)
    : formData.steps;
  let showProgressImages = formData.progress_images;

  // Set enabling flags

  // Initialize the progress bar
  initProgress(totalSteps, showProgressImages);

  // POST, use response to listen for events
  fetch(form.action, {
    method: form.method,
    headers: new Headers({ 'content-type': 'application/json' }),
    body: JSON.stringify(formData),
  })
    .then((response) => response.json())
    .then((data) => {
      var jobId = data.jobId;
      socket.emit('join_room', { room: jobId });
    });

  form.querySelector('fieldset').setAttribute('disabled', '');
}

function fieldSetEnableChecked(event) {
  cb = event.target;
  fields = cb.closest('fieldset');
  fields.disabled = !cb.checked;
}

// Socket listeners
socket.on('job_started', (data) => {});

socket.on('dream_result', (data) => {
  var jobId = data.jobId;
  var dreamId = data.dreamId;
  var dreamRequest = data.dreamRequest;
  var src = 'api/images/' + dreamId;

  priorResultsLoadState.offset += 1;
  appendOutput(src, dreamRequest.seed, dreamRequest);

  resetProgress(false);
});

socket.on('dream_progress', (data) => {
  // TODO: it'd be nice if we could get a seed reported here, but the generator would need to be updated
  var step = data.step;
  var totalSteps = data.totalSteps;
  var jobId = data.jobId;
  var dreamId = data.dreamId;

  var progressType = data.progressType;
  if (progressType === 'GENERATION') {
    var src = data.hasProgressImage
      ? 'api/intermediates/' + dreamId + '/' + step
      : null;
    setProgress(step, totalSteps, src);
  } else if (progressType === 'UPSCALING_STARTED') {
    // step and totalSteps are used for upscale count on this message
    document.getElementById('processing_cnt').textContent = step;
    document.getElementById('processing_total').textContent = totalSteps;
    document.getElementById('scaling-inprocess-message').style.display =
      'block';
  } else if (progressType == 'UPSCALING_DONE') {
    document.getElementById('scaling-inprocess-message').style.display = 'none';
  }
});

socket.on('job_canceled', (data) => {
  resetForm();
  resetProgress();
});

socket.on('job_done', (data) => {
  jobId = data.jobId;
  socket.emit('leave_room', { room: jobId });

  resetForm();
  resetProgress();
});

window.onload = async () => {
  document.querySelector('#prompt').addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      const form = e.target.form;
      generateSubmit(form);
    }
  });
  document.querySelector('#generate-form').addEventListener('submit', (e) => {
    e.preventDefault();
    const form = e.target;

    generateSubmit(form);
  });
  document.querySelector('#generate-form').addEventListener('change', (e) => {
    saveFields(e.target.form);
  });
  document.querySelector('#reset-seed').addEventListener('click', (e) => {
    document.querySelector('#seed').value = 0;
    saveFields(e.target.form);
  });
  document.querySelector('#reset-all').addEventListener('click', (e) => {
    clearFields(e.target.form);
  });
  document.querySelector('#remove-image').addEventListener('click', (e) => {
    initimg.value = null;
  });
  loadFields(document.querySelector('#generate-form'));

  document.querySelector('#cancel-button').addEventListener('click', () => {
    fetch('/api/cancel').catch((e) => {
      console.error(e);
    });
  });
  document.documentElement.addEventListener('keydown', (e) => {
    if (e.key === 'Escape')
      fetch('/api/cancel').catch((err) => {
        console.error(err);
      });
  });

  if (!config.gfpgan_model_exists) {
    document.querySelector('#gfpgan').style.display = 'none';
  }

  window.addEventListener('scroll', () => {
    if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight) {
      loadPriorResults();
    }
  });

  // Enable/disable forms by checkboxes
  document
    .querySelectorAll('legend > input[type=checkbox]')
    .forEach(function (cb) {
      cb.addEventListener('change', fieldSetEnableChecked);
      fieldSetEnableChecked({ target: cb });
    });

  // Load some of the previous results
  loadPriorResults();

  // Image drop/upload WIP
  /*
    let drop = document.getElementById('dropper');
    function ondrop(event) {
      let dreamData = event.dataTransfer.getData('dream');
      if (dreamData) {
        var dream = JSON.parse(decodeURIComponent(dreamData));
        alert(dream.dreamId);
      }
    };

    function ondragenter(event) {
      event.preventDefault();
    };

    function ondragover(event) {
      event.preventDefault();
    };

    function ondragleave(event) {

    }

    drop.addEventListener('drop', ondrop);
    drop.addEventListener('dragenter', ondragenter);
    drop.addEventListener('dragover', ondragover);
    drop.addEventListener('dragleave', ondragleave);
    */
};