import { warnOnce } from './app-dom.js';

export function createServerGrid(options) {
  const {
    container,
    dataUrl,
    appBase = '/',
    columns,
    mapData,
    total = (data) => data.total,
    sortColumns = [],
    paginationLimit = 10,
    language = {},
    search = null,
    filters = [],
    urlSync = true,
    urlState = null,
    gridjs,
    rowDblClick = null,
    actions = null,
    rowDataset = null,
    selection = null
  } = options || {};

  const gridjsLib = gridjs || window.gridjs;
  const containerEl = typeof container === 'string' ? document.querySelector(container) : container;
  if (!containerEl || !gridjsLib) {
    if (!containerEl) {
      warnOnce('UI_EL_MISSING', `Missing grid container: ${container}`, { module: 'grid-factory' });
    }
    if (!gridjsLib) {
      warnOnce('UI_LIB_MISSING', 'Grid.js not available', { module: 'grid-factory' });
    }
    return null;
  }

  const params = new URLSearchParams(window.location.search);
  const urlStateConfig = {
    pageParam: 'page',
    sortOrderParam: 'order',
    sortDirParam: 'dir',
    cleanFirstPage: true,
    syncPage: true,
    syncSort: true,
    ...(urlState || {})
  };
  const pageParam = urlStateConfig.pageParam || 'page';
  const sortOrderParam = urlStateConfig.sortOrderParam || 'order';
  const sortDirParam = urlStateConfig.sortDirParam || 'dir';
  const resolveUrl = (path) => new URL(path, appBase).toString();
  const addParam = (url, key, value) => {
    const nextUrl = new URL(url);
    nextUrl.searchParams.set(key, value);
    return nextUrl.toString();
  };
  const deleteParam = (url, key) => {
    const nextUrl = new URL(url);
    nextUrl.searchParams.delete(key);
    return nextUrl.toString();
  };
  const clearSortParams = (url) => deleteParam(deleteParam(url, sortOrderParam), sortDirParam);
  const parsePage = (rawValue) => {
    const parsed = Number.parseInt(String(rawValue ?? ''), 10);
    if (!Number.isFinite(parsed) || parsed < 1) {
      return 1;
    }
    return parsed;
  };
  const validSortKeys = new Set(
    (sortColumns || [])
      .filter((key) => typeof key === 'string')
      .map((key) => key.trim())
      .filter(Boolean)
  );
  const normalizeDir = (rawValue) => {
    const normalized = String(rawValue ?? '').trim().toLowerCase();
    return normalized === 'asc' || normalized === 'desc' ? normalized : '';
  };
  let currentPage = urlStateConfig.syncPage ? parsePage(params.get(pageParam)) : 1;
  let currentSort = null;
  if (urlStateConfig.syncSort) {
    const initialOrder = String(params.get(sortOrderParam) ?? '').trim();
    const initialDir = normalizeDir(params.get(sortDirParam));
    if (initialOrder !== '' && initialDir !== '' && validSortKeys.has(initialOrder)) {
      currentSort = { order: initialOrder, dir: initialDir };
    }
  }

  const getInput = (input) => {
    if (!input) {return null;}
    return typeof input === 'string' ? document.querySelector(input) : input;
  };

  const getInputValue = (input) => {
    if (!input) {return '';}
    if (input.type === 'checkbox') {
      return input.checked ? (input.value || '1') : '';
    }
    if (input.tagName === 'SELECT' && input.multiple) {
      return Array.from(input.selectedOptions || []).map((option) => option.value).filter(Boolean);
    }
    return (input.value ?? '').toString();
  };

  const setInputValue = (input, value) => {
    if (!input) {return;}
    if (input.type === 'checkbox') {
      input.checked = value === true || value === 'true' || value === '1';
    } else if (input.tagName === 'SELECT' && input.multiple) {
      const values = Array.isArray(value)
        ? value.map((item) => item.toString())
        : value
          ? value.toString().split(',').map((item) => item.trim()).filter(Boolean)
          : [];
      Array.from(input.options || []).forEach((option) => {
        option.selected = values.includes(option.value);
      });
    } else {
      input.value = value;
    }
    if (input._multiSelectInstance && typeof input._multiSelectInstance.setValues === 'function') {
      const values = Array.isArray(value)
        ? value.map((item) => item.toString())
        : value
          ? value.toString().split(',').map((item) => item.trim()).filter(Boolean)
          : [];
      input._multiSelectInstance.setValues(values);
    }
  };

  let searchValue = '';
  let searchParam = 'search';
  let searchInput = null;
  let searchDebounce = 250;
  if (search) {
    searchInput = getInput(search.input);
    searchParam = search.param || searchParam;
    searchDebounce = search.debounce ?? searchDebounce;
    const initial = params.get(searchParam) ?? search.default ?? '';
    searchValue = initial || '';
    if (searchInput) {
      setInputValue(searchInput, initial);
    }
  }

  const filterState = filters.map((filter) => {
    const input = getInput(filter.input);
    const initial = params.get(filter.param);
    const value = initial !== null ? initial : (filter.default ?? '');
    if (input) {
      const parsed = typeof filter.parse === 'function' ? filter.parse(value) : value;
      setInputValue(input, parsed);
    }
    return { ...filter, input, value };
  });

  const normalizeValue = (filter, value) => {
    if (typeof filter.normalize === 'function') {
      return filter.normalize(value);
    }
    return value;
  };

  const buildDataParams = () => {
    const query = new URLSearchParams();
    if (searchValue) {
      query.set(searchParam, searchValue);
    }
    filterState.forEach((filter) => {
      const raw = getInputValue(filter.input) || filter.value || '';
      const normalized = normalizeValue(filter, raw);
      if (Array.isArray(normalized)) {
        const list = normalized.map((item) => item.toString()).filter(Boolean);
        if (list.length) {
          query.set(filter.param, list.join(','));
        }
        return;
      }
      if (normalized !== '' && normalized !== null && typeof normalized !== 'undefined') {
        query.set(filter.param, normalized);
      }
    });
    if (currentSort?.order && currentSort?.dir) {
      query.set(sortOrderParam, currentSort.order);
      query.set(sortDirParam, currentSort.dir);
    }
    return query;
  };

  const buildUrlParams = () => {
    const query = buildDataParams();
    if (!urlStateConfig.syncSort) {
      query.delete(sortOrderParam);
      query.delete(sortDirParam);
    }
    if (urlStateConfig.syncPage) {
      if (urlStateConfig.cleanFirstPage && currentPage <= 1) {
        query.delete(pageParam);
      } else {
        query.set(pageParam, String(currentPage));
      }
    } else {
      query.delete(pageParam);
    }
    return query;
  };

  const baseUrl = () => {
    const url = new URL(dataUrl, appBase);
    const query = buildDataParams();
    query.forEach((value, key) => url.searchParams.set(key, value));
    return url.toString();
  };

  const updateUrl = () => {
    if (!urlSync) {return;}
    const url = new URL(window.location.href);
    if (searchParam) { url.searchParams.delete(searchParam); }
    filterState.forEach((filter) => url.searchParams.delete(filter.param));
    url.searchParams.delete(sortOrderParam);
    url.searchParams.delete(sortDirParam);
    url.searchParams.delete(pageParam);
    const query = buildUrlParams();
    query.forEach((value, key) => url.searchParams.set(key, value));
    window.history.replaceState({}, '', url.toString());
  };

  const actionConfig = actions && actions.enabled ? {
    label: actions.label || 'Actions',
    index: Number.isInteger(actions.index) ? actions.index : null,
    items: Array.isArray(actions.items) ? actions.items : [],
    getRowId: typeof actions.getRowId === 'function' ? actions.getRowId : null,
    wrapperClass: actions.wrapperClass || 'grid-actions',
    onAction: typeof actions.onAction === 'function' ? actions.onAction : null
  } : null;
  const selectionConfig = selection && selection.enabled ? {
    id: selection.id || 'selectRow',
    index: Number.isInteger(selection.index) ? selection.index : 0,
    component: selection.component || null,
    props: selection.props || null,
    onChange: typeof selection.onChange === 'function' ? selection.onChange : null,
    getSelectedIds: typeof selection.getSelectedIds === 'function' ? selection.getSelectedIds : null,
    selectAllLabel: selection.selectAllLabel || 'Select all',
    selectAllEnabled: selection.selectAllEnabled !== false
  } : null;

  const insertActionColumn = (cols) => {
    if (!actionConfig) {return cols;}
    const actionColumn = {
      name: actionConfig.label,
      sort: false,
      formatter: (_cell, row) => {
        const id = actionConfig.getRowId ? actionConfig.getRowId(row) : null;
        if (!id) {
          return gridjsLib.html('');
        }
        const buttons = actionConfig.items.map((item) => {
          if (!item || !item.key) {return '';}
          const label = typeof item.label === 'function' ? item.label(row, id) : item.label;
          if (!label) {return '';}
          const className = item.className ? ` ${item.className}` : '';
          const type = item.type || 'button';
          if (type === 'link') {
            const href = typeof item.href === 'function' ? item.href({ row, id }) : item.href;
            const url = href || '#';
            return `<a role="button" class="${className.trim()}" data-grid-action="${item.key}" href="${url}">${label}</a>`;
          }
          return `<button type="button" class="${className.trim()}" data-grid-action="${item.key}">${label}</button>`;
        }).join('');
        return gridjsLib.html(`<div class="${actionConfig.wrapperClass}" role="group">${buttons}</div>`);
      }
    };

    const next = [...cols];
    const index = actionConfig.index === null ? next.length : Math.min(Math.max(actionConfig.index, 0), next.length);
    next.splice(index, 0, actionColumn);
    return next;
  };

  const insertSelectionColumn = (cols) => {
    if (!selectionConfig) {return cols;}
    const headerLabel = selectionConfig.selectAllLabel || 'Select all';
    const header = selectionConfig.selectAllEnabled
      ? gridjsLib.html(`<input type="checkbox" data-grid-select-all aria-label="${headerLabel}">`)
      : '';
    const selectionColumn = {
      id: selectionConfig.id,
      name: header,
      sort: false,
      plugin: {
        id: selectionConfig.id
      }
    };

    const next = [...cols];
    const index = Math.min(Math.max(selectionConfig.index ?? 0, 0), next.length);
    next.splice(index, 0, selectionColumn);
    return next;
  };

  const insertActionSort = (cols) => {
    if (!actionConfig || !cols.length) {return cols;}
    const next = [...cols];
    const index = actionConfig.index === null ? next.length : Math.min(Math.max(actionConfig.index, 0), next.length);
    next.splice(index, 0, null);
    return next;
  };

  const insertSelectionSort = (cols) => {
    if (!selectionConfig || !cols.length) {return cols;}
    const next = [...cols];
    const index = Math.min(Math.max(selectionConfig.index ?? 0, 0), next.length);
    next.splice(index, 0, null);
    return next;
  };

  const mapDataWithActions = (data) => {
    const rows = mapData ? mapData(data) : [];
    if (!actionConfig || !Array.isArray(rows)) {
      return rows;
    }
    const index = actionConfig.index === null ? null : Math.min(Math.max(actionConfig.index, 0), Infinity);
    return rows.map((row) => {
      if (!Array.isArray(row)) {return row;}
      const next = [...row];
      const insertAt = index === null ? next.length : Math.min(index, next.length);
      next.splice(insertAt, 0, null);
      return next;
    });
  };

  const serverConfig = {
    url: baseUrl(),
    then: mapDataWithActions,
    total
  };

  let finalSortColumns = sortColumns;
  if (selectionConfig) {
    finalSortColumns = insertSelectionSort(finalSortColumns);
  }
  if (actionConfig) {
    finalSortColumns = insertActionSort(finalSortColumns);
  }
  const sortConfig = finalSortColumns.length ? {
    enabled: true,
    server: {
      url: (prev, columns) => {
        if (!columns.length) {
          // Keep URL/bootstrap sort when grid has no active sort columns yet (e.g. initial load from deep-link).
          if (currentSort?.order && currentSort?.dir) {
            return addParam(addParam(clearSortParams(prev), sortOrderParam, currentSort.order), sortDirParam, currentSort.dir);
          }
          currentSort = null;
          return clearSortParams(prev);
        }
        const column = columns[0];
        const key = finalSortColumns[column.index];
        if (!key) {
          if (currentSort?.order && currentSort?.dir) {
            return addParam(addParam(clearSortParams(prev), sortOrderParam, currentSort.order), sortDirParam, currentSort.dir);
          }
          currentSort = null;
          return clearSortParams(prev);
        }
        const direction = column.direction === 1 ? 'asc' : 'desc';
        currentSort = { order: key, dir: direction };
        if (urlSync && urlStateConfig.syncSort) {
          updateUrl();
        }
        return addParam(addParam(clearSortParams(prev), sortOrderParam, key), sortDirParam, direction);
      }
    }
  } : { enabled: false };

  const paginationConfig = {
    limit: paginationLimit,
    page: Math.max(currentPage - 1, 0),
    // We control page resets explicitly (search/filter/reset actions) to keep deep-link pages stable.
    resetPageOnUpdate: false,
    server: {
      url: (prev, page, limit) => {
        const parsedPage = Number.parseInt(String(page ?? ''), 10);
        const parsedLimit = Number.parseInt(String(limit ?? ''), 10);
        const nextPage = Number.isFinite(parsedPage) && parsedPage >= 0 ? parsedPage : 0;
        const nextLimit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : paginationLimit;
        currentPage = nextPage + 1;
        if (urlSync && urlStateConfig.syncPage) {
          updateUrl();
        }
        const offset = nextPage * nextLimit;
        return addParam(addParam(prev, 'limit', nextLimit), 'offset', offset);
      }
    }
  };

  const plugins = [];
  if (selectionConfig?.component) {
    plugins.push({
      id: selectionConfig.id,
      component: selectionConfig.component,
      props: selectionConfig.props || {}
    });
  }

  const grid = new gridjsLib.Grid({
    columns: insertActionColumn(insertSelectionColumn(columns)),
    server: serverConfig,
    search: false,
    sort: sortConfig,
    pagination: paginationConfig,
    language,
    plugins
  });

  grid.render(containerEl);
  setTimeout(() => {
    initSelectAll();
  }, 0);

  // Prevent empty-state flash before data arrives.
  let hasLoadedOnce = false;
  const markLoadedOnce = () => {
    if (hasLoadedOnce) {return;}
    hasLoadedOnce = true;
    containerEl.classList.add('gridjs-has-loaded');
  };
  const setUpdating = () => containerEl.classList.add('gridjs-is-updating');
  const clearUpdating = () => containerEl.classList.remove('gridjs-is-updating');

  const store = grid?.config?.store;
  if (store?.subscribe) {
    store.subscribe((state) => {
      if (!state) {return;}
      // Grid.js status: 0=Init, 1=Loading, 2=Loaded, 3=Rendered, 4=Error
      if (state.status === 1) {
        setUpdating();
      } else {
        clearUpdating();
      }
      if (state.status >= 2) {
        markLoadedOnce();
      }
      if (state.status >= 3) {
        initSelectAll();
      }
    });
  }

  const applyRowDataset = () => {
    if (typeof rowDataset !== 'function') {return;}
    const rowEls = containerEl.querySelectorAll('.gridjs-tbody .gridjs-tr');
    const dataRows = grid.config.store.getState().data?.rows || [];
    rowEls.forEach((rowEl, index) => {
      const rowData = dataRows[index] || null;
      if (!rowData) {return;}
      const attrs = rowDataset(rowData, index) || {};
      Object.keys(attrs).forEach((key) => {
        const value = attrs[key];
        const attr = `data-${key}`;
        if (value === null || typeof value === 'undefined' || value === '') {
          rowEl.removeAttribute(attr);
        } else {
          rowEl.setAttribute(attr, String(value));
        }
      });
    });
  };

  const initSelectAll = () => {
    if (!selectionConfig?.component || !selectionConfig.selectAllEnabled) {return;}
    const header = containerEl.querySelector(`th[data-column-id="${selectionConfig.id}"] input[data-grid-select-all]`);
    if (!header) {return;}
    if (header.dataset.bound === '1') {
      return;
    }
    header.dataset.bound = '1';

    const getRowCheckboxes = () => Array.from(
      containerEl.querySelectorAll(`td[data-column-id="${selectionConfig.id}"] input[type="checkbox"]`)
    );

    const updateHeaderState = () => {
      const boxes = getRowCheckboxes();
      const total = boxes.length;
      const checked = boxes.filter((box) => box.checked).length;
      header.indeterminate = total > 0 && checked > 0 && checked < total;
      header.checked = total > 0 && checked === total;
      header.disabled = total === 0;
    };

    header.addEventListener('change', () => {
      const boxes = getRowCheckboxes();
      boxes.forEach((box) => {
        if (box.checked !== header.checked) {
          box.click();
        }
      });
      updateHeaderState();
    });

    updateHeaderState();
  };

  if (typeof rowDataset === 'function') {
    const observer = new MutationObserver(() => applyRowDataset());
    observer.observe(containerEl, { childList: true, subtree: true });
    applyRowDataset();
  }

  const getRowDataByElement = (rowEl) => {
    if (!rowEl) {return null;}
    const rows = Array.from(rowEl.parentElement?.children || []);
    const rowIndex = rows.indexOf(rowEl);
    if (rowIndex < 0) {return null;}
    const dataRows = grid.config.store.getState().data?.rows || [];
    const rowData = dataRows[rowIndex] || null;
    if (!rowData) {return null;}
    return { rowData, rowIndex };
  };

  if (rowDblClick) {
    const excludeSelector = rowDblClick.exclude || '.grid-actions, button, a, input, select, textarea';
    containerEl.addEventListener('dblclick', (event) => {
      if (excludeSelector && event.target.closest(excludeSelector)) {
        return;
      }
      const rowEl = event.target.closest('.gridjs-tr');
      const rowInfo = getRowDataByElement(rowEl);
      if (!rowInfo) {
        return;
      }
      const { rowData, rowIndex } = rowInfo;
      const getUrl = rowDblClick.getUrl;
      if (typeof getUrl !== 'function') {
        return;
      }
      const url = getUrl(rowData, rowIndex);
      if (url) {
        window.location.href = url;
      }
    });
  }

  if (actionConfig) {
    containerEl.addEventListener('click', async (event) => {
      const actionEl = event.target.closest('[data-grid-action]');
      if (!actionEl) {
        return;
      }
      const rowEl = actionEl.closest('.gridjs-tr');
      const rowInfo = getRowDataByElement(rowEl);
      if (!rowInfo) {
        return;
      }
      const { rowData } = rowInfo;
      const actionKey = actionEl.getAttribute('data-grid-action') || '';
      const actionItem = actionConfig.items.find((item) => item && item.key === actionKey);
      if (!actionItem) {
        return;
      }
      const id = actionConfig.getRowId ? actionConfig.getRowId(rowData) : null;
      if (!id) {
        return;
      }
      const confirmText = typeof actionItem.confirm === 'function'
        ? actionItem.confirm({ row: rowData, id })
        : actionItem.confirm;
      if (confirmText && !window.confirm(confirmText)) {
        return;
      }
      if (actionItem.type === 'link') {
        const href = typeof actionItem.href === 'function'
          ? actionItem.href({ row: rowData, id })
          : actionItem.href;
        if (href) {
          event.preventDefault();
          window.location.href = href;
        }
        return;
      }
      if (actionConfig.onAction) {
        event.preventDefault();
        await actionConfig.onAction({ action: actionKey, id, row: rowData, event, grid, container: containerEl });
      }
    });
  }

  const updateGrid = (options = {}) => {
    const resetPage = options && options.resetPage === true;
    if (resetPage) {
      currentPage = 1;
    }
    updateUrl();
    const nextConfig = {
      server: { ...serverConfig, url: baseUrl() }
    };
    if (resetPage) {
      nextConfig.pagination = { ...paginationConfig, page: 0 };
    }
    grid.updateConfig(nextConfig).forceRender();
  };

  const debounce = (fn, delay = 250) => {
    let timer;
    return (...args) => {
      clearTimeout(timer);
      timer = setTimeout(() => fn(...args), delay);
    };
  };

  if (searchInput) {
    searchInput.addEventListener('input', debounce((event) => {
      searchValue = event.target.value.trim();
      updateGrid({ resetPage: true });
    }, searchDebounce));
  }

  filterState.forEach((filter) => {
    if (!filter.input) {return;}
    const eventName = filter.event || 'change';
    filter.input.addEventListener(eventName, () => {
      filter.value = getInputValue(filter.input);
      updateGrid({ resetPage: true });
    });
  });

  const resetFilters = () => {
    if (search) {
      const defaultValue = search.default ?? '';
      searchValue = defaultValue;
      if (searchInput) {
        setInputValue(searchInput, defaultValue);
      }
    }
    filterState.forEach((filter) => {
      const defaultValue = filter.default ?? '';
      filter.value = defaultValue;
      if (filter.input) {
        setInputValue(filter.input, defaultValue);
      }
    });
    updateGrid({ resetPage: true });
  };

  const getSortParams = () => (currentSort ? { ...currentSort } : null);
  const selectionApi = selectionConfig && selectionConfig.component ? {
    getSelectedIds: () => {
      if (selectionConfig.getSelectedIds) {
        return selectionConfig.getSelectedIds({ grid, container: containerEl });
      }
      const plugin = grid?.config?.plugin?.get?.(selectionConfig.id);
      const store = plugin?.props?.store;
      const state = store?.state ?? store?.getState?.();
      if (!state) {return [];}
      if (Array.isArray(state.selected)) {return state.selected;}
      if (Array.isArray(state.ids)) {return state.ids;}
      if (state.rows && typeof state.rows === 'object') {
        return Object.keys(state.rows).filter((key) => state.rows[key]);
      }
      if (state instanceof Set) {return Array.from(state);}
      if (Array.isArray(state)) {return state;}
      if (typeof state === 'object') {
        return Object.keys(state).filter((key) => state[key]);
      }
      return [];
    },
    clear: () => {
      const plugin = grid?.config?.plugin?.get?.(selectionConfig.id);
      const store = plugin?.props?.store;
      if (store?.dispatch) {
        store.dispatch('reset');
      } else if (store?.reset) {
        store.reset();
      }
    }
  } : null;

  if (selectionConfig?.onChange && selectionConfig.component) {
    grid.on('ready', () => {
      const plugin = grid?.config?.plugin?.get?.(selectionConfig.id);
      const store = plugin?.props?.store;
      const sync = () => {
        selectionConfig.onChange(selectionApi?.getSelectedIds?.() || []);
      };
      if (store?.on) {store.on('updated', sync);}
      if (store?.subscribe) {store.subscribe(sync);}
      sync();
    });
  }

  grid.on('ready', () => {
    initSelectAll();
    setTimeout(() => {
      if (typeof window.requestFsLightboxRefresh === 'function') {
        window.requestFsLightboxRefresh();
        return;
      }
      if (typeof window.refreshFsLightbox === 'function') {
        window.refreshFsLightbox();
      } else {
        window.__fsLightboxRefreshPending = true;
      }
    }, 0);
  });

  return { grid, container: containerEl, updateGrid, updateUrl, baseUrl, resolveUrl, getSortParams, resetFilters, selection: selectionApi };
}
