UNPKG

create-nexaui-electron

Version:

Create Nexa App - Tool untuk membuat aplikasi Nexa Electron

4,552 lines 139 kB
// import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.12.1/firebase-app.js';
// import { getDatabase, ref, set, get, update, remove, push, onValue } from 'https://www.gstatic.com/firebasejs/9.12.1/firebase-database.js';

export function Ngorei() {
  return {
    Helper: function () {
      return {
        Encode: function (row) {
          return Encode(row);
        },
        Decode: function (row) {
          return Decode(row);
        },
      };
    },
    Tokenize: function (userData, cradensial) {
      const SECRET_KEY = cradensial.replace(/-/g, "");
      return tokenize(userData, SECRET_KEY);
    },
    Components: function () {
      return {
        stylesheet: function (sty) {
          return stylesheet(sty);
        },
        UILogger: function (row) {
          const NexaUI = new Ngorei().Network();
          if (row === true) {
            NexaUI.localStorage().add({
              key: "UILogger",
              data: {
                status: true,
              },
            });
          } else if (row === false) {
            NexaUI.localStorage().del("UILogger");
          }
          // return new UILogger();
        },

        ViewStack: function () {
          return new ViewStack();
        },
        SectionId: function () {
          // return new SectionId();
        },
        Accordion: function () {
          // return Accordion();
        },
        Alerts: function () {
          // return Alerts();
        },
        buttonGroups: function () {
          // return buttonGroups();
        },
        Carousel: function () {
          // return initCarousels();
        },
        Collapse: function () {
          // return Collapse();
        },
        Dropdown: function () {
          // return Dropdown();
        },
        Lightbox: function () {
          // return new Lightbox();
        },
        Sidebar: function (darkModeButton) {
          // return new SidebarMenu();
        },
        ListGroup: function () {
          // return new ListGroup();
        },
        Offcanvas: function () {
          // return new Offcanvas();
        },
        Popover: function () {
          // return new Popover();
        },
        Progress: function () {
          // return Progress();
        },
        Scrollspy: function () {
          // return Scrollspy();
        },
        Search: function () {
          // return initializeSearch();
        },
        Sortable: function () {
          // return new Sortable();
        },
        Toast: function () {
          // return Toast();
        },
        Tooltips: function () {
          // return Tooltips();
        },
        DarkMode: function (darkModeButton) {
          //return new DarkMode();
        },
        getViewStack: function () {
          return new getViewStack();
        },
        PrismTab: function () {
          const tabCodeContainers = document.querySelectorAll(".nx-tabcode");

          tabCodeContainers.forEach((container) => {
            const tabBtns = container.querySelectorAll(".nx-tabcode-btn");

            tabBtns.forEach((btn) => {
              btn.addEventListener("click", function () {
                // Hapus kelas active hanya dari tab dalam container yang sama
                container
                  .querySelectorAll(".nx-tabcode-btn")
                  .forEach((b) => b.classList.remove("active"));
                container
                  .querySelectorAll(".nx-tabcode-pane")
                  .forEach((pane) => pane.classList.remove("active"));

                // Tambahkan kelas active ke tab yang diklik
                this.classList.add("active");
                document
                  .getElementById(this.dataset.tab)
                  .classList.add("active");
              });
            });
          });
        },
        Prism: function () {
          wrapCodeWithTerminal();

          window.copyCode = function (button) {
            try {
              // Ambil kode dari elemen
              const pre = button.closest(".terminal").querySelector("pre");
              const code = pre.querySelector("code").innerText;
              // Buat elemen textarea temporary
              const textarea = document.createElement("textarea");
              textarea.value = code;
              textarea.style.position = "fixed"; // Hindari scrolling
              textarea.style.opacity = "0"; // Sembunyikan elemen
              // Tambahkan ke dokumen
              document.body.appendChild(textarea);
              // Select dan copy
              textarea.select();
              document.execCommand("copy");
              // Bersihkan
              document.body.removeChild(textarea);
              // Feedback visual
              const originalText = button.textContent;
              button.innerHTML =
                '<i class="icon-feather-copy"></i> ' + "Tersalin";
              button.classList.add("bg-green-600");
              setTimeout(() => {
                button.innerHTML =
                  '<i class="icon-feather-copy"></i> ' + originalText;
                button.classList.remove("bg-green-600");
              }, 2000);
            } catch (err) {
              console.error("Gagal menyalin:", err);
              alert("Maaf, gagal menyalin kode");
            }
          };
        },
      };
    },
    createForm: async function (ret, callback) {
      // const hasil = await createForm(ret, callback);
      return createForm(ret, callback);
    },
    // createModal: function () {
    //   return createModal();
    // },
    Render: function () {
      return {
        View: function (row) {
          return new View(row);
        },
        Tabel: function (row) {
          return {
            Matrix: function (row) {
              return new TabelMatrix(row);
            },
          };
        },

        SinglePageApp: function (e) {
          const spa = new SinglePageApp();
          return spa.SinglePageApplication(e);
        },
        latSinglePageApp: function (result) {
          const latSing = latSinglePageApp({
            elementById: result.elementById,
            endpoint: result.key,
            forceReload: false,
            data: result.data.data,
          })
            .then((response) => {
              if (response) {
                const contentElement = document.getElementById(
                  result.data.elementById
                );
                contentElement.innerHTML = response;
              } else {
                console.log("Data tidak ditemukan");
              }
            })
            .catch((error) => {
              console.error("Terjadi kesalahan:", error);
            });

          return latSing;
        },
      };
    },

    Network: function () {
      return {
        tatiye: function () {
          return tatiye;
        },
        WebSocket: function () {
          return tatiye;
        },
        Queue: function (row) {
          return Queue(row);
        },
        Brief: function (row) {
          return Brief(row);
        },
        Crypto: function () {
          return new Crypto();
        },
        Buckets: function (row) {
          return Buckets(row);
        },
        RTDb: function (callback, token) {
          return RTDb(callback, token);
        },
        filterRow: function (data, propertyMap) {
          return filterRow(data, propertyMap);
        },
        indexDB: function () {
          const db = new classIndexDB();
          return {
            add: async function (row) {
              try {
                const key = row.key;
                const data = row.data;
                const timestamp = Date.now();
                const hasil = await db.saveData(key, data, timestamp);
                const tersimpan = await db.getData(key);
                return tersimpan;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            get: async function (key) {
              try {
                const data = await db.getData(key);
                return data;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            ref: async function () {
              try {
                const allData = await db.getAllData();
                return allData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            up: async function (key, newData) {
              try {
                await db.updateData(key, newData);
                const updatedData = await db.getData(key);
                return updatedData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            del: async function (key) {
              try {
                const result = await db.deleteData(key);
                return result;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            latest: async function () {
              try {
                const latestData = await db.getLatestData();
                return latestData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
          };
        },
        localStorage: function () {
          const storage = new classLocalStorage();
          return {
            add: async function (row) {
              try {
                const key = row.key;
                const data = row.data;
                const timestamp = Date.now();
                await storage.saveData(key, data, timestamp);
                const tersimpan = await storage.getData(key);
                return tersimpan;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            get: async function (key) {
              try {
                const data = await storage.getData(key);
                return data;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            ref: async function () {
              try {
                const allData = await storage.getAllData();
                return allData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            up: async function (key, newData) {
              try {
                await storage.updateData(key, newData);
                const updatedData = await storage.getData(key);
                return updatedData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            del: async function (key) {
              try {
                const result = await storage.deleteData(key);
                return result;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            latest: async function () {
              try {
                const latestData = await storage.getLatestData();
                return latestData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
          };
        },
        cookies: function () {
          const storage = new classCookies();
          return {
            add: async function (row, options = {}) {
              try {
                const key = row.key;
                const data = row.data;
                const timestamp = Date.now();
                await storage.saveData(key, data, timestamp, options);
                const tersimpan = await storage.getData(key);
                return tersimpan;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            get: async function (key) {
              try {
                const data = await storage.getData(key);
                return data;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            ref: async function () {
              try {
                const allData = await storage.getAllData();
                return allData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            up: async function (key, newData, options = {}) {
              try {
                await storage.updateData(key, newData, options);
                const updatedData = await storage.getData(key);
                return updatedData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            del: async function (key, options = {}) {
              try {
                const result = await storage.deleteData(key, options);
                return result;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            latest: async function () {
              try {
                const latestData = await storage.getLatestData();
                return latestData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
          };
        },
        sessionStorage: function () {
          const storage = new classSessionStorage();
          return {
            add: async function (row) {
              try {
                const key = row.key;
                const data = row.data;
                const timestamp = Date.now();
                await storage.saveData(key, data, timestamp);
                const tersimpan = await storage.getData(key);
                return tersimpan;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            get: async function (key) {
              try {
                const data = await storage.getData(key);
                return data;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            ref: async function () {
              try {
                const allData = await storage.getAllData();
                return allData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            up: async function (key, newData) {
              try {
                await storage.updateData(key, newData);
                const updatedData = await storage.getData(key);
                return updatedData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            del: async function (key) {
              try {
                const result = await storage.deleteData(key);
                return result;
              } catch (error) {
                console.error("Error:", error);
              }
            },
            latest: async function () {
              try {
                const latestData = await storage.getLatestData();
                return latestData;
              } catch (error) {
                console.error("Error:", error);
              }
            },
          };
        },
      };
    },
  };
}

// Mendefinisikan module pattern untuk Network
if (typeof define === "function" && define.amd) {
  // AMD
  define([], () => Ngorei);
} else if (typeof module === "object" && module.exports) {
  // CommonJS/Node.js
  module.exports = Ngorei;
} else {
  // Browser global
  window.Ngorei = Ngorei;
  window.dbs = new Ngorei(); // Instance default
}
//Komponen Network





// Fungsi untuk mengelola cookie di browser
export function cookies(element) {
  // Fungsi untuk mengatur cookie
  const setCookie = (name, value, days) => {
    let expires = "";
    if (days) {
      const date = new Date();
      date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
      expires = "; expires=" + date.toUTCString();
    }
    document.cookie = name + "=" + value + expires + "; path=/";
  };

  // Fungsi untuk mendapatkan nilai cookie (dengan decode)
  const getCookie = (name) => {
    const nameEQ = name + "=";
    const ca = document.cookie.split(";");
    for (let i = 0; i < ca.length; i++) {
      let c = ca[i];
      while (c.charAt(0) === " ") c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) === 0) {
        // Tambahkan decodeURIComponent untuk decode nilai cookie
        return decodeURIComponent(c.substring(nameEQ.length, c.length));
      }
    }
    return null;
  };

  // Fungsi untuk menghapus cookie
  const deleteCookie = (name) => {
    document.cookie = name + "=; Max-Age=-99999999;";
  };

  return {
    set: setCookie,
    get: getCookie,
    delete: deleteCookie,
  };
}

const cookieManager = cookies();
let HOSTSOKET; // Deklarasi di luar blok
const HOSTW = window.location.host.split(".")[0];
if (HOSTW === "192") {
  HOSTSOKET = window.location.host;
} else {
  HOSTSOKET = "127.0.0.1";
}
// Membuat koneksi WebSocket
const WS_HOST = HOSTSOKET;
const WS_PORT = 8080;
export const createWebSocketConnection = () => {
  return false;
};

const app = {
  app: "Ngorei",
  version: "v1.0.4",
  copyright: "2013-2024",
  vid: cookieManager.get("VID"),
  url: window.location.origin,
};
export default app;
//Komponen Network
export class classIndexDB {
  constructor(dbName = "Database", dbVersion = 1, storeName = "Data") {
    this.dbName = dbName;
    this.dbVersion = dbVersion;
    this.storeName = storeName;
  }

  openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.dbVersion);

      request.onerror = (event) =>
        reject("Error membuka database: " + event.target.error);

      request.onsuccess = (event) => resolve(event.target.result);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        const objectStore = db.createObjectStore(this.storeName, {
          keyPath: "key",
        });
        objectStore.createIndex("updated_at", "updated_at", { unique: false });
      };
    });
  }

  async saveData(key, data, updated_at) {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readwrite");
      const store = transaction.objectStore(this.storeName);
      const getRequest = store.get(key);

      getRequest.onsuccess = (event) => {
        const existingData = event.target.result;
        if (existingData && existingData.updated_at >= updated_at) {
          resolve("Data sudah yang terbaru");
        } else {
          const updateRequest = store.put({
            key: key,
            data: data,
            updated_at: updated_at,
          });
          updateRequest.onsuccess = () =>
            resolve("Data berhasil disimpan/diperbarui");
          updateRequest.onerror = (event) =>
            reject("Error menyimpan/memperbarui data: " + event.target.error);
        }
      };

      getRequest.onerror = (event) =>
        reject("Error memeriksa data: " + event.target.error);
    });
  }

  async getData(key) {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readonly");
      const store = transaction.objectStore(this.storeName);
      const request = store.get(key);

      request.onsuccess = (event) => resolve(event.target.result);
      request.onerror = (event) =>
        reject("Error mengambil data: " + event.target.error);
    });
  }

  async deleteData(key) {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readwrite");
      const store = transaction.objectStore(this.storeName);
      const request = store.delete(key);

      request.onsuccess = () => resolve("Data berhasil dihapus");
      request.onerror = (event) =>
        reject("Error menghapus data: " + event.target.error);
    });
  }
  async getAllData() {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readonly");
      const store = transaction.objectStore(this.storeName);
      const request = store.getAll();

      request.onsuccess = (event) => resolve(event.target.result);
      request.onerror = (event) =>
        reject("Error mengambil semua data: " + event.target.error);
    });
  }

  async updateData(key, newData) {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readwrite");
      const store = transaction.objectStore(this.storeName);
      const getRequest = store.get(key);

      getRequest.onsuccess = (event) => {
        const existingData = event.target.result;
        if (!existingData) {
          reject("Data tidak ditemukan");
          return;
        }

        const updatedObject = {
          key: key,
          data: { ...existingData.data, ...newData },
          updated_at: Date.now(),
        };

        const updateRequest = store.put(updatedObject);
        updateRequest.onsuccess = () => resolve("Data berhasil diupdate");
        updateRequest.onerror = (event) =>
          reject("Error mengupdate data: " + event.target.error);
      };

      getRequest.onerror = (event) =>
        reject("Error memeriksa data: " + event.target.error);
    });
  }

  async getLatestData() {
    const db = await this.openDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.storeName], "readonly");
      const store = transaction.objectStore(this.storeName);
      const index = store.index("updated_at");
      const request = index.openCursor(null, "prev");

      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
          resolve(cursor.value);
        } else {
          resolve(null);
        }
      };
      request.onerror = (event) =>
        reject("Error mengambil data terbaru: " + event.target.error);
    });
  }
}

export class classLocalStorage {
  constructor(prefix = "") {
    this.prefix = prefix;
  }

  saveData(key, data, updated_at) {
    return new Promise((resolve) => {
      const fullKey = this.prefix + key;
      const existingData = localStorage.getItem(fullKey);

      if (existingData) {
        const parsed = JSON.parse(existingData);
        if (parsed.updated_at >= updated_at) {
          resolve("Data sudah yang terbaru");
          return;
        }
      }

      const saveObject = {
        key: key,
        data: data,
        updated_at: updated_at,
      };

      localStorage.setItem(fullKey, JSON.stringify(saveObject));
      resolve("Data berhasil disimpan/diperbarui");
    });
  }

  getData(key) {
    return new Promise((resolve) => {
      const data = localStorage.getItem(this.prefix + key);
      resolve(data ? JSON.parse(data) : null);
    });
  }

  deleteData(key) {
    return new Promise((resolve) => {
      localStorage.removeItem(this.prefix + key);
      resolve("Data berhasil dihapus");
    });
  }

  getAllData() {
    return new Promise((resolve) => {
      const allData = [];
      for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        if (key.startsWith(this.prefix)) {
          const data = JSON.parse(localStorage.getItem(key));
          allData.push(data);
        }
      }
      resolve(allData);
    });
  }

  updateData(key, newData) {
    return new Promise((resolve, reject) => {
      const fullKey = this.prefix + key;
      const existingData = localStorage.getItem(fullKey);

      if (!existingData) {
        reject("Data tidak ditemukan");
        return;
      }

      const parsed = JSON.parse(existingData);
      const updatedObject = {
        key: key,
        data: { ...parsed.data, ...newData },
        updated_at: Date.now(),
      };

      localStorage.setItem(fullKey, JSON.stringify(updatedObject));
      resolve("Data berhasil diupdate");
    });
  }

  async getLatestData() {
    return new Promise((resolve) => {
      let latestData = null;
      let latestTimestamp = 0;

      for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        if (key.startsWith(this.prefix)) {
          const data = JSON.parse(localStorage.getItem(key));
          if (data.updated_at > latestTimestamp) {
            latestTimestamp = data.updated_at;
            latestData = data;
          }
        }
      }
      resolve(latestData);
    });
  }
}

export class classCookies {
  constructor(prefix = "app_") {
    this.prefix = prefix;
  }

  saveData(key, data, updated_at, options = {}) {
    return new Promise((resolve) => {
      const fullKey = this.prefix + key;
      const existingData = this.getCookie(fullKey);

      if (existingData) {
        const parsed = JSON.parse(existingData);
        if (parsed.updated_at >= updated_at) {
          resolve("Data sudah yang terbaru");
          return;
        }
      }

      const saveObject = {
        key: key,
        data: data,
        updated_at: updated_at,
      };

      // Default options
      const defaultOptions = {
        expires: 365, // hari
        path: "/",
        secure: false,
        sameSite: "Lax",
      };

      const cookieOptions = { ...defaultOptions, ...options };

      // Set expires
      let expires = "";
      if (cookieOptions.expires) {
        const date = new Date();
        date.setTime(
          date.getTime() + cookieOptions.expires * 24 * 60 * 60 * 1000
        );
        expires = `expires=${date.toUTCString()};`;
      }

      // Build cookie string
      let cookieString = `${fullKey}=${encodeURIComponent(
        JSON.stringify(saveObject)
      )};${expires}`;
      cookieString += `path=${cookieOptions.path};`;

      if (cookieOptions.secure) cookieString += "secure;";
      if (cookieOptions.sameSite)
        cookieString += `sameSite=${cookieOptions.sameSite};`;

      document.cookie = cookieString;
      resolve("Data berhasil disimpan/diperbarui");
    });
  }

  getCookie(key) {
    const name = this.prefix + key + "=";
    const decodedCookie = decodeURIComponent(document.cookie);
    const cookieArray = decodedCookie.split(";");

    for (let i = 0; i < cookieArray.length; i++) {
      let cookie = cookieArray[i].trim();
      if (cookie.indexOf(name) === 0) {
        return cookie.substring(name.length);
      }
    }
    return null;
  }

  getData(key) {
    return new Promise((resolve) => {
      const data = this.getCookie(key);
      resolve(data ? JSON.parse(data) : null);
    });
  }

  deleteData(key, options = {}) {
    return new Promise((resolve) => {
      const fullKey = this.prefix + key;
      const defaultOptions = {
        path: "/",
        secure: false,
        sameSite: "Lax",
      };

      const cookieOptions = { ...defaultOptions, ...options };

      // Set expired date to past
      document.cookie = `${fullKey}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${cookieOptions.path}`;
      resolve("Data berhasil dihapus");
    });
  }

  getAllData() {
    return new Promise((resolve) => {
      const allData = [];
      const decodedCookie = decodeURIComponent(document.cookie);
      const cookieArray = decodedCookie.split(";");

      for (let i = 0; i < cookieArray.length; i++) {
        const cookie = cookieArray[i].trim();
        if (cookie.indexOf(this.prefix) === 0) {
          const equalPos = cookie.indexOf("=");
          const value = cookie.substring(equalPos + 1);
          try {
            const data = JSON.parse(value);
            allData.push(data);
          } catch (e) {
            console.error("Error parsing cookie:", e);
          }
        }
      }
      resolve(allData);
    });
  }

  updateData(key, newData, options = {}) {
    return new Promise(async (resolve, reject) => {
      const existingData = await this.getData(key);

      if (!existingData) {
        reject("Data tidak ditemukan");
        return;
      }

      const updatedObject = {
        key: key,
        data: { ...existingData.data, ...newData },
        updated_at: Date.now(),
      };

      await this.saveData(
        key,
        updatedObject.data,
        updatedObject.updated_at,
        options
      );
      resolve("Data berhasil diupdate");
    });
  }

  async getLatestData() {
    return new Promise((resolve) => {
      let latestData = null;
      let latestTimestamp = 0;
      const decodedCookie = decodeURIComponent(document.cookie);
      const cookieArray = decodedCookie.split(";");

      for (let i = 0; i < cookieArray.length; i++) {
        const cookie = cookieArray[i].trim();
        if (cookie.indexOf(this.prefix) === 0) {
          const equalPos = cookie.indexOf("=");
          const value = cookie.substring(equalPos + 1);
          try {
            const data = JSON.parse(value);
            if (data.updated_at > latestTimestamp) {
              latestTimestamp = data.updated_at;
              latestData = data;
            }
          } catch (e) {
            console.error("Error parsing cookie:", e);
          }
        }
      }
      resolve(latestData);
    });
  }
}

export class classSessionStorage {
  constructor(prefix = "app_") {
    this.prefix = prefix;
  }

  saveData(key, data, updated_at) {
    return new Promise((resolve) => {
      const fullKey = this.prefix + key;
      const existingData = sessionStorage.getItem(fullKey);

      if (existingData) {
        const parsed = JSON.parse(existingData);
        if (parsed.updated_at >= updated_at) {
          resolve("Data sudah yang terbaru");
          return;
        }
      }

      const saveObject = {
        key: key,
        data: data,
        updated_at: updated_at,
      };

      sessionStorage.setItem(fullKey, JSON.stringify(saveObject));
      resolve("Data berhasil disimpan/diperbarui");
    });
  }

  getData(key) {
    return new Promise((resolve) => {
      const data = sessionStorage.getItem(this.prefix + key);
      resolve(data ? JSON.parse(data) : null);
    });
  }

  deleteData(key) {
    return new Promise((resolve) => {
      sessionStorage.removeItem(this.prefix + key);
      resolve("Data berhasil dihapus");
    });
  }

  getAllData() {
    return new Promise((resolve) => {
      const allData = [];
      for (let i = 0; i < sessionStorage.length; i++) {
        const key = sessionStorage.key(i);
        if (key.startsWith(this.prefix)) {
          const data = JSON.parse(sessionStorage.getItem(key));
          allData.push(data);
        }
      }
      resolve(allData);
    });
  }

  updateData(key, newData) {
    return new Promise((resolve, reject) => {
      const fullKey = this.prefix + key;
      const existingData = sessionStorage.getItem(fullKey);

      if (!existingData) {
        reject("Data tidak ditemukan");
        return;
      }

      const parsed = JSON.parse(existingData);
      const updatedObject = {
        key: key,
        data: { ...parsed.data, ...newData },
        updated_at: Date.now(),
      };

      sessionStorage.setItem(fullKey, JSON.stringify(updatedObject));
      resolve("Data berhasil diupdate");
    });
  }

  async getLatestData() {
    return new Promise((resolve) => {
      let latestData = null;
      let latestTimestamp = 0;

      for (let i = 0; i < sessionStorage.length; i++) {
        const key = sessionStorage.key(i);
        if (key.startsWith(this.prefix)) {
          const data = JSON.parse(sessionStorage.getItem(key));
          if (data.updated_at > latestTimestamp) {
            latestTimestamp = data.updated_at;
            latestData = data;
          }
        }
      }
      resolve(latestData);
    });
  }
}

/**
 * @class Ngorei
 * @description Kelas utama untuk manajemen komponen dan DOM
 */
export class NgoreiDOM {
  constructor() {
    this.DOM = new TDSDOM();

    this.Components = function () {
      return {};
    };
  }
}



// Export instance WebSocket default
export const tatiye = false;

export function RTDb(callback, token) {
  // let pesanData;
  // tatiye.onopen = function() {
  //   const subscribeMsg = {
  //     type: 'subscribe',
  //     endpoint:token
  //   };
  //   tatiye.send(JSON.stringify(subscribeMsg));
  // };
  // tatiye.onmessage = function(event) {
  //  const data = JSON.parse(event.data);
  //  if (data.type === 'update') {
  //    pesanData = data.data.response;
  //    callback(pesanData);
  //  }
  // };
  // tatiye.onerror = function(error) {
  //   console.error('WebSocket error:', error);
  // };
  // tatiye.onclose = function() {
  //   console.log('Terputus dari WebSocket server');
  //   setTimeout(RTDb, 5000);
  // };
}
// ... existing connection code ...
export function Buckets(data = {}) {
  // return new Promise((resolve, reject) => {
  //   if (tatiye.readyState === WebSocket.OPEN) {
  //     const apiRequest = {
  //       type: 'apiRequest',
  //       endpoint: data.endpoint,
  //       vid:app.vid,
  //       payload: data.body
  //     };
  //     const messageHandler = (e) => {
  //       try {
  //         const response = JSON.parse(e.data);
  //         if (response.type === "apiResponse") {
  //              tatiye.removeEventListener("message", messageHandler);
  //              if (response.data.payload.vid===app.vid) {
  //                 resolve(response.data.payload.response);
  //              }
  //         }
  //       } catch (error) {
  //         console.error('Error dalam messageHandler:', error);
  //         reject(error);
  //       }
  //     };
  //     tatiye.addEventListener("message", messageHandler);
  //     tatiye.send(JSON.stringify(apiRequest));
  //   } else {
  //     console.error('WebSocket Status:', tatiye.readyState);
  //     reject(new Error("WebSocket belum terhubung"));
  //   }
  // });
}

// filebrowser
export async function filebrowser(serverUrl, fileInput, additionalData = {}) {
  const formdata = new FormData();
  formdata.append("file", fileInput.files[0]);

  // Menambahkan data tambahan secara dinamis
  Object.entries(additionalData).forEach(([key, value]) => {
    formdata.append(key, value);
  });

  const requestOptions = {
    method: "POST",
    body: formdata,
    redirect: "follow",
  };

  try {
    const response = await fetch(app.url + "/sdk/" + serverUrl, requestOptions);
    const result = await response.json();
    return result;
  } catch (error) {
    console.error("Error uploading file:", error);
    throw error;
  }
}

// PRECODE
export function getLanguageIcon(language) {
  if (!language || typeof language !== "string") {
    return "fas fa-code";
  }
  const iconMap = {
    html: "fab fa-html5",
    css: "fab fa-css3-alt",
    js: "fab fa-js",
    javascript: "fab fa-js",
    python: "fab fa-python",
    php: "fab fa-php",
    java: "fab fa-java",
    react: "fab fa-react",
    vue: "fab fa-vuejs",
    angular: "fab fa-angular",
    node: "fab fa-node-js",
    sass: "fab fa-sass",
    wordpress: "fab fa-wordpress",
    git: "fab fa-git-alt",
    json: "fas fa-code",
    wrapped: "fas fa-code",
  };

  return iconMap[language.toLowerCase()] || "fas fa-code"; // Default icon
}
// PRE CODE
export function wrapCodeWithTerminal() {
  const codeBlocks = document.querySelectorAll("pre > code:not(.wrapped)");
  codeBlocks.forEach((codeBlock) => {
    // Tandai kode yang sudah diproses
    // codeBlock.classList.add("wrapped");

    const classAttr = codeBlock.className;
    const language = classAttr.replace("language-", "").replace("wrapped", ""); // Hapus class wrapped dari string language
    const title = codeBlock.getAttribute("title") || "";
    const setIcon = codeBlock.getAttribute("icon") || language;
    const defaultHeight = 400; // Nilai default dalam pixel
    const setmaxHeight =
      parseInt(codeBlock.getAttribute("maxHeight")) || defaultHeight;
    const defaultwidth = ""; // Nilai default dalam pixel
    const setmaxWidth =
      parseInt(codeBlock.getAttribute("maxwidth")) || defaultwidth;
    const pxmaxWidth = setmaxWidth + "px"; // Gunakan variabel yang sudah dikonversi
    const pxmaxHeight = setmaxHeight + "px"; // Gunakan variabel yang sudah dikonversi
    const languageIcon = getLanguageIcon(setIcon);
    let newsTitiel = "";
    if (title) {
      newsTitiel = title + "." + setIcon;
    } else {
      newsTitiel = language;
    }
    const terminal = document.createElement("div");
    terminal.className = "terminal";
    terminal.style.width = pxmaxWidth;
    const terminalHeader = document.createElement("div");
    terminalHeader.className = "terminal-header";
    terminalHeader.innerHTML = `
      <span>
        <i class="${languageIcon}" aria-hidden="true"></i> 
        ${newsTitiel}
      </span>
      <div class="terminal-buttons">
        <button onclick="copyCode(this)" class="terminal-copy-btn" aria-label="Salin kode">
          <i class="icon-feather-copy" aria-hidden="true"></i>
        </button>
      </div>
    `;

    const terminalFooter = document.createElement("div");
    terminalFooter.className = "terminal-footer";
    if (codeBlock.offsetHeight > setmaxHeight) {
      terminalFooter.innerHTML = `
        <button class="terminal-code-btn" onclick="toggleCode(this)">Lihat selengkapnya</button>
      `;
    }

    const preElement = codeBlock.parentElement;

    preElement.parentNode.insertBefore(terminal, preElement);
    terminal.appendChild(terminalHeader);
    terminal.appendChild(preElement);
    terminal.appendChild(terminalFooter);

    if (codeBlock.offsetHeight > setmaxHeight) {
      preElement.style.maxHeight = pxmaxHeight;
      preElement.style.overflow = "hidden";
    }
  });
}
window.toggleCode = function (button) {
  const terminal = button.closest(".terminal");
  const preElement = terminal.querySelector("pre");
  const codeElement = preElement.querySelector("code");
  const defaultHeight = 300;
  const maxHeight =
    (parseInt(codeElement.getAttribute("maxHeight")) || defaultHeight) + "px";

  if (preElement.style.maxHeight === maxHeight) {
    preElement.style.maxHeight = "none";
    button.textContent = "Lihat lebih sedikit";
  } else {
    preElement.style.maxHeight = maxHeight;
    button.textContent = "Lihat selengkapnya";
  }
};

export function md5(string) {
  function RotateLeft(lValue, iShiftBits) {
    return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
  }

  function AddUnsigned(lX, lY) {
    var lX4, lY4, lX8, lY8, lResult;
    lX8 = lX & 0x80000000;
    lY8 = lY & 0x80000000;
    lX4 = lX & 0x40000000;
    lY4 = lY & 0x40000000;
    lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
    if (lX4 & lY4) {
      return lResult ^ 0x80000000 ^ lX8 ^ lY8;
    }
    if (lX4 | lY4) {
      if (lResult & 0x40000000) {
        return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
      } else {
        return lResult ^ 0x40000000 ^ lX8 ^ lY8;
      }
    } else {
      return lResult ^ lX8 ^ lY8;
    }
  }

  function F(x, y, z) {
    return (x & y) | (~x & z);
  }
  function G(x, y, z) {
    return (x & z) | (y & ~z);
  }
  function H(x, y, z) {
    return x ^ y ^ z;
  }
  function I(x, y, z) {
    return y ^ (x | ~z);
  }

  function FF(a, b, c, d, x, s, ac) {
    a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
    return AddUnsigned(RotateLeft(a, s), b);
  }

  function GG(a, b, c, d, x, s, ac) {
    a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
    return AddUnsigned(RotateLeft(a, s), b);
  }

  function HH(a, b, c, d, x, s, ac) {
    a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
    return AddUnsigned(RotateLeft(a, s), b);
  }

  function II(a, b, c, d, x, s, ac) {
    a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
    return AddUnsigned(RotateLeft(a, s), b);
  }

  function ConvertToWordArray(string) {
    var lWordCount;
    var lMessageLength = string.length;
    var lNumberOfWords_temp1 = lMessageLength + 8;
    var lNumberOfWords_temp2 =
      (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
    var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
    var lWordArray = Array(lNumberOfWords - 1);
    var lBytePosition = 0;
    var lByteCount = 0;
    while (lByteCount < lMessageLength) {
      lWordCount = (lByteCount - (lByteCount % 4)) / 4;
      lBytePosition = (lByteCount % 4) * 8;
      lWordArray[lWordCount] =
        lWordArray[lWordCount] |
        (string.charCodeAt(lByteCount) << lBytePosition);
      lByteCount++;
    }
    lWordCount = (lByteCount - (lByteCount % 4)) / 4;
    lBytePosition = (lByteCount % 4) * 8;
    lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
    lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
    lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
    return lWordArray;
  }

  function WordToHex(lValue) {
    var WordToHexValue = "",
      WordToHexValue_temp = "",
      lByte,
      lCount;
    for (lCount = 0; lCount <= 3; lCount++) {
      lByte = (lValue >>> (lCount * 8)) & 255;
      WordToHexValue_temp = "0" + lByte.toString(16);
      WordToHexValue =
        WordToHexValue +
        WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
    }
    return WordToHexValue;
  }

  function Utf8Encode(string) {
    string = string.replace(/\r\n/g, "\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n);
      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if (c > 127 && c < 2048) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }
    }

    return utftext;
  }

  var x = Array();
  var k, AA, BB, CC, DD, a, b, c, d;
  var S11 = 7,
    S12 = 12,
    S13 = 17,
    S14 = 22;
  var S21 = 5,
    S22 = 9,
    S23 = 14,
    S24 = 20;
  var S31 = 4,
    S32 = 11,
    S33 = 16,
    S34 = 23;
  var S41 = 6,
    S42 = 10,
    S43 = 15,
    S44 = 21;

  string = Utf8Encode(string);

  x = ConvertToWordArray(string);

  a = 0x67452301;
  b = 0xefcdab89;
  c = 0x98badcfe;
  d = 0x10325476;

  for (k = 0; k < x.length; k += 16) {
    AA = a;
    BB = b;
    CC = c;
    DD = d;
    a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
    d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
    c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
    b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
    a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
    d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
    c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
    b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
    a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
    d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
    c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
    b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
    a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
    d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
    c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
    b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
    a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
    d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
    c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
    b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
    a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
    d = GG(d, a, b, c, x[k + 10], S22, 0x02441453);
    c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
    b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
    a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
    d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
    c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
    b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
    a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
    d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
    c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
    b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
    a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
    d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
    c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
    b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
    a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
    d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
    c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
    b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
    a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
    d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
    c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
    b = HH(b, c, d, a, x[k + 6], S34, 0x04881d05);
    a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
    d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
    c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
    b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
    a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
    d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
    c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
    b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
    a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
    d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
    c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
    b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
    a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
    d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
    c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
    b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
    a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
    d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
    c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
    b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
    a = AddUnsigned(a, AA);
    d = AddUnsigned(d, DD);
    c = AddUnsigned(c, CC);
    b = AddUnsigned(b, BB);
  }
  var temp = WordToHex(a) + WordToHex(d) + WordToHex(c) + WordToHex(b);
  return temp.toUpperCase();
}

export function md5Str(input) {
  const hash = md5(input);
  const codes = [];
  // Membagi hash menjadi 5 bagian yang sama (6 karakter)
  for (let i = 0; i < 5; i++) {
    codes.push(hash.substr(i * 6, 6));
  }
  return codes.join("-");
}
// Fungsi untuk melakukan HTTP request dengan fitur retry, timeout, dan validasi
export function BriefStorage(element) {
  // Konstanta untuk timeout
  const TIMEOUT_DURATION = 5000;
  const MAX_RETRIES = 3;
  const RETRY_DELAY = 1000;

  // Validasi URL
  const validateUrl = (url) => {
    try {
      new URL(url);
      return true;
    } catch {
      throw new Error("URL tidak valid");
    }
  };

  // Rate limiting
  const rateLimiter = {
    lastCall: 0,
    minInterval: 5, // 100ms antara request
    checkLimit() {
      const now = Date.now();
      if (now - this.lastCall < this.minInterval) {
        throw new Error("Terlalu banyak request. Mohon tunggu sebentar.");
      }
      this.lastCall = now;
    },
  };

  // Fungsi helper untuk setup request
  const setupRequest = (method, data = null) => {
    const controller = new AbortController();
    const config = {
      method,
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      signal: controller.signal,
    };
    if (data) {
      if (typeof data !== "object") {
        throw new Error("Data harus berupa object");
      }
      config.body = JSON.stringify(data);
    }
    return { controller, config };
  };

  // Fungsi retry
  const retry = async (fn, retries = MAX_RETRIES) => {
    try {
      return await fn();
    } catch (error) {
      if (retries <= 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
      console.log(`Mencoba kembali... Sisa percobaan: ${retries - 1}`);
      return retry(fn, retries - 1);
    }
  };

  async function getData(url) {
    validateUrl(url);
    rateLimiter.checkLimit();

    const { controller, config } = setupRequest("GET");
    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_DURATION);

    return retry(async () => {
      try {
        const response = await fetch(url, config);
        clearTimeout(timeoutId);

        if (!response.ok) {
          throw {
            type: "HTTPError",
            status: response.status,
            message: `HTTP error! status: ${response.status}`,
            timestamp: new Date().toISOString(),
            url,
          };
        }

        const data = await response.json();
        console.log({
          type: "Success",
          method: "GET",
          url,
          timestamp: new Date().toISOString(),
        });
        return data;
      } catch (error) {
        clearTimeout(timeoutId);
        // console.error({
        //   type: "Error",
        //   method: "GET",
        //   url,
        //   error: error.message,
        //   timestamp: new Date().toISOString(),
        // });
        throw error;
      }
    });
  }

  async function sdk(url, data) {
    // const cookieManager = cookies();
    // const userCookie = cookieManager.get('HOST'); // returns 'john'
    const bseURI = app.url + "/sdk/" + url;
    validateUrl(bseURI);
    rateLimiter.checkLimit();

    const { controller, config } = setupRequest("POST", data);
    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_DURATION);

    return retry(async () => {
      try {
        const response = await fetch(bseURI, config);
        clearTimeout(timeoutId);

        if (!response.ok) {
          throw {
            type: "HTTPError",
            status: response.status,
            message: `HTTP error! status: ${response.status}`,
            timestamp: new Date().toISOString(),
            url,
            data,
          };
        }

        const result = await response.json();
        return result;
      } catch (error) {
        clearTimeout(timeoutId);
        console.error({
          type: "Error",
          method: "POST",
          url,
          error: error.message,
          data,
          timestamp: new Date().toISOString(),
        });
        throw error;
      }
    });
  }
  return { element, getData, sdk };
}

export async function Brief(row) {
  try {
    if (!row || !row.endpoint) {
      throw new Error("Parameter row dan endpoint diperlukan");
    }
    const briefInstance = BriefStorage(row);
    const data = await briefInstance.sdk(row.endpoint, row);
    return data;
  } catch (error) {
    console.error("Error dalam Brief:", error);
    throw error;
  }
}

export function Queue(row) {
  return {
    add: async function () {
      try {
        if (!row || !row.endpoint) {
          throw new Error("Parameter  dan endpoint diperlukan");
        }

        const briefInstance = BriefStorage(row);
        const data = await briefInstance.sdk(row.endpoint, row);
        return data;
      } catch (error) {
        console.error("Error dalam Queue:", error);
        throw error;
      }
    },
    up: async function (id) {
      try {
        if (!row || !row.endpoint) {
          throw new Error("Parameter  dan endpoint diperlukan");
        }
        const gabungArray = { ...row, id: id };
        const briefInstance = BriefStorage(row);
        const data = await briefInstance.sdk(row.endpoint, gabungArray);
        return { ...row.payload, id: id, data };
      } catch (error) {
        console.error("Error dalam Queue:", error);
        throw error;
      }
    },
    get: async function (id) {
      try {
        if (!row || !row) {
          throw new Error("Parameter  dan endpoint diperlukan");
        }
        const briefInstance = BriefStorage(row);
        const data = await briefInstance.sdk(row, { id: id });
        return data;
      } catch (error) {
        console.error("Error dalam Queue:", error);
        throw error;
      }
    },
    view: async function () {
      try {
        if (!row || !row.endpoint) {
          throw new Error("Parameter  dan endpoint diperlukan");
        }
        const briefInstance = BriefStorage(row);
        const data = await briefInstance.sdk(row.endpoint, row);
        return data;
      } catch (error) {
        console.error("Error dalam Queue:", error);
        throw error;
      }
    },
    del: async function (id) {
      try {
        if (!row || !row) {
          throw new Error("Parameter  dan endpoint diperlukan");
        }
        const briefInstance = BriefStorage(row);
        const data = await briefInstance.sdk(row, { id: id });
        return data;
      } catch (error) {
        console.error("Error dalam Queue:", error);
        throw error;
      }
    },
  };
}

// COMPONENE
/**
 * @class TDSDOM
 * @description Kelas untuk manajemen DOM dan template
 */
class TDSDOM {
  constructor() {
    /**
     * Helper function untuk escape karakter regex
     * @param {string} string - String yang akan di-escape
     * @returns {string} String yang sudah di-escape
     */
    const escapeRegExp = (string) => {
      return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    };

    /**
     * Render template dengan data
     * @param {string} template - Template string
     * @param {Object} data - Data untuk dirender
     * @param {Element} element - Element template
     */
    this.render = function (template, data, element) {
      try {
        // Validasi input
        if (!template || typeof template !== "string") {
          throw new Error("Template harus berupa string");
        }
        if (!data || typeof data !== "object") {
          throw new Error("Data harus berupa object");
        }

        let result = template;
        const dataKeys = Object.keys(data);

        // Proses setiap key data
        dataKeys.forEach((key) => {
          const items = data[key];
          if (!Array.isArray(items)) {
            console.warn(`Data untuk key ${key} bukan array:`, items);
            return;
          }

          // Pattern untuk mencari tag template
          // Support multiple format tags termasuk Mustache-style
          const startTags = [
            `{@${key}}`,
            `[${key}]`,
            `[@${key}]`,
            `<!--${key}-->`,
            `{{${key}}}`, // Mustache-style
            `{{#${key}}}`, // Mustache block
            `{{{${key}}}`, // Mustache unescaped
            `{$${key}}`, // PHP-style variable
            `{#${key}}`, // Hash-style variable
            `\${${key}}`, // Template literal style
          ];
          const endTags = [
            `{/${key}}`,
            `[/${key}]`,
            `[/${key}]`,
            `<!--/${key}-->`,
            `{{/${key}}}`, // Mustache-style
            `{{/${key}}}`, // Mustache block
            `{{{/${key}}}`, // Mustache unescaped
            `{/${key}}`, // PHP-style closing
            `{/${key}}`, // Hash-style closing
            `\${/${key}}`, // Template literal style closing
          ];

          // Debug log

          // Cek format yang digunakan
          let templateStart = -1;
          let templateEnd = -1;
          let usedStartTag = "";
          let usedEndTag = "";
          let tagFound = false;

          // Cek format mana yang digunakan dengan case insensitive
          for (let i = 0; i < startTags.length; i++) {
            const startTagRegex = new RegExp(escapeRegExp(startTags[i]), "i");
            const startMatch = result.match(startTagRegex);

            if (startMatch) {
              templateStart = startMatch.index;
              usedStartTag = startMatch[0];

              const endTagRegex = new RegExp(escapeRegExp(endTags[i]), "i");
              const remainingContent = result.slice(
                templateStart + usedStartTag.length
              );
              const endMatch = remainingContent.match(endTagRegex);

              if (endMatch) {
                templateEnd =
                  templateStart + usedStartTag.length + endMatch.index;
                usedEndTag = endMatch[0];
                tagFound = true;
                //console.debug(`Tag ditemukan: ${usedStartTag} ... ${usedEndTag}`);
                break;
              }
            }
          }

          if (!tagFound) {
            console.warn(
              `Tag template untuk "${key}" tidak ditemukan dalam template`
            );
            console.warn("Template yang tersedia:", result);
            return;
          }

          const itemTemplate = result.substring(
            templateStart + usedStartTag.length,
            templateEnd
          );

          // console.debug(`Template item untuk "${key}":`, itemTemplate);

          // Render setiap item dengan dukungan Mustache yang lebih baik
          let renderedItems = items
            .map((item) => {
              let itemResult = itemTemplate;

              // Replace semua placeholder dengan nilai item
              Object.keys(item).forEach((prop) => {
                const value = item[prop] ?? "";
                // Support multiple format placeholders termasuk Mustache
                const patterns = [
                  new RegExp(`{${prop}}`, "g"),
                  new RegExp(`\\[${prop}\\]`, "g"),
                  new RegExp(`<!--${prop}-->`, "g"),
                  new RegExp(`{{${prop}}}`, "g"), // Mustache escaped
                  new RegExp(`{{{${prop}}}}`, "g"), // Mustache unescaped
                  new RegExp(`{{&${prop}}}`, "g"), // Mustache unescaped alternative
                  new RegExp(`{\\$${prop}}`, "g"), // PHP-style variable
                  new RegExp(`{#${prop}}`, "g"), // Hash-style variable
                  new RegExp(`\\$\\{${prop}\\}`, "g"), // Template literal style
                ];

                patterns.forEach((pattern) => {
                  // Untuk format Mustache escaped, escape HTML
                  if (
                    pattern.toString().includes("{{") &&
                    !pattern.toString().includes("{{{")
                  ) {
                    itemResult = itemResult.replace(
                      pattern,
                      this.sanitize(String(value))
                    );
                  } else {
                    itemResult = itemResult.replace(pattern, String(value));
                  }
                });
              });

              return itemResult;
            })
            .join("");

          // Replace template dengan hasil render
          result = result.replace(
            `${usedStartTag}${itemTemplate}${usedEndTag}`,
            renderedItems
          );
        });

        // Debug log hasil akhir

        return result;
      } catch (error) {
        console.error("Error dalam render:", error);
        console.error("Template:", template);
        console.error("Data:", data);
        return "";
      }
    };

    /**
     * Parse string template menjadi DOM elements
     * @param {string} template - Template string
     * @returns {DocumentFragment}
     */
    this.parse = function (template) {
      const parser = new DOMParser();
      const doc = parser.parseFromString(template, "text/html");
      const fragment = document.createDocumentFragment();

      while (doc.body.firstChild) {
        fragment.appendChild(doc.body.firstChild);
      }

      return fragment;
    };

    /**
     * Sanitize string untuk mencegah XSS
     * @param {string} str - String yang akan disanitize
     * @returns {string}
     */
    this.sanitize = function (str) {
      const div = document.createElement("div");
      div.textContent = str;
      return div.innerHTML;
    };
  }
}
// SPA
export class SinglePageApp {
  constructor() {
    this.dbName = "spaCache";
    this.storeName = "responses";
    this.db = null;
    this.loadingHTML = `
      <div class="loading">
        <div class="spinner"></div>
        <p>Memuat konten...</p>
      </div>
    `;
    this.retryConfig = {
      maxRetries: 3,
      retryDelay: 1000,
      backoffMultiplier: 1.5,
    };
  }

  async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          const store = db.createObjectStore(this.storeName, { keyPath: "id" });
          store.createIndex("timestamp", "timestamp", { unique: false });
        }
      };
    });
  }

  isCryptoSupported() {
    return window.crypto && window.crypto.subtle;
  }

  async getEncryptionKey(endpoint) {
    const keyData = new TextEncoder().encode(
      endpoint || "default-endpoint-key"
    );
    return await crypto.subtle.importKey("raw", keyData, "AES-GCM", false, [
      "encrypt",
      "decrypt",
    ]);
  }

  async encryptData(data, endpoint) {
    if (!this.isCryptoSupported()) {
      return { raw: true, data };
    }

    try {
      const key = await this.getEncryptionKey(endpoint);
      const iv = crypto.getRandomValues(new Uint8Array(12));
      const encodedData = new TextEncoder().encode(data);

      const encryptedData = await crypto.subtle.encrypt(
        { name: "AES-GCM", iv },
        key,
        encodedData
      );

      return {
        iv: Array.from(iv),
        data: Array.from(new Uint8Array(encryptedData)),
      };
    } catch (error) {
      console.warn("Enkripsi gagal:", error);
      return { raw: true, data };
    }
  }

  async decryptData(encryptedObj, endpoint) {
    if (encryptedObj.raw) {
      return encryptedObj.data;
    }

    try {
      const key = await this.getEncryptionKey(endpoint);
      const decrypted = await crypto.subtle.decrypt(
        { name: "AES-GCM", iv: new Uint8Array(encryptedObj.iv) },
        key,
        new Uint8Array(encryptedObj.data)
      );

      return new TextDecoder().decode(decrypted);
    } catch (error) {
      console.warn("Dekripsi gagal:", error);
      throw error;
    }
  }

  async saveToCache(key, data, endpoint) {
    const transaction = this.db.transaction([this.storeName], "readwrite");
    const store = transaction.objectStore(this.storeName);

    try {
      const objToEncrypt = JSON.stringify({
        data: data,
        timestamp: new Date().getTime(),
      });
      const encryptedData = await this.encryptData(objToEncrypt, endpoint);

      await store.put({
        id: key,
        encryptedContent: encryptedData,
      });
    } catch (error) {
      console.warn("Cache write error:", error);
    }
  }

  async getFromCache(key) {
    const transaction = this.db.transaction([this.storeName], "readonly");
    const store = transaction.objectStore(this.storeName);

    return new Promise(async (resolve, reject) => {
      try {
        const request = store.get(key);
        request.onsuccess = async () => {
          if (!request.result) {
            resolve(null);
            return;
          }

          const decryptedStr = await this.decryptData(
            request.result.encryptedContent
          );
          const decryptedObj = JSON.parse(decryptedStr);

          resolve({
            id: key,
            data: decryptedObj.data,
            timestamp: decryptedObj.timestamp,
          });
        };
        request.onerror = () => reject(request.error);
      } catch (error) {
        console.warn("Cache read error:", error);
        reject(error);
      }
    });
  }

  async clearOldCache() {
    const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 hari
    const now = new Date().getTime();

    const transaction = this.db.transaction([this.storeName], "readwrite");
    const store = transaction.objectStore(this.storeName);
    const index = store.index("timestamp");

    const range = IDBKeyRange.upperBound(now - maxAge);
    index.openCursor(range).onsuccess = (event) => {
      const cursor = event.target.result;
      if (cursor) {
        store.delete(cursor.primaryKey);
        cursor.continue();
      }
    };
  }

  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async fetchWithRetry(url, options, retryCount = 0) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response;
    } catch (error) {
      if (retryCount >= this.retryConfig.maxRetries) {
        throw new Error(
          `Gagal setelah ${this.retryConfig.maxRetries} percobaan: ${error.message}`
        );
      }

      const waitTime =
        this.retryConfig.retryDelay *
        Math.pow(this.retryConfig.backoffMultiplier, retryCount);
      console.warn(
        `Percobaan ke-${
          retryCount + 1
        } gagal. Mencoba ulang dalam ${waitTime}ms...`
      );

      await this.delay(waitTime);
      return this.fetchWithRetry(url, options, retryCount + 1);
    }
  }

  async SinglePageApplication(e) {
    const encodedData = btoa(JSON.stringify(e));
    const State = {
      url: "https://" + e.endpoint,
      data: e.data || e,
      elementById: e.elementById,
      encodedData: encodedData,
      endpoint: e.endpoint,
    };

    if (!this.db) {
      await this.initDB();
    }

    const contentElement = document.getElementById(State.elementById);
    contentElement.innerHTML = this.loadingHTML;

    const cacheKey = md5Str(State.encodedData + "_v1");

    try {
      const cachedData = await this.getFromCache(cacheKey);
      if (cachedData) {
        contentElement.innerHTML = cachedData.data;
        return cachedData.data;
      }
    } catch (error) {
      console.warn("Cache read error:", error);
    }

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5000);

      const response = await this.fetchWithRetry(
        app.url + "/worker/" + cacheKey,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            ...State.data,
            key: md5Str(State.url) || "",
            brief: State.url || "",
            pageparser: window.location.href,
            timestamp: new Date().getTime(),
          }),
          signal: controller.signal,
        }
      );

      clearTimeout(timeoutId);

      const responseData = await response.text();

      try {
        await this.saveToCache(cacheKey, responseData, State.endpoint);
        await this.clearOldCache();
      } catch (e) {
        console.warn("Cache write error:", e);
      }

      contentElement.innerHTML = responseData;
      return responseData;
    } catch (error) {
      const errorMessage = error.message.includes("Gagal setelah")
        ? error.message
        : `Error: ${error.message}`;

      contentElement.innerHTML = `<div class="error">${errorMessage}</div>`;
      return contentElement.innerHTML;
    }
  }
}

// // Inisialisasi Worker
// const worker = new Worker(app.url + "/js/Worker.js");

// // Fungsi untuk menggunakan Worker
// const useWorker = (action, data) => {
//   return new Promise((resolve, reject) => {
//     worker.onmessage = (e) => {
//       const { action: responseAction, result, error } = e.data;
//       if (responseAction === `${action}Complete`) {
//         resolve(result);
//       } else if (responseAction === "error") {
//         reject(new Error(error));
//       }
//     };

//     worker.onerror = (error) => {
//       reject(error);
//     };

//     worker.postMessage({ action, data });
//   });
// };

// // Contoh penggunaan Worker untuk filter
// async function filterItems(items, filters) {
//   try {
//     const filteredItems = await useWorker("filter", { items, filters });
//     console.log("Filtered items:", filteredItems);
//     return filteredItems;
//   } catch (error) {
//     console.error("Error filtering items:", error);
//   }
// }

// // Contoh penggunaan Worker untuk search
// async function searchItems(items, query, searchFields) {
//   try {
//     const searchResults = await useWorker("search", {
//       items,
//       query,
//       searchFields,
//     });
//     console.log("Search results:", searchResults);
//     return searchResults;
//   } catch (error) {
//     console.error("Error searching items:", error);
//   }
// }

export async function latSinglePageApp(e) {
  // Inisialisasi Worker $dataset['pageparser']

  const worker = new Worker(app.url + "/js/Worker.js");
  const encodedData = btoa(JSON.stringify(e));
  const State = {
    url: "https://" + e.endpoint,
    data: e.data || e,
    elementById: e.elementById,
    encodedData: encodedData,
    endpoint: e.endpoint,
  };
  try {
    const response = await fetch(app.url + "/worker/" + md5Str(encodedData), {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ...State.data,
        key: md5Str(State.url) || "",
        brief: State.url || "",
        pageparser: window.location.href,
        timestamp: new Date().getTime(),
      }),
    });

    return await response.text();
  } catch (error) {
    console.error("Error:", error);
    return null;
  } finally {
    // Terminate worker setelah selesai
    worker.terminate();
  }
}

// AND SPA
export function Encode(argument) {
  if (!argument) {
    throw new Error("Input tidak boleh kosong");
  }

  try {
    // Konversi input ke string jika bukan string
    const inputString =
      typeof argument === "string" ? argument : JSON.stringify(argument);

    // Encode ke Base64 dan buat URL-safe
    const encodedString = btoa(inputString)
      .replace(/\+/g, "-")
      .replace(/\//g, "_")
      .replace(/=+$/, "");

    return encodedString;
  } catch (error) {
    throw new Error(`Gagal mengencode data: ${error.message}`);
  }
}

// URL-safe Base64 Decode
export function Decode(argument) {
  if (!argument || typeof argument !== "string") {
    throw new Error("Input harus berupa string");
  }

  try {
    // Konversi ke format Base64 standar
    const paddedString = argument.replace(/-/g, "+").replace(/_/g, "/");
    const padding = (4 - (paddedString.length % 4)) % 4;
    const base64String =
      padding > 0 ? paddedString + "=".repeat(padding) : paddedString;

    // Decode Base64
    const decodedString = atob(base64String);

    // Bersihkan dan parse JSON
    const cleanedData = decodedString
      .replace(/'/g, '"')
      .replace(/([{,]\s*)(\w+):/g, '$1"$2":');

    return JSON.parse(cleanedData);
  } catch (error) {
    throw new Error(`Gagal mendecode data: ${error.message}`);
  }
}

//Tabel Matrix
export class TabelMatrix {
  constructor(options) {
    this.options = options;
    this.currentPage = 1;
    this.paginationId = options.pagination || "pagination";
    this.paginationPosition = options.paginationPosition || "center";
    this.searchId = options.search;
    this.exportOptions = options.export || {};

    // Pastikan data tersedia sebelum menyalin
    if (options.data) {
      this.data = {
        columns: [...(options.data.columns || [])],
        data: options.data.data
          ? JSON.parse(JSON.stringify(options.data.data))
          : [],
      };

      this.originalData = {
        columns: [...(options.data.columns || [])],
        data: options.data.data
          ? JSON.parse(JSON.stringify(options.data.data))
          : [],
      };
    } else {
      this.data = { columns: [], data: [] };
      this.originalData = { columns: [], data: [] };
    }

    this.virtualScrolling = options.virtualScrolling || false;
    this.chunkSize = options.chunkSize || 100;
    this.bufferSize = options.bufferSize || 50;
    this.rowHeight = options.rowHeight || 40;
    this.memoryLimit = options.memoryLimit || 100000;
    this.visibleRows = [];
    this.scrollTop = 0;
    this.lastScrollTop = 0;
    this.scrollThrottle = null;
    this.renderRequestId = null;
    this.isScrolling = false;

    this.requiredLibraries = {
      xlsx: {
        name: "XLSX",
        url: "https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js",
      },
      pdf: {
        name: "jspdf",
        url: "https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js",
      },
      autoTable: {
        name: "jspdf-autotable",
        url: "https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js",
        depends: "pdf",
      },
    };

    this.headerStyles = {
      backgroundColor: options.headerBackgroundColor || "#e5e9f2",
      color: options.headerTextColor || "#000",
      fontSize: options.headerFontSize || "14px",
      fontWeight: options.headerFontWeight || "700",
    };

    this.searchIndex = {};
    this.searchableColumns = options.searchableColumns || [];

    // Pindahkan buildSearchIndex setelah data diinisialisasi
    if (this.data && this.data.columns) {
      this.buildSearchIndex();
    }

    this.filters = options.filters || {};

    this.columnFormatters = options.columnFormatters || {};

    // Tambahkan properti baru untuk menangani kolom yang dapat diedit
    this.editableColumns = options.editableColumns || {};

    // Properti untuk menyimpan konfigurasi editor yang sedang aktif
    this.activeEditor = null;

    this.init();
    this.setupFilters();
    this.setupExportOptions();
  }

  init() {
    const tabelSet = document.getElementById(this.options.containerId);
    if (!tabelSet) return;

    this.data = this.options.data;
    this.perPage = this.options.perPage || 10;
    this.currentPage = 1;
    this.sortDirection = {};

    this.setupExportOptions();

    if (this.virtualScrolling) {
      //console.log('Virtual scrolling aktif');
      this.setupVirtualScrolling();
    } else {
      this.createTable();
    }

    this.setupSearch();
    if (!this.virtualScrolling) {
      this.createPagination();
    }
    this.setupExportButtons();
    this.setupErrorHandling();
  }

  setupErrorHandling() {
    window.onerror = (message, source, lineno, colno, error) => {
      // console.error('Terjadi kesalahan:', message, 'di', source, 'baris', lineno);
    };
  }

  sortTable(columnField) {
    const direction = this.sortDirection[columnField] || "asc";
    const multiplier = direction === "asc" ? 1 : -1;

    this.data.data.sort((a, b) => {
      if (a[columnField] < b[columnField]) return -1 * multiplier;
      if (a[columnField] > b[columnField]) return 1 * multiplier;
      return 0;
    });

    this.sortDirection[columnField] = direction === "asc" ? "desc" : "asc";
    this.createTable();
  }

  createTable() {
    if (this.virtualScrolling) {
      if (!this.tableWrapper) return;
      this.tableWrapper.innerHTML = "";

      const table = this.createTableElement();
      const thead = this.createTableHeader();
      const tbody = document.createElement("tbody");

      table.appendChild(thead);
      table.appendChild(tbody);
      this.tableWrapper.appendChild(table);

      this.handleScroll(this.scrollWrapper);
      this.updateSortIcons();
    } else {
      this.clearContainer();
      const table = this.createTableElement();
      const thead = this.createTableHeader();
      const tbody = this.createTableBody();

      table.appendChild(thead);
      table.appendChild(tbody);

      this.appendTableToContainer(table);
      this.updateSortIcons();
      this.updatePaginationInfo();
    }
  }

  clearContainer() {
    const container = document.getElementById(this.options.containerId);
    container.innerHTML = "";
  }

  createTableElement() {
    const table = document.createElement("table");
    table.style = "width:100%;";
    return table;
  }

  createTableHeader() {
    const thead = document.createElement("thead");
    const headerRow1 = document.createElement("tr");
    const headerRow2 = document.createElement("tr");

    const numberHeader = document.createElement("th");
    this.applyStyles(numberHeader, {
      backgroundColor: this.headerStyles.backgroundColor,
      color: this.headerStyles.color,
      fontSize: this.headerStyles.fontSize,
      fontWeight: this.headerStyles.fontWeight,
      textAlign: "center",
      verticalAlign: "middle",
      padding: "10px",
      border: "1px solid #ccc",
    });
    numberHeader.rowSpan = 2;
    numberHeader.innerHTML = "No";
    headerRow1.appendChild(numberHeader);

    this.data.columns.forEach((column) => {
      const th = document.createElement("th");
      this.applyStyles(th, {
        backgroundColor: this.headerStyles.backgroundColor,
        color: this.headerStyles.color,
        fontSize: this.headerStyles.fontSize,
        fontWeight: this.headerStyles.fontWeight,
        textAlign: "center",
        verticalAlign: "middle",
        padding: "10px",
        border: "1px solid #ccc",
        cursor: "pointer",
      });

      if (column.columns) {
        th.colSpan = column.columns.filter(
          (subCol) => subCol.colum !== false
        ).length;
        th.rowSpan = 1;
        th.innerHTML = `${
          column.title.split("\n")[0]
        } <span class="sort-icon  pull-right" data-field="${
          column.field
        }"></span>`;
        headerRow1.appendChild(th);

        column.columns.forEach((subCol) => {
          // Skip kolom jika colum adalah false
          if (subCol.colum === false) return;

          const subTh = document.createElement("th");
          subTh.innerHTML = `${
            subCol.title.split("\n")[0]
          } <span class="sort-icon  pull-right" data-field="${
            subCol.field
          }"></span>`;

          // Tambahkan class jika ada
          if (subCol.class) {
            subTh.className = subCol.class;
          }

          this.applyStyles(subTh, {
            backgroundColor: this.headerStyles.backgroundColor,
            color: this.headerStyles.color,
            fontSize: this.headerStyles.fontSize,
            fontWeight: this.headerStyles.fontWeight,
            textAlign: "center",
            verticalAlign: "middle",
            padding: "10px",
            border: "1px solid #ccc",
            cursor: "pointer",
          });
          subTh.onclick = () => this.sortTable(subCol.field);
          headerRow2.appendChild(subTh);
        });
      } else {
        th.rowSpan = 2;
        th.colSpan = 1;
        th.innerHTML = `${
          column.title.split("\n")[0]
        } <span class="sort-icon pull-right" data-field="${
          column.field
        }"></span>`;
        th.onclick = () => this.sortTable(column.field);
        headerRow1.appendChild(th);

        // Tambahkan class jika ada
        if (column.class) {
          th.className = column.class;
        }
      }
    });

    thead.appendChild(headerRow1);
    thead.appendChild(headerRow2);
    return thead;
  }

  createTableBody() {
    const tbody = document.createElement("tbody");
    const startIndex = (this.currentPage - 1) * this.perPage;
    const endIndex = startIndex + this.perPage;
    const visibleData = this.data.data.slice(startIndex, endIndex);

    visibleData.forEach((rowData, index) => {
      const row = document.createElement("tr");

      // Sel nomor
      const numberCell = document.createElement("td");
      numberCell.innerText = startIndex + index + 1;
      this.applyStyles(numberCell, {
        textAlign: "center",
        fontSize: "13px",
        fontWeight: "400",
        backgroundColor: "#ffffff",
        padding: "10px",
        border: "1px solid #ccc",
        verticalAlign: "top",
      });
      row.appendChild(numberCell);

      // Iterasi kolom
      this.data.columns.forEach((column) => {
        if (column.columns) {
          column.columns.forEach((subColumn) => {
            // Skip kolom jika colum adalah false
            if (subColumn.colum === false) return;

            const td = this.createDataCell(rowData, subColumn, index);
            row.appendChild(td);
          });
        } else {
          const td = this.createDataCell(rowData, column, index);
          row.appendChild(td);
        }
      });
      tbody.appendChild(row);
    });

    return tbody;
  }

  appendTableToContainer(table) {
    const container = document.getElementById(this.options.containerId);
    container.appendChild(table);
  }

  updateSortIcons() {
    const icons = document.querySelectorAll(".sort-icon");
    icons.forEach((icon) => {
      const field = icon.getAttribute("data-field");
      icon.innerHTML = "";
      if (this.sortDirection[field]) {
        icon.innerHTML =
          this.sortDirection[field] === "asc"
            ? ' <i class="icon-feather-chevron-up"></i>'
            : ' <i class="icon-feather-chevron-down"></i>';
      }
    });
  }

  setupSearch() {
    if (this.searchId) {
      const searchInput = document.getElementById(this.searchId);
      if (searchInput) {
        //console.log('Search input ditemukan:', this.searchId);
        searchInput.addEventListener("input", (e) => {
          //console.log('Mencari:', e.target.value);
          this.performSearch(e.target.value);
        });
      } else {
        console.warn(
          `Elemen pencarian dengan id "${this.searchId}" tidak ditemukan.`
        );
      }
    }
  }

  buildSearchIndex() {
    //console.log('Building search index for columns:', this.searchableColumns);

    // Pastikan data tersedia sebelum membangun index
    if (!this.data || !this.data.columns) {
      console.warn("Data atau kolom belum tersedia untuk search index");
      return;
    }

    if (!this.searchableColumns || this.searchableColumns.length === 0) {
      // Jika searchableColumns tidak ditentukan, indeks semua kolom
      this.searchableColumns = this.data.columns.reduce((acc, col) => {
        if (col.columns) {
          return [...acc, ...col.columns.map((subCol) => subCol.field)];
        }
        return [...acc, col.field];
      }, []);
    }

    // Reset search index
    this.searchIndex = {};

    // Buat indeks untuk setiap kolom yang dapat dicari
    this.searchableColumns.forEach((field) => {
      this.searchIndex[field] = new Map();

      if (!this.data || !this.data.data) {
        console.warn("Data tidak tersedia untuk indexing");
        return;
      }

      this.data.data.forEach((row, idx) => {
        if (row[field] === undefined) {
          console.warn(`Field "${field}" tidak ditemukan pada baris ${idx}`);
          return;
        }

        const value = String(row[field]).toLowerCase();
        if (!this.searchIndex[field].has(value)) {
          this.searchIndex[field].set(value, new Set());
        }
        this.searchIndex[field].get(value).add(idx);
      });
    });

    //console.log('Search index built:', this.searchIndex);
  }

  performSearch(searchText) {
    clearTimeout(this.searchTimeout);
    this.searchTimeout = setTimeout(() => {
      const startTime = performance.now();
      searchText = searchText.toLowerCase();

      if (searchText.length === 0) {
        this.data = {
          ...this.originalData,
          data: [...this.originalData.data],
        };
        this.refreshTable();
        return;
      }

      // Gunakan filter langsung untuk pencarian sederhana
      const filteredData = this.originalData.data.filter((row) => {
        return this.searchableColumns.some((field) => {
          const value = String(row[field] || "").toLowerCase();
          return value.includes(searchText);
        });
      });

      // Update data tabel dengan hasil pencarian
      this.data = {
        ...this.originalData,
        data: filteredData,
      };

      const endTime = performance.now();
      //console.log(`Pencarian selesai dalam ${endTime - startTime}ms`);

      this.refreshTable();
    }, 300);
  }

  refreshTable() {
    this.currentPage = 1;
    if (this.virtualScrolling) {
      this.setupVirtualScrolling();
    } else {
      this.createTable();
      this.createPagination();
    }
  }

  // Tambahkan cache untuk hasil pencarian
  searchCache = new Map();

  performSearchWithCache(searchText) {
    clearTimeout(this.searchTimeout);
    this.searchTimeout = setTimeout(() => {
      searchText = searchText.toLowerCase();

      // Cek cache
      if (this.searchCache.has(searchText)) {
        this.data.data = this.searchCache.get(searchText);
        this.refreshTable();
        return;
      }

      // Lakukan pencarian
      const results = this.performSearch(searchText);

      // Simpan ke cache
      if (this.searchCache.size > 100) {
        // Batasi ukuran cache
        const firstKey = this.searchCache.keys().next().value;
        this.searchCache.delete(firstKey);
      }
      this.searchCache.set(searchText, results);
    }, 300);
  }

  // Tambahkan metode untuk pencarian fuzzy
  performFuzzySearch(searchText, threshold = 0.3) {
    return this.originalData.data.filter((row) => {
      return this.searchableColumns.some((field) => {
        const value = String(row[field]).toLowerCase();
        return (
          this.calculateLevenshteinDistance(value, searchText.toLowerCase()) <=
          threshold
        );
      });
    });
  }

  calculateLevenshteinDistance(str1, str2) {
    const track = Array(str2.length + 1)
      .fill(null)
      .map(() => Array(str1.length + 1).fill(null));

    for (let i = 0; i <= str1.length; i++) track[0][i] = i;
    for (let j = 0; j <= str2.length; j++) track[j][0] = j;

    for (let j = 1; j <= str2.length; j++) {
      for (let i = 1; i <= str1.length; i++) {
        const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
        track[j][i] = Math.min(
          track[j][i - 1] + 1,
          track[j - 1][i] + 1,
          track[j - 1][i - 1] + indicator
        );
      }
    }

    return track[str2.length][str1.length];
  }

  applyStyles(element, styles) {
    Object.keys(styles).forEach((style) => {
      element.style[style] = styles[style];
    });
  }

  createPagination() {
    // Cek apakah paginasi dibutuhkan
    if (!this.paginationId || this.virtualScrolling) {
      return;
    }

    const paginationContainer = document.getElementById(this.paginationId);
    if (!paginationContainer) {
      console.warn(
        `Elemen paginasi dengan id "${this.paginationId}" tidak ditemukan, paginasi tidak akan ditampilkan.`
      );
      return;
    }

    // Hapus paginasi yang ada jika ada
    paginationContainer.innerHTML = "";

    // Jika tidak ada data, tidak perlu membuat paginasi
    if (!this.data || !this.data.data || this.data.data.length === 0) {
      return;
    }

    const paginationList = document.createElement("ul");
    paginationList.className = `pagination justify-content-${this.paginationPosition}`;

    paginationContainer.appendChild(paginationList);

    this.updatePaginationInfo(paginationList);
  }

  updatePaginationInfo(paginationList) {
    // Jika paginationList tidak diberikan, coba dapatkan dari container
    if (!paginationList && this.paginationId) {
      const paginationContainer = document.getElementById(this.paginationId);
      if (!paginationContainer) {
        return;
      }
      paginationList = paginationContainer.querySelector(".pagination");
      if (!paginationList) {
        return;
      }
    }

    // Jika masih tidak ada paginationList, keluar
    if (!paginationList) {
      return;
    }

    // Bersihkan paginasi yang ada
    paginationList.innerHTML = "";

    const totalPages = Math.ceil(this.data.data.length / this.perPage);
    if (totalPages <= 1) {
      return; // Tidak perlu paginasi jika hanya 1 halaman
    }

    const createPageItem = (
      text,
      pageNum,
      active = false,
      disabled = false
    ) => {
      const li = document.createElement("li");
      li.className = `page-item ${active ? "active" : ""} ${
        disabled ? "disabled" : ""
      }`;
      const a = document.createElement("a");
      a.className = "page-link";
      a.href = "#";
      a.innerText = text;
      if (!disabled) {
        a.addEventListener("click", (e) => {
          e.preventDefault();
          if (text === "Previous") {
            this.goToPage(this.currentPage - 1);
          } else if (text === "Next") {
            this.goToPage(this.currentPage + 1);
          } else if (text === "Last") {
            this.goToPage(totalPages);
          } else if (text === "First") {
            this.goToPage(1);
          } else {
            this.goToPage(pageNum);
          }
        });
      }
      li.appendChild(a);
      return li;
    };

    // Tambahkan tombol First
    paginationList.appendChild(
      createPageItem("First", 1, false, this.currentPage === 1)
    );

    // Tambahkan tombol Previous
    paginationList.appendChild(
      createPageItem(
        "Previous",
        this.currentPage - 1,
        false,
        this.currentPage === 1
      )
    );

    // Tambahkan nomor halaman
    let startPage = Math.max(1, this.currentPage - 2);
    let endPage = Math.min(startPage + 4, totalPages);
    if (endPage - startPage < 4) {
      startPage = Math.max(1, endPage - 4);
    }

    for (let i = startPage; i <= endPage; i++) {
      paginationList.appendChild(createPageItem(i, i, i === this.currentPage));
    }

    // Tambahkan tombol Next
    paginationList.appendChild(
      createPageItem(
        "Next",
        this.currentPage + 1,
        false,
        this.currentPage === totalPages
      )
    );

    // Tambahkan tombol Last
    paginationList.appendChild(
      createPageItem("Last", totalPages, false, this.currentPage === totalPages)
    );

    // Tambahkan info halaman
    const pageInfo = document.createElement("li");
    pageInfo.className = "page-item disabled";
    pageInfo.innerHTML = `<span class="page-link">
            Halaman ${this.currentPage} dari ${totalPages}
            (Total: ${this.data.data.length} data)
        </span>`;
    paginationList.appendChild(pageInfo);
  }

  goToPage(pageNum) {
    const totalPages = Math.ceil(this.data.data.length / this.perPage);
    if (pageNum >= 1 && pageNum <= totalPages && pageNum !== this.currentPage) {
      this.currentPage = pageNum;
      this.createTable();
      this.updatePaginationInfo();
    }
  }

  nextPage() {
    this.goToPage(this.currentPage + 1);
  }

  prevPage() {
    this.goToPage(this.currentPage - 1);
  }

  setupExportButtons() {
    Object.entries(this.exportOptions).forEach(([format, buttonId]) => {
      const button = document.getElementById(buttonId);
      if (button) {
        button.textContent = format; // Set label tombol sesuai format
        button.addEventListener("click", () => {
          this.exportData(format);
        });
      } else {
        console.warn(`Elemen ekspor dengan id "${buttonId}" tidak ditemukan.`);
      }
    });
  }

  async exportData(format) {
    const formatKey = format.toLowerCase();
    if (this.exportFormats[formatKey]) {
      try {
        // Load library yang diperlukan
        if (formatKey === "xlsx") {
          await this.loadLibrary("xlsx");
        } else if (formatKey === "pdf") {
          await this.loadLibrary("pdf");
          await this.loadLibrary("autoTable");
        }

        this.exportFormats[formatKey].processor();
      } catch (error) {
        console.error(`Error saat export ${format}:`, error);
        this.triggerExportCallback("error", formatKey, error);
      }
    } else {
      console.warn(`Format ekspor "${format}" tidak dikenali.`);
    }
  }

  setupExportOptions() {
    this.exportFormats = {
      xlsx: {
        mimeType:
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        extension: ".xlsx",
        processor: this.exportXLSX.bind(this),
      },
      pdf: {
        mimeType: "application/pdf",
        extension: ".pdf",
        processor: this.exportPDF.bind(this),
      },
      csv: {
        mimeType: "text/csv",
        extension: ".csv",
        processor: this.exportCSV.bind(this),
      },
      json: {
        mimeType: "application/json",
        extension: ".json",
        processor: this.exportJSON.bind(this),
      },
    };
  }

  exportXLSX() {
    try {
      if (typeof XLSX === "undefined") {
        throw new Error("Library XLSX tidak tersedia");
      }

      // Persiapkan data untuk export
      const exportData = this.prepareExportData();

      // Konfigurasi worksheet
      const ws = XLSX.utils.json_to_sheet(exportData.data);

      // Styling worksheet
      const wsStyles = {
        "!cols": exportData.columns.map(() => ({ wch: 15 })), // Auto width
        "!rows": [{ hpt: 25 }], // Header height
      };

      // Merge dengan style yang ada
      ws["!cols"] = wsStyles["!cols"];
      ws["!rows"] = wsStyles["!rows"];

      const wb = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(wb, ws, this.options.sheetName || "Sheet1");

      // Tambahkan metadata
      wb.Props = {
        Title: this.options.fileName || "Export Data",
        Author: "System",
        CreatedDate: new Date(),
      };

      XLSX.writeFile(wb, `${this.options.fileName || "export"}.xlsx`);

      this.triggerExportCallback("success", "xlsx");
    } catch (error) {
      console.error("Error exporting XLSX:", error);
      this.triggerExportCallback("error", "xlsx", error);
    }
  }

  async exportPDF() {
    try {
      if (typeof window.jspdf === "undefined") {
        await this.loadLibrary("pdf");
        await this.loadLibrary("autoTable");
      }

      const { jsPDF } = window.jspdf;
      const doc = new jsPDF({
        orientation: this.options.pdfOrientation || "landscape",
        unit: "mm",
        format: this.options.pdfFormat || "a4",
      });

      // Tambahkan header
      if (this.options.title) {
        doc.setFontSize(16);
        doc.text(this.options.title, 14, 15);
      }

      // Konfigurasi autoTable
      doc.autoTable({
        startY: this.options.title ? 25 : 15,
        head: [this.getHeaderRow()],
        body: this.getDataRows(),
        styles: {
          fontSize: 8,
          cellPadding: 2,
          overflow: "linebreak",
          font: "helvetica",
        },
        headStyles: {
          fillColor: [66, 139, 202],
          textColor: 255,
          fontSize: 9,
          fontStyle: "bold",
          halign: "center",
        },
        columnStyles: this.getPDFColumnStyles(),
        didDrawPage: (data) => {
          // Footer
          doc.setFontSize(8);
          doc.text(
            `Diekspor pada: ${new Date().toLocaleString()}`,
            data.settings.margin.left,
            doc.internal.pageSize.height - 10
          );
        },
        margin: { top: 15, right: 15, bottom: 15, left: 15 },
        theme: "grid",
      });

      // Simpan file
      doc.save(`${this.options.fileName || "export"}.pdf`);
      this.triggerExportCallback("success", "pdf");
    } catch (error) {
      console.error("Error exporting PDF:", error);
      this.triggerExportCallback("error", "pdf", error);
    }
  }

  exportCSV() {
    try {
      const exportData = this.prepareExportData();
      const headers = this.getHeaderRow().join(",");
      const rows = this.getDataRows().map((row) => row.join(","));

      const csvContent = [headers, ...rows].join("\n");
      const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });

      this.downloadFile(blob, "csv");
      this.triggerExportCallback("success", "csv");
    } catch (error) {
      console.error("Error exporting CSV:", error);
      this.triggerExportCallback("error", "csv", error);
    }
  }

  exportJSON() {
    try {
      const exportData = {
        metadata: {
          exportDate: new Date(),
          totalRecords: this.data.data.length,
          filters: this.activeFilters || {},
          sorting: this.sortDirection || {},
        },
        data: this.data.data,
      };

      const blob = new Blob([JSON.stringify(exportData, null, 2)], {
        type: "application/json",
      });

      this.downloadFile(blob, "json");
      this.triggerExportCallback("success", "json");
    } catch (error) {
      console.error("Error exporting JSON:", error);
      this.triggerExportCallback("error", "json", error);
    }
  }

  // Tambahkan metode untuk memuat data secara bertahap
  loadDataInChunks(chunkSize = 1000) {
    const totalData = this.originalData.data.length;
    let loadedData = 0;

    const loadChunk = () => {
      const chunk = this.originalData.data.slice(
        loadedData,
        loadedData + chunkSize
      );
      this.data.data = this.data.data.concat(chunk);
      loadedData += chunk.length;

      this.createTable();

      if (loadedData < totalData) {
        setTimeout(loadChunk, 0);
      }
    };

    loadChunk();
  }

  static createInstance(options) {
    return new TabelMatrix(options);
  }

  setupVirtualScrolling() {
    if (!this.virtualScrolling) return;

    const container = document.getElementById(this.options.containerId);
    container.innerHTML = "";

    // Buat wrapper untuk virtual scrolling
    const tableWrapper = document.createElement("div");
    tableWrapper.className = "virtual-scroll-wrapper";
    tableWrapper.style.height = `${this.options.virtualScrollHeight || 400}px`;
    tableWrapper.style.overflowY = "auto";
    tableWrapper.style.position = "relative";
    tableWrapper.style.width = "100%";
    tableWrapper.style.border = "1px solid #ddd";
    tableWrapper.style.backgroundColor = "#fff";

    // Buat table dengan struktur fixed
    const table = document.createElement("table");
    table.style.width = "100%";
    table.style.borderCollapse = "collapse";
    table.style.tableLayout = "fixed";

    // Buat dan tambahkan thead
    const thead = this.createTableHeader();
    thead.style.position = "sticky";
    thead.style.top = "0";
    thead.style.zIndex = "1";
    thead.style.backgroundColor = "#fff";
    table.appendChild(thead);

    // Buat tbody container
    const tbodyContainer = document.createElement("div");
    tbodyContainer.style.position = "relative";
    tbodyContainer.style.width = "100%";
    // Set tinggi total untuk scrolling
    tbodyContainer.style.height = `${this.data.data.length * this.rowHeight}px`;

    // Buat tbody untuk konten yang terlihat
    const tbody = document.createElement("tbody");
    tbody.style.position = "absolute";
    tbody.style.width = "100%";

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

    this.tableWrapper = tableWrapper;
    this.tbody = tbody;
    this.tbodyContainer = tbodyContainer;
    this.table = table;

    // Hitung jumlah baris yang terlihat
    const visibleRowCount = Math.ceil(
      tableWrapper.clientHeight / this.rowHeight
    );

    // Sesuaikan buffer size
    this.bufferSize = Math.min(
      Math.ceil(visibleRowCount), // Buffer 1x jumlah baris yang terlihat
      50 // Maksimal 50 baris buffer
    );

    let lastScrollTime = 0;
    const scrollThrottleMs = 16;

    tableWrapper.addEventListener("scroll", () => {
      const now = performance.now();
      if (now - lastScrollTime >= scrollThrottleMs) {
        lastScrollTime = now;
        this.handleScroll(tableWrapper);
      }
    });

    // Render awal
    this.handleScroll(tableWrapper);
  }

  handleScroll(wrapper) {
    if (!wrapper || !this.data.data.length) return;

    const scrollTop = wrapper.scrollTop;
    const viewportHeight = wrapper.clientHeight;
    const totalHeight = this.data.data.length * this.rowHeight;

    // Hitung indeks baris yang terlihat
    const startIndex = Math.floor(scrollTop / this.rowHeight);
    const visibleCount = Math.ceil(viewportHeight / this.rowHeight);

    // Hitung range data dengan buffer
    const start = Math.max(0, startIndex - this.bufferSize);
    const end = Math.min(
      this.data.data.length,
      startIndex + visibleCount + this.bufferSize
    );

    // Update scroll container height jika perlu
    this.tbodyContainer.style.height = `${totalHeight}px`;

    // Render hanya jika range berubah
    const currentRange = `${start}-${end}`;
    if (this.lastRange !== currentRange) {
      this.lastRange = currentRange;
      this.renderRows(start, end);
    }
  }

  renderRows(start, end) {
    const fragment = document.createDocumentFragment();

    for (let i = start; i < end; i++) {
      const rowData = this.data.data[i];
      if (!rowData) continue;

      const row = document.createElement("tr");
      row.style.position = "absolute";
      row.style.top = `${i * this.rowHeight}px`;
      row.style.width = "100%";
      row.style.height = `${this.rowHeight}px`;
      row.style.backgroundColor = "#ffffff";
      row.style.display = "table";
      row.style.tableLayout = "fixed";

      // Tambahkan nomor
      const numberCell = document.createElement("td");
      numberCell.textContent = (i + 1).toString();
      this.applyStyles(numberCell, {
        textAlign: "center",
        fontSize: "13px",
        fontWeight: "400",
        backgroundColor: "#ffffff",
        padding: "10px",
        border: "1px solid #ccc",
        verticalAlign: "middle",
        height: `${this.rowHeight}px`,
      });
      row.appendChild(numberCell);

      // Render sel data
      this.data.columns.forEach((column) => {
        if (column.columns) {
          column.columns.forEach((subColumn) => {
            const td = this.createDataCell(rowData, subColumn, i);
            row.appendChild(td);
          });
        } else {
          const td = this.createDataCell(rowData, column, i);
          row.appendChild(td);
        }
      });

      fragment.appendChild(row);
    }

    // Update tbody
    this.tbody.innerHTML = "";
    this.tbody.appendChild(fragment);
  }

  createDataCell(rowData, column, rowIndex) {
    const td = document.createElement("td");
    const fieldToUse = column.field1 || column.field;
    const editConfig = this.editableColumns[fieldToUse];

    // Tambahkan class jika ada
    if (column.class) {
      td.className = column.class;
      if (editConfig) {
        td.className += " editable-cell";
      }
    } else if (editConfig) {
      td.className = "editable-cell";
    }

    // Buat container untuk konten
    const contentDiv = document.createElement("div");
    contentDiv.style.cssText = "position: relative; min-height: 20px;";

    // Simpan nilai asli
    const originalValue = rowData[column.field];

    // Format nilai untuk tampilan
    let formattedValue = "";
    if (
      (editConfig && editConfig[0] === "search") ||
      editConfig?.type === "search"
    ) {
      // Untuk tipe search, ambil label dari opsi
      const options = Array.isArray(editConfig)
        ? editConfig[5]
        : editConfig.options;
      // Cek apakah originalValue adalah object
      const searchValue =
        typeof originalValue === "object" ? originalValue.value : originalValue;
      const option = options?.find((opt) => opt.value === searchValue);
      formattedValue = option ? option.label : searchValue || "";
    } else {
      formattedValue = this.formatCellValue(originalValue, column);
    }

    contentDiv.innerHTML = formattedValue || "";

    // Tambahkan ikon edit jika sel dapat diedit
    if (editConfig) {
      const iconSpan = document.createElement("span");
      iconSpan.className = "edit-icon";
      iconSpan.innerHTML =
        '<i data-feather="edit-2" style="width: 14px; height: 14px;"></i>';
      td.appendChild(iconSpan);

      if (typeof feather !== "undefined") {
        feather.replace();
      }

      // Event listener untuk edit
      td.addEventListener("click", () => {
        if (this.activeEditor) return;

        // Cek kondisional editing jika ada
        if (editConfig.conditional && editConfig.conditional.editable) {
          if (!editConfig.conditional.editable(rowData)) {
            return; // Tidak dapat diedit berdasarkan kondisi
          }
        }

        const editor = this.createEditor(
          editConfig.type || editConfig[0],
          editConfig,
          originalValue,
          (newValue) => {
            if (newValue !== null) {
              // Simpan nilai baru
              rowData[column.field] = newValue;

              // Format dan tampilkan nilai baru
              const newFormattedValue = this.formatCellValue(newValue, column);
              contentDiv.innerHTML = newFormattedValue;

              if (this.options.onCellEdit) {
                this.options.onCellEdit(
                  rowData,
                  column.field,
                  newValue,
                  rowIndex
                );
              }
            }
            editor.remove();
            contentDiv.style.display = "block";
            iconSpan.style.display = "block";
          }
        );

        contentDiv.style.display = "none";
        iconSpan.style.display = "none";
        td.appendChild(editor);
        editor.focus();
        this.activeEditor = editor;
      });
    }

    td.appendChild(contentDiv);

    this.applyStyles(td, {
      fontSize: "13px",
      fontWeight: "400",
      backgroundColor: "#ffffff",
      padding: "10px",
      border: "1px solid #ccc",
      textAlign: typeof rowData[column.field] === "number" ? "right" : "left",
      verticalAlign: "top",
    });

    // Tambahkan CSS classes untuk alignment
    if (td.className.includes("center")) {
      td.style.textAlign = "center";
    } else if (td.className.includes("right")) {
      td.style.textAlign = "right";
    } else if (td.className.includes("left")) {
      td.style.textAlign = "left";
    }

    // Tambahkan CSS class untuk bold
    if (td.className.includes("bold")) {
      td.style.fontWeight = "bold";
    }

    return td;
  }

  // Tambahkan juga method formatCellValue jika belum ada
  formatCellValue(value, column) {
    if (value === null || value === undefined) return "";

    const fieldToUse = column.field1 || column.field;

    // Jika ada formatter dan nilai bukan HTML
    if (this.columnFormatters[fieldToUse] && !this.isHTML(value)) {
      return this.columnFormatters[fieldToUse](value);
    }

    // Jika nilai sudah dalam format HTML atau tidak ada formatter
    return value;
  }

  // Tambahkan method isHTML jika belum ada
  isHTML(str) {
    if (typeof str !== "string") return false;
    return str.trim().startsWith("<") && str.trim().endsWith(">");
  }

  // Tambahkan method formatDate ke dalam class TabelMatrix
  formatDate(date, format) {
    const pad = (num) => String(num).padStart(2, "0");

    const formatMap = {
      Y: date.getFullYear(),
      y: date.getFullYear().toString().slice(-2),
      m: pad(date.getMonth() + 1),
      d: pad(date.getDate()),
      H: pad(date.getHours()),
      i: pad(date.getMinutes()),
      s: pad(date.getSeconds()),
      j: date.getDate(),
      n: date.getMonth() + 1,
      F: new Intl.DateTimeFormat("id-ID", { month: "long" }).format(date),
      M: new Intl.DateTimeFormat("id-ID", { month: "short" }).format(date),
      l: new Intl.DateTimeFormat("id-ID", { weekday: "long" }).format(date),
      D: new Intl.DateTimeFormat("id-ID", { weekday: "short" }).format(date),
    };

    return format
      .split("")
      .map((char) => formatMap[char] || char)
      .join("");
  }

  // Tambahkan juga method untuk parsing tanggal
  parseDate(dateString, format) {
    // Jika dateString sudah dalam format ISO atau timestamp
    if (!isNaN(Date.parse(dateString))) {
      return new Date(dateString);
    }

    // Jika format custom
    const formatParts = format.split(/[^YymdHis]/);
    const dateParts = dateString.split(/[^0-9]/);
    const formatMap = {};

    format.split("").forEach((char, index) => {
      if ("YymdHis".includes(char)) {
        formatMap[char] =
          dateParts[formatParts.findIndex((part) => part.includes(char))];
      }
    });

    const year = formatMap["Y"] || formatMap["y"] || new Date().getFullYear();
    const month = (formatMap["m"] || 1) - 1;
    const day = formatMap["d"] || 1;
    const hours = formatMap["H"] || 0;
    const minutes = formatMap["i"] || 0;
    const seconds = formatMap["s"] || 0;

    return new Date(year, month, day, hours, minutes, seconds);
  }

  // Tambahkan method Reload
  Reload(options) {
    // Validasi parameter
    if (!options || (!options.row && !options.data)) {
      console.warn(
        "Parameter reload tidak valid. Gunakan format: { row: dataset } atau { data: { columns: [], data: [] }}"
      );
      return;
    }

    try {
      // Update data berdasarkan parameter yang diberikan
      if (options.row) {
        // Jika hanya update data rows
        this.data.data = [...options.row];
        this.originalData.data = [...options.row];
      } else if (options.data) {
        // Jika update keseluruhan struktur data
        this.data = {
          columns: [...(options.data.columns || this.data.columns)],
          data: [...options.data.data],
        };
        this.originalData = {
          columns: [...(options.data.columns || this.data.columns)],
          data: [...options.data.data],
        };
      }

      // Reset state
      this.currentPage = 1;
      this.sortDirection = {};

      // Rebuild search index
      this.buildSearchIndex();

      // Reset filters jika ada
      if (this.filters) {
        Object.keys(this.filters).forEach((filterKey) => {
          const filterConfig = this.filters[filterKey];
          const filterElement = document.getElementById(filterConfig.element);
          if (filterElement) {
            if (filterConfig.type === "select") {
              this.populateFilterOptions(filterElement, filterConfig.field);
            }
            filterElement.value = "all";
          }
        });
      }

      // Perbarui tampilan
      if (this.virtualScrolling) {
        this.setupVirtualScrolling();
      } else {
        this.createTable();
        this.createPagination();
      }

      //console.log('Tabel berhasil dimuat ulang');
      return true;
    } catch (error) {
      console.error("Gagal memuat ulang tabel:", error);
      return false;
    }
  }

  // Tambahkan method addTabel
  addTabel(newData) {
    try {
      // Validasi parameter
      if (!newData || !newData.columns) {
        throw new Error(
          "Data tidak valid. Format yang benar: { columns: [...] }"
        );
      }

      // Buat data row baru dari values di kolom
      const newRow = {};

      // Map nilai-nilai dari kolom baru ke struktur kolom yang ada
      newData.columns.forEach((column) => {
        if (column.columns) {
          column.columns.forEach((subCol) => {
            // Gunakan field dari struktur kolom yang ada jika ada
            const existingSubCol = this.originalData.columns
              .find((c) => c.columns)
              ?.columns.find((sc) => sc.title === subCol.title);

            const field =
              existingSubCol?.field ||
              subCol.field ||
              `field_${Math.random().toString(36).substr(2, 9)}`;
            if (subCol.value !== undefined) {
              newRow[field] = subCol.value;
            }
          });
        } else {
          // Untuk kolom tunggal
          const existingCol = this.originalData.columns.find(
            (c) => c.title === column.title
          );

          const field = existingCol?.field || column.field || column.value;
          if (column.value !== undefined) {
            newRow[field] = column.value;
          }
        }
      });

      // Gabungkan data baru dengan data yang ada
      this.data = {
        // Gunakan struktur kolom yang ada
        columns: [...this.originalData.columns],
        // Tambahkan data baru di awal array
        data: [newRow, ...this.originalData.data],
      };

      // Update originalData juga
      this.originalData = {
        columns: [...this.originalData.columns],
        data: [newRow, ...this.originalData.data],
      };

      // Reset state
      this.currentPage = 1;
      this.sortDirection = {};

      // Rebuild search index
      this.buildSearchIndex();

      // Perbarui tampilan
      if (this.virtualScrolling) {
        this.setupVirtualScrolling();
      } else {
        this.createTable();
        this.createPagination();
      }

      //console.log('Data baru berhasil ditambahkan di urutan pertama');
      return true;
    } catch (error) {
      console.error("Gagal menambahkan data:", error);
      return false;
    }
  }

  // Tambahkan method filterKey ke dalam class TabelMatrix
  filterKey(key, value) {
    try {
      // Validasi parameter
      if (!key || value === undefined) {
        throw new Error(
          "Parameter tidak valid. Gunakan format: filterKey(key, value)"
        );
      }

      // Simpan data yang difilter
      const filteredData = this.originalData.data.filter((row) => {
        // Konversi nilai ke string untuk perbandingan yang konsisten
        const rowValue = String(row[key] || "").toLowerCase();
        const searchValue = String(value).toLowerCase();

        // Gunakan includes untuk pencocokan parsial
        return rowValue.includes(searchValue);
      });

      // Update data tabel dengan hasil filter
      this.data = {
        columns: [...this.originalData.columns],
        data: filteredData,
      };

      // Reset state
      this.currentPage = 1;
      this.sortDirection = {};

      // Rebuild search index
      this.buildSearchIndex();

      // Perbarui tampilan
      if (this.virtualScrolling) {
        this.setupVirtualScrolling();
      } else {
        this.createTable();
        this.createPagination();
      }

      //console.log(`Data berhasil difilter berdasarkan ${key}=${value}`);
      return true;
    } catch (error) {
      console.error("Gagal memfilter data:", error);
      return false;
    }
  }

  // Tambahkan method untuk mereset filter
  resetFilter() {
    // Kembalikan ke data original
    this.data = {
      columns: [...this.originalData.columns],
      data: [...this.originalData.data],
    };

    // Reset state
    this.currentPage = 1;
    this.sortDirection = {};

    // Rebuild search index
    this.buildSearchIndex();

    // Perbarui tampilan
    if (this.virtualScrolling) {
      this.setupVirtualScrolling();
    } else {
      this.createTable();
      this.createPagination();
    }

    //console.log('Filter direset');
  }

  // Method baru untuk menangani pembuatan editor
  createEditor(type, config, value, callback) {
    if (type === "search") {
      const editor = document.createElement("select");

      // Handle konfigurasi array format
      let width, placeholder, searchable, options;

      if (Array.isArray(config)) {
        [, width, , placeholder, searchable, options] = config;
      } else {
        width = config.width || 6;
        placeholder = config.placeholder;
        searchable = config.searchable;
        options = config.options;
      }

      editor.className = `form-control col-md-${width}`;
      editor.innerHTML = `<option value="">${
        placeholder || "Pilih..."
      }</option>`;

      // Handle options
      const optionsList = Array.isArray(options)
        ? options
        : typeof options === "function"
        ? options(this.currentRow)
        : [];

      // Tambahkan semua opsi ke select dan cari label yang sesuai dengan value
      let selectedLabel = "";
      optionsList.forEach((opt) => {
        const option = document.createElement("option");
        option.value = opt.value;
        option.textContent = opt.label;

        // Simpan label jika value cocok
        if (value === opt.value) {
          option.selected = true;
          selectedLabel = opt.label;
        }

        editor.appendChild(option);
      });

      // Tambahkan fitur searchable
      if (searchable) {
        if (window.jQuery && window.jQuery.fn.select2) {
          jQuery(editor).select2({
            placeholder: placeholder || "Pilih...",
            allowClear: true,
            width: "100%",
            dropdownParent: jQuery(editor).parent(),
            // Inisialisasi dengan label yang sesuai
            initSelection: function (element, callback) {
              if (value && selectedLabel) {
                callback({ id: value, text: selectedLabel });
              }
            },
          });

          // Set nilai awal untuk select2
          if (value && selectedLabel) {
            const newOption = new Option(selectedLabel, value, true, true);
            jQuery(editor).append(newOption).trigger("change");
          }
        }
      }

      // Event handlers
      editor.addEventListener("change", () => {
        const newValue = editor.value;
        const selectedOption = optionsList.find(
          (opt) => opt.value === newValue
        );
        if (newValue && selectedOption) {
          // Hanya simpan value, bukan object
          callback(newValue);
        } else {
          callback(null);
        }
        this.activeEditor = null;
      });

      editor.addEventListener("blur", () => {
        setTimeout(() => {
          if (!this.activeEditor) return;
          const newValue = editor.value;
          const selectedOption = optionsList.find(
            (opt) => opt.value === newValue
          );
          if (newValue && selectedOption) {
            // Hanya simpan value, bukan object
            callback(newValue);
          } else {
            callback(null);
          }
          this.activeEditor = null;
        }, 100);
      });

      editor.addEventListener("keydown", (e) => {
        if (e.key === "Escape") {
          e.preventDefault();
          this.activeEditor = null;
          callback(null);
        }
      });

      return editor;
    }

    // Lanjutkan dengan kode editor yang sudah ada
    const cleanValue = this.cleanValueForEditor(value);

    const editor = document.createElement(
      type === "select" ? "select" : "input"
    );

    // Dapatkan konfigurasi yang diperluas
    const editorConfig = this.getEditorConfig(type, config, cleanValue);

    switch (type) {
      case "text":
      case "number":
        editor.type = type;
        editor.className = `form-control col-md-${editorConfig.width}`;
        editor.placeholder = editorConfig.placeholder;
        editor.value = cleanValue;

        // Tambahkan validasi HTML5
        if (editorConfig.validation) {
          if (editorConfig.validation.required) editor.required = true;
          if (editorConfig.validation.minLength)
            editor.minLength = editorConfig.validation.minLength;
          if (editorConfig.validation.maxLength)
            editor.maxLength = editorConfig.validation.maxLength;
          if (editorConfig.validation.pattern)
            editor.pattern = editorConfig.validation.pattern;
          if (type === "number") {
            if (editorConfig.validation.min)
              editor.min = editorConfig.validation.min;
            if (editorConfig.validation.max)
              editor.max = editorConfig.validation.max;
            if (editorConfig.validation.step)
              editor.step = editorConfig.validation.step;
          }
        }
        break;

      case "select":
        editor.className = `form-control col-md-${editorConfig.width}`;
        editor.innerHTML = `<option value="">${editorConfig.placeholder}</option>`;

        // Handle dynamic options
        const options =
          typeof editorConfig.options === "function"
            ? editorConfig.options(this.currentRow)
            : editorConfig.options;

        options.forEach((opt) => {
          const option = document.createElement("option");
          option.value = opt.value;
          option.textContent = opt.label;
          option.selected = cleanValue === opt.value;
          editor.appendChild(option);
        });

        // Add searchable feature if enabled
        if (editorConfig.searchable) {
          this.makeSelectSearchable(editor);
        }
        break;

      case "datepicker":
        editor.type = "date";
        editor.className = `form-control col-md-${editorConfig.width}`;
        editor.placeholder = editorConfig.placeholder;

        // Handle date constraints
        if (editorConfig.minDate) editor.min = editorConfig.minDate;
        if (editorConfig.maxDate) editor.max = editorConfig.maxDate;

        // Format tanggal untuk input
        if (cleanValue) {
          const date = new Date(cleanValue);
          editor.value = date.toISOString().split("T")[0];
        }

        // Tambahkan event handler khusus untuk datepicker
        editor.addEventListener("change", () => {
          if (!editor.value) {
            callback(null);
            return;
          }

          const date = new Date(editor.value);
          let formattedDate;

          // Format sesuai dengan placeholder yang ditentukan
          const format = editorConfig.placeholder;
          if (format) {
            formattedDate = this.formatDate(date, format);
          } else {
            formattedDate = date.toISOString().split("T")[0];
          }

          callback(formattedDate);
          this.activeEditor = null;
        });

        break;

      case "textarea":
        const textarea = document.createElement("textarea");
        textarea.className = `form-control col-md-${editorConfig.width}`;
        textarea.placeholder = editorConfig.placeholder;
        textarea.value = cleanValue;
        textarea.rows = editorConfig.rows || 3;
        if (editorConfig.maxLength) textarea.maxLength = editorConfig.maxLength;
        return textarea;

      case "checkbox":
      case "radio":
        const wrapper = document.createElement("div");
        wrapper.className = `col-md-${editorConfig.width}`;

        editorConfig.options.forEach((opt) => {
          const container = document.createElement("div");
          container.className = `form-check`;

          const input = document.createElement("input");
          input.type = type;
          input.className = "form-check-input";
          input.name = editorConfig.name || "group";
          input.value = opt.value;
          input.checked = cleanValue === opt.value;

          const label = document.createElement("label");
          label.className = "form-check-label";
          label.textContent = opt.label;

          container.appendChild(input);
          container.appendChild(label);
          wrapper.appendChild(container);
        });
        return wrapper;
    }

    // Tambahkan event handlers
    if (editorConfig.events) {
      Object.entries(editorConfig.events).forEach(([event, handler]) => {
        editor.addEventListener(event, handler);
      });
    }

    // Event handlers default
    editor.addEventListener("blur", () => {
      setTimeout(() => {
        if (!this.activeEditor) return;

        let newValue = editor.value;

        // Validasi
        if (
          editorConfig.validation &&
          !this.validateValue(newValue, editorConfig.validation)
        ) {
          editor.classList.add("is-invalid");
          return;
        }

        // Transform nilai setelah edit
        if (editorConfig.transform && editorConfig.transform.afterEdit) {
          newValue = editorConfig.transform.afterEdit(newValue);
        }

        this.activeEditor = null;
        callback(newValue);
      }, 100);
    });

    editor.addEventListener("keydown", (e) => {
      if (e.key === "Enter" && type !== "textarea") {
        e.preventDefault();
        let newValue = editor.value;

        if (
          editorConfig.validation &&
          !this.validateValue(newValue, editorConfig.validation)
        ) {
          editor.classList.add("is-invalid");
          return;
        }

        if (editorConfig.transform && editorConfig.transform.afterEdit) {
          newValue = editorConfig.transform.afterEdit(newValue);
        }

        this.activeEditor = null;
        callback(newValue);
      }
      if (e.key === "Escape") {
        e.preventDefault();
        this.activeEditor = null;
        callback(null);
      }
    });

    return editor;
  }

  // Method baru untuk mendapatkan konfigurasi editor yang diperluas
  getEditorConfig(type, config, value) {
    // Jika config adalah array (format lama)
    if (Array.isArray(config)) {
      return {
        type: type,
        width: config[1],
        label: config[2],
        placeholder: config[3],
        options: config[4] || [],
      };
    }

    // Format baru (object)
    return {
      type: type,
      width: config.width || 12,
      label: config.label || "",
      placeholder: config.placeholder || "",
      validation: config.validation || {},
      transform: config.transform || {},
      events: config.events || {},
      options: config.options || [],
      searchable: config.searchable || false,
      multiple: config.multiple || false,
      rows: config.rows,
      maxLength: config.maxLength,
      minDate: config.minDate,
      maxDate: config.maxDate,
      format: config.format,
      conditional: config.conditional || {},
    };
  }

  // Method untuk validasi nilai
  validateValue(value, validation) {
    if (!validation) return true;

    if (validation.required && !value) return false;
    if (validation.minLength && value.length < validation.minLength)
      return false;
    if (validation.maxLength && value.length > validation.maxLength)
      return false;
    if (validation.pattern && !validation.pattern.test(value)) return false;
    if (validation.custom && !validation.custom(value)) return false;

    return true;
  }

  // Method untuk membuat select searchable
  makeSelectSearchable(select) {
    // Implementasi fitur pencarian untuk select
    // Bisa menggunakan library seperti select2 atau implementasi custom
  }

  // Method untuk membersihkan HTML untuk editor
  cleanValueForEditor(value) {
    if (!value) return "";

    // Jika nilai bukan string, konversi ke string
    if (typeof value !== "string") {
      return String(value);
    }

    // Jika nilai adalah HTML
    if (this.isHTML(value)) {
      const temp = document.createElement("div");
      temp.innerHTML = value;
      const cleanText = temp.textContent || temp.innerText || "";
      temp.remove();
      return cleanText;
    }

    // Jika nilai bukan HTML, kembalikan apa adanya
    return value;
  }

  // Tambahkan method helper untuk mendapatkan label dari nilai search
  getSearchLabel(value, config) {
    if (!value) return "";

    const options = config.options || config[4] || [];
    const option = options.find((opt) => opt.value === value);
    return option ? option.label : value;
  }

  // Tambahkan method setupFilters
  setupFilters() {
    if (!this.filters) return;

    // Iterasi semua filter yang didefinisikan
    Object.keys(this.filters).forEach((filterKey) => {
      const filterConfig = this.filters[filterKey];
      const filterElement = document.getElementById(filterConfig.element);

      if (filterElement) {
        // Jika tipe select, populate options
        if (filterConfig.type === "select") {
          this.populateFilterOptions(filterElement, filterConfig.field);
        }

        // Tambahkan event listener
        filterElement.addEventListener("change", () => {
          this.filterData();
        });
      }
    });
  }

  // Tambahkan method populateFilterOptions
  populateFilterOptions(selectElement, field) {
    // Dapatkan nilai unik untuk field
    const uniqueValues = [
      ...new Set(
        this.originalData.data.map((item) => item[field]).filter(Boolean)
      ),
    ] // Remove null/undefined
      .sort();

    // Tambahkan options ke select element
    selectElement.innerHTML = `
            <option value="all">Semua</option>
            ${uniqueValues
              .map((value) => `<option value="${value}">${value}</option>`)
              .join("")}
        `;
  }

  // Tambahkan method filterData
  filterData() {
    // Mulai dengan semua data original
    let filteredData = [...this.originalData.data];

    // Terapkan setiap filter secara berurutan
    Object.keys(this.filters).forEach((filterKey) => {
      const filterConfig = this.filters[filterKey];
      const filterElement = document.getElementById(filterConfig.element);

      if (
        filterElement &&
        filterElement.value &&
        filterElement.value !== "all"
      ) {
        filteredData = filteredData.filter((row) => {
          const filterField = filterConfig.field;
          const rowValue = row[filterField];

          // Handle different filter types
          switch (filterConfig.type) {
            case "select":
              return String(rowValue) === String(filterElement.value);
            case "date":
              if (!rowValue || !filterElement.value) return false;
              const rowDate = new Date(rowValue);
              const filterDate = new Date(filterElement.value);
              return rowDate.toDateString() === filterDate.toDateString();
            default:
              return String(rowValue) === String(filterElement.value);
          }
        });
      }
    });

    // Update data tabel dengan hasil filter
    this.data = {
      columns: [...this.originalData.columns],
      data: filteredData,
    };

    // Reset state dan perbarui tampilan
    this.currentPage = 1;
    this.createTable();
    this.createPagination();
    this.buildSearchIndex();
  }
}
//AND Tabel Matrix
// DarkMode

// AND DarkMode
export class Crypto {
  constructor(tokenize) {
    this.init = new Ngorei();
    this.Net = new Ngorei().Network();
  }

  async authenticate(data, callbacks = {}) {
    try {
      // 1. Generate token
      const encrypt = this.init.Tokenize(
        {
          payload: data.payload,
        },
        data.endpoint
      );

      // Callback untuk hasil token
      if (callbacks.onToken) {
        callbacks.onToken(encrypt);
      }

      // 2. Kirim request
      const response = await this.Net.Brief({
        endpoint: data.endpoint,
        token: encrypt,
      });

      // Callback untuk status permintaan
      if (callbacks.onStatus) {
        callbacks.onStatus(response);
      }

      // 3. Decrypt response
      const decrypt = this.init.Tokenize(
        {
          payload: response.data.token,
        },
        data.endpoint
      );

      // Callback untuk hasil akhir
      if (callbacks.onResult) {
        callbacks.onResult(decrypt);
      }

      return decrypt;
    } catch (error) {
      // console.error('Authentication error:', error);
      // throw error;
    }
  }
}










export function onCookie(name, value) {
  // Membuat cookie dengan waktu kedaluwarsa sesi
  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(
    value
  )}; path=/`;
}

export function getOnCookie(name) {
  const cookies = document.cookie.split("; ");
  for (let cookie of cookies) {
    const [cookieName, cookieValue] = cookie.split("=");
    if (decodeURIComponent(cookieName) === name) {
      return decodeURIComponent(cookieValue);
    }
  }
  return null;
}
export class getViewStack {
  getViewInfo() {
    const currentUrl = window.location.hash.substring(1);
    const activeView = document.querySelector("[data-stack][data-active]");

    return {
      URL: currentUrl || "home",
      View: activeView?.id || "home",
      Page: currentUrl ? currentUrl.split("/")[0] : "home",
      Params: currentUrl ? currentUrl.split("/").slice(1).join(", ") : "",
    };
  }
}




const TOKEN_CONFIG = {
  expiresIn: 60 * 60 * 24 * 7, // 7 hari dalam detik
  algorithm: "AES-CBC",
  keySize: 256,
};
export function encryptToken(data, cradensial) {
  const tokenData = {
    ...data,
    exp: Math.floor(Date.now() / 1000) + TOKEN_CONFIG.expiresIn,
    iat: Math.floor(Date.now() / 1000),
  };
  try {
    const jsonString = JSON.stringify(tokenData);
    const keyBytes = CryptoJS.SHA256(cradensial);
    const iv = CryptoJS.lib.WordArray.random(16);
    const encrypted = CryptoJS.AES.encrypt(jsonString, keyBytes, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
    });
    const ivCiphertext = iv.concat(encrypted.ciphertext);
    return encodeURIComponent(CryptoJS.enc.Base64.stringify(ivCiphertext));
  } catch (error) {
    throw new Error("Gagal mengenkripsi data: " + error.message);
  }
}

export function decryptToken(token, cradensial) {
  try {
    const decodedToken = decodeURIComponent(token);
    const cipherData = CryptoJS.enc.Base64.parse(decodedToken);

    const keyBytes = CryptoJS.SHA256(cradensial);

    const iv = CryptoJS.lib.WordArray.create(cipherData.words.slice(0, 4));
    const ciphertext = CryptoJS.lib.WordArray.create(cipherData.words.slice(4));

    const decrypted = CryptoJS.AES.decrypt(
      { ciphertext: ciphertext },
      keyBytes,
      {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
      }
    );
    const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
    if (!decryptedString) {
      throw new Error("Hasil dekripsi kosong");
    }
    const decoded = JSON.parse(decryptedString);

    if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
      throw new Error("Token sudah kadaluarsa");
    }

    return decoded;
  } catch (error) {
    throw new Error("Token tidak valid: " + error.message);
  }
}

export function tokenize(userData, cradensial) {
  if (!userData?.payload) {
    throw new Error("Data tidak lengkap");
  }
  const { payload } = userData;

  if (typeof payload === "string") {
    return decryptToken(payload, cradensial);
  }

  if (
    Array.isArray(payload) ||
    (typeof payload === "object" && payload !== null)
  ) {
    return encryptToken(userData, cradensial);
  }

  throw new Error("Format payload tidak valid");
}



// stylesheet
export function stylesheet(sy) {
  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = "./" + sy;
  document.head.appendChild(link);
}