// Tabulator-based spreadsheet view for sets
document.addEventListener('DOMContentLoaded', () => {
    const tableElement = document.getElementById('sets-table');
    if (!tableElement) return;

    const urls = JSON.parse(document.getElementById('sets-table-urls').textContent);
    const storages = JSON.parse(document.getElementById('sets-table-storages').textContent);
    const purchaseLocations = JSON.parse(document.getElementById('sets-table-purchase-locations').textContent);

    // id -> name lookups, used by both the cell formatter and the select editor
    const storageNames = Object.fromEntries(storages.map(s => [s.id, s.name]));
    const purchaseLocationNames = Object.fromEntries(purchaseLocations.map(p => [p.id, p.name]));

    // Tabulator's "list" editor wants {value, label} pairs. The actual
    // "no storage"/"no purchase location" data value is '', but Tabulator's
    // list HEADER FILTER treats an empty-string filter value as "cleared"
    // (i.e. no filter applied), so that option would be indistinguishable
    // from clearing the filter. Use a non-empty sentinel for filtering
    // instead, paired with a custom headerFilterFunc below.
    const NONE_SENTINEL = '__none__';

    const storageOptions = [
        { value: '', label: 'No storage' },
        ...storages.map(s => ({ value: s.id, label: s.name })),
    ];
    const purchaseLocationOptions = [
        { value: '', label: 'No purchase location' },
        ...purchaseLocations.map(p => ({ value: p.id, label: p.name })),
    ];
    const storageFilterOptions = [
        { value: NONE_SENTINEL, label: 'No storage' },
        ...storages.map(s => ({ value: s.id, label: s.name })),
    ];
    const purchaseLocationFilterOptions = [
        { value: NONE_SENTINEL, label: 'No purchase location' },
        ...purchaseLocations.map(p => ({ value: p.id, label: p.name })),
    ];

    // Matches the sentinel against an empty row value, otherwise exact match
    function noneAwareFilter(headerValue, rowValue) {
        if (headerValue === NONE_SENTINEL) return !rowValue;
        return rowValue === headerValue;
    }

    // Column field names (storage_id, purchase_location_id, ...) don't
    // always match the URL keys (storage, purchase_location, ...), so map
    // between them explicitly instead of assuming they're identical.
    const fieldToUrlKey = {
        description: 'description',
        storage_id: 'storage',
        purchase_location_id: 'purchase_location',
        purchase_date: 'purchase_date',
        purchase_price: 'purchase_price',
    };

    // Build the save URL for a field on a given row (swap in the real id)
    function urlFor(urlKey, id) {
        return urls[urlKey].replace('ID_PLACEHOLDER', id);
    }

    // Save one field for one row. On failure, revert the cell and alert -
    // never silently keep an edit that didn't actually persist.
    function saveField(cell) {
        const field = cell.getColumn().getField();
        const urlKey = fieldToUrlKey[field];
        const row = cell.getRow().getData();
        const id = row.id;
        const value = cell.getValue();

        if (!urlKey || !(urlKey in urls)) {
            console.warn(`No save URL configured for field "${field}", skipping save`);
            return;
        }

        fetch(urlFor(urlKey, id), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ value: value }),
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error(`Response status: ${response.status} (${response.statusText})`);
                }
                return response.json();
            })
            .then(json => {
                // The app's error handler returns HTTP 200 with an "error"
                // key for validation/application errors, not a 4xx status.
                if (json && 'error' in json) {
                    throw new Error(json.error);
                }
            })
            .catch(error => {
                console.error(`Failed to save "${field}":`, error);
                alert(`Failed to save "${field}": ${error.message}`);
                cell.restoreOldValue();
            });
    }

    const table = new Tabulator('#sets-table', {
        ajaxURL: urls.data,
        layout: 'fitDataFill',
        height: '75vh',
        variableHeight: true,
        pagination: true,
        paginationSize: 50,
        paginationSizeSelector: [25, 50, 100, 250, true],
        movableColumns: true,
        columns: [
            {
                title: '',
                field: 'image',
                width: 80,
                minWidth: 40,
                resizable: true,
                hozAlign: 'center',
                headerSort: false,
                frozen: true,
                formatter: (cell) => {
                    const url = cell.getValue();
                    if (!url) return '';
                    return `<img src="${url}" alt="" style="width: 100%; height: auto; display: block;">`;
                },
            },
            {
                title: 'Set',
                field: 'set',
                width: 100,
                headerFilter: 'input',
                formatter: (cell) => {
                    const row = cell.getRow().getData();
                    const url = urlFor('details', row.id);
                    const value = cell.getValue() ?? '';
                    return `<a href="${url}">${value}</a>`;
                },
            },
            {
                title: 'Name',
                field: 'name',
                width: 250,
                headerFilter: 'input',
            },
            {
                title: 'Year',
                field: 'year',
                width: 90,
                hozAlign: 'right',
                headerFilter: 'list',
                headerFilterParams: { valuesLookup: true, clearable: true },
                headerFilterFunc: '=',
            },
            {
                title: 'Theme',
                field: 'theme',
                width: 150,
                headerFilter: 'list',
                headerFilterParams: { valuesLookup: true, clearable: true },
                headerFilterFunc: '=',
            },
            {
                title: 'Parts',
                field: 'number_of_parts',
                width: 90,
                hozAlign: 'right',
                headerFilter: 'input',
            },
            {
                title: 'Description',
                field: 'description',
                width: 220,
                editor: 'input',
                headerFilter: 'input',
                cellEdited: saveField,
            },
            {
                title: 'Storage',
                field: 'storage_id',
                width: 160,
                editor: 'list',
                editorParams: { values: storageOptions },
                headerFilter: 'list',
                headerFilterParams: { values: storageFilterOptions, clearable: true },
                headerFilterFunc: noneAwareFilter,
                formatter: (cell) => storageNames[cell.getValue()] || '',
                cellEdited: saveField,
            },
            {
                title: 'Purchase location',
                field: 'purchase_location_id',
                width: 160,
                editor: 'list',
                editorParams: { values: purchaseLocationOptions },
                headerFilter: 'list',
                headerFilterParams: { values: purchaseLocationFilterOptions, clearable: true },
                headerFilterFunc: noneAwareFilter,
                formatter: (cell) => purchaseLocationNames[cell.getValue()] || '',
                cellEdited: saveField,
            },
            {
                title: 'Purchase date',
                field: 'purchase_date',
                width: 130,
                editor: 'input',
                editorParams: { elementAttributes: { placeholder: 'YYYY/MM/DD' } },
                cellEdited: saveField,
            },
            {
                title: 'Purchase price',
                field: 'purchase_price',
                width: 130,
                hozAlign: 'right',
                editor: 'number',
                editorParams: { step: 0.01 },
                cellEdited: saveField,
            },
        ],
    });

    const clearFiltersButton = document.getElementById('sets-table-clear-filters');
    if (clearFiltersButton) {
        clearFiltersButton.addEventListener('click', () => {
            table.clearHeaderFilter();
        });
    }

    table.on('columnResized', (column) => {
        if (column.getField() === 'image') {
            table.getRows().forEach((row) => row.normalizeHeight());
        }
    });

    table.on('renderComplete', () => {
        const images = tableElement.querySelectorAll('.tabulator-cell img');
        const pending = [];

        images.forEach((img) => {
            if (!img.complete) {
                pending.push(new Promise((resolve) => {
                    img.addEventListener('load', resolve, { once: true });
                    img.addEventListener('error', resolve, { once: true }); // don't hang forever on a broken thumbnail
                }));
            }
        });

        if (pending.length) {
            Promise.all(pending).then(() => {
                table.getRows().forEach((row) => row.normalizeHeight());
            });
        }
    });

});
