// grid.js
window.gridMethods = {
    createHandsontableGrid(tabId, configKey, config) {
        const currentTab = window.vm.openTabs.find(tab => tab.id === tabId);
        if (!currentTab) {
            console.error(`GridMethods: Tab ${tabId} not found.`);
            return;
        }

        const container = document.getElementById(`handsontable-${tabId}-${configKey}`);
        if (!container) {
            console.error(`Container handsontable-${tabId}-${configKey} not found`);
            return;
        }

        if (currentTab.gridInstances[configKey] && typeof currentTab.gridInstances[configKey].destroy === 'function') {
            currentTab.gridInstances[configKey].destroy();
        }
        currentTab.gridInstances[configKey] = null;

        const isReadOnly = window.globalAkses === 2;

        const formulaRenderer = (instance, td, row, col, prop, value, cellProperties) => {
            td.classList.add('formula-cell');
            const rowData = instance.getSourceDataAtRow(row);
            const field = config.specificFields.find(f => f.id === prop);
            if (field && field.type === 'formula' && rowData) {
                const calculatedValue = window.formMethods.calculateFormula(config, rowData, field, true);
                td.innerText = calculatedValue;
            } else {
                td.innerText = value !== null && value !== undefined ? value : '';
            }
            return td;
        };

        const passwordRenderer = (instance, td, row, col, prop, value, cellProperties) => {
            td.classList.add('password-cell');
            td.innerText = value ? '********' : '';
            return td;
        };

        const fileRenderer = (instance, td, row, col, prop, value, cellProperties) => {
            // ... (isi fungsi fileRenderer tetap sama) ...
            if (!instance || typeof instance.getSourceDataAtRow !== 'function') {
                td.innerHTML = 'Error: Grid instance not ready for file upload.';
                return td;
            }
            const rowData = instance.getSourceDataAtRow(row);
            const idField = config.specificFields.find(f => f.autoIncrement || f.id === 'id');
            let canUpload = false;
            if (rowData && idField && rowData[idField.id]) {
                canUpload = true;
            }

            let acceptFileType = '*/*'; 
            const currentFieldConfig = config.specificFields.find(f => f.id === prop);
            if (currentFieldConfig && currentFieldConfig.fileType) {
                if (currentFieldConfig.fileType === 'image') acceptFileType = 'image/*';
                else if (currentFieldConfig.fileType === 'pdf') acceptFileType = 'application/pdf';
            }

            let uploadButtonHTML = '';
            if (!isReadOnly && canUpload) {
                 uploadButtonHTML = `<input type="file" accept="${acceptFileType}" style="display: none;" onchange="window.handleGridFileUpload('${tabId}', '${configKey}', ${row}, '${prop}', this)"><button class="btn btn-sm btn-info" style="padding: 2px 5px; font-size: 0.8em;" onclick="this.previousElementSibling.click()"><i class="fas fa-upload"></i></button>`;
            } else if (!isReadOnly && !canUpload) {
                uploadButtonHTML = '<button class="btn btn-sm btn-info" style="padding: 2px 5px; font-size: 0.8em;" disabled title="Simpan data dulu untuk upload"><i class="fas fa-upload"></i></button>';
            }

            td.innerHTML = uploadButtonHTML;
            if (value) {
                td.innerHTML += (uploadButtonHTML ? ' ' : '') + `<a href="${value}" target="_blank" class="btn btn-sm btn-secondary btn-view-file" style="padding: 2px 5px; font-size: 0.8em;"><i class="fas fa-eye"></i></a>`;
            } else if (!uploadButtonHTML) {
                td.innerHTML = '<span class="no-file-text">-</span>';
            }
            return td;
        };

        // Filter specificFields untuk kolom yang akan dirender di Handsontable
        const gridColumnFields = config.specificFields.filter(field => field.type !== 'html');

        const columns = gridColumnFields.map(field => {
            const col = { data: field.id, title: field.label };
            if (field.width) col.width = field.width;
            if (isReadOnly && field.id !== config.filterx) col.readOnly = true; 
            else if (field.readonly) col.readOnly = true;

            switch (field.type) {
                case 'text': col.type = 'text'; break;
                case 'number':
                    col.type = 'numeric';
                    col.numericFormat = { pattern: field.sum || field.type === 'formula' ? '0,0' : '0,0.00', culture: 'id-ID' }; 
                    break;
                case 'date':
                    col.type = 'date';
                    col.dateFormat = 'YYYY-MM-DD'; 
                    col.correctFormat = true;
                    break;
                case 'checkbox':
                    col.type = 'checkbox';
                    col.checkedTemplate = '1';
                    col.uncheckedTemplate = '0';
                    col.className = 'htCenter'; 
                    break;
                case 'select':
                    col.type = 'dropdown';
                    // Memastikan options adalah array sebelum map, dan itemnya adalah objek
                    col.source = Array.isArray(field.options) ? field.options.map(opt => typeof opt === 'object' && opt !== null ? opt.value : opt) : [];
                    col.strict = true;
                    col.allowInvalid = false;
                    break;
                case 'autocomplete':
                    col.type = 'autocomplete';
                    col.source = (query, process) => {
                        if (field.link && field.link[0]) {
                            const link = field.link[0];
                            window.searchAutocomplete(query, results => process(results), link.table, link.field1, link.field2);
                        } else { process([]); }
                    };
                    col.strict = false; 
                    col.allowInvalid = true; 
                    col.filter = true;
                    col.trimDropdown = false;
                    break;
                case 'formula':
                    col.readOnly = true;
                    col.renderer = formulaRenderer;
                    col.type = 'numeric'; 
                    col.numericFormat = { pattern: '0,0', culture: 'id-ID' };
                    break;
                case 'file':
                    col.renderer = fileRenderer;
                    col.readOnly = true; 
                    break;
                case 'password':
                    col.type = 'password'; 
                    col.renderer = passwordRenderer; 
                    break;
                case 'dropdown': 
                    col.type = 'dropdown';
                    col.source = field.source ? field.source.map(s => s.split(' _ ')[0].trim()) : [];
                     col.cells = function (row, col, prop) {
                        const cellProperties = {};
                        const rowData = this.instance.getSourceDataAtRow(row);
                        if (rowData && field.source) {
                            cellProperties.source = field.source.map(s => s.split(' _ ')[0].trim());
                        }
                        return cellProperties;
                    };
                    col.renderer = function (instance, td, row, col, prop, value, cellProperties) {
                        Handsontable.renderers.DropdownRenderer.apply(this, arguments);
                        if (value && field.source) {
                            const matchedOption = field.source.find(s => s.split(' _ ')[0].trim() === String(value).trim());
                            td.innerHTML = matchedOption ? matchedOption.split(' _ ').pop().trim() : value;
                        } else {
                            td.innerHTML = value;
                        }
                    };
                    col.strict = true;
                    col.allowInvalid = false;
                    break;
                // tipe 'html' tidak masuk sini karena sudah difilter dari gridColumnFields
                default: col.type = 'text'; break;
            }
            if (field.required && !col.readOnly) {
                 col.validator = function (value, callback) {
                    if (field.type === 'checkbox') { 
                        callback(true);
                    } else {
                        callback(value !== null && value !== undefined && String(value).trim() !== '');
                    }
                };
            }
            return col;
        });

        if (!isReadOnly) {
            columns.push({
                data: '_actions',
                title: 'Aksi',
                renderer: (instance, td, row, col, prop, value, cellProperties) => {
                    // ... (isi renderer aksi tetap sama) ...
                     if (!instance.isEmptyRow(row)) { 
                        const spareRowsCount = instance.getSettings().minSpareRows;
                        if (spareRowsCount > 0 && row >= (instance.countRows() - spareRowsCount)) {
                            td.innerHTML = '';
                        } else {
                            td.innerHTML = `<button class="htButtons delete-row" title="Hapus Baris"><i class="fas fa-trash-alt"></i></button>`;
                            td.style.textAlign = 'center';
                        }
                     } else {
                        td.innerHTML = ''; 
                     }
                    return td;
                },
                readOnly: true,
                width: 70
            });
        }

        const hiddenColumnsConfig = {
            columns: gridColumnFields // Gunakan field yang sudah difilter
                .map((field, index) => (field.hidden ? index : -1))
                .filter(index => index !== -1),
            indicators: true
        };

        const hot = new Handsontable(container, {
           data: currentTab.gridData && currentTab.gridData[configKey] ? currentTab.gridData[configKey] : [],
            columns: columns,
            rowHeaders: true,
            fixedColumnsLeft: 0,
            colHeaders: gridColumnFields.map(f => f.label).concat(isReadOnly ? [] : ['Aksi']),
            minSpareRows: isReadOnly ? 0 : (config.model === 'grid' ? 1 : 0),
            autoRowSize: true,
            stretchH: 'none',
            autoWrapRow: false,
            autoWrapCol: false,
            licenseKey: 'non-commercial-and-evaluation',
            enterMoves: { row: 0, col: 1 },
            tabMoves: { row: 0, col: 1 },
            columnSorting: true,
            filters: true,
            hiddenColumns: hiddenColumnsConfig,
            dropdownMenu: ['filter_by_condition', 'filter_action_bar', 'filter_by_value'],
            manualRowMove: !isReadOnly,
            manualColumnMove: true,
            contextMenu: !isReadOnly ? ['row_above', 'row_below', 'remove_row', '---------', 'undo', 'redo'] : false,
            // preventOverflow: 'horizontal',
            fixedRowsTop: 1,
            visibleRows: 11,

            afterChange: async (changes, source) => {
                if (source === 'loadData' || source === 'internalUpdate' || !changes || isReadOnly) {
                     // Panggil updateGridSummary bahkan saat loadData atau internalUpdate agar agregat terhitung
                    if (source === 'loadData' || source === 'internalUpdate') {
                        if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                            window.gridMethods.updateGridSummary(tabId, configKey, config);
                        }
                    }
                    return;
                }


                const hotInstance = currentTab.gridInstances[configKey];
                if (!hotInstance) return;

                for (const change of changes) {
                    const [row, prop, oldValue, newValue] = change;
                    if (prop === '_actions') continue; 

                    const rowData = hotInstance.getSourceDataAtRow(row);
                    if (!rowData || hotInstance.isEmptyRow(row)) continue;

                    const fieldConfigChanged = config.specificFields.find(f => f.id === prop);

                    if (fieldConfigChanged && (fieldConfigChanged.type === 'autocomplete' || fieldConfigChanged.type === 'dropdown') && fieldConfigChanged.link && newValue) {
                        // ... (logika autocomplete/dropdown tetap sama) ...
                        const linkInfo = fieldConfigChanged.link[0];
                        let idPart = newValue;
                        let namePart = '';

                        if (fieldConfigChanged.type === 'autocomplete' && String(newValue).includes(' _ ')) {
                            const parts = String(newValue).split(' _ ');
                            idPart = parts[0].trim();
                            namePart = parts.length > 1 ? parts[1].trim() : '';
                        } else if (fieldConfigChanged.type === 'dropdown' && fieldConfigChanged.source) {
                            const matchedSource = fieldConfigChanged.source.find(s => s.startsWith(idPart + ' _ '));
                            if (matchedSource) {
                                namePart = matchedSource.split(' _ ').pop().trim();
                            }
                        }

                        if (rowData[prop] !== idPart) { 
                            hotInstance.setDataAtCell(row, hotInstance.propToCol(prop), idPart, 'internalUpdate');
                        }
                        if (linkInfo.field3 && namePart && rowData[linkInfo.field3] !== namePart) {
                            hotInstance.setDataAtCell(row, hotInstance.propToCol(linkInfo.field3), namePart, 'internalUpdate');
                        }
                    }

                    config.specificFields.forEach(f => {
                        if (f.type === 'formula' && f.id !== prop) { // Hindari kalkulasi ulang field formula itu sendiri jika dia yang berubah (seharusnya tidak terjadi karena readonly)
                            const calculated = window.formMethods.calculateFormula(config, rowData, f, false);
                            if (rowData[f.id] !== calculated) {
                                hotInstance.setDataAtCell(row, hotInstance.propToCol(f.id), calculated, 'internalUpdate');
                            }
                        }
                    });

                    const idField = config.specificFields.find(f => f.autoIncrement || f.id === 'id');
                    const currentId = rowData[idField.id];
                    let hasMeaningfulData = false;
                    const dataToSend = {};

                    // BARU: Logika untuk menentukan field mana yang akan dikirim ke database
                    const dataFieldTypes = ['text', 'number', 'date', 'checkbox', 'select', 'autocomplete', 'password', 'dropdown'];


                    config.specificFields.forEach(f => {
                        // Hanya kirim field yang merupakan tipe data aktual dan bukan virtual/display
                        if (dataFieldTypes.includes(f.type) || (f.type === 'formula' && f.saveValue === true)) {
                            const val = rowData[f.id];
                            if (val !== undefined && val !== null && String(val).trim() !== '') {
                                dataToSend[f.id] = (f.type === 'checkbox') ? (val ? '1' : '0') : val;
                                if (f.id !== idField.id) hasMeaningfulData = true;
                            } else if (f.type === 'checkbox') { 
                               dataToSend[f.id] = '0';
                            } else if (f.id === idField.id && val) { 
                               dataToSend[f.id] = val;
                            } else if (f.type === 'number' && val === 0){ // Izinkan angka 0 dikirim
                                dataToSend[f.id] = 0;
                                hasMeaningfulData = true;
                            }
                            // Untuk field yang boleh null, Anda mungkin perlu logika tambahan
                        }
                    });

                    if (config.filter && currentTab.parentFilterValue !== null && currentTab.parentFilterValue !== undefined) {
                        // Pastikan field filter (dari parent) juga ada di dataToSend jika merupakan kolom di tabel anak
                        if (config.specificFields.some(sf => sf.id === config.filter && dataFieldTypes.includes(sf.type))) {
                           dataToSend[config.filter] = currentTab.parentFilterValue;
                        }
                        hasMeaningfulData = true; 
                    }


                    if (hasMeaningfulData || (currentId && oldValue !== newValue && dataFieldTypes.includes(fieldConfigChanged.type))) { 
                        try {
                            let saveResult;
                            if (currentId && currentId > 0) { 
                                saveResult = await window.rubahjson(config.table, dataToSend, currentId, '');
                            } else { 
                                if (idField.autoIncrement && dataToSend.hasOwnProperty(idField.id) && !dataToSend[idField.id]) {
                                    delete dataToSend[idField.id]; 
                                }
                                saveResult = await window.tambahjson(config.table, dataToSend);
                                if (saveResult.success && saveResult.id > 0) {
                                    hotInstance.setDataAtCell(row, hotInstance.propToCol(idField.id), saveResult.id, 'internalUpdate');
                                }
                            }

                            if (!saveResult.success) {
                                alert('Gagal menyimpan data grid: ' + (saveResult.message || saveResult.error || 'Unknown error'));
                                hotInstance.setDataAtCell(row, hotInstance.propToCol(prop), oldValue, 'internalUpdate'); 
                            } else {
                                console.log(`Grid data saved for row ${row}, ID: ${currentId || saveResult.id}`);
                                if (config.filterx && prop === config.filterx) {
                                    currentTab.parentFilterValue = newValue; 
                                    currentTab.parentFilterKeyField = config.filterx;
                                    window.vm.refreshDetailGrids(tabId, configKey); 
                                }
                            }
                        } catch (error) {
                            alert('Error menyimpan data grid: ' + error.message);
                            hotInstance.setDataAtCell(row, hotInstance.propToCol(prop), oldValue, 'internalUpdate');
                        }
                    }
                }
                if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                    window.gridMethods.updateGridSummary(tabId, configKey, config);
                }
            },
            afterSelectionEnd: (r, c, r2, c2, selectionLayerLevel) => {
                // ... (isi afterSelectionEnd tetap sama) ...
                if (selectionLayerLevel !== 0) return; 
                const hotInstance = currentTab.gridInstances[configKey];
                if (!hotInstance || !config.filterx) return; 

                const selectedRowData = hotInstance.getSourceDataAtRow(r); 
                if (selectedRowData && !hotInstance.isEmptyRow(r)) {
                    currentTab.parentFilterValue = selectedRowData[config.filterx];
                    currentTab.parentFilterKeyField = config.filterx;
                } else { 
                    currentTab.parentFilterValue = null;
                    currentTab.parentFilterKeyField = config.filterx; 
                }
                window.vm.refreshDetailGrids(tabId, configKey); 
            },
            afterLoadData: (sourceData, initialLoad, source) => {
                // ... (isi afterLoadData tetap sama, pastikan updateGridSummary dipanggil) ...
                if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                    window.gridMethods.updateGridSummary(tabId, configKey, config);
                }

                if (config.filterx && initialLoad) { 
                    const hotInstance = currentTab.gridInstances[configKey];
                    if (hotInstance && hotInstance.countRows() > 0) {
                        const spareRowsCount = hotInstance.getSettings().minSpareRows;
                        if (spareRowsCount === 0 || hotInstance.countRows() > spareRowsCount) { 
                            hotInstance.selectCell(0,0); 
                        } else { 
                             currentTab.parentFilterValue = null;
                             currentTab.parentFilterKeyField = config.filterx;
                             window.vm.refreshDetailGrids(tabId, configKey); 
                        }
                    } else { 
                        currentTab.parentFilterValue = null;
                        currentTab.parentFilterKeyField = config.filterx;
                        window.vm.refreshDetailGrids(tabId, configKey); 
                    }
                }
            },
            afterRender: (isForced) => { 
                // ... (isi afterRender untuk tombol hapus tetap sama) ...
                 if (isReadOnly || !currentTab.gridInstances[configKey] || !container) {
                     return;
                 }
                 const hotInstance = currentTab.gridInstances[configKey];

                 container.querySelectorAll('button.delete-row').forEach(button => {
                    const newButton = button.cloneNode(true);
                    if (button.parentNode) {
                        button.parentNode.replaceChild(newButton, button);
                    } else {
                        return;
                    }

                    newButton.addEventListener('click', async (event) => {
                        event.stopPropagation(); 

                        const tdElement = newButton.closest('td');
                        if (!tdElement) {
                            console.error("Tidak bisa menemukan elemen <td> untuk tombol hapus.");
                            return;
                        }

                        const cellCoordinates = hotInstance.getCell(tdElement); 
                        if (!cellCoordinates) {
                            console.error("Tidak bisa mendapatkan koordinat sel HOT.");
                            return;
                        }
                        const rowVisualIndex = cellCoordinates.row; 

                        if (rowVisualIndex < 0 || rowVisualIndex >= hotInstance.countRows()) {
                            console.error('Indeks baris tidak valid:', rowVisualIndex);
                            return;
                        }
                        
                        const spareRowsCount = hotInstance.getSettings().minSpareRows;
                        if (spareRowsCount > 0 && rowVisualIndex >= (hotInstance.countRows() - spareRowsCount)) {
                            console.log('Mencoba menghapus spare row, operasi dibatalkan.');
                            return; 
                        }

                        const rowData = hotInstance.getSourceDataAtRow(rowVisualIndex);
                        const idFieldConfig = config.specificFields.find(f => f.autoIncrement || f.id === 'id');

                        if (!idFieldConfig) {
                            alert('Error: Konfigurasi field ID tidak ditemukan.');
                            return;
                        }
                        
                        if (rowData && rowData[idFieldConfig.id] && rowData[idFieldConfig.id] > 0) { 
                            if (confirm(`Apakah Anda yakin ingin menghapus data dengan ID: ${rowData[idFieldConfig.id]}?`)) {
                                try {
                                    const result = await window.hapus(config.table, idFieldConfig.id, rowData[idFieldConfig.id]);
                                    if (result.success) {
                                        alert('Data berhasil dihapus dari server.');
                                        hotInstance.alter('remove_row', rowVisualIndex, 1); 
                                        
                                        if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                                            window.gridMethods.updateGridSummary(tabId, configKey, config);
                                        }

                                        if (config.filterx) {
                                            const selection = hotInstance.getSelected();
                                            let newParentFilterValue = null;
                                            if (selection && selection.length > 0) {
                                                const selectedRange = selection[0];
                                                const selectedRowAfterDelete = Math.min(selectedRange[0], selectedRange[2]);
                                                if (hotInstance.countRows() > 0 && selectedRowAfterDelete < hotInstance.countRows() &&
                                                    !(spareRowsCount > 0 && selectedRowAfterDelete >= (hotInstance.countRows() - spareRowsCount))) {
                                                    const selectedData = hotInstance.getSourceDataAtRow(selectedRowAfterDelete);
                                                    if (selectedData && !hotInstance.isEmptyRow(selectedRowAfterDelete)) {
                                                        newParentFilterValue = selectedData[config.filterx];
                                                    }
                                                }
                                            }
                                            const currentVueTab = window.vm.openTabs.find(t => t.id === tabId);
                                            if (currentVueTab && currentVueTab.parentFilterValue !== newParentFilterValue) {
                                                currentVueTab.parentFilterValue = newParentFilterValue;
                                            }
                                            window.vm.refreshDetailGrids(tabId, configKey);
                                        }
                                    } else {
                                        alert('Gagal menghapus data dari server: ' + (result.message || result.error || 'Unknown error'));
                                    }
                                } catch (error) {
                                    alert('Error saat menghubungi server untuk menghapus: ' + error.message);
                                }
                            }
                        } else if (rowData && !hotInstance.isEmptyRow(rowVisualIndex)) { 
                            if (confirm('Hapus baris ini dari tampilan? Data belum tersimpan di server.')) {
                                hotInstance.alter('remove_row', rowVisualIndex, 1);
                                if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                                    window.gridMethods.updateGridSummary(tabId, configKey, config);
                                }
                            }
                        } else { 
                             if(!hotInstance.isEmptyRow(rowVisualIndex)) { 
                                hotInstance.alter('remove_row', rowVisualIndex, 1);
                                if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
                                    window.gridMethods.updateGridSummary(tabId, configKey, config);
                                }
                            }
                        }
                    });
                 });
            }
        });
        currentTab.gridInstances[configKey] = hot;
        window.vm.$nextTick(() => { 
            if (currentTab.gridInstances[configKey]) {
                 currentTab.gridInstances[configKey].render();
            }
        });
    },

    async loadGridData(tabId, configKey, config, filterValue = null) {
        // ... (isi loadGridData tetap sama, pastikan updateGridSummary dipanggil setelah data dimuat) ...
        const currentTab = window.vm.openTabs.find(tab => tab.id === tabId);
        if (!currentTab) { console.error(`Grid load: Tab ${tabId} not found.`); return; }

        let hot = currentTab.gridInstances[configKey];
        if (!hot) {
            console.warn(`Handsontable instance for ${configKey} in tab ${tabId} not yet created. Creating now.`);
            if (config && config.model === 'grid') {
                 this.createHandsontableGrid(tabId, configKey, config);
                 await window.vm.$nextTick();
                 hot = currentTab.gridInstances[configKey];
                 if (!hot) {
                     console.error(`GRID.JS: Gagal membuat instance grid ${configKey} saat loadGridData.`);
                     return;
                 }
            } else {
                console.error(`GRID.JS: Config tidak ditemukan atau model bukan grid untuk ${configKey}.`);
                return;
            }
        }

        hot.loadData([]); 

        let result;
        try {
            if (config.filter && filterValue !== null && filterValue !== undefined && String(filterValue).trim() !== '') {
                result = await window.ambil(config.table, config.filter, filterValue, filterValue);
            } else if (config.filter && (filterValue === null || filterValue === undefined || String(filterValue).trim() === '')) {
                result = []; 
            }
            else { 
                result = await window.ambil(config.table, 'id', '0', '99999999999'); 
            }

            let data = result;

            if (data && data.error) {
                alert(`Error memuat data grid ${config.nama}: ${data.error}`); data = [];
            } else if (data && data.success === false) {
                alert(`Gagal memuat data grid ${config.nama}: ${data.message || data.error}`); data = [];
            } else if (!Array.isArray(data)) {
                console.warn(`Data grid ${config.nama} bukan array. Menggunakan array kosong.`); data = [];
            }

            const formattedData = data.map(row => {
                const formattedRow = {};
                config.specificFields.forEach(field => {
                    let value = row[field.id] !== undefined ? row[field.id] : '';
                    if (field.type === 'checkbox') {
                        value = (value === '1' || value === 1 || value === true) ? '1' : '0';
                    }
                    formattedRow[field.id] = value;
                });
                return formattedRow;
            });

            if (!currentTab.gridData) currentTab.gridData = {};
            currentTab.gridData[configKey] = formattedData;


            if (currentTab.gridInstances[configKey]) {
                 currentTab.gridInstances[configKey].loadData(formattedData); // Ini akan memicu afterLoadData
            }

        } catch (e) {
            console.error(`Gagal memuat data untuk grid ${config.nama} (tabel ${config.table}):`, e);
            if (currentTab.gridInstances[configKey]) {
                currentTab.gridInstances[configKey].loadData([]);
            }
            alert(`Gagal memuat data untuk ${config.nama}. Detail di konsol.`);
        }
    },

    refreshDetailGrids(tabId) {
        console.warn("gridMethods.refreshDetailGrids dipanggil langsung, ini mungkin tidak benar. Gunakan vm.refreshDetailGrids.");
    },

    updateGridSummary(tabId, configKey, config) {
        const currentTab = window.vm.openTabs.find(tab => tab.id === tabId);
        if (!currentTab || !currentTab.gridInstances || !currentTab.gridInstances[configKey] || !config) {
            console.warn(`updateGridSummary: Tab, instance grid, atau config tidak ditemukan untuk tabId: ${tabId}, configKey: ${configKey}`);
            return;
        }
    
        const hot = currentTab.gridInstances[configKey];
        if (!hot) { 
             console.warn(`updateGridSummary: Instance HOT tidak ada untuk ${configKey} di tab ${tabId}`);
             return;
        }
    
        let actualRows = 0;
        const rowCount = hot.countRows(); 
        const spareRows = hot.getSettings().minSpareRows;
        const dataRowCount = spareRows > 0 ? Math.max(0, rowCount - spareRows) : rowCount;
    
        for (let i = 0; i < dataRowCount; i++) {
            if (!hot.isEmptyRow(i)) {
                actualRows++;
            }
        }
    
        const newAggregates = {};
        if (config.specificFields && hot && dataRowCount >= 0) { // Memastikan dataRowCount tidak negatif
            config.specificFields.forEach(field => {
                if (field.type === 'number' || field.type === 'formula') {
                    let columnTotal = 0;
                    for (let i = 0; i < dataRowCount; i++) { 
                        if (!hot.isEmptyRow(i)) {
                            const rowData = hot.getSourceDataAtRow(i);
                            if (rowData) { 
                                let value;
                                if (field.type === 'formula') {
                                    value = window.formMethods.calculateFormula(config, rowData, field, false); 
                                } else { 
                                    value = parseFloat(rowData[field.id]);
                                }
                                if (typeof value === 'number' && !isNaN(value)) {
                                    columnTotal += value;
                                }
                            }
                        }
                    }
                    newAggregates[`${config.table}_${field.id}`] = columnTotal;
                }
            });
        }
        
        // Pastikan gridAggregates diinisialisasi di currentTab (seharusnya sudah oleh proses.js)
        if (!currentTab.gridAggregates) {
            currentTab.gridAggregates = {}; // Fallback, idealnya sudah ada
        }
        currentTab.gridAggregates[configKey] = newAggregates; // Simpan agregat yang baru dihitung
        
        // Update DOM untuk summary standar jika masih digunakan
        let summaryText = `Total Baris: ${actualRows}`;
        config.specificFields.forEach(field => {
            if (field.sum && (field.type === 'formula' || field.type === 'number')) { 
                const aggregateKey = `${config.table}_${field.id}`;
                if (newAggregates.hasOwnProperty(aggregateKey)) {
                    summaryText += `, Total ${field.label}: ${newAggregates[aggregateKey].toLocaleString('id-ID', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`;
                }
            }
        });
    
        const summaryElement = document.getElementById(`summary-${tabId}-${configKey}`);
        if (summaryElement) {
            summaryElement.innerText = summaryText;
            summaryElement.style.display = 'block'; 
        }
         // Memaksa Vue untuk mendeteksi perubahan pada objek bersarang jika diperlukan (Vue 3 biasanya otomatis)
        if (window.vm) {
            window.vm.$forceUpdate(); 
        }
    },

    insertRowOrScrollToSpare(tabId, configKey) {
        // ... (isi insertRowOrScrollToSpare tetap sama, pastikan updateGridSummary dipanggil) ...
        const currentTab = window.vm.openTabs.find(tab => tab.id === tabId);
        if (!currentTab || window.globalAkses === 2) return; 

        const hot = currentTab.gridInstances[configKey];
        const currentGridConfig = currentTab.dbConfigs[configKey];

        if (!hot || !currentGridConfig) return;

        const spareRows = hot.getSettings().minSpareRows;
        if (spareRows > 0) {
            const firstSpareRowIndex = hot.countRows() - spareRows;
            hot.selectCell(firstSpareRowIndex, 0);
            hot.scrollViewportTo(firstSpareRowIndex, 0, true, true); 
        } else { 
            const lastDataRowIndex = hot.countRows(); 
            hot.alter('insert_row_below', lastDataRowIndex > 0 ? lastDataRowIndex -1 : 0);
            const newActualRowIndex = hot.countRows() -1 ; 

            if (currentGridConfig.filter && currentTab.parentFilterValue !== null && currentTab.parentFilterValue !== undefined) {
                const filterColIndex = hot.propToCol(currentGridConfig.filter);
                if (filterColIndex !== null && filterColIndex !== -1) {
                    hot.setDataAtCell(newActualRowIndex, filterColIndex, currentTab.parentFilterValue, 'internalUpdate');
                }
            }

            window.vm.$nextTick(() => {
                hot.selectCell(newActualRowIndex, 0);
                hot.scrollViewportTo(newActualRowIndex, 0, true, true);
            });
        }
        if (window.gridMethods && typeof window.gridMethods.updateGridSummary === 'function') {
            window.gridMethods.updateGridSummary(tabId, configKey, currentGridConfig);
        }
    },

    exportGridToCsv(tabId, configKey) {
        // ... (isi exportGridToCsv tetap sama) ...
        const currentTab = window.vm.openTabs.find(tab => tab.id === tabId);
        if (!currentTab) return;

        const hot = currentTab.gridInstances[configKey];
        const config = currentTab.dbConfigs[configKey];
        if (!hot || !config) {
            alert("Instans Handsontable atau konfigurasi tidak ditemukan.");
            return;
        }

        const dataToExport = [];
        const colHeaders = hot.getColHeader(); // Ini mengambil label kolom yang benar berdasarkan gridColumnFields
        dataToExport.push(colHeaders.join(';')); // Header sudah benar karena colHeaders dari gridColumnFields


        const rowCount = hot.countRows();
        const spareRows = hot.getSettings().minSpareRows;
        const dataRowCount = spareRows > 0 ? rowCount - spareRows : rowCount;

        for (let i = 0; i < dataRowCount; i++) {
            if (hot.isEmptyRow(i)) continue;

            const row = hot.getSourceDataAtRow(i); 
            if (!row) continue;
            
            // Gunakan gridColumnFields untuk memastikan urutan dan field yang diekspor sesuai dengan yang ditampilkan
            const gridColumnFieldsForExport = config.specificFields.filter(field => field.type !== 'html' && !field.hidden && field.id !== '_actions');

            const rowValues = gridColumnFieldsForExport.map(field => {
                    let value = row[field.id];
                    if (field.type === 'formula') {
                        value = window.formMethods.calculateFormula(config, row, field, false); 
                    } else if (field.type === 'checkbox') {
                        value = (value === '1' || value === true) ? 'Ya' : 'Tidak';
                    } else if (field.type === 'password') {
                        value = '********';
                    } else if (field.type === 'dropdown' && field.source && value) {
                         const matchedOption = field.source.find(s => s.split(' _ ')[0].trim() === String(value).trim());
                         value = matchedOption ? matchedOption.split(' _ ').pop().trim() : value;
                    }
                    return `"${String(value === null || value === undefined ? '' : value).replace(/"/g, '""')}"`;
                });
            dataToExport.push(rowValues.join(';'));
        }
        const csvContent = "data:text/csv;charset=utf-8," + encodeURIComponent(dataToExport.join("\r\n"));
        const link = document.createElement("a");
        link.setAttribute("href", csvContent);
        link.setAttribute("download", `${currentTab.menuId}_${configKey}_grid_data_${new Date().toISOString().slice(0,10)}.csv`);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        alert('Data grid berhasil diexport ke CSV!');
    },
};