/**
 * Adds a named stylesheet to the document with an optional ability to replace an existing one.
 *
 * @param {string} name - The unique name (ID) of the stylesheet.
 * @param {string} css - The CSS rules as a string.
 * @param {boolean} [force=false] - Whether to replace the existing stylesheet if it exists.
 * @returns {void}
 */
export function addNamedStyleSheet(name, css, force = false) {
  const existingStyleSheet = document.getElementById(name)

  if (existingStyleSheet && !force) {
    console.debug(
      `Stylesheet with name "${name}" already exists. Skipping addition.`,
    )
    return
  }

  if (existingStyleSheet && force) {
    console.debug(`Stylesheet with name "${name}" exists. Replacing...`)
    existingStyleSheet.remove()
  }

  const styleElement = document.createElement('style')
  styleElement.id = name
  styleElement.type = 'text/css'

  styleElement.appendChild(document.createTextNode(css))
  document.head.appendChild(styleElement)

  console.debug(`Stylesheet with name "${name}" added.`)
}

export const ensureMTBStyles = () => {
  const S = {
    fg: 'var(--fg-color)',
    bgi: 'var(--comfy-input-bg)',
    bgm: 'var(--comfy-menu-bg)',
    border: 'var(--comfy-border)',
    borderHover: 'var(--comfy-border-hover)',
    box: 'var(--comfy-box)',
    accent: 'var(--p-button-text-primary-color)',
  }
  const common = `
.mtb_sidebar {
  display: flex;
  flex-direction: column;
  background: ${S.bgm};
}
.mtb_img_grid {
  display: flex;
  flex-wrap: wrap;
  overflow: scroll;
  gap: 1em;
  align-items: center;
  justify-content: center;
  height: 100%;
  width: 100%;
}
.mtb_tools {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
`
  const inputs = `
/* SELECT */
.mtb_select {
    appearance: none;
  display: grid;
  grid-template-areas: "select";
  padding: 10px;
  background-color: ${S.bgi};
  border: none;
  border-radius: 5px;
  font-size: 14px;
  color: ${S.fg};
  cursor: pointer;
  width: 100%;
}

@supports (-moz-appearance:none) {
.mtb_select{
  grid-area: select;
  background: ${S.bgi} url('data:image/gif;base64,R0lGODlhBgAGAKEDAFVVVX9/f9TU1CgmNyH5BAEKAAMALAAAAAAGAAYAAAIODA4hCDKWxlhNvmCnGwUAOw==') right center no-repeat !important;
  background-position: calc(100% - 5px) center !important;
  -moz-appearance:none !important;
}

/* styling the dropdown arrow for browsers that support it */
.mtb_select:after {
  content: "";
  width: 0.8em;
  height: 0.5em;
  background-color: ${S.fg};
  clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}

.mtb_select:focus {
  outline: none;
  border-color: #0056b3;
}

.mtb_select > option {
  padding: 10px;
  background-color: ${S.bgi};
  border:none;
  color: ${S.fg};
}

.mtb_select > option:hover {
  background-color: red;
  color: ${S.fg};
}

/* SLIDER */
.mtb_slider[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 10px;
  background: ${S.bgm};
  border-radius: 5px;
  outline: none;
  opacity: 0.7;
  transition: opacity .2s;
  padding: 1em;
}

/* slider track */
.mtb_slider[type="range"]::-webkit-slider-runnable-track,
.mtb_slider[type="range"]::-moz-range-track {
  width: 100%;
  height: 10px;
  background: ${S.bgi};
  border-radius: 5px;
}


/* progress */
.mtb_slider[type="range"]::-moz-range-progress {
  background-color: ${S.accent};
  height:10px;
  border-radius: 5px;
}

/* slider thumb (the handle) */
.mtb_slider[type="range"]::-webkit-slider-thumb,
.mtb_slider[type="range"]::-moz-range-thumb
{
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: ${S.fg};
  border: none;
  cursor: pointer;
  filter: drop-shadow(1px 1px 4px black);
}

.mtb_slider[type="range"]:focus {
  opacity: 1;
}

.mtb_slider[type=range]:-moz-focusring{
    outline: 1px solid red;
    outline-offset: -1px;
}

.mtb_slider[type="range"]:hover::-webkit-slider-thumb,
.mtb_slider[type="range"]:active::-webkit-slider-thumb {
  background-color: ${S.accent};
}
`
  addNamedStyleSheet(
    'mtb_ui',
    `
${common}
${inputs}
`,
  )
}

/**
 * Creates a DOM element with optional styles, class, and id.
 *
 * @param {string} kind - The tag name of the element. Supports class and id syntax (e.g. 'div.class#id').
 * @param {Object} [style] - CSS styles to apply to the element.
 * @returns {HTMLElement} - The created DOM element.
 */
export const makeElement = (kind, style) => {
  let [real_kind, className] = kind.split('.')
  let id

  if (className?.includes('#')) {
    ;[className, id] = className.split('#')
  }

  const el = document.createElement(real_kind)

  if (style) {
    Object.assign(el.style, style)
  }

  if (className) {
    el.classList.add(...className.split(' ')) // Support multiple classes
  }

  if (id) {
    el.id = id
  }

  return el
}
/**
 * Clears all child elements of the given parent element.
 *
 * @param {HTMLElement} el - The parent element whose children should be removed.
 */
export const clearElement = (el) => {
  while (el.firstChild) {
    el.removeChild(el.firstChild)
  }
}
/**
 * Creates a labeled element (input, select, etc.).
 *
 * @param {HTMLElement} el - The element to label.
 * @param {string} labelText - The label text.
 * @returns {HTMLDivElement} - A div containing the label and the element.
 */
export const makeLabeledElement = (el, labelText) => {
  const wrapper = makeElement('div.mtb_labeled_element', {
    marginBottom: '1em',
  })
  const label = makeElement('label', {
    display: 'block',
    marginBottom: '0.5em',
  })
  label.textContent = labelText
  wrapper.appendChild(label)
  wrapper.appendChild(el)
  return wrapper
}

/**
 * Converts a camelCase CSS property to kebab-case.
 *
 * @param {string} prop - The camelCase CSS property.
 * @returns {string} - The kebab-case CSS property.
 */
const camelToKebab = (prop) =>
  prop.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)

/**
 * Parses the style string into an object of CSS property-value pairs.
 *
 * @param {string} styleString - The CSS rule text (e.g., "color: red; background-color: blue;").
 * @returns {Object} - An object with camelCase CSS properties.
 */
const parseStyleString = (styleString) => {
  const styleObj = {}
  for (const rule of styleString.split(';')) {
    const [property, value] = rule.split(':').map((item) => item.trim())
    if (property && value) {
      const camelProp = property.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
      styleObj[camelProp] = value
    }
  }
  return styleObj
}

/**
 * Defines a new CSS class with the provided styles, or skips if the class already exists.
 *
 * @param {string} className - The name of the CSS class to define.
 * @param {Object} classStyles - An object containing camelCase CSS property-value pairs.
 */
export function defineCSSClass(className, classStyles) {
  const styleSheets = document.styleSheets
  let classExists = false
  let existingStyleString = ''
  const classExistsInStyleSheet = (styleSheet) => {
    const rules = styleSheet.rules || styleSheet.cssRules
    for (const rule of rules) {
      if (rule.selectorText === `.${className}`) {
        classExists = true
        existingStyleString = rule.style.cssText // Capture existing styles
        return true
      }
    }
    return false
  }

  for (const styleSheet of styleSheets) {
    if (classExistsInStyleSheet(styleSheet)) {
      console.debug(`Class ${className} already exists, merging styles...`)
      break
    }
  }
  const existingStyles = classExists
    ? parseStyleString(existingStyleString)
    : {}
  const mergedStyles = { ...existingStyles, ...classStyles }

  const stylesString = Object.entries(mergedStyles)
    .map(([key, value]) => `${camelToKebab(key)}: ${value};`)
    .join(' ')

  if (!classExists) {
    console.debug(`Defining new class ${className}...`)
    if (styleSheets[0].insertRule) {
      styleSheets[0].insertRule(`.${className} { ${stylesString} }`, 0)
    } else if (styleSheets[0].addRule) {
      styleSheets[0].addRule(`.${className}`, stylesString, 0)
    }
  } else {
    console.debug(`Updating existing class ${className} with merged styles...`)
    for (const styleSheet of styleSheets) {
      const rules = styleSheet.rules || styleSheet.cssRules
      for (const rule of rules) {
        if (rule.selectorText === `.${className}`) {
          rule.style.cssText = stylesString // Update the existing rule
        }
      }
    }
  }

  console.debug(
    `Class ${className} has been defined/updated with styles:`,
    mergedStyles,
  )
}

/**
 * Renders a sidebar and ensures it resizes correctly when the window is resized.
 *
 * @param {HTMLElement} el - The element where the sidebar is rendered.
 * @param {HTMLElement} cont - The content container of the sidebar.
 * @param {HTMLElement[]} elems - Array of elements to append to the sidebar.
 * @returns {Object} - A handle with a method to unregister the resize event.
 */
export const renderSidebar = (el, cont, elems) => {
  el.appendChild(cont)

  if (!el.parentNode) {
    return
  }
  el.parentNode.style.overflowY = 'clip'
  cont.style.height = `${el.parentNode.offsetHeight}px`

  const resizeHandler = () => {
    cont.style.height = `${el.parentNode.offsetHeight}px`
  }
  window.addEventListener('resize', resizeHandler)

  for (const elem of elems) {
    cont.appendChild(elem)
  }

  return {
    unregister: () => {
      window.removeEventListener('resize', resizeHandler)
    },
  }
}

/**
 * Creates a <select> dropdown with given options.
 *
 * @param {string[]} options - The options for the select element.
 * @param {string} [current] - The currently selected option (optional).
 * @returns {HTMLSelectElement} - The created <select> element.
 */
export const makeSelect = (options, current = undefined) => {
  const selector = makeElement('select.mtb_select', {
    width: 'auto',
    margin: '1em',
  })

  for (const option of options) {
    const opt = makeElement('option')
    opt.value = option
    opt.innerHTML = option
    selector.appendChild(opt)
  }

  if (current !== undefined) {
    if (options.includes(current)) {
      selector.value = current
    } else {
      console.error(
        `You tried to select an option that doesn't exist (${current}). Options: ${options}`,
      )
    }
  }

  return selector
}

/**
 * Creates an <input type="range"> slider element with given parameters.
 *
 * @param {number} min - Minimum value of the slider.
 * @param {number} max - Maximum value of the slider.
 * @param {number} [value] - Initial value of the slider.
 * @param {number} [step] - Step value for the slider.
 * @returns {HTMLInputElement} - The created slider element.
 */
export const makeSlider = (min, max, value = undefined, step = undefined) => {
  const slider = makeElement('input.mtb_slider', {
    width: '100%',
  })

  slider.type = 'range'
  slider.min = min || 0
  slider.max = max || 100
  slider.value = value || slider.min
  slider.step = step || 1

  return slider
}

/**
 * Creates a button element.
 *
 * @param {string} label - The label for the button.
 * @param {Object} [style] - Optional styles to apply to the button.
 * @param {Function} [onClick] - Optional click handler.
 * @returns {HTMLButtonElement} - The created button element.
 */
export const makeButton = (label, style = {}, onClick = undefined) => {
  const button = makeElement('button.mtb_button', style)
  button.textContent = label

  if (onClick) {
    button.addEventListener('click', onClick)
  }

  return button
}

/**
 * Creates a resizable splitter between two elements.
 *
 * @param {HTMLElement} el1 - The first element.
 * @param {HTMLElement} el2 - The second element.
 * @param {'vertical' | 'horizontal'} direction - Splitter direction (vertical or horizontal).
 * @param {'absolute' | 'normal'} mode - Splitter mode: 'absolute' for free resizing, 'normal' for layout-based resizing.
 * @returns {HTMLDivElement} - The container with resizable splitter.
 */
export const makeSplitter = (
  el1,
  el2,
  direction = 'vertical',
  mode = 'normal',
) => {
  const container = makeElement('div.mtb_splitter_container', {
    display: mode === 'absolute' ? 'block' : 'flex',
    flexDirection: direction === 'vertical' ? 'row' : 'column',
    position: mode === 'absolute' ? 'relative' : 'static',
    height: '100%',
    width: '100%',
  })

  const handle = makeElement('div.mtb_splitter_handle', {
    backgroundColor: '#ccc',
    cursor: direction === 'vertical' ? 'col-resize' : 'row-resize',
    width: direction === 'vertical' ? '5px' : '100%',
    height: direction === 'horizontal' ? '5px' : '100%',
  })

  let isResizing = false

  handle.addEventListener('mousedown', () => {
    isResizing = true
  })

  window.addEventListener('mouseup', () => {
    isResizing = false
  })

  window.addEventListener('mousemove', (e) => {
    if (!isResizing) return
    if (direction === 'vertical') {
      const newWidth = e.clientX - container.offsetLeft
      el1.style.width = `${newWidth}px`
      el2.style.width = `${container.offsetWidth - newWidth}px`
    } else {
      const newHeight = e.clientY - container.offsetTop
      el1.style.height = `${newHeight}px`
      el2.style.height = `${container.offsetHeight - newHeight}px`
    }
  })

  container.appendChild(el1)
  container.appendChild(handle)
  container.appendChild(el2)

  return container
}
