// main.js
var app = Vue.createApp({
    data: function () {
        return {
            selectedId: 1,
            isSidebarOpen: true,
            activeMenuId: null,
            subMenuOpen: {},
            menus: Array.isArray(window.menus) ? JSON.parse(JSON.stringify(window.menus)) : [],
            openTabs: [],
            activeTabId: null,
            nextTabId: 0,
            isLoadingConfig: false,
            isLayoutEditMode: false,
            unsavedLayoutChanges: false,
            searchGridModalVisible: false,
            searchGridCurrentTabId: null,
            searchGridCurrentConfigKey: null,
            searchGridOriginalRecordId: null,
            // Data properties untuk image modal dan upload
            isImageModalVisible: false,
            currentModalImageSrc: '',
            isModalImageZoomed: false,
            selectedFormImageFile: null, // Untuk menyimpan file yang dipilih sebelum upload
        };
    },
    methods: {
        // ... (gabungan methods dari form, grid, proses tetap sama) ...
        ... (function () {
            var merged = {};
            if (window.formMethods) {
                for (var key in window.formMethods) {
                    if (typeof window.formMethods[key] === 'function') {
                        (function (currentKey) {
                            merged[currentKey] = function () {
                                if (window.formMethods && typeof window.formMethods[currentKey] === 'function') {
                                    return window.formMethods[currentKey].apply(this, arguments);
                                } else { console.error("Error: formMethods function '" + currentKey + "' not found."); return Promise.reject(new Error("formMethods function " + currentKey + " not found.")); }
                            };
                        })(key);
                    }
                }
            }
            if (window.gridMethods) {
                for (var key in window.gridMethods) {
                    if (typeof window.gridMethods[key] === 'function') {
                        (function (currentKey) {
                            merged[currentKey] = function () {
                                if (window.gridMethods && typeof window.gridMethods[currentKey] === 'function') {
                                    return window.gridMethods[currentKey].apply(this, arguments);
                                } else { console.error("Error: gridMethods function '" + currentKey + "' not found."); return Promise.reject(new Error("gridMethods function " + currentKey + " not found.")); }
                            };
                        })(key);
                    }
                }
            }
            if (window.prosesMethods) {
                for (var key in window.prosesMethods) {
                    if (typeof window.prosesMethods[key] === 'function') {
                        (function (currentKey) {
                            if (['loadAppConfiguration', 'saveAppConfiguration', 'postOpenMenuDataLoad', 'processActiveTabDataLoad', 'refreshDetailGridsOrchestration'].includes(currentKey)) {
                                merged[currentKey] = function () {
                                    var args = [this].concat(Array.prototype.slice.call(arguments));
                                    if (window.prosesMethods && typeof window.prosesMethods[currentKey] === 'function') {
                                        return window.prosesMethods[currentKey].apply(null, args);
                                    } else { console.error("Error: Critical prosesMethods function '" + currentKey + "' not found."); return Promise.reject(new Error("Proses method " + currentKey + " is not available.")); }
                                };
                            } else {
                                merged[currentKey] = function () {
                                    if (window.prosesMethods && typeof window.prosesMethods[currentKey] === 'function') {
                                        return window.prosesMethods[currentKey].apply(this, arguments);
                                    } else { console.error("Error: prosesMethods function '" + currentKey + "' not found."); return Promise.reject(new Error("Proses method " + currentKey + " is not available.")); }
                                };
                            }
                        })(key);
                    }
                }
            }
            // Gabungkan layoutMethods
            if (window.layoutMethods) {
                for (var key in window.layoutMethods) {
                    if (typeof window.layoutMethods[key] === 'function') {
                        (function (currentKey) { // IIFE untuk menangkap currentKey
                            merged[currentKey] = function () {
                                if (window.layoutMethods && typeof window.layoutMethods[currentKey] === 'function') {
                                    // Panggil fungsi dari layoutMethods dengan konteks Vue instance (this)
                                    return window.layoutMethods[currentKey].apply(this, arguments);
                                } else {
                                    console.error("Error: layoutMethods function '" + currentKey + "' not found.");
                                    // Kembalikan Promise yang ditolak jika fungsi async diharapkan
                                    return Promise.reject(new Error("layoutMethods function " + currentKey + " not found."));
                                }
                            };
                        })(key);
                    }
                }
            }
            return merged;
        })(),

        async togglePosting(tabId, configKey) {
            const currentTab = this.openTabs.find(tab => tab.id === tabId);
            if (!currentTab) return;

            const config = currentTab.dbConfigs[configKey];
            const record = currentTab.currentRecord[configKey];
            if (!record || !record.id) {
                alert("Pilih record yang valid untuk diposting.");
                return;
            }

            // Tentukan state posting berikutnya
            const currentPostingStatus = record.posting || '0';
            const newPostingStatus = currentPostingStatus == '1' ? '0' : '1';
            const actionText = newPostingStatus == '1' ? 'mem-posting' : 'membuka posting';

            if (!confirm(`Anda yakin ingin ${actionText} record ini?`)) {
                return;
            }
            
            try {
                const payload = {
                    posting: newPostingStatus
                };

                // Panggil window.rubahjson untuk mengupdate record di DB
                const response = await window.rubahjson(config.table, payload, record.id, 'id');

                if (response.success) {
                    // Update state di frontend
                    record.posting = newPostingStatus;
                    alert(`Record berhasil di-${actionText}.`);
                    
                    // Paksa refresh detail grid untuk menerapkan status readonly
                    this.refreshDetailGrids(tabId, configKey);
                    
                    // Paksa Vue untuk update UI (tombol, dll)
                    this.$forceUpdate();
                } else {
                    alert('Gagal mengupdate status posting: ' + (response.error || 'Unknown error'));
                    console.error('Gagal posting:', response);
                }
            } catch (error) {
                alert('Error saat mengubah status posting. Cek konsol untuk detail.');
                console.error('Error AJAX saat posting:', error);
            }
        },        

        logoutUser: function() {
            // if (confirm("Apakah Anda yakin ingin keluar dari sesi ini?")) {
                // Hapus data sesi dari localStorage
                localStorage.removeItem('userSession');
                window.location.href = 'login.html';
            // }
        },

        printRecord(tabId, configKey) {
            const currentTab = this.openTabs.find(tab => tab.id === tabId);
            if (!currentTab) {
                alert("Tab tidak ditemukan.");
                return;
            }

            const record = currentTab.currentRecord[configKey];
            const config = currentTab.dbConfigs[configKey];
            const idField = config.specificFields.find(f => f.autoIncrement || f.id === 'id');

            if (!record || !idField || !record[idField.id]) {
                alert("Pilih record yang valid dan sudah tersimpan untuk dicetak.");
                return;
            }

            const recordId = record[idField.id];
            const menuId = currentTab.menuId;

            // Membuka cetak.html di tab baru dengan parameter URL
            const url = `cetak.html?menuId=${encodeURIComponent(menuId)}&recordId=${encodeURIComponent(recordId)}`;
            window.open(url, '_blank');
        },

executeTombolAction(tabId, configKey, fieldConfig) {
            const currentTab = this.openTabs.find(tab => tab.id === tabId);
            if (!currentTab || !fieldConfig || !fieldConfig.ekspresi_tombol) {
                console.error("Aksi tombol dibatalkan: Konfigurasi tidak lengkap.");
                return;
            }

            const expression = String(fieldConfig.ekspresi_tombol).trim();
            const match = expression.match(/^([a-zA-Z0-9_]+)\s*\((.*)\)$/);
            alert(expression);
            alert(match);

            if (!match || !match[1] || typeof window[match[1]] !== 'function') {
                alert(`Error: Fungsi "${match ? match[1] : 'N/A'}" tidak ditemukan atau format ekspresi salah.`);
                console.error(`Gagal mengeksekusi ekspresi tombol:`, expression);
                return;
            }

            const functionName = match[1];
            const argsString = match[2] || '';
            const record = currentTab.currentRecord[configKey];

            try {
                let parsedArgs = [];
                if (argsString.trim() !== '') {
                    // Logika parsing argumen yang disempurnakan, mirip dengan 'evaluateFormFunction'
                    parsedArgs = argsString.split(',').map(arg => {
                        arg = arg.trim();
                        // Handle string literal
                        if ((arg.startsWith("'") && arg.endsWith("'")) || (arg.startsWith('"') && arg.endsWith('"'))) {
                            return arg.substring(1, arg.length - 1);
                        }
                        // Handle placeholder field dari record ($fieldId)
                        if (arg.startsWith('$') && record) {
                            const fieldName = arg.substring(1);
                            if (record.hasOwnProperty(fieldName)) {
                                return record[fieldName];
                            }
                            console.warn(`Placeholder '${arg}' tidak ditemukan di record.`);
                            return null; // atau undefined
                        }
                        // Handle angka
                        if (!isNaN(arg) && arg !== '') {
                            return parseFloat(arg);
                        }
                        // Default: anggap sebagai string biasa (tanpa kutip)
                        return arg;
                    });
                }
                
                // Eksekusi fungsi global dengan argumen yang sudah diparsing
                const result = window[functionName].apply(null, parsedArgs);

                // Tampilkan hasil jika ada (opsional, tapi berguna)
                if (result !== undefined && result !== null) {
                    alert(`Hasil dari "${fieldConfig.label}":\n\n` + JSON.stringify(result, null, 2));
                }

            } catch (e) {
                alert(`Terjadi error saat menjalankan fungsi "${functionName}":\n` + e.message);
                console.error(`Error mengeksekusi ekspresi tombol "${expression}":`, e);
            }
        },        

        getDropdownValue(optionText) {
            // Fungsi ini untuk mendapatkan bagian sebelum " _ " sebagai value
            if (!optionText) return ''; // Pengaman jika data kosong
            return optionText.split(' _ ')[0].trim();
        },
        getDropdownLabel(optionText) {
            // Fungsi ini untuk mendapatkan bagian setelah " _ " sebagai teks tampilan
            if (!optionText) return ''; // Pengaman jika data kosong
            const parts = optionText.split(' _ ');
            return parts.length > 1 ? parts[1].trim() : parts[0].trim();
        },

        loadAppConfiguration: function () {
            var self = this;
            return window.prosesMethods.loadAppConfiguration(self).then(function () {
                self.addLayoutMenuToSidebar();
            }).catch(function (error) {
                console.error("Error loading app configuration in main.js:", error);
            });
        },
        saveAppConfiguration: function () {
            return window.prosesMethods.saveAppConfiguration(this);
        },
        addLayoutMenuToSidebar: function () {
            var tataLetakMenuId = 'tataLetakMenu';
            if (!this.menus.find(function (menu) { return menu.id === tataLetakMenuId; })) {
                var tataLetakParentMenu = {
                    id: tataLetakMenuId,
                    name: 'Tata Letak',
                    icon: 'fas fa-layer-group',
                    children: [
                        { id: 'startLayoutEdit', name: 'Mulai Edit Layout', icon: 'fas fa-edit', parent: tataLetakMenuId, action: 'startLayoutEditMode' },
                        { id: 'finishLayoutEdit', name: 'Selesai Edit Layout', icon: 'fas fa-check-circle', parent: tataLetakMenuId, action: 'finishLayoutEditMode' },
                        { id: 'saveLayoutChanges', name: 'Simpan Layout', icon: 'fas fa-save', parent: tataLetakMenuId, action: 'saveLayoutEdits' },
                        { id: 'resetCurrentLayoutConfirm', name: 'Reset Layout Tab Ini', icon: 'fas fa-undo', parent: tataLetakMenuId, action: 'confirmResetLayout' }
                    ]
                };
                this.menus.push(tataLetakParentMenu);
            }

            var logoutMenuId = 'userLogout';
            if (!this.menus.find(function (menu) { return menu.id === logoutMenuId; })) {
                var logoutMenu = {
                    id: logoutMenuId,
                    name: 'Logout',
                    icon: 'fas fa-sign-out-alt', // Ikon untuk logout
                    action: 'logoutUser' // Nama method yang akan kita buat
                };
                this.menus.push(logoutMenu); // Menambahkan ke akhir daftar menu
            }

        },

        toggleSidebar: function () { this.isSidebarOpen = !this.isSidebarOpen; },

        handleMenuClick: function (menuItem, parentMenuId = null) {
            // Jika item yang diklik memiliki anak, itu adalah folder
            if (menuItem.children && menuItem.children.length > 0) {
                this.subMenuOpen[menuItem.id] = !this.subMenuOpen[menuItem.id];
                // Jika folder tidak memiliki aksi langsung (db, action, dll.), tandai sebagai aktif
                if (!menuItem.db && !menuItem.action && !menuItem.function && !menuItem.link && !menuItem.link_external) {
                    this.activeMenuId = menuItem.id;
                }
                // Jika sebuah folder juga memiliki konfigurasi 'db', 'action', dll.,
                // dan Anda ingin itu bisa diklik untuk aksi DAN sebagai folder,
                // maka Anda mungkin perlu memanggil openMenuInNewTab di sini juga.
                // Namun, umumnya folder hanya untuk navigasi.
                // Contoh: Jika folder juga merupakan link, mungkin panggil openMenuInNewTab
                // if (menuItem.db || menuItem.action || menuItem.link || menuItem.function) {
                //    this.openMenuInNewTab(menuItem.id);
                // }

            } else {
                // Ini adalah item daun atau item dengan aksi langsung
                this.openMenuInNewTab(menuItem.id);
            }

            // Logika untuk menutup submenu lain (opsional, untuk UI yang lebih bersih)
            // Menutup submenu lain pada level yang sama jika ada parentMenuId
            if (parentMenuId) {
                const parentMenu = this.findMenuById(this.menus, parentMenuId);
                if (parentMenu && parentMenu.children) {
                    parentMenu.children.forEach(sibling => {
                        // Jika sibling adalah folder, punya anak, dan bukan item yang baru saja di-toggle, dan sedang terbuka
                        if (sibling.id !== menuItem.id && sibling.children && sibling.children.length > 0 && this.subMenuOpen[sibling.id]) {
                            if (!(menuItem.children && menuItem.children.length > 0 && menuItem.id === sibling.id)) { // Jangan tutup diri sendiri jika ini folder yg baru dibuka
                                // this.subMenuOpen[sibling.id] = false; // Baris ini menyebabkan submenu langsung tertutup jika ada item lain di level yg sama. Mungkin nonaktifkan.
                            }
                        }
                    });
                }
            } else if (!menuItem.children || menuItem.children.length === 0) {
                // Jika item top-level (level 0) yang merupakan daun diklik, tutup semua folder top-level lain
                // Jika item top-level (level 0) yang merupakan folder diklik, biarkan terbuka
                // Ini ditangani oleh kondisi !menuItem.children di atas
            }
            // Jika item yang baru saja di-toggle adalah folder, jangan tutup folder lain di level yang sama secara otomatis
            // kecuali jika itu adalah perilaku yang diinginkan.
            // Untuk UX yang lebih umum, pengguna mungkin ingin beberapa folder terbuka.
        },

        isAncestor: function (potentialChild, potentialAncestor) { // Helper untuk handleMenuClick
            if (!potentialAncestor.children || potentialAncestor.children.length === 0) {
                return false;
            }
            if (potentialAncestor.children.some(child => child.id === potentialChild.id)) {
                return true;
            }
            // Cek rekursif ke anak-anak dari potentialAncestor
            for (const child of potentialAncestor.children) {
                if (this.isAncestor(potentialChild, child)) {
                    return true;
                }
            }
            return false;
        },

        openMenuInNewTab: async function (menuId) { // Pastikan fungsi ini 'async'
            var self = this;
            var selectedMenu = this.findMenuById(this.menus, menuId);
            if (!selectedMenu) {
                console.error('Menu item not found:', menuId);
                return;
            }

            this.activeMenuId = menuId;

            // Logika untuk item menu dengan 'action' atau link eksternal tetap sama
            if (selectedMenu.action && typeof this[selectedMenu.action] === 'function') {
                this[selectedMenu.action]();
                return;
            }
            if (selectedMenu.link_external) {
                window.open(selectedMenu.link_external, '_blank');
                return;
            }
            // ... (logika lain untuk link & function tetap sama)

            // ================= AWAL BLOK PERUBAHAN UTAMA =================
            // Kondisi untuk memicu lazy-loading:
            // Jika item menu tidak memiliki 'children' yang sudah terdefinisi
            // DAN bukan merupakan item 'action' sederhana.
            if (selectedMenu.link) {
                // Bagian kode untuk membuka tab (dipindahkan dari bawah)
                var existingTab = this.openTabs.find(tab => tab.menuId === menuId);
                if (existingTab) {
                    this.setActiveTab(existingTab.id);
                    return; // Hentikan eksekusi
                }

                var newTabId = "tab-" + this.nextTabId++;
                // Pastikan initializeNewTabData menangani 'link' dengan benar
                var newTabObject = window.prosesMethods.initializeNewTabData(selectedMenu, newTabId);
                //await this.populateDynamicDropdowns(newTabObject);

                this.openTabs.push(newTabObject);
                this.activeTabId = newTabId;

                // Tidak ada data load yang diperlukan untuk iframe, jadi langsung return.
                return; 
            }
            // ========== AKHIR BLOK PERBAIKAN UNTUK IFRAME ==========


            // ================= AWAL BLOK LAZY-LOADING =================
            // Kondisi untuk memicu lazy-loading:
            // Jika item menu tidak memiliki 'children' yang sudah terdefinisi
            // DAN bukan merupakan item 'action' sederhana.
            if (!selectedMenu.children && !selectedMenu.action) {
                try {
                    // Pengecekan ini untuk memastikan kita hanya memuat jika detailnya belum ada.
                    const mainDbKey = selectedMenu.db ? Object.keys(selectedMenu.db)[0] : null;
                    const needsLoading = !mainDbKey || !selectedMenu.db[mainDbKey].specificFields;

                    if (needsLoading) {
                        console.log(`Memuat detail untuk node: ${selectedMenu.id} dari tabel 'node'...`);
                        const nodeDataArray = await window.ambil('node', 'node', selectedMenu.id, selectedMenu.id);

                        if (nodeDataArray && nodeDataArray.length > 0) {
                            const nodeJsonString = nodeDataArray[0].json;
                            if (nodeJsonString) {
                                const detailedConfig = JSON.parse(nodeJsonString);

                                // --- INI LOGIKA PERCABANGAN BARU ---
                                if (detailedConfig.children && Array.isArray(detailedConfig.children)) {
                                    // KASUS 1: Node adalah FOLDER SUBMENU
                                    console.log(`Node '${selectedMenu.id}' adalah sebuah submenu. Menyuntikkan 'children'...`);
                                    selectedMenu.children = detailedConfig.children; // Suntikkan array submenu
                                    this.subMenuOpen[selectedMenu.id] = true;      // Buka/expand menu di sidebar
                                    this.$forceUpdate();                           // Paksa Vue untuk render ulang sidebar
                                    return; // PENTING: Hentikan eksekusi, JANGAN buka tab baru.

                                } else if (detailedConfig.db) {
                                    // KASUS 2: Node adalah FORM/GRID (logika lama)
                                    console.log(`Node '${selectedMenu.id}' adalah form/grid. Menyuntikkan 'db'...`);
                                    selectedMenu.db = detailedConfig.db; // Ganti properti db dengan yang detail

                                } else {
                                    console.error(`Konfigurasi dari node '${selectedMenu.id}' tidak valid. Tidak ditemukan properti 'db' atau 'children'.`);
                                    return; // Hentikan jika config tidak valid
                                }
                            }
                        } else {
                            console.warn(`Tidak ada konfigurasi ditemukan di tabel 'node' untuk ID: ${selectedMenu.id}`);
                            // Jika tidak ada node, dan item menu ini tidak punya properti 'db', maka ini item kosong.
                            if (!selectedMenu.db) return;
                        }
                    }
                } catch (e) {
                    alert(`Error saat memuat konfigurasi untuk menu '${selectedMenu.name}'. Lihat konsol.`);
                    console.error("Error pada logika lazy-loading:", e);
                    return;
                }
            }
            // ================= AKHIR BLOK PERUBAHAN UTAMA =================


            // Jika setelah semua proses di atas item menu ini tidak punya 'db',
            // berarti itu adalah folder dan tidak boleh membuka tab.
            if (!selectedMenu.db) {
                console.log(`Item menu '${selectedMenu.name}' adalah folder, tidak membuka tab.`);
                return;
            }

            // Bagian selanjutnya dari kode untuk membuka tab tetap sama
            var existingTab = this.openTabs.find(tab => tab.menuId === menuId);
            if (existingTab) {
                this.setActiveTab(existingTab.id);
                return;
            }

            var newTabId = "tab-" + this.nextTabId++;
            var newTabObject = window.prosesMethods.initializeNewTabData(selectedMenu, newTabId);
            
            await this.populateDynamicDropdowns(newTabObject);

            this.openTabs.push(newTabObject);
            this.activeTabId = newTabId;

            return this.$nextTick().then(() => {
                const newTabObject = this.openTabs.find(t => t.id === newTabId);
                if (newTabObject && newTabObject.dbConfigs) {
                    // Iterasi semua konfigurasi dalam tab yang baru dibuat
                    Object.keys(newTabObject.dbConfigs).forEach(configKey => {
                        const config = newTabObject.dbConfigs[configKey];
                        // Jika modelnya adalah grid dan instance-nya belum ada, buat sekarang.
                        // $nextTick memastikan elemennya sudah ada di DOM.
                        if (config.model === 'grid' && !newTabObject.gridInstances[configKey]) {
                            this.createHandsontableGrid(newTabObject.id, configKey, config);
                        }
                    });
                }

                // Panggil proses pemuatan data setelah memastikan grid (jika ada) sudah dibuat.
                if (window.prosesMethods && typeof window.prosesMethods.postOpenMenuDataLoad === 'function') {
                    return window.prosesMethods.postOpenMenuDataLoad(this, newTabObject);
                } else {
                    console.error("Error in openMenuInNewTab: window.prosesMethods.postOpenMenuDataLoad is not a function!");
                    return Promise.reject(new Error("postOpenMenuDataLoad is not available."));
                }
            }).catch(function (error) {
                console.error("Error in openMenuInNewTab after nextTick:", error);
                throw error;
            });
        },
        setActiveTab: function (tabId) {
            var self = this;
            var prevActiveTab = this.openTabs.find(function (tab) { return tab.id === self.activeTabId; });
            if (prevActiveTab && prevActiveTab.id !== tabId && prevActiveTab.gridInstances) {
                for (var key in prevActiveTab.gridInstances) {
                    if (prevActiveTab.gridInstances[key] && typeof prevActiveTab.gridInstances[key].destroy === 'function') {
                        prevActiveTab.gridInstances[key].destroy();
                    }
                }
                prevActiveTab.gridInstances = {};
            }
            this.activeTabId = tabId;
            var currentTab = this.openTabs.find(function (tab) { return tab.id === tabId; });
            if (currentTab) {
                this.activeMenuId = currentTab.menuId;
                this.populateDynamicDropdowns(currentTab);

                return this.$nextTick().then(function () {
                    if (currentTab.dbConfigs) {
                        Object.keys(currentTab.dbConfigs).forEach(function (dbKey) {
                            var config = currentTab.dbConfigs[dbKey];
                            if (config.model === 'grid') {
                                if (!currentTab.gridInstances[dbKey]) {
                                    self.createHandsontableGrid(currentTab.id, dbKey, config);
                                } else {
                                    if (currentTab.gridInstances[dbKey] && typeof currentTab.gridInstances[dbKey].render === 'function') {
                                        currentTab.gridInstances[dbKey].render();
                                    }
                                }
                            }
                        });
                    }
                    return window.prosesMethods.processActiveTabDataLoad(self, currentTab);
                }).catch(function (error) {
                    console.error("Error in setActiveTab after nextTick:", error);
                });
            } else {
                console.warn("Tab tidak ditemukan saat setActiveTab:", tabId);
                return Promise.resolve();
            }
        },
        closeTab: function (tabId) {
            var index = this.openTabs.findIndex(function (tab) { return tab.id === tabId; });
            if (index !== -1) {
                var tabToClose = this.openTabs[index];
                if (this.isLayoutEditMode && this.unsavedLayoutChanges && this.activeTabId === tabId) {
                    if (!confirm("Ada perubahan tata letak yang belum disimpan pada tab ini. Anda yakin ingin menutupnya? Perubahan akan hilang.")) { return; }
                }
                if (tabToClose.gridInstances) {
                    for (var key in tabToClose.gridInstances) {
                        if (tabToClose.gridInstances[key] && typeof tabToClose.gridInstances[key].destroy === 'function') {
                            tabToClose.gridInstances[key].destroy();
                        }
                    }
                }
                this.openTabs.splice(index, 1);
                if (this.activeTabId === tabId) {
                    this.activeTabId = null;
                    if (this.openTabs.length > 0) {
                        var newActiveIndex = Math.max(0, index - 1);
                        this.setActiveTab(this.openTabs[newActiveIndex] ? this.openTabs[newActiveIndex].id : (this.openTabs[0] ? this.openTabs[0].id : null));
                    } else { this.activeMenuId = null; }
                }
            }
        },
        findMenuById: function (menusArray, id) {
            for (var i = 0; i < (menusArray || []).length; i++) {
                var menu = menusArray[i];
                if (menu.id === id) return menu;
                if (menu.children && menu.children.length > 0) { // Pastikan children ada dan tidak kosong
                    var found = this.findMenuById(menu.children, id);
                    if (found) return found;
                }
            }
            return null;
        },
        refreshDetailGrids: function (tabId, actingParentConfigKey) {
            window.prosesMethods.refreshDetailGridsOrchestration(this, tabId, actingParentConfigKey);
        },
        shouldHideGrid: function (tabId, config) { return false; },
        // Versi BARU - Salin dan ganti fungsi lama di main.js
        processDynamicHtml: function (tab, htmlFieldConfig) {
            if (!htmlFieldConfig || typeof htmlFieldConfig.content !== 'string') return '';
            let content = htmlFieldConfig.content;
            const regex = /{{(.*?)}}/g;

            // Perlu menjalankan loop penggantian berulang kali jika ada placeholder di dalam placeholder lain (kasus jarang)
            // Tapi untuk kasus ini, kita proses semua placeholder yang ditemukan.
            const replacements = [];
            let match;
            while ((match = regex.exec(content)) !== null) {
                replacements.push(match);
            }

            for (const match of replacements.reverse()) { // Reverse agar index tidak bergeser
                const expression = match[1].trim();
                let evalResult;

                try {
                    // Prioritas 1: Cari di Grid Aggregates
                    if (tab.gridAggregates) {
                        for (const configKey in tab.gridAggregates) {
                            if (tab.gridAggregates[configKey] && tab.gridAggregates[configKey].hasOwnProperty(expression)) {
                                const val = tab.gridAggregates[configKey][expression];
                                // Format angka agar lebih mudah dibaca
                                evalResult = typeof val === 'number' ? val.toLocaleString('id-ID') : val;
                                break; // Hentikan pencarian jika sudah ditemukan
                            }
                        }
                    }

                    // Prioritas 2: Cari di Current Record dari form utama (jika tidak ditemukan di agregat)
                    if (evalResult === undefined && tab.currentRecord && tab.mainConfigKey && tab.currentRecord[tab.mainConfigKey]) {
                        if (tab.currentRecord[tab.mainConfigKey].hasOwnProperty(expression)) {
                            evalResult = tab.currentRecord[tab.mainConfigKey][expression];
                        }
                    }

                    // Prioritas 3: Cek fungsi global (jika masih belum ditemukan)
                    if (evalResult === undefined && typeof window[expression] === 'function') {
                        evalResult = window[expression]();
                    }

                    // Lakukan penggantian
                    const valueToInsert = evalResult !== undefined ? evalResult : ''; // Ganti dengan string kosong jika tidak ditemukan
                    content = content.substring(0, match.index) + valueToInsert + content.substring(match.index + match[0].length);

                } catch (e) {
                    console.error('Error evaluating dynamic HTML expression:', expression, e);
                    content = content.substring(0, match.index) + `Error` + content.substring(match.index + match[0].length);
                }
            }
            return content;
        },
        // main.js
        evaluateFormFunction: function (tab, formConfigKey, functionFieldConfig) {
            // Prioritas pertama: Gunakan functionName dan args
            if (functionFieldConfig && functionFieldConfig.functionName && typeof window[functionFieldConfig.functionName] === 'function') {
                try {
                    let args = [];
                    if (functionFieldConfig.args && Array.isArray(functionFieldConfig.args)) {
                        args = functionFieldConfig.args.map(argConfig => {
                            if (argConfig.type === 'fieldValue') {
                                return tab.currentRecord[formConfigKey] ? tab.currentRecord[formConfigKey][argConfig.value] : undefined;
                            } else if (argConfig.type === 'literal') {
                                return argConfig.value;
                            }
                            return undefined;
                        });
                    }
                    // --- PERUBAHAN DI SINI ---
                    const result = window[functionFieldConfig.functionName].apply(null, args);
                    return String(result); // Hasilnya dikonversi menjadi String
                } catch (e) {
                    console.error(`Error executing function ${functionFieldConfig.functionName} via functionName/args:`, e);
                    return `Error executing function: ${functionFieldConfig.functionName}`;
                }
            }
            // Prioritas kedua: Parsing dari properti "isi"
            else if (functionFieldConfig && typeof functionFieldConfig.isi === 'string' && functionFieldConfig.isi.trim() !== '') {
                const isiString = functionFieldConfig.isi.trim();
                const match = isiString.match(/^([a-zA-Z0-9_]+)\s*\((.*)\)$/);

                if (match && match[1] && typeof window[match[1]] === 'function') {
                    const functionNameFromString = match[1];
                    const argsString = match[2];

                    try {
                        let parsedArgs = [];
                        if (argsString.trim() !== '') {
                            parsedArgs = argsString.split(',').map(arg => {
                                arg = arg.trim();
                                let potentialValue = arg;
                                let isQuoted = false;
                                if ((potentialValue.startsWith("'") && potentialValue.endsWith("'")) || (potentialValue.startsWith('"') && potentialValue.endsWith('"'))) {
                                    potentialValue = potentialValue.substring(1, potentialValue.length - 1);
                                    isQuoted = true;
                                }
                                if (potentialValue.startsWith('$')) {
                                    const fieldName = potentialValue.substring(1);
                                    if (tab.currentRecord[formConfigKey] && tab.currentRecord[formConfigKey].hasOwnProperty(fieldName)) {
                                        return tab.currentRecord[formConfigKey][fieldName];
                                    } else {
                                        // console.warn(`Placeholder '${arg}' tidak dapat menemukan field sumber dengan id '${fieldName}'.`);
                                        return null;
                                    }
                                }
                                if (isQuoted) {
                                    return potentialValue;
                                }
                                if (!isNaN(potentialValue) && potentialValue !== '') {
                                    return parseFloat(potentialValue);
                                }
                                return potentialValue;
                            });
                        }
                        // --- PERUBAHAN DI SINI ---
                        const result = window[functionNameFromString].apply(null, parsedArgs);
                        return String(result); // Hasilnya dikonversi menjadi String
                    } catch (e) {
                        console.error(`Error executing function ${functionNameFromString} from "isi" ('${isiString}'):`, e);
                        return `Error parsing/executing "isi": ${functionNameFromString}`;
                    }
                } else {
                    console.warn(`Could not parse "isi" string or function not found: ${isiString}`);
                    return `Invalid "isi" format or function missing: ${isiString.substring(0, 20)}...`;
                }
            }
            return ''; // Default
        },
        startLayoutEditMode: function () { this.isLayoutEditMode = true; this.activeMenuId = 'startLayoutEdit'; },
        finishLayoutEditMode: async function () {
            if (this.unsavedLayoutChanges) {
                if (confirm("Ada perubahan tata letak yang belum disimpan. Simpan sekarang?")) {
                    await this.initiateSaveLayout(); // initiateSaveLayout sudah di-mixin dari layoutMethods
                } else {
                    // Jika pengguna memilih "Tidak", reload konfigurasi dari server untuk membuang perubahan lokal
                    alert("Perubahan tata letak lokal akan dibuang. Memuat ulang konfigurasi dari server...");
                    await this.loadAppConfiguration(); // Muat ulang konfigurasi dari server
                    this.unsavedLayoutChanges = false;
                }
            }
            this.isLayoutEditMode = false;
            this.activeMenuId = 'finishLayoutEdit';
        },
        saveLayoutEdits: function () {
            this.initiateSaveLayout(); // initiateSaveLayout sudah di-mixin dari layoutMethods
            this.activeMenuId = 'saveLayoutChanges';
        },
        confirmResetLayout: function () {
            this.resetCurrentTabLayout(); // resetCurrentTabLayout sudah di-mixin dari layoutMethods
            this.activeMenuId = 'resetCurrentLayoutConfirm';
        },
        // Metode handleFieldMouseDown, handleInputMouseDown, initiateSaveLayout, 
        // resetCurrentTabLayout, getFieldStyle, getInputStyle, getFormFrameStyle
        // akan di-mixin dari layout.js
        showSearchGridModal: function (tabId, configKey) {
            var self = this;
            var currentTab = this.openTabs.find(function (tab) { return tab.id === tabId; });
            if (!currentTab || !currentTab.dbConfigs[configKey] || currentTab.dbConfigs[configKey].model !== 'form') {
                alert("Fungsi pencarian hanya untuk form.");
                return;
            }

            this.searchGridCurrentTabId = tabId;
            this.searchGridCurrentConfigKey = configKey;

            var idFieldInForm = (currentTab.dbConfigs[configKey].specificFields.find(function (f) { return f.id === 'id' || f.autoIncrement; }));
            var idFieldNameInForm = idFieldInForm ? idFieldInForm.id : 'id';
            var currentRecordForId = currentTab.currentRecord[configKey];
            this.searchGridOriginalRecordId = currentRecordForId && currentRecordForId[idFieldNameInForm] ? currentRecordForId[idFieldNameInForm] : null;


            var formConfig = currentTab.dbConfigs[configKey];

            var modalContainer = document.getElementById('searchGridModalContainer');
            if (modalContainer) modalContainer.innerHTML = '';

            if (typeof window.createInteractiveGrid === "function") {
                window.createInteractiveGrid(
                    '#searchGridModalContainer',
                    formConfig,
                    function (selectedId) {
                        var targetTabId = self.searchGridCurrentTabId;
                        var targetConfigKey = self.searchGridCurrentConfigKey;

                        document.getElementById('searchGridModal').style.display = 'none';
                        self.searchGridModalVisible = false;

                        var promiseChain = Promise.resolve();

                        if (selectedId !== null && selectedId !== undefined) {
                            var targetTabForLoad = self.openTabs.find(function (t) { return t.id === targetTabId; });
                            if (!targetTabForLoad || !targetTabForLoad.dbConfigs[targetConfigKey]) {
                                self.hideSearchGridModal();
                                return;
                            }
                            var targetFormConfigForLoad = targetTabForLoad.dbConfigs[targetConfigKey];

                            promiseChain = self.loadFormData(targetTabId, targetConfigKey, targetFormConfigForLoad, 'specificId', selectedId)
                                .then(function () {
                                    targetTabForLoad.formMode[targetConfigKey] = 'view';
                                }).catch(function (error) {
                                    console.error("SearchGrid Modal: Exception saat memanggil loadFormData:", error);
                                    alert("Gagal memuat data yang dipilih.");
                                });
                        } else {
                            if (self.searchGridOriginalRecordId && targetTabId && targetConfigKey) {
                                var targetTabForRestore = self.openTabs.find(function (t) { return t.id === targetTabId; });
                                var targetFormConfigForRestore = targetTabForRestore ? targetTabForRestore.dbConfigs[targetConfigKey] : null;
                                if (targetFormConfigForRestore) {
                                    promiseChain = self.loadFormData(targetTabId, targetConfigKey, targetFormConfigForRestore, 'specificId', self.searchGridOriginalRecordId);
                                }
                            }
                        }

                        promiseChain.then(function () {
                            self.hideSearchGridModal();
                        }).catch(function () {
                            self.hideSearchGridModal();
                        });
                    }
                );
                document.getElementById('searchGridModal').style.display = 'block';
                this.searchGridModalVisible = true;
            } else {
                console.error("Fungsi createInteractiveGrid tidak ditemukan.");
                alert("Error: Fitur pencarian tidak dapat diinisialisasi.");
            }
        },
        hideSearchGridModal: function () {
            var modal = document.getElementById('searchGridModal');
            if (modal) modal.style.display = 'none';
            this.searchGridModalVisible = false;

            var modalContainer = document.getElementById('searchGridModalContainer');
            if (modalContainer) {
                modalContainer.innerHTML = '';
            }
            this.searchGridCurrentTabId = null;
            this.searchGridCurrentConfigKey = null;
            this.searchGridOriginalRecordId = null;
            console.log("SearchGrid Modal: Modal ditutup dan state direset.");
        },

        // Metode untuk Image Handling
        triggerFormImageUpload(tabId, configKey) {
            document.getElementById(`formImageUpload-${tabId}-${configKey}`).click();
        },
        handleFormImageSelection(event, tabId, configKey) {
            const file = event.target.files[0];
            if (file) {
                this.selectedFormImageFile = file;
                console.log("File gambar dipilih:", file.name, "untuk tab:", tabId, "config:", configKey);
                this.uploadSelectedFormImage(tabId, configKey);
            }
            event.target.value = null; // Reset input file agar change event terpicu lagi untuk file yang sama
        },
        async uploadSelectedFormImage(tabId, configKey) {
            const currentTab = this.openTabs.find(tab => tab.id === tabId);
            if (!currentTab || !this.selectedFormImageFile || !currentTab.currentRecord[configKey] || !currentTab.currentRecord[configKey].id) {
                alert("Pastikan record sudah ada dan tersimpan (memiliki ID) sebelum mengupload gambar, atau pilih file terlebih dahulu.");
                this.selectedFormImageFile = null;
                return;
            }

            const record = currentTab.currentRecord[configKey];
            const config = currentTab.dbConfigs[configKey]; // Ini adalah config dari db di menu
            const currentImagePaths = record.image_paths ? String(record.image_paths).split(',').filter(p => p && p.trim() !== '').map(p => p.trim()) : [];

            // Cari urutan berikutnya. Jika kosong, mulai dari 1. Jika tidak, cari angka terbesar + 1
            let nextSequence = 1;
            if (currentImagePaths.length > 0) {
                const numericPaths = currentImagePaths.map(Number).filter(n => !isNaN(n));
                if (numericPaths.length > 0) {
                    nextSequence = Math.max(...numericPaths) + 1;
                }
            }

            try {
                // Panggil fungsi global dari crud.js
                // alert(config.table+ record.id+ nextSequence+ this.selectedFormImageFile);
                alert(config.table + record.id + nextSequence + this.selectedFormImageFile);
                const result = await window.uploadFormImage(config.table, record.id, nextSequence, this.selectedFormImageFile);
                if (result.success && result.filePath) {
                    alert('Gambar berhasil diupload!');

                    currentImagePaths.push(String(nextSequence));
                    // Urutkan sequence number sebelum join
                    record.image_paths = currentImagePaths.map(Number).sort((a, b) => a - b).join(',');

                    // Langsung update field image_paths di database
                    const fieldToUpdate = { image_paths: record.image_paths };
                    const savePathResult = await window.rubahjson(config.table, fieldToUpdate, record.id, '');
                    if (!savePathResult.success) {
                        console.warn("Gagal menyimpan path gambar ke database:", savePathResult.error);
                        alert("Gambar diupload, tapi gagal update path di DB. Coba simpan form manual atau refresh.");
                    } else {
                        console.log("Path gambar berhasil diupdate di DB.");
                    }

                    this.selectedFormImageFile = null;
                    this.$forceUpdate(); // Paksa update tampilan
                } else {
                    throw new Error(result.message || "Gagal upload gambar dari server.");
                }
            } catch (error) {
                console.error("Error uploading form image:", error);
                alert("Gagal mengupload gambar: " + error.message);
                this.selectedFormImageFile = null;
            }
        },
        async deleteFormImage(tabId, configKey, sequenceToDelete) {
            const currentTab = this.openTabs.find(tab => tab.id === tabId);
            if (!currentTab || !currentTab.currentRecord[configKey] || !currentTab.currentRecord[configKey].id) {
                alert("Record tidak valid untuk menghapus gambar.");
                return;
            }
            if (!confirm(`Anda yakin ingin menghapus gambar ke-${sequenceToDelete} ini?`)) {
                return;
            }

            const record = currentTab.currentRecord[configKey];
            const config = currentTab.dbConfigs[configKey]; // Ini adalah config dari db di menu

            try {
                // Panggil aksi baru di PHP untuk menghapus file fisik dan update DB
                const result = await $.ajax({ // Menggunakan jQuery AJAX karena belum ada fungsi globalnya di crud.js
                    type: "POST", url: "crud.php",
                    data: {
                        action: 'delete_form_image',
                        table_name: config.table,
                        record_id: record.id,
                        sequence: sequenceToDelete,
                        xbrand: window.xbrand, xcab: window.xcab // Sertakan jika diperlukan oleh backend
                    },
                    dataType: 'json'
                });

                if (result.success) {
                    alert("Gambar berhasil dihapus.");
                    // Update image_paths di frontend dari respons server
                    record.image_paths = result.newImagePaths || '';
                    this.$forceUpdate(); // Paksa update tampilan
                } else {
                    throw new Error(result.message || "Gagal menghapus gambar dari server.");
                }
            } catch (error) {
                console.error("Error deleting form image:", error);
                alert("Gagal menghapus gambar: " + error.message);
            }
        },
        parsedImagePaths(pathsString) {
            if (!pathsString || typeof pathsString !== 'string') return [];
            return pathsString.split(',')
                .map(p => p.trim()) // Hilangkan spasi
                .filter(p => p !== '') // Hilangkan string kosong hasil split jika ada koma berlebih
                .map(Number) // Ubah ke angka
                .filter(n => !isNaN(n) && Number.isInteger(n) && n > 0) // Pastikan angka valid dan positif
                .sort((a, b) => a - b); // Urutkan
        },
        openImageModal(tableName, recordId, sequence) {
            this.currentModalImageSrc = `${tableName}/${recordId}_${sequence}.jpg?t=${new Date().getTime()}`; // Cache buster
            this.isImageModalVisible = true;
            this.isModalImageZoomed = false; // Reset zoom state
        },
        closeImageModal() {
            this.isImageModalVisible = false;
            this.currentModalImageSrc = '';
        },
        toggleZoomModalImage() {
            this.isModalImageZoomed = !this.isModalImageZoomed;
        }


    },
    mounted: function () {
        this.loadAppConfiguration();
    },
    watch: {
        isSidebarOpen: function (newVal) {
            var self = this;
            this.$nextTick(function () {
                if (self.activeTabId) {
                    var currentTab = self.openTabs.find(function (tab) { return tab.id === self.activeTabId; });
                    if (currentTab && currentTab.gridInstances) {
                        Object.keys(currentTab.gridInstances).forEach(function (gridKey) {
                            if (currentTab.gridInstances[gridKey] && typeof currentTab.gridInstances[gridKey].render === 'function') {
                                currentTab.gridInstances[gridKey].render();
                            }
                        });
                    }
                }
            });
        }
    }
});

window.vm = app.mount('#app');

// ... (handleGridFileUpload tetap sama) ...
window.handleGridFileUpload = function (tabId, configKey, row, prop, input) {
    var vueApp = window.vm;
    var currentTab = vueApp.openTabs.find(function (tab) { return tab.id === tabId; });
    if (!currentTab || !currentTab.dbConfigs || !currentTab.dbConfigs[configKey]) {
        alert('Error: Konfigurasi tab/grid tidak ditemukan untuk upload.');
        return Promise.resolve();
    }
    var config = currentTab.dbConfigs[configKey];
    var hotInstance = currentTab.gridInstances[configKey];
    if (!hotInstance) {
        alert('Error: Instans grid tidak ditemukan.');
        return Promise.resolve();
    }

    var file = input.files[0];
    if (!file) {
        alert('Pilih file untuk diunggah.');
        return Promise.resolve();
    }

    var idField = config.specificFields.find(function (f) { return f.autoIncrement || f.id === 'id'; });
    if (!idField) {
        alert('Error: Field ID tidak ditemukan di konfigurasi grid.');
        return Promise.resolve();
    }

    var rowData = hotInstance.getSourceDataAtRow(row);
    if (!rowData) {
        alert('Error: Data baris tidak ditemukan.');
        return Promise.resolve();
    }
    var idValue = rowData[idField.id];
    if (!idValue) {
        alert('Error: ID baris tidak ada. Simpan data baris terlebih dahulu.');
        return Promise.resolve();
    }
    input.value = null; // Reset input file

    return vueApp.uploadFile(config.table, idValue, prop, file).then(function (uploadResult) { // uploadFile adalah method dari form.js, di-mixin ke Vue
        if (uploadResult.success && uploadResult.url) {
            hotInstance.setDataAtCell(row, hotInstance.propToCol(prop), uploadResult.url, 'internalUpdate');
            alert('File berhasil diunggah ke grid!');
        } else {
            alert('Gagal mengunggah file ke grid: ' + (uploadResult.message || 'Error tidak diketahui'));
        }
    }).catch(function (error) {
        console.error('Upload file grid gagal:', error);
        alert('Gagal mengunggah file grid: ' + error.message);
    });
};




