/**
 * domain-export.js
 * Export Domains tool.
 *
 * Depends on common.js for:
 *   checkAuthSession, startAuthSessionMonitor, initEventButtons, showBody,
 *   get_config_data, set_config_data
 */

'use strict';

///////////////////////////////////////////////////////////////////////////////
// State
///////////////////////////////////////////////////////////////////////////////

var columnsMeta        = {};
var selectedColumns    = [];
var datasets           = [];
var computedColumns    = {};
var SCALAR_FIELDTYPES = ['string', 'integer', 'float', 'boolean', 'date', 'datetime'];

function getEligibleColumns() {
    return Object.keys(columnsMeta).filter(function (colName) {
        var col = columnsMeta[colName];
        if (!col) return false;
        if (colName === 'domain') return true;
        if (!(col.gridview == 1 || col.gridview === true)) return false;
        if (EXCLUDED_COLUMNS.indexOf(colName) !== -1) return false;
        if (SCALAR_FIELDTYPES.indexOf(col.fieldtype) === -1) return false;
        return true;
    });
}

var activeDatasetIndex = -1;
var DRAGGED_ROW        = null;
var DRAWER_DRAGGING    = false;

///////////////////////////////////////////////////////////////////////////////
// Boot
///////////////////////////////////////////////////////////////////////////////

document.addEventListener('DOMContentLoaded', async function () {
    var loggedIn = await checkAuthSession();
    if (!loggedIn) return;

    startAuthSessionMonitor();
    initEventButtons();

    await loadColumns();
    await loadCategories();
    await initDatasets();

    setupEventHandlers();
    rebuildComputedListUI();
    updateSummary();
    updateSaveButtonLabel();

    setContentHeight();
    window.addEventListener('resize', setContentHeight);

    showBody();
});

///////////////////////////////////////////////////////////////////////////////
// Layout
///////////////////////////////////////////////////////////////////////////////

function setContentHeight() {
    var header = document.getElementById('appheader');
    if (!header) return;
    var h = header.offsetHeight;
    if (h > 0) {
        document.documentElement.style.setProperty('--appheader-height', h + 'px');
    }
}

window.addEventListener('load', setContentHeight);

///////////////////////////////////////////////////////////////////////////////
// Drawer
///////////////////////////////////////////////////////////////////////////////

function openDrawer() {
    var drawer  = document.getElementById('admin-drawer');
    var overlay = document.getElementById('drawer-overlay');
    if (!drawer) return;
    drawer.classList.add('open');
    overlay.classList.add('open');
    document.body.classList.add('drawer-open');
}

function closeDrawer() {
    var drawer  = document.getElementById('admin-drawer');
    var overlay = document.getElementById('drawer-overlay');
    if (!drawer) return;
    drawer.classList.remove('open');
    overlay.classList.remove('open');
    document.body.classList.remove('drawer-open');
}

///////////////////////////////////////////////////////////////////////////////
// Datasets
///////////////////////////////////////////////////////////////////////////////

async function initDatasets() {
    datasets = await loadDatasetsFromServer();
    populateDatasetSelect();

    if (datasets.length) {
        autoLoadDataset(0);
    } else if (IS_ADMIN) {
        // No datasets yet — open drawer automatically so admin can configure
        openDrawer();
    }
}

async function loadDatasetsFromServer() {
    try {
        var raw = await get_config_data(EXPORT_DOMAINS_CONFIG_KEY, API_BASE_URL);
        if (!raw) return [];
        var parsed = JSON.parse(raw);
        if (parsed && Array.isArray(parsed.datasets)) return parsed.datasets;
        return [];
    } catch (e) {
        return [];
    }
}

async function saveDatasetsToServer() {
    await set_config_data(EXPORT_DOMAINS_CONFIG_KEY, { datasets: datasets }, API_BASE_URL);
}

function populateDatasetSelect() {
    // Both admin dropdown (in main area) and user dropdown (in sidebar)
    var selectors = ['dataset_select', 'dataset_select_main'];

    selectors.forEach(function (id) {
        var select = document.getElementById(id);
        if (!select) return;

        select.innerHTML = '<option value="">No data set selected</option>';
        datasets.forEach(function (ds, index) {
            var opt         = document.createElement('option');
            opt.value       = String(index);
            opt.textContent = ds.name;
            select.appendChild(opt);
        });

        if (activeDatasetIndex >= 0 && activeDatasetIndex < datasets.length) {
            select.value = String(activeDatasetIndex);
        }
    });

    updateDeleteButton();
}

function autoLoadDataset(index) {
    if (index < 0 || index >= datasets.length) return;
    activeDatasetIndex = index;
    applyDataset(datasets[index]);

    var nameInput = document.getElementById('dataset_name');
    if (nameInput && IS_ADMIN) nameInput.value = datasets[index].name;

    populateDatasetSelect();
    updateDeleteButton();
    updateSaveButtonLabel();
}

function applyDataset(ds) {
    var catSelect  = document.getElementById('filter_category');
    var queryInput = document.getElementById('filter_query');
    if (catSelect)  catSelect.value  = ds.category || '';
    if (queryInput) queryInput.value = ds.query    || '';

    var boolSel = document.getElementById('bool_format');
    if (boolSel && ds.boolFormat) boolSel.value = ds.boolFormat;

    computedColumns = ds.computed ? Object.assign({}, ds.computed) : {};
    rebuildComputedListUI();

    selectedColumns = Array.isArray(ds.columns) ? ds.columns.slice() : [];

    if (IS_ADMIN) {
        var container = document.getElementById('columns-list');
        if (container) {
            container.querySelectorAll('.columns-list-checkbox').forEach(function (cb) {
                cb.checked = selectedColumns.indexOf(cb.value) !== -1;
            });
            renderColumnList(getCurrentSearch());
        }
    }

    rebuildSelectedColumnsConfigUI();

    if (ds.labels) {
        Object.keys(ds.labels).forEach(function (colName) {
            var input = document.getElementById('header_for_' + colName);
            if (input) input.value = ds.labels[colName];
        });
    }

    updateSummary();
}

///////////////////////////////////////////////////////////////////////////////
// Save Button Label
///////////////////////////////////////////////////////////////////////////////

function updateSaveButtonLabel() {
    var btn       = document.getElementById('btn_save_dataset');
    var nameInput = document.getElementById('dataset_name');
    if (!btn || !nameInput) return;

    var name       = nameInput.value.trim();
    var isExisting = activeDatasetIndex >= 0 &&
                     datasets[activeDatasetIndex] &&
                     datasets[activeDatasetIndex].name === name;

    btn.textContent = isExisting ? 'Update Dataset' : 'Save as New Dataset';
}

function updateDeleteButton() {
    var btn = document.getElementById('btn_delete_dataset');
    if (!btn) return;
    btn.style.display = (IS_ADMIN && activeDatasetIndex >= 0) ? 'block' : 'none';
}

///////////////////////////////////////////////////////////////////////////////
// Dataset Event Handlers
///////////////////////////////////////////////////////////////////////////////

function handleDatasetSelectChange(e) {
    var val = e.target.value;

    // Keep both dropdowns in sync
    var selectors = ['dataset_select', 'dataset_select_main'];
    selectors.forEach(function (id) {
        var sel = document.getElementById(id);
        if (sel && sel !== e.target) sel.value = val;
    });

    if (val === '') {
        activeDatasetIndex = -1;
        updateDeleteButton();
        updateSaveButtonLabel();
        return;
    }

    var index = parseInt(val, 10);
    if (isNaN(index) || index < 0 || index >= datasets.length) return;

    autoLoadDataset(index);
    setStatus('Data set "' + datasets[index].name + '" loaded.', false);
}

function handleNewDataset() {
    activeDatasetIndex = -1;

    var selectors = ['dataset_select', 'dataset_select_main'];
    selectors.forEach(function (id) {
        var sel = document.getElementById(id);
        if (sel) sel.value = '';
    });

    var nameInput  = document.getElementById('dataset_name');
    var catSelect  = document.getElementById('filter_category');
    var queryInput = document.getElementById('filter_query');

    if (nameInput)  nameInput.value  = '';
    if (catSelect)  catSelect.value  = '';
    if (queryInput) queryInput.value = '';

    selectedColumns = [];
    computedColumns = {};

    if (IS_ADMIN) {
        var container = document.getElementById('columns-list');
        if (container) {
            container.querySelectorAll('.columns-list-checkbox').forEach(function (cb) {
                cb.checked = false;
            });
            renderColumnList(getCurrentSearch());
        }
    }

    selectedColumns = ['domain'];
    rebuildComputedListUI();
    rebuildSelectedColumnsConfigUI();
    updateSummary();
    updateDeleteButton();
    updateSaveButtonLabel();

    if (nameInput) nameInput.focus();
    openDrawer();
}

function handleSaveDataset() {
    var nameInput = document.getElementById('dataset_name');
    if (!nameInput) return;

    var name = nameInput.value.trim();
    if (!name) {
        setDrawerStatus('Please enter a name for the data set.', true);
        nameInput.focus();
        return;
    }
    if (!selectedColumns.length) {
        setDrawerStatus('Please select at least one column before saving.', true);
        return;
    }

    var catSelect  = document.getElementById('filter_category');
    var queryInput = document.getElementById('filter_query');
    var boolSel    = document.getElementById('bool_format');

    // Ensure computed columns are included in selectedColumns in current table order
    Object.keys(computedColumns).forEach(function (c) {
        if (selectedColumns.indexOf(c) === -1) selectedColumns.push(c);
    });

    var dataset = {
        name:       name,
        category:   catSelect  ? catSelect.value        : '',
        query:      queryInput ? queryInput.value.trim() : '',
        columns:    selectedColumns.slice(),   // includes computed cols in correct order
        labels:     collectHeaderLabels(),
        boolFormat: boolSel ? boolSel.value : '10',
        computed:   Object.assign({}, computedColumns)
    };

    var existingIndex = -1;
    for (var i = 0; i < datasets.length; i++) {
        if (datasets[i].name === name) { existingIndex = i; break; }
    }

    if (existingIndex >= 0) {
        datasets[existingIndex] = dataset;
        activeDatasetIndex      = existingIndex;
    } else {
        datasets.push(dataset);
        activeDatasetIndex = datasets.length - 1;
    }

    populateDatasetSelect();
    updateSaveButtonLabel();
    updateDeleteButton();

    setDrawerStatus('Saving…', false);

    saveDatasetsToServer()
        .then(function () {
            setDrawerStatus('✓ "' + name + '" saved.', false);
        })
        .catch(function (e) {
            setDrawerStatus('✗ Save failed: ' + e.message, true);
        });
}

function handleDeleteDataset() {
    if (activeDatasetIndex < 0 || activeDatasetIndex >= datasets.length) return;

    var name = datasets[activeDatasetIndex].name;
    if (!window.confirm('Delete data set "' + name + '"?')) return;

    datasets.splice(activeDatasetIndex, 1);
    activeDatasetIndex = -1;

    var nameInput = document.getElementById('dataset_name');
    if (nameInput) nameInput.value = '';

    populateDatasetSelect();
    updateDeleteButton();
    updateSaveButtonLabel();
    rebuildSelectedColumnsConfigUI();

    setDrawerStatus('Deleting…', false);

    saveDatasetsToServer()
        .then(function () {
            setDrawerStatus('✓ "' + name + '" deleted.', false);
        })
        .catch(function (e) {
            setDrawerStatus('✗ Delete failed: ' + e.message, true);
        });
}

///////////////////////////////////////////////////////////////////////////////
// Column Loading
///////////////////////////////////////////////////////////////////////////////

async function loadColumns() {
    try {
        var res  = await fetch(API_BASE_URL + '?c=list&t=domain&oper=columns', { credentials: 'same-origin' });
        var data = await res.json();
        if (!data || !data.columns) return;

        columnsMeta = data.columns;

        if (IS_ADMIN) {
            renderColumnList();
            initColumnSearch();
        }
    } catch (e) {
        var el = document.getElementById('columns-list');
        if (el) el.textContent = 'Error loading columns.';
    }
}

function renderColumnList(filterText) {
    var container = document.getElementById('columns-list');
    if (!container) return;

    container.innerHTML = '';

    var search      = (filterText || '').toLowerCase().trim();
    var selectedSet = new Set(selectedColumns);

    var eligible = getEligibleColumns();

    // Ensure domain is always first in the ordered list
    if (eligible.indexOf('domain') === -1) eligible.unshift('domain');

    var ordered = selectedColumns
        .filter(function (c) {
            // Computed columns stay in selectedColumns for ordering but never appear in the picker
            if (Object.prototype.hasOwnProperty.call(computedColumns, c)) return false;
            return eligible.indexOf(c) !== -1;
        })
        .concat(eligible.filter(function (c) { return !selectedSet.has(c); }).sort());

    ordered.forEach(function (colName) {
        var col       = columnsMeta[colName] || {};
        var labelText = (col.label && col.label.trim()) ? col.label : autoLabel(colName);

        if (search &&
            labelText.toLowerCase().indexOf(search) === -1 &&
            colName.toLowerCase().indexOf(search) === -1) return;

        var item         = document.createElement('div');
        item.className   = 'columns-list-item';
        item.dataset.col = colName;

        var checkbox       = document.createElement('input');
        checkbox.type      = 'checkbox';
        checkbox.value     = colName;
        checkbox.checked   = selectedSet.has(colName) || colName === 'domain';
        checkbox.id        = 'chk_' + colName;
        checkbox.className = 'columns-list-checkbox';
        if (colName === 'domain') {
            // domain is always required — lock it
            checkbox.disabled = true;
            item.title = 'Domain is always included';
        } else {
            checkbox.addEventListener('change', onColumnToggle);
        }

        var labelWrap       = document.createElement('label');
        labelWrap.htmlFor   = 'chk_' + colName;
        labelWrap.className = 'columns-list-label';

        var labelMain         = document.createElement('span');
        labelMain.className   = 'columns-list-label-main';
        labelMain.textContent = labelText;

        var labelKey          = document.createElement('span');
        labelKey.className    = 'columns-list-label-key';
        labelKey.textContent  = colName;

        labelWrap.appendChild(labelMain);
        labelWrap.appendChild(labelKey);
        item.appendChild(checkbox);
        item.appendChild(labelWrap);
        container.appendChild(item);

        if (checkbox.checked) item.setAttribute('draggable', 'true');
    });

    enableColumnDragAndDrop();
}

function onColumnToggle() {
    rebuildSelectedFromDOM();
    rebuildSelectedColumnsConfigUI();
    updateSummary();
    renderColumnList(getCurrentSearch());
}

function rebuildSelectedFromDOM() {
    var container = document.getElementById('columns-list');
    if (!container) return;
    selectedColumns = [];
    container.querySelectorAll('.columns-list-item').forEach(function (item) {
        var cb = item.querySelector('input[type="checkbox"]');
        if (cb && cb.checked) selectedColumns.push(cb.value);
        if (cb) item.setAttribute('draggable', cb.checked ? 'true' : 'false');
    });
    // domain is always first — ensure it's present
    if (selectedColumns.indexOf('domain') === -1) {
        selectedColumns.unshift('domain');
    }
}

function initColumnSearch() {
    var input = document.getElementById('column-search');
    if (!input) return;
    input.addEventListener('input', function () { renderColumnList(this.value); });
}

function getCurrentSearch() {
    var input = document.getElementById('column-search');
    return input ? input.value : '';
}

///////////////////////////////////////////////////////////////////////////////
// Column Drag and Drop
///////////////////////////////////////////////////////////////////////////////

function enableColumnDragAndDrop() {
    var container = document.getElementById('columns-list');
    if (!container) return;

    container.querySelectorAll('.columns-list-item').forEach(function (item) {
        item.addEventListener('dragstart', function (e) {
            var cb = item.querySelector('input[type="checkbox"]');
            if (!cb || !cb.checked) { e.preventDefault(); return; }
            DRAWER_DRAGGING = true;
            setTimeout(function () { item.classList.add('dragging'); }, 0);
        });

        item.addEventListener('dragend', function () {
            if (!DRAWER_DRAGGING) return;
            item.classList.remove('dragging');
            DRAWER_DRAGGING = false;
            rebuildSelectedFromDOM();
            rebuildSelectedColumnsConfigUI();
            updateSummary();
        });

        item.addEventListener('dragover', function (e) {
            e.preventDefault();
            if (!DRAWER_DRAGGING) return;
            var draggingEl = container.querySelector('.columns-list-item.dragging');
            if (!draggingEl || draggingEl === item) return;
            var cb = item.querySelector('input[type="checkbox"]');
            if (!cb || !cb.checked) return;
            var after = e.clientY > item.getBoundingClientRect().top + item.getBoundingClientRect().height / 2;
            container.insertBefore(draggingEl, after ? item.nextSibling : item);
        });
    });
}

///////////////////////////////////////////////////////////////////////////////
// Selected Columns Config Table
///////////////////////////////////////////////////////////////////////////////

function rebuildSelectedColumnsConfigUI() {
    var container = document.getElementById('selected-columns-config');
    if (!container) return;

    container.innerHTML = '';

    var allCols = selectedColumns.slice();
    Object.keys(computedColumns).forEach(function (c) {
        if (allCols.indexOf(c) === -1) allCols.push(c);
    });

    if (!allCols.length) {
        container.innerHTML = '<div class="no-columns-msg">' +
            (IS_ADMIN
                ? 'Click <strong>⚙ Configure</strong> to select columns and set up data sets.'
                : 'Load a data set from the left panel to get started.') +
            '</div>';
        return;
    }

    var table       = document.createElement('table');
    table.className = 'selected-columns-table';

    var thead     = document.createElement('thead');
    var headerRow = document.createElement('tr');

    if (IS_ADMIN) {
        headerRow.appendChild(document.createElement('th'));
    }

    var thName = document.createElement('th');
    thName.textContent = 'Column';
    headerRow.appendChild(thName);

    var thLabel = document.createElement('th');
    thLabel.textContent = 'CSV heading';
    headerRow.appendChild(thLabel);

    thead.appendChild(headerRow);
    table.appendChild(thead);

    var tbody = document.createElement('tbody');

    allCols.forEach(function (colName) {
        var meta       = columnsMeta[colName] || {};
        var labelText  = (meta.label && meta.label.trim()) ? meta.label : autoLabel(colName);
        var isComputed = Object.prototype.hasOwnProperty.call(computedColumns, colName);

        var row         = document.createElement('tr');
        row.dataset.col = colName;
        row.draggable   = !!IS_ADMIN;

        if (IS_ADMIN) {
            var tdHandle      = document.createElement('td');
            tdHandle.innerHTML = '<span class="col-drag-handle">☰</span>';
            row.appendChild(tdHandle);

            row.addEventListener('dragstart', function () {
                DRAGGED_ROW = row;
                setTimeout(function () { row.classList.add('dragging'); }, 0);
            });
            row.addEventListener('dragend', function () {
                DRAGGED_ROW = null;
                row.classList.remove('dragging');
                updateColumnOrderFromDOM();
                updateSummary();
            });
            row.addEventListener('dragover', function (e) {
                e.preventDefault();
                row.classList.add('drag-over');
            });
            row.addEventListener('dragleave', function () {
                row.classList.remove('drag-over');
            });
            row.addEventListener('drop', function (e) {
                e.preventDefault();
                row.classList.remove('drag-over');
                if (!DRAGGED_ROW || DRAGGED_ROW === row) return;
                var rows = Array.from(tbody.querySelectorAll('tr'));
                if (rows.indexOf(DRAGGED_ROW) < rows.indexOf(row)) row.after(DRAGGED_ROW);
                else row.before(DRAGGED_ROW);
                updateColumnOrderFromDOM();
                updateSummary();
            });
        }

        var tdName = document.createElement('td');
        tdName.textContent = isComputed
            ? colName + ' (' + computedColumns[colName] + ')'
            : colName;
        if (isComputed) tdName.style.fontStyle = 'italic';
        row.appendChild(tdName);

        var tdHeader  = document.createElement('td');
        var input     = document.createElement('input');
        input.type    = 'text';
        input.className = 'header-input';
        input.id      = 'header_for_' + colName;
        input.placeholder = labelText;
        tdHeader.appendChild(input);
        row.appendChild(tdHeader);

        tbody.appendChild(row);
    });

    table.appendChild(tbody);
    container.appendChild(table);
}

function updateColumnOrderFromDOM() {
    var tbody = document.querySelector('#selected-columns-config table tbody');
    if (!tbody) return;
    // Include computed columns in selectedColumns so order is fully preserved
    selectedColumns = Array.from(tbody.querySelectorAll('tr'))
        .map(function (tr) { return tr.dataset.col; });

    // Auto-save column order for admins when a dataset is active
    if (IS_ADMIN && activeDatasetIndex >= 0 && datasets[activeDatasetIndex]) {
        datasets[activeDatasetIndex].columns = selectedColumns.slice();
        saveDatasetsToServer();
    }
}

function collectHeaderLabels() {
    var labels  = {};
    var allCols = selectedColumns.concat(Object.keys(computedColumns));
    allCols.forEach(function (colName) {
        var input    = document.getElementById('header_for_' + colName);
        var meta     = columnsMeta[colName] || {};
        var defLabel = (meta.label && meta.label.trim()) ? meta.label : autoLabel(colName);
        labels[colName] = (input && input.value.trim()) ? input.value.trim() : defLabel;
    });
    return labels;
}

///////////////////////////////////////////////////////////////////////////////
// Computed Columns
///////////////////////////////////////////////////////////////////////////////

function handleAddComputed() {
    var nameInput = document.getElementById('computed_col_name');
    var exprInput = document.getElementById('computed_col_expr');
    if (!nameInput || !exprInput) return;

    var name = nameInput.value.trim();
    var expr = exprInput.value.trim();

    if (!name.match(/^[a-zA-Z0-9_]+$/)) {
        setDrawerStatus('Invalid column name. Use only letters, numbers and underscores.', true);
        return;
    }
    if (!expr.startsWith('!') && !expr.startsWith('#')) {
        setDrawerStatus('Supported expressions: !field_name (invert boolean) or #1 (serial index starting at 1).', true);
        return;
    }
    if (columnsMeta[name]) {
        setDrawerStatus('A real column already uses this name.', true);
        return;
    }

    computedColumns[name] = expr;
    // Add to selectedColumns so it appears in the table and can be reordered
    if (selectedColumns.indexOf(name) === -1) {
        selectedColumns.push(name);
    }
    rebuildComputedListUI();
    rebuildSelectedColumnsConfigUI();
    updateSummary();

    nameInput.value = '';
    exprInput.value = '';
    setDrawerStatus('Column "' + name + '" added. Save the data set to persist it.', false);
}

function rebuildComputedListUI() {
    var container = document.getElementById('computed-list');
    if (!container) return;

    var names = Object.keys(computedColumns);
    if (!names.length) {
        container.textContent = 'None defined.';
        return;
    }

    container.innerHTML = '';
    names.forEach(function (name) {
        var row       = document.createElement('div');
        row.className = 'computed-item';

        var span         = document.createElement('span');
        span.className   = 'computed-name';
        span.textContent = name + ' = ' + computedColumns[name];

        var btn           = document.createElement('button');
        btn.type          = 'button';
        btn.className     = 'computed-remove-btn';
        btn.textContent   = '×';
        btn.title         = 'Remove ' + name;
        btn.addEventListener('click', function () {
            delete computedColumns[name];
            selectedColumns = selectedColumns.filter(function (c) { return c !== name; });
            rebuildComputedListUI();
            rebuildSelectedColumnsConfigUI();
            updateSummary();
        });

        row.appendChild(span);
        row.appendChild(btn);
        container.appendChild(row);
    });
}

///////////////////////////////////////////////////////////////////////////////
// Categories
///////////////////////////////////////////////////////////////////////////////

async function loadCategories() {
    try {
        var res  = await fetch(API_BASE_URL + '?c=list&t=category&oper=user', { credentials: 'same-origin' });
        var data = await res.json();
        if (!data || !Array.isArray(data.ids)) return;

        var selects = document.querySelectorAll('#filter_category');
        selects.forEach(function (select) {
            data.ids.forEach(function (cat) {
                var opt         = document.createElement('option');
                opt.value       = cat.id;
                opt.textContent = cat.name;
                select.appendChild(opt);
            });
        });
    } catch (e) { /* stays at "All categories" */ }
}

///////////////////////////////////////////////////////////////////////////////
// Summary
///////////////////////////////////////////////////////////////////////////////

function updateSummary() {
    var el = document.getElementById('export-summary');
    if (!el) return;

    var parts = [];

    var catSelect = document.getElementById('filter_category');
    if (catSelect) {
        var catText = catSelect.options[catSelect.selectedIndex]
            ? catSelect.options[catSelect.selectedIndex].textContent
            : 'All';
        parts.push('Category: ' + (catSelect.value ? catText : 'All'));
    }

    var queryInput = document.getElementById('filter_query');
    if (queryInput && queryInput.value.trim()) {
        parts.push('Filter: ' + queryInput.value.trim());
    }

    var allCols = selectedColumns.slice();
    Object.keys(computedColumns).forEach(function (c) {
        if (allCols.indexOf(c) === -1) allCols.push(c);
    });

    var includeAll = document.getElementById('btn_include_all_columns');
    if (includeAll && includeAll.classList.contains('active')) {
        var extraCount = getEligibleColumns().filter(function (c) {
            return allCols.indexOf(c) === -1;
        }).length;
        parts.push(allCols.length + ' selected + ' + extraCount + ' additional column' + (extraCount === 1 ? '' : 's'));
    } else {
        parts.push(allCols.length
            ? allCols.length + ' column' + (allCols.length === 1 ? '' : 's')
            : 'No columns selected');
    }

    var boolSel = document.getElementById('bool_format');
    if (boolSel) {
        var boolMap = { '10': '1/0', 'YN': 'Y/N', 'YESNO': 'Yes/No', 'TF': 'True/False', 'EN': 'Enabled/Disabled' };
        parts.push('Booleans: ' + (boolMap[boolSel.value] || boolSel.value));
    }

    el.textContent = parts.join('  ·  ');
}

///////////////////////////////////////////////////////////////////////////////
// Export / Preview
///////////////////////////////////////////////////////////////////////////////

async function runExport(isPreview) {
    setStatus('', false);
    document.getElementById('preview-area').innerHTML = '';

    var allCols = selectedColumns.slice();

    // Add any computed columns not yet in selectedColumns (edge case)
    Object.keys(computedColumns).forEach(function (c) {
        if (allCols.indexOf(c) === -1) allCols.push(c);
    });

    // Include all remaining columns if button is active
    var chkAll = document.getElementById('btn_include_all_columns');
    if (chkAll && chkAll.classList.contains('active')) {
        getEligibleColumns().sort().forEach(function (c) {
            if (allCols.indexOf(c) === -1) allCols.push(c);
        });
    }

    if (!allCols.length) {
        setStatus('Please select at least one column or load a data set.', true);
        return;
    }

    var rowsPerPage = parseInt((document.getElementById('rows_per_page') || {}).value, 10) || 500;
    var catSelect   = document.getElementById('filter_category');
    var queryInput  = document.getElementById('filter_query');
    var boolSel     = document.getElementById('bool_format');

    var categoryId = catSelect  ? catSelect.value        : '';
    var query      = queryInput ? queryInput.value.trim() : '';
    var boolFormat = boolSel    ? boolSel.value           : '10';
    var labels     = collectHeaderLabels();

    try {
        setStatus(isPreview ? 'Loading preview…' : 'Preparing export…', false);

        var page       = 1;
        var allRows    = [];
        var totalPages = null;

        while (true) {
            var params = new URLSearchParams();
            params.set('c', 'grid');
            params.set('t', 'domain');
            params.set('oper', 'get');

            var apiColumns = allCols.filter(function (c) {
                return !Object.prototype.hasOwnProperty.call(computedColumns, c);
            });
            params.set('columns', apiColumns.join(','));
            params.set('page', String(page));
            params.set('rows', String(rowsPerPage));
            if (categoryId) params.set('cid', categoryId);
            if (query)      params.set('query', query);

            var res  = await fetch(API_BASE_URL + '?' + params.toString(), { credentials: 'same-origin' });
            var data = await res.json();

            if (!data || !Array.isArray(data.rows)) {
                setStatus('Unexpected API response.', true);
                return;
            }

            var pageRows = data.rows.map(function (row, i) {
                return extractRowValues(row, allCols, boolFormat, allRows.length + i);
            });

            allRows = allRows.concat(pageRows);

            if (isPreview && allRows.length >= 20) {
                allRows = allRows.slice(0, 20);
                break;
            }

            if (totalPages === null) {
                totalPages = Math.max(1, parseInt(data.total, 10) || 1);
            }

            if (page >= totalPages) break;
            page++;
        }

        if (!allRows.length) {
            setStatus('No rows found for the current filters.', false);
            return;
        }

        if (isPreview) {
            renderPreview(allCols, allRows, labels);
            setStatus('Showing first ' + allRows.length + ' rows.', false);
            return;
        }

        downloadCsv(allCols, allRows, labels);
        setStatus('✓ Export complete — ' + allRows.length + ' rows downloaded.', false);

    } catch (e) {
        setStatus('Export error: ' + e, true);
    }
}

function renderPreview(cols, rows, labels) {
    var html = '<table class="preview-table"><thead><tr>';
    cols.forEach(function (c) { html += '<th>' + esc(labels[c] || c) + '</th>'; });
    html += '</tr></thead><tbody>';
    rows.forEach(function (rowVals) {
        html += '<tr>';
        rowVals.forEach(function (v) { html += '<td>' + esc(String(v)) + '</td>'; });
        html += '</tr>';
    });
    html += '</tbody></table>';
    document.getElementById('preview-area').innerHTML = html;
}

function downloadCsv(cols, rows, labels) {
    var lines = [];
    lines.push(cols.map(function (c) { return csvEscape(labels[c] || c); }).join(','));
    rows.forEach(function (rowVals) {
        lines.push(rowVals.map(function (v) { return csvEscape(String(v)); }).join(','));
    });

    var now      = new Date();
    var filename = 'wmd-export-'
        + now.getFullYear()
        + String(now.getMonth() + 1).padStart(2, '0')
        + String(now.getDate()).padStart(2, '0') + '.csv';

    var blob = new Blob([lines.join('\r\n')], { type: 'text/csv;charset=utf-8;' });
    var link = document.createElement('a');
    link.href          = URL.createObjectURL(blob);
    link.download      = filename;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(link.href);
}

///////////////////////////////////////////////////////////////////////////////
// Row Value Extraction
///////////////////////////////////////////////////////////////////////////////

function extractRowValues(row, cols, boolFormat, rowIndex) {
    return cols.map(function (colName) {
        if (Object.prototype.hasOwnProperty.call(computedColumns, colName)) {
            return evalComputed(computedColumns[colName], row, boolFormat, rowIndex);
        }
        return normalizeValue(getCellValue(row, colName), boolFormat);
    });
}

function getCellValue(row, colName) {
    if (row && Object.prototype.hasOwnProperty.call(row, colName)) return row[colName];
    if (row && row.cell && Object.prototype.hasOwnProperty.call(row.cell, colName)) return row.cell[colName];
    return '';
}

function normalizeValue(val, boolFormat) {
    if (val === null || val === undefined) return '';
    if (val === 1 || val === 0 || val === '1' || val === '0' || val === true || val === false) {
        return mapBoolean(val, boolFormat);
    }
    return val;
}

function mapBoolean(val, format) {
    var b = (val === 1 || val === '1' || val === true || val === 'true');
    switch (format) {
        case 'YN':    return b ? 'Y'       : 'N';
        case 'YESNO': return b ? 'Yes'     : 'No';
        case 'TF':    return b ? 'True'    : 'False';
        case 'EN':    return b ? 'Enabled' : 'Disabled';
        default:      return b ? '1'       : '0';
    }
}

function evalComputed(expr, row, boolFormat, rowIndex) {
    expr = String(expr).trim();
    if (expr.charAt(0) === '!') {
        // Parse: !field_name[:FalseValue:TrueValue]
        var rest   = expr.slice(1);
        var parts  = rest.split(':');
        var field  = parts[0];
        var raw    = getCellValue(row, field);
        var b      = (raw === 1 || raw === '1' || raw === true || raw === 'true');
        var inverted = !b;

        if (parts.length >= 3) {
            // Custom labels: false label = parts[1], true label = parts[2]
            return inverted ? parts[2] : parts[1];
        }
        // Fall back to global boolean format
        return mapBoolean(inverted ? 1 : 0, boolFormat);
    }
    if (expr.charAt(0) === '#') {
        var start = parseInt(expr.slice(1), 10);
        if (isNaN(start)) start = 1;
        return start + (rowIndex || 0);
    }
    return '';
}

///////////////////////////////////////////////////////////////////////////////
// Event Handlers
///////////////////////////////////////////////////////////////////////////////

function setupEventHandlers() {
    // Drawer open/close
    var btnOpen = document.getElementById('btn_open_drawer');
    if (btnOpen) btnOpen.addEventListener('click', openDrawer);

    var btnClose = document.getElementById('btn_close_drawer');
    if (btnClose) btnClose.addEventListener('click', closeDrawer);

    var btnCloseBottom = document.getElementById('btn_close_drawer_bottom');
    if (btnCloseBottom) btnCloseBottom.addEventListener('click', closeDrawer);

    var overlay = document.getElementById('drawer-overlay');
    if (overlay) overlay.addEventListener('click', closeDrawer);

    document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') closeDrawer();
    });

    // Dataset selects (both admin in main area and user in sidebar)
    ['dataset_select', 'dataset_select_main'].forEach(function (id) {
        var sel = document.getElementById(id);
        if (sel) sel.addEventListener('change', handleDatasetSelectChange);
    });

    if (IS_ADMIN) {
        var btnNew = document.getElementById('btn_new_dataset');
        if (btnNew) btnNew.addEventListener('click', handleNewDataset);

        var btnSave = document.getElementById('btn_save_dataset');
        if (btnSave) btnSave.addEventListener('click', handleSaveDataset);

        var btnDelete = document.getElementById('btn_delete_dataset');
        if (btnDelete) btnDelete.addEventListener('click', handleDeleteDataset);

        var btnAddComputed = document.getElementById('btn_add_computed');
        if (btnAddComputed) btnAddComputed.addEventListener('click', handleAddComputed);

        var btnHelp = document.getElementById('btn_computed_help');
        var helpEl  = document.getElementById('computed-help');
        if (btnHelp && helpEl) {
            btnHelp.addEventListener('click', function () {
                var isOpen = helpEl.classList.toggle('open');
                btnHelp.classList.toggle('active', isOpen);
            });
        }

        var nameInput = document.getElementById('dataset_name');
        if (nameInput) nameInput.addEventListener('input', updateSaveButtonLabel);
    }

    var btnPreview = document.getElementById('btn_preview');
    if (btnPreview) btnPreview.addEventListener('click', function () { runExport(true); });

    var btnExport = document.getElementById('btn_export');
    if (btnExport) btnExport.addEventListener('click', function () { runExport(false); });

    var catSelect = document.getElementById('filter_category');
    if (catSelect) catSelect.addEventListener('change', updateSummary);

    var queryInput = document.getElementById('filter_query');
    if (queryInput) queryInput.addEventListener('input', updateSummary);

    var boolSel = document.getElementById('bool_format');
    if (boolSel) boolSel.addEventListener('change', updateSummary);

    var btnAllCols = document.getElementById('btn_include_all_columns');
    if (btnAllCols) btnAllCols.addEventListener('click', function () {
        btnAllCols.classList.toggle('active');
        updateSummary();
    });
}

///////////////////////////////////////////////////////////////////////////////
// Status
///////////////////////////////////////////////////////////////////////////////

function setStatus(message, isError) {
    var el = document.getElementById('status-area');
    if (!el) return;
    el.textContent = message || '';
    el.className   = 'submit-status-text' + (message ? (isError ? ' error' : ' success') : '');
}

function setDrawerStatus(message, isError) {
    var el = document.getElementById('config-save-status');
    if (!el) return;
    el.textContent = message || '';
    el.className   = 'submit-status-text' + (message ? (isError ? ' error' : ' success') : '');
}

///////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////

function autoLabel(name) {
    if (!name) return '';
    return name.replace(/_/g, ' ').trim().replace(/\b\w/g, function (c) { return c.toUpperCase(); });
}

function esc(str) {
    return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function csvEscape(value) {
    var v = String(value);
    return /[",\r\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v;
}