import './style.css';

const YES = 'yes';
const EMPTY_RESULT_TEXT = 'No matching records found';

const isYes = (value) => String(value || '').toLowerCase() === YES;

const toInteger = (value, fallback = 1) => {
  const parsed = Number.parseInt(String(value), 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
};

const clamp = (value, min, max) => Math.min(max, Math.max(min, value));

const clampPageSize = (value, min, max, fallback = 8) => {
  const parsed = toInteger(value, fallback);
  return clamp(parsed, min, max);
};

const ensureSelectValue = (select, value) => {
  if (!(select instanceof HTMLSelectElement)) {
    return;
  }

  const target = String(value);
  Array.from(select.options).forEach((item) => {
    if (item.dataset.dynamic === YES && item.value !== target) {
      item.remove();
    }
  });

  const option = Array.from(select.options).find((item) => item.value === target);
  if (!option) {
    const dynamic = document.createElement('option');
    dynamic.value = target;
    dynamic.textContent = target;
    dynamic.dataset.dynamic = YES;
    select.appendChild(dynamic);
  }

  select.value = target;
};

const toComparableValue = (raw) => {
  const text = String(raw || '').trim();
  const normalized = text.replace(/[\s,$%]/g, '').replace(/,/g, '');
  const maybeNumber = Number.parseFloat(normalized);

  if (normalized !== '' && Number.isFinite(maybeNumber)) {
    return { kind: 'number', value: maybeNumber };
  }

  return { kind: 'string', value: text.toLowerCase() };
};

const compareRows = (leftRow, rightRow, columnIndex, direction, leftIndex, rightIndex) => {
  const leftCell = leftRow.cells[columnIndex]?.textContent || '';
  const rightCell = rightRow.cells[columnIndex]?.textContent || '';
  const leftValue = toComparableValue(leftCell);
  const rightValue = toComparableValue(rightCell);

  let result = 0;
  if (leftValue.kind === 'number' && rightValue.kind === 'number') {
    result = leftValue.value - rightValue.value;
  } else {
    result = String(leftValue.value).localeCompare(String(rightValue.value), undefined, { numeric: true, sensitivity: 'base' });
  }

  if (result === 0) {
    result = leftIndex - rightIndex;
  }

  return direction === 'desc' ? -result : result;
};

const applyHeaderSortClasses = (state) => {
  state.headerCells.forEach((cell, index) => {
    cell.classList.remove('is-sorted-asc', 'is-sorted-desc');
    if (state.sort.column !== index) {
      return;
    }
    cell.classList.add(state.sort.direction === 'desc' ? 'is-sorted-desc' : 'is-sorted-asc');
  });
};

const createPageButton = (label, page, options = {}) => {
  const button = document.createElement('button');
  button.type = 'button';
  button.className = 'calio-table-page';
  button.textContent = label;
  button.dataset.page = String(page);

  if (options.nav) {
    button.classList.add('is-nav');
  }

  if (options.ariaLabel) {
    button.setAttribute('aria-label', options.ariaLabel);
    button.title = options.ariaLabel;
  }

  if (options.active) {
    button.classList.add('is-active');
    button.setAttribute('aria-current', 'page');
  }

  if (options.disabled) {
    button.classList.add('is-disabled');
    button.disabled = true;
  }

  return button;
};

const buildPageItems = (totalPages, currentPage) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, index) => index + 1);
  }

  const items = [1];
  const start = Math.max(2, currentPage - 1);
  const end = Math.min(totalPages - 1, currentPage + 1);

  if (start > 2) {
    items.push('ellipsis-left');
  }

  for (let page = start; page <= end; page += 1) {
    items.push(page);
  }

  if (end < totalPages - 1) {
    items.push('ellipsis-right');
  }

  items.push(totalPages);

  return items;
};

const renderPagination = (state, totalRows) => {
  if (!state.footer.pagination) {
    return;
  }

  const paginationNode = state.footer.pagination;
  paginationNode.replaceChildren();

  if (!state.options.pagination || totalRows <= state.options.pageSize) {
    paginationNode.hidden = true;
    return;
  }

  const totalPages = Math.max(1, Math.ceil(totalRows / state.options.pageSize));
  state.page = Math.min(Math.max(1, state.page), totalPages);
  paginationNode.hidden = totalPages <= 1;

  const fragment = document.createDocumentFragment();
  fragment.appendChild(
    createPageButton('‹', state.page - 1, {
      disabled: state.page === 1,
      nav: true,
      ariaLabel: 'Previous page',
    })
  );

  const pageItems = buildPageItems(totalPages, state.page);
  pageItems.forEach((item) => {
    if (typeof item !== 'number') {
      const dots = document.createElement('span');
      dots.className = 'calio-table-page is-disabled is-ellipsis';
      dots.textContent = '...';
      fragment.appendChild(dots);
      return;
    }

    fragment.appendChild(
      createPageButton(String(item), item, {
        active: item === state.page,
        ariaLabel: `Page ${item}`,
      })
    );
  });

  fragment.appendChild(
    createPageButton('›', state.page + 1, {
      disabled: state.page === totalPages,
      nav: true,
      ariaLabel: 'Next page',
    })
  );
  paginationNode.appendChild(fragment);
};

const renderInfo = (state, totalRows, visibleRows, totalBeforeFilter) => {
  if (!state.footer.info) {
    return;
  }

  const infoNode = state.footer.info;
  if (!state.options.info) {
    infoNode.hidden = true;
    return;
  }

  infoNode.hidden = false;

  if (totalRows === 0 || visibleRows === 0) {
    infoNode.textContent = 'Showing 0 to 0 of 0 entries';
    return;
  }

  const start = state.options.pagination ? (state.page - 1) * state.options.pageSize + 1 : 1;
  const end = state.options.pagination ? Math.min(totalRows, start + visibleRows - 1) : totalRows;
  if (state.query !== '' && totalBeforeFilter > totalRows) {
    infoNode.textContent = `Showing ${start} to ${end} of ${totalRows} entries (filtered from ${totalBeforeFilter} total entries)`;
    return;
  }

  infoNode.textContent = `Showing ${start} to ${end} of ${totalRows} entries`;
};

const renderEmptyRow = (state) => {
  const row = document.createElement('tr');
  row.className = 'calio-table-no-result-row';

  const cell = document.createElement('td');
  cell.className = 'calio-table-no-result';
  cell.colSpan = state.columnCount;
  cell.textContent = EMPTY_RESULT_TEXT;

  row.appendChild(cell);
  return row;
};

const applyData = (state) => {
  const query = state.query.trim().toLowerCase();
  const totalBeforeFilter = state.rows.length;

  let rows = state.rows.filter((row) => {
    if (query === '') {
      return true;
    }
    return (row.textContent || '').toLowerCase().includes(query);
  });

  if (state.options.ordering && state.sort.column > -1) {
    rows = rows
      .map((row, index) => ({ row, index }))
      .sort((left, right) => compareRows(left.row, right.row, state.sort.column, state.sort.direction, left.index, right.index))
      .map((item) => item.row);
  }

  const totalRows = rows.length;
  let visibleRows = rows;

  if (state.options.pagination) {
    const pageCount = Math.max(1, Math.ceil(totalRows / state.options.pageSize));
    state.page = Math.min(Math.max(1, state.page), pageCount);
    const start = (state.page - 1) * state.options.pageSize;
    visibleRows = rows.slice(start, start + state.options.pageSize);
  } else {
    state.page = 1;
  }

  const fragment = document.createDocumentFragment();
  if (visibleRows.length === 0) {
    fragment.appendChild(renderEmptyRow(state));
  } else {
    visibleRows.forEach((row) => fragment.appendChild(row));
  }

  state.tbody.replaceChildren(fragment);
  applyHeaderSortClasses(state);
  renderInfo(state, totalRows, visibleRows.length, totalBeforeFilter);
  renderPagination(state, totalRows);
};

const bindOrdering = (state) => {
  if (!state.options.ordering) {
    return;
  }

  state.headerCells.forEach((cell, index) => {
    cell.classList.add('is-sortable');
    cell.setAttribute('tabindex', '0');
    cell.setAttribute('role', 'button');

    const onSort = () => {
      if (state.sort.column === index) {
        state.sort.direction = state.sort.direction === 'asc' ? 'desc' : 'asc';
      } else {
        state.sort.column = index;
        state.sort.direction = 'asc';
      }
      state.page = 1;
      applyData(state);
    };

    cell.addEventListener('click', onSort);
    cell.addEventListener('keydown', (event) => {
      if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        onSort();
      }
    });
  });
};

const bindSearch = (state) => {
  if (!state.options.search || !state.searchInput) {
    return;
  }

  let inputTimer = null;
  state.searchInput.addEventListener('input', () => {
    if (inputTimer) {
      window.clearTimeout(inputTimer);
    }
    inputTimer = window.setTimeout(() => {
      state.query = state.searchInput.value || '';
      state.page = 1;
      applyData(state);
    }, 90);
  });
};

const normalizeTable = (widget, table) => {
  table.classList.add('calio-table-element');

  if (!table.tBodies || table.tBodies.length === 0) {
    const body = document.createElement('tbody');

    const rows = Array.from(table.querySelectorAll('tr')).filter((row) => {
      const parentTag = row.parentElement?.tagName || '';
      return parentTag !== 'THEAD' && parentTag !== 'TFOOT';
    });

    rows.forEach((row) => body.appendChild(row));
    table.appendChild(body);
  }

  if (!table.tHead && !widget.classList.contains('is-hide-header')) {
    const firstRow = table.tBodies[0]?.rows?.[0];
    if (firstRow) {
      const header = document.createElement('thead');
      const headerRow = document.createElement('tr');
      Array.from(firstRow.cells).forEach((cell) => {
        const th = document.createElement('th');
        th.scope = 'col';
        th.replaceChildren(...Array.from(cell.childNodes).map((node) => node.cloneNode(true)));
        headerRow.appendChild(th);
      });
      header.appendChild(headerRow);
      table.insertBefore(header, table.firstChild);
      firstRow.remove();
    }
  }

  if (table.tHead && table.tHead.rows.length === 0 && !widget.classList.contains('is-hide-header')) {
    const firstRow = table.tBodies[0]?.rows?.[0];
    if (firstRow) {
      const headerRow = document.createElement('tr');
      Array.from(firstRow.cells).forEach((cell) => {
        const th = document.createElement('th');
        th.scope = 'col';
        th.replaceChildren(...Array.from(cell.childNodes).map((node) => node.cloneNode(true)));
        headerRow.appendChild(th);
      });
      table.tHead.appendChild(headerRow);
      firstRow.remove();
    }
  }
};

const initWidget = (widget, force = false) => {
  if (!(widget instanceof HTMLElement)) {
    return;
  }

  if (!force && widget.dataset.calioTableInit === YES) {
    return;
  }

  widget.dataset.calioTableInit = YES;

  if (!isYes(widget.dataset.datatable)) {
    return;
  }

  const table = widget.querySelector('table');
  if (!(table instanceof HTMLTableElement)) {
    return;
  }

  normalizeTable(widget, table);

  const tbody = table.tBodies?.[0];
  if (!(tbody instanceof HTMLTableSectionElement)) {
    return;
  }

  const headerCells = Array.from(table.tHead?.rows?.[0]?.cells || []);
  const rows = Array.from(tbody.rows);
  const searchInput = widget.querySelector('.calio-table-search');
  const lengthSelect = widget.querySelector('.calio-table-length');
  const lengthCustomInput = widget.querySelector('.calio-table-length-custom');
  const info = widget.querySelector('.calio-table-info');
  const pagination = widget.querySelector('.calio-table-pagination');

  const minPageSize = toInteger(widget.dataset.pageSizeMin, 1);
  const maxPageSize = Math.max(minPageSize, toInteger(widget.dataset.pageSizeMax, 200));
  const defaultPageSize = toInteger(widget.dataset.pageSize, 8);

  let initialPageSize = defaultPageSize;
  if (lengthSelect instanceof HTMLSelectElement) {
    initialPageSize = toInteger(lengthSelect.value, initialPageSize);
  }
  if (lengthCustomInput instanceof HTMLInputElement) {
    initialPageSize = toInteger(lengthCustomInput.value, initialPageSize);
  }
  initialPageSize = clampPageSize(initialPageSize, minPageSize, maxPageSize, defaultPageSize);

  if (lengthCustomInput instanceof HTMLInputElement) {
    lengthCustomInput.value = String(initialPageSize);
  }
  if (lengthSelect instanceof HTMLSelectElement) {
    ensureSelectValue(lengthSelect, initialPageSize);
  }

  const state = {
    widget,
    table,
    tbody,
    rows,
    headerCells,
    searchInput: searchInput instanceof HTMLInputElement ? searchInput : null,
    query: '',
    sort: {
      column: -1,
      direction: 'asc',
    },
    page: 1,
    columnCount: Math.max(
      headerCells.length,
      rows[0]?.cells?.length || 1
    ),
    options: {
      search: isYes(widget.dataset.search),
      ordering: isYes(widget.dataset.ordering) && headerCells.length > 0,
      pagination: isYes(widget.dataset.pagination),
      info: isYes(widget.dataset.info),
      lengthMenu: isYes(widget.dataset.lengthMenu),
      lengthCustom: isYes(widget.dataset.lengthCustom),
      pageSize: initialPageSize,
      pageSizeMin: minPageSize,
      pageSizeMax: maxPageSize,
    },
    footer: {
      info: info instanceof HTMLElement ? info : null,
      pagination: pagination instanceof HTMLElement ? pagination : null,
    },
  };

  bindOrdering(state);
  bindSearch(state);

  if (state.options.lengthMenu && lengthSelect instanceof HTMLSelectElement) {
    lengthSelect.addEventListener('change', () => {
      state.options.pageSize = clampPageSize(
        lengthSelect.value,
        state.options.pageSizeMin,
        state.options.pageSizeMax,
        state.options.pageSize
      );
      if (lengthCustomInput instanceof HTMLInputElement) {
        lengthCustomInput.value = String(state.options.pageSize);
      }
      ensureSelectValue(lengthSelect, state.options.pageSize);
      state.page = 1;
      applyData(state);
    });
  }

  if (state.options.lengthCustom && lengthCustomInput instanceof HTMLInputElement) {
    let customTimer = null;

    const applyCustomValue = () => {
      if (customTimer) {
        window.clearTimeout(customTimer);
        customTimer = null;
      }

      state.options.pageSize = clampPageSize(
        lengthCustomInput.value,
        state.options.pageSizeMin,
        state.options.pageSizeMax,
        state.options.pageSize
      );
      lengthCustomInput.value = String(state.options.pageSize);
      if (lengthSelect instanceof HTMLSelectElement) {
        ensureSelectValue(lengthSelect, state.options.pageSize);
      }
      state.page = 1;
      applyData(state);
    };

    lengthCustomInput.addEventListener('change', applyCustomValue);
    lengthCustomInput.addEventListener('input', () => {
      if (customTimer) {
        window.clearTimeout(customTimer);
      }

      customTimer = window.setTimeout(() => {
        applyCustomValue();
      }, 220);
    });
    lengthCustomInput.addEventListener('blur', applyCustomValue);
    lengthCustomInput.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        event.preventDefault();
        applyCustomValue();
      }
    });
  }

  if (state.footer.pagination) {
    state.footer.pagination.addEventListener('click', (event) => {
      const target = event.target;
      if (!(target instanceof HTMLElement)) {
        return;
      }

      const button = target.closest('.calio-table-page');
      if (!(button instanceof HTMLButtonElement)) {
        return;
      }

      if (button.classList.contains('is-disabled')) {
        return;
      }

      const nextPage = toInteger(button.dataset.page, state.page);
      if (nextPage !== state.page) {
        state.page = nextPage;
        applyData(state);
      }
    });
  }

  applyData(state);
};

const initScope = (scope = document, options = {}) => {
  const force = Boolean(options.force);
  const root = scope instanceof Element ? scope : document;
  root.querySelectorAll('.calio-table-widget').forEach((widget) => {
    initWidget(widget, force);
  });
};

document.addEventListener('DOMContentLoaded', () => {
  initScope(document);
});

let calioTableHookBound = false;
const bindElementorTable = () => {
  if (calioTableHookBound) {
    return true;
  }

  if (!window.elementorFrontend?.hooks) {
    return false;
  }

  window.elementorFrontend.hooks.addAction('frontend/element_ready/calio-table.default', ($scope) => {
    const root = $scope?.[0] || null;
    if (root) {
      initScope(root, { force: true });
    }
  });

  calioTableHookBound = true;
  return true;
};

window.addEventListener('elementor/frontend/init', bindElementorTable);
if (!bindElementorTable()) {
  let attempts = 0;
  const timer = window.setInterval(() => {
    attempts += 1;
    if (bindElementorTable() || attempts >= 40) {
      window.clearInterval(timer);
    }
  }, 250);
}
