/* @license This file Copyright © 2020-2023 Mnemosyne LLC.
   It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
   or any future license endorsed by Mnemosyne LLC.
   License text can be found in the licenses/ folder. */

import isEqual from 'lodash.isequal';

export const Utils = {
  /** Given a numerator and denominator, return a ratio string */
  ratio(numerator, denominator) {
    let result = Math.floor((100 * numerator) / denominator) / 100;

    // check for special cases
    if (
      result === Number.POSITIVE_INFINITY ||
      result === Number.NEGATIVE_INFINITY
    ) {
      result = -2;
    } else if (Number.isNaN(result)) {
      result = -1;
    }

    return result;
  },

  /**
   * Checks to see if the content actually changed before poking the DOM.
   */
  setInnerHTML(e, html) {
    if (!e) {
      return;
    }

    /* innerHTML is listed as a string, but the browser seems to change it.
     * For example, "&infin;" gets changed to "∞" somewhere down the line.
     * So, let's use an arbitrary  different field to test our state... */
    if (e.currentHTML !== html) {
      e.currentHTML = html;
      e.innerHTML = html;
    }
  },
};

function toggleClass(buttons, button, pages, page, callback) {
  for (const element of buttons.children) {
    element.classList.toggle('selected', element === button);
  }
  for (const element of pages.children) {
    element.classList.toggle('hidden', element !== page);
  }
  if (callback) {
    callback(page);
  }
}

export function createTextualTabsContainer(id, tabs, callback) {
  const root = document.createElement('div');
  root.id = id;
  root.classList.add('tabs-container');

  const buttons = document.createElement('div');
  buttons.classList.add('tabs-buttons');
  root.append(buttons);

  const pages = document.createElement('div');
  pages.classList.add('tabs-pages');
  root.append(pages);

  const button_array = [];
  for (const [button_id, page, tabname] of tabs) {
    const button = document.createElement('button');
    button.id = button_id;
    button.classList.add('tabs-button');
    button.setAttribute('type', 'button');
    button.textContent = tabname;
    buttons.append(button);
    button_array.push(button);

    page.classList.add('hidden', 'tabs-page');
    pages.append(page);

    button.addEventListener('click', () =>
      toggleClass(buttons, button, pages, page, callback)
    );
  }

  button_array[0].classList.add('selected');
  pages.children[0].classList.remove('hidden');

  return {
    buttons: button_array,
    root,
  };
}

export function createTabsContainer(id, tabs, callback) {
  const root = document.createElement('div');
  root.id = id;
  root.classList.add('tabs-container');

  const buttons = document.createElement('div');
  buttons.classList.add('tabs-buttons');
  root.append(buttons);

  const pages = document.createElement('div');
  pages.classList.add('tabs-pages');
  root.append(pages);

  const button_array = [];
  for (const [button_id, page] of tabs) {
    const button = document.createElement('button');
    button.id = button_id;
    button.classList.add('tabs-button');
    button.setAttribute('type', 'button');
    buttons.append(button);
    button_array.push(button);

    page.classList.add('hidden', 'tabs-page');
    pages.append(page);

    button.addEventListener('click', () =>
      toggleClass(buttons, button, pages, page, callback)
    );
  }

  button_array[0].classList.add('selected');
  pages.children[0].classList.remove('hidden');

  return {
    buttons: button_array,
    root,
  };
}

export function createDialogContainer(id) {
  const root = document.createElement('dialog');
  root.classList.add('dialog-container', 'popup', id);
  root.open = true;
  root.setAttribute('role', 'dialog');

  const win = document.createElement('div');
  win.classList.add('dialog-window');
  root.append(win);

  const logo = document.createElement('div');
  logo.classList.add('dialog-logo');
  win.append(logo);

  const heading = document.createElement('div');
  heading.classList.add('dialog-heading');
  win.append(heading);

  const message = document.createElement('div');
  message.classList.add('dialog-message');
  win.append(message);

  const workarea = document.createElement('div');
  workarea.classList.add('dialog-workarea');
  win.append(workarea);

  const buttons = document.createElement('div');
  buttons.classList.add('dialog-buttons');
  win.append(buttons);

  const bbegin = document.createElement('span');
  bbegin.classList.add('dialog-buttons-begin');
  buttons.append(bbegin);

  const dismiss = document.createElement('button');
  dismiss.classList.add('dialog-dismiss-button');
  dismiss.textContent = 'Cancel';
  buttons.append(dismiss);

  const confirm = document.createElement('button');
  confirm.textContent = 'OK';
  buttons.append(confirm);

  const bend = document.createElement('span');
  bend.classList.add('dialog-buttons-end');
  buttons.append(bend);

  return {
    confirm,
    dismiss,
    heading,
    message,
    root,
    workarea,
  };
}

export function makeUUID() {
  // source: https://stackoverflow.com/a/2117523/6568470
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
    (
      c ^
      (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
    ).toString(16)
  );
}

export function createSection(title) {
  const root = document.createElement('fieldset');
  root.classList.add('section');

  const legend = document.createElement('legend');
  legend.classList.add('title');
  legend.textContent = title;
  root.append(legend);

  const content = document.createElement('div');
  content.classList.add('content');
  root.append(content);

  return { content, root };
}

export function createInfoSection(title, labels) {
  const children = [];
  const { root, content } = createSection(title);

  for (const label_text of labels) {
    const label_element = document.createElement('label');
    label_element.textContent = label_text;
    content.append(label_element);

    const item = document.createElement('div');
    item.id = makeUUID();
    content.append(item);
    label_element.setAttribute('for', item.id);
    children.push(item);
  }

  return { children, root };
}

export function debounce(callback, wait = 100) {
  let timeout = null;
  return (...arguments_) => {
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        callback(...arguments_);
      }, wait);
    }
  };
}

export function deepEqual(a, b) {
  return isEqual(a, b);
}

function setOrDeleteAttribute(element, attribute, b) {
  if (b) {
    element.setAttribute(attribute, true);
  } else {
    element.removeAttribute(attribute);
  }
}
export function setEnabled(element, b) {
  setOrDeleteAttribute(element, 'disabled', !b);
}
export function setChecked(element, b) {
  setOrDeleteAttribute(element, 'checked', b);
}

export function addCheckedClass(element, b) {
  element.classList.toggle('checked', b);
}

function getBestMenuPos(r, bounds) {
  let { x, y } = r;
  const { width, height } = r;

  if (x > bounds.x + bounds.width - width && x - width >= bounds.x) {
    x -= width;
  } else {
    x = Math.min(x, bounds.x + bounds.width - width);
  }

  if (y > bounds.y + bounds.height - height && y - height >= bounds.y) {
    y -= height;
  } else {
    y = Math.min(y, bounds.y + bounds.height - height);
  }

  return new DOMRect(x, y, width, height);
}

export function movePopup(popup, x, y, boundingElement) {
  const initial_pos = new DOMRect(x, y, popup.clientWidth, popup.clientHeight);
  const clamped_pos = getBestMenuPos(
    initial_pos,
    boundingElement.getBoundingClientRect()
  );
  popup.style.left = `${clamped_pos.left}px`;
  popup.style.top = `${clamped_pos.top}px`;
}

export class OutsideClickListener extends EventTarget {
  constructor(element) {
    super();
    this.listener = (event_) => {
      if (!element.contains(event_.target)) {
        this.dispatchEvent(new MouseEvent(event_.type, event_));
        event_.preventDefault();
      }
    };
    Object.seal(this);
    this.start();
  }
  start() {
    setTimeout(() => document.addEventListener('click', this.listener), 0);
  }
  stop() {
    document.removeEventListener('click', this.listener);
  }
}

export function setTextContent(e, text) {
  if (e.textContent !== text) {
    e.textContent = text;
  }
}
