// assets/js/notifbank.js
import { requireSession, apiFetch, brandDisplayName, getQueryParam } from "../app.js";

const activeBrand = (getQueryParam("brand") || "tiara").toLowerCase();
const REQUIRED_SCOPE_PAGE = "notifikasi";

const DEFAULT_API_BASE = "https://notifbot.tiaratt888.workers.dev";
const API_BASE = String(
  getQueryParam("api") ||
  localStorage.getItem("notifbot_api_base") ||
  DEFAULT_API_BASE
).trim().replace(/\/+$/g, "");

const PANEL_API_BASE = String(
  getQueryParam("panel_api") ||
  localStorage.getItem("panel_api_base") ||
  "https://panel-api.tiaratt888.workers.dev"
).trim().replace(/\/+$/g, "");

let PANEL_USER = "admin";
let ALLOWED_SHEETS = [];
let USERNAME_MAP = {};
let OP_DEFAULT_CASE = "upper";

function getPanelBearerToken() {
  const stored = sessionStorage.getItem("panel_token_bearer");
  if (stored) return stored;
  const m = document.cookie.match(/(?:^|;\s*)panel_token=([^;]+)/);
  return m ? decodeURIComponent(m[1]) : "";
}

async function panelApiFetch(path, options = {}) {
  const bearerToken = getPanelBearerToken();

  const res = await fetch(`${PANEL_API_BASE}${path}`, {
    method: options.method || "GET",
    credentials: "include",
    headers: {
      "Accept": "application/json",
      "content-type": "application/json",
      "X-Panel-User": PANEL_USER || "admin",
      ...(bearerToken ? { "Authorization": `Bearer ${bearerToken}` } : {}),
      ...(options.headers || {})
    },
    body: options.body
  });

  const json = await res.json().catch(() => ({}));

  if (!res.ok) {
    throw Object.assign(
      new Error(json?.error || json?.message || ("HTTP " + res.status)),
      { response: json }
    );
  }

  return json;
}

const brandLogoMap = {
  tiara: "assets/img/tiaratoto.png",
  tts:  "assets/img/totosakti.png",
  ttm:  "assets/img/totomantap.png",
};

const brandBadgeText = {
  tiara: "TIARATOTO",
  tts:  "TOTOSAKTI",
  ttm:  "TOTOMANTAP",
}[activeBrand] || "BRAND";

const els = {
  desktopLogo: document.getElementById("desktopBrandLogo"),
  mobileLogo: document.getElementById("mobileBrandLogo"),
  sideMenuDesktop: document.getElementById("sideMenuDesktop"),
  sideMenuMobile: document.getElementById("sideMenuMobile"),
  brandLabel: document.getElementById("brandLabel"),
  brandDropdown: document.getElementById("brandDropdown"),
  brandBtn: document.getElementById("brandBtn"),
  userBtn: document.getElementById("userMenuBtn"),
  userDropdown: document.getElementById("userDropdown"),
  topUser: document.getElementById("topUser"),
  themeToggleBtn: document.getElementById("themeToggleBtn"),
  ddThemeText: document.getElementById("ddThemeText"),
  ddLogoutText: document.getElementById("ddLogoutText"),
  logoutBtn: document.getElementById("logoutBtn"),
  hamburgerBtn: document.getElementById("hamburgerBtn"),
  mobileSidebar: document.getElementById("mobileSidebar"),
  closeSidebarBtn: document.getElementById("closeSidebarBtn"),
  sidebarOverlay: document.getElementById("sidebarOverlay"),
  pageTitle: document.getElementById("pageTitle"),
  brandBadge: document.getElementById("brandBadge"),
  pageDesc: document.getElementById("pageDesc"),

  forbiddenOverlay: document.getElementById("forbiddenOverlay"),
  forbiddenTitle: document.getElementById("forbiddenTitle"),
  forbiddenDesc: document.getElementById("forbiddenDesc"),
  goHomeBtnText: document.getElementById("goHomeBtnText"),

  quickNavBtn: document.getElementById("quickNavBtn"),
  quickNavPanel: document.getElementById("quickNavPanel"),
  qnavNotifBank: document.getElementById("qnavNotifBank"),
  qnavNotifEwallet: document.getElementById("qnavNotifEwallet"),

  uiPageSize: document.getElementById("uiPageSize"),
  uiBankTujuan: document.getElementById("uiBankTujuan"),
  uiNamaRek: document.getElementById("uiNamaRek"),
  btnRefresh: document.getElementById("btnRefresh"),

  tbody: document.getElementById("tbody"),
  footInfo: document.getElementById("footInfo"),
  footHint: document.getElementById("footHint"),

  settingsModal: document.getElementById("settingsModal"),
  settingsBackdrop: document.getElementById("settingsBackdrop"),
  settingsCloseBtn: document.getElementById("settingsCloseBtn"),
  settingsCloseBtn2: document.getElementById("settingsCloseBtn2"),
  setEwalletType: document.getElementById("setEwalletType"),
  setNamaRek: document.getElementById("setNamaRek"),
  setNoRek: document.getElementById("setNoRek"),
  setTujuanSheet: document.getElementById("setTujuanSheet"),
  setAddBtn: document.getElementById("setAddBtn"),
  setTbody: document.getElementById("setTbody"),
  setEmpty: document.getElementById("setEmpty"),
  setCount: document.getElementById("setCount"),
  btnHapusSemua: document.getElementById("btnHapusSemua"),
  btnTableGear: document.getElementById("btnTableGear"),
};

const esc = (v) => String(v ?? "")
  .replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")
  .replaceAll('"',"&quot;").replaceAll("'","&#39;");

function fmtMoneyDot(n){
  const x = Math.round(Number(n || 0));
  return String(x).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}

function cleanSenderName(v){
  let s = String(v ?? "")
    .replace(/[\p{Extended_Pictographic}\p{Emoji_Presentation}\uFE0F]/gu, "")
    .replace(/\s+/g, " ")
    .trim();

  if (!s) return "";

  const tidy = (x) => String(x ?? "")
    .replace(/[\p{Extended_Pictographic}\p{Emoji_Presentation}\uFE0F]/gu, "")
    .replace(/\s+/g, " ")
    .replace(/\b(?:di kategori|kategori|dengan|biaya|admin|saldo|referensi|trx|transaksi|jam|pukul|pada|ke)\b.*$/i, "")
    .replace(/[.!?,:;]+$/g, "")
    .trim();

  // Format umum: "... dari NAMA ..."
  const fromMatch = s.match(
    /\b(?:dari|from)\s+(.+?)(?=\s+\b(?:di kategori|kategori|dengan|biaya|admin|saldo|referensi|trx|transaksi|jam|pukul|pada|ke)\b|[.!?,]|$)/i
  );
  if (fromMatch?.[1]) {
    const name = tidy(fromMatch[1]);
    if (name) return name;
  }

  // Format BRImo: "KET.:NBMB RIZKI GUNAWAN TO MUHAMAD FERDIANSY"
  // atau "KET.:BFST7 84601043914530VALENTINUS D:SSPIIDJA"
  const ketMatch = s.match(/\bKET\.?\s*:?\s*(.+)$/i);
  if (ketMatch?.[1]) {
    let tail = String(ketMatch[1]).trim();

    tail = tail
      .replace(/\bD\s*:.*$/i, "")
      .replace(/\bTO\b.*$/i, "")
      .trim();

    // ambil bagian nama di ujung
    const endName = tail.match(/([A-Z][A-Z\s.'&/-]{2,})$/);
    if (endName?.[1]) {
      let name = endName[1].trim();

      // buang kode prefix pendek seperti NBMB / BFST7 kalau ada
      name = name.replace(/^[A-Z0-9]{3,10}\s+/, "").trim();

      name = tidy(name);
      if (name) return name;
    }
  }

  // fallback: kalau memang sudah nama, tetap tampilkan apa adanya yang sudah dibersihkan
  return tidy(s);
}

function normalizeScopes(src) {
  if (Array.isArray(src)) return src.map(String).map(s => s.trim().toLowerCase()).filter(Boolean);
  if (src && typeof src === "object") return Object.keys(src).filter(k => !!src[k]).map(k => k.toLowerCase());
  if (typeof src === "string") return src.split(/[,\s]+/).map(s => s.trim().toLowerCase()).filter(Boolean);
  return [];
}

function normalizeUsernameMap(src) {
  if (!src || typeof src !== "object" || Array.isArray(src)) return {};
  const out = {};
  for (const [k, v] of Object.entries(src)) {
    const key = String(k || "").trim();
    const val = String(v ?? "").trim();
    if (!key) continue;
    out[key] = val;
    out[key.toLowerCase()] = val;
    out[key.toUpperCase()] = val;
  }
  return out;
}

function applyOperatorCase(v) {
  const s = String(v || "").trim();
  if (!s) return s;
  const mode = String(OP_DEFAULT_CASE || "upper").toLowerCase();
  if (mode === "lower") return s.toLowerCase();
  if (mode === "title") return s.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
  return s.toUpperCase();
}

function resolveOperatorLabel() {
  const rawUser = String(PANEL_USER || "").trim();
  const mapped =
    USERNAME_MAP[rawUser] ||
    USERNAME_MAP[rawUser.toLowerCase()] ||
    USERNAME_MAP[rawUser.toUpperCase()] ||
    rawUser ||
    "UNKNOWN";
  return applyOperatorCase(mapped);
}

function hasScope(name, { isSuperadmin, scopes }) {
  if (isSuperadmin) return true;
  name = String(name || "").toLowerCase();
  return scopes.includes(name);
}

function normalizeSheetNames(src) {
  return [...new Set(
    (Array.isArray(src) ? src : [])
      .map(v => String(v || "").trim().toUpperCase())
      .filter(Boolean)
  )].sort();
}

function isAllowedSheetName(sheet) {
  const wanted = String(sheet || "").trim().toUpperCase();
  return !!wanted && ALLOWED_SHEETS.includes(wanted);
}

function assertAllowedSheetClient(sheet, label = "Tujuan sheet") {
  const wanted = String(sheet || "").trim().toUpperCase();

  if (!wanted) {
    throw new Error(`${label} wajib dipilih.`);
  }
  if (!isAllowedSheetName(wanted)) {
    throw new Error(`${label} tidak ada di profile:${activeBrand}:bank.allowed_sheets.`);
  }

  return wanted;
}

function buildSheetOptions(selected = "") {
  const cur = String(selected || "").trim().toUpperCase();
  const source = normalizeSheetNames(ALLOWED_SHEETS);

  const opts = [`<option value="">Pilih sheet</option>`];

  if (cur && !source.includes(cur)) {
    opts.push(
      `<option value="${esc(cur)}" selected>${esc(cur)} • (TIDAK DIIZINKAN)</option>`
    );
  }

  for (const v of source) {
    opts.push(
      `<option value="${esc(v)}" ${cur === v ? "selected" : ""}>${esc(v)}</option>`
    );
  }

  return opts.join("");
}

function fillSheetSelectOptions() {
  if (!els.setTujuanSheet) return;
  if (!ALLOWED_SHEETS.length) {
    els.setTujuanSheet.innerHTML = `<option value="" selected>(Tidak ada sheet)</option>`;
    return;
  }
  els.setTujuanSheet.innerHTML = buildSheetOptions("");
  els.setTujuanSheet.value = "";
}

async function loadAllowedSheets() {
  try {
    const data = await panelApiFetch(`/api/panelcfg/${activeBrand}/bank`);
    const allowed = data?.allowed_sheets || data?.cfg?.allowed_sheets || [];

    ALLOWED_SHEETS = normalizeSheetNames(allowed);
    USERNAME_MAP = normalizeUsernameMap(data?.username_map || {});
    OP_DEFAULT_CASE = String(data?.op_default_case || "upper").trim().toLowerCase() || "upper";

    fillSheetSelectOptions();

    if (Array.isArray(REKLIST) && REKLIST.length) {
      renderRekTable();
    }

    if (els.footHint) {
      els.footHint.textContent = ALLOWED_SHEETS.length
        ? `Allowed sheets loaded: ${ALLOWED_SHEETS.length}`
        : "Tidak ada allowed sheets dari panel-api";
    }
  } catch (err) {
    ALLOWED_SHEETS = [];
    USERNAME_MAP = {};
    OP_DEFAULT_CASE = "upper";
    fillSheetSelectOptions();

    if (Array.isArray(REKLIST) && REKLIST.length) {
      renderRekTable();
    }

    if (els.footHint) {
      els.footHint.textContent = `Gagal load allowed sheets: ${String(err.message || err)}`;
    }
  }
}

function apiUrl(path, params = {}) {
  const u = API_BASE
    ? new URL(API_BASE + path)
    : new URL(path, window.location.origin);

  Object.entries(params).forEach(([k, v]) => {
    if (v === undefined || v === null) return;
    const s = String(v);
    if (!s) return;
    u.searchParams.set(k, s);
  });

  return u.toString();
}

async function apiGet(path, params = {}) {
  const res = await fetch(apiUrl(path, params), {
    method: "GET",
    credentials: "include",
    headers: { "Accept": "application/json" },
    cache: "no-store",
  });
  const data = await res.json().catch(() => null);

  if (!res.ok) {
    const msg = data?.error || `HTTP ${res.status}`;
    throw new Error(msg);
  }
  if (!data || data.ok === false) {
    throw new Error(data?.error || "API error");
  }
  return data;
}

async function apiAdmin(path, { method = "GET", body = null } = {}) {
  const bearerToken = getPanelBearerToken();

  const res = await fetch(apiUrl(path), {
    method,
    credentials: "include",
    headers: {
      "Accept": "application/json",
      "X-Panel-User": PANEL_USER || "admin",
      ...(bearerToken ? { "Authorization": `Bearer ${bearerToken}` } : {}),
      ...(body ? { "Content-Type": "application/json" } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
    cache: "no-store",
  });

  const data = await res.json().catch(() => null);

  if (!res.ok) {
    throw new Error(data?.error || data?.message || `HTTP ${res.status}`);
  }
  if (!data || data.ok === false) {
    throw new Error(data?.error || data?.message || "API error");
  }

  return data;
}

function sidebarClockCard(prefix) {
  return `
    <div class="mb-4">
      <div class="rounded-lg border border-slate-300 dark:border-[#3f3f46]
                  bg-slate-50 dark:bg-[#2a2a2a]
                  text-[12px] leading-tight text-slate-700 dark:text-slate-200 p-3">
        <div class="grid grid-cols-[max-content_1ch_1fr] items-baseline gap-x-2 gap-y-1">
          <div class="text-slate-500 dark:text-slate-400 font-medium">Tanggal</div>
          <div class="text-slate-500 dark:text-slate-400">:</div>
          <div id="sidebarDate${prefix}" class="font-semibold text-slate-800 dark:text-slate-100">-</div>

          <div class="text-slate-500 dark:text-slate-400 font-medium">Waktu</div>
          <div class="text-slate-500 dark:text-slate-400">:</div>
          <div id="sidebarTime${prefix}" class="font-semibold text-slate-800 dark:text-slate-100">-</div>
        </div>
      </div>
    </div>
  `;
}

function updateSidebarClock() {
  const now = new Date();
  const weekdayId = new Intl.DateTimeFormat('id-ID', { timeZone:'Asia/Jakarta', weekday:'long' }).format(now);
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone:'Asia/Jakarta', year:'numeric', month:'numeric', day:'2-digit',
    hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false
  }).formatToParts(now);

  let dd="", mm="", yyyy="", hh="", mi="", ss="";
  parts.forEach(p=>{
    if(p.type==="day") dd=p.value;
    if(p.type==="month") mm=p.value;
    if(p.type==="year") yyyy=p.value;
    if(p.type==="hour") hh=p.value;
    if(p.type==="minute") mi=p.value;
    if(p.type==="second") ss=p.value;
  });

  const bulanShort = ["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"];
  const tanggalStr = `${weekdayId}, ${dd} ${bulanShort[parseInt(mm,10)-1] || mm} ${yyyy}`;
  const waktuStr   = `${hh}:${mi}:${ss}`;

  ["Desktop","Mobile"].forEach(prefix=>{
    const d = document.getElementById("sidebarDate"+prefix);
    const t = document.getElementById("sidebarTime"+prefix);
    if (d) d.textContent = tanggalStr;
    if (t) t.textContent = waktuStr;
  });
}

function iconSVG(name, highlight=false) {
  const base = highlight ? "text-blue-500 dark:text-blue-400" : "text-slate-500 dark:text-slate-400";
  if (name==="home")   return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M3 12L12 3l9 9"/><path d="M9 21V12h6v9"/></svg></span>`;
  if (name==="sheets") return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="9" y1="4" x2="9" y2="22"/></svg></span>`;
  if (name==="brand")  return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><circle cx="19" cy="12" r="3"/><circle cx="5" cy="12" r="3"/></svg></span>`;
  if (name==="profile")return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span>`;
  if (name==="chat")   return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg></span>`;
  if (name==="users")  return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M17 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>`;
  if (name==="report") return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M9 3h6a2 2 0 0 1 2 2v16l-5-3-5 3V5a2 2 0 0 1 2-2z"/></svg></span>`;
  if (name==="zap")    return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg></span>`;
  if (name==="bank")   return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M3 10h18"/><path d="M5 10v10"/><path d="M9 10v10"/><path d="M15 10v10"/><path d="M19 10v10"/><path d="M4 20h16"/><path d="M12 3l9 5H3l9-5z"/></svg></span>`;
  if (name==="wallet") return `<span class="${base}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3"/><path d="M21 12h-7a2 2 0 0 0 0 4h7v-4z"/><circle cx="16" cy="14" r="1"/></svg></span>`;
  return "";
}

function scopeForHref(href) {
  if (!href) return null;
  if (href.includes("withdraw-awd")) return "autowd";
  if (href.includes("pendingan") || href.includes("mistake")) return "laporan";
  if (href.includes("global.html") || href.includes("brand.html") || href.includes("profile-edit.html")) return "brand";
  if (href.includes("notifbank") || href.includes("notifewallet")) return "notifikasi";
  if (href.includes("bank.html") || href.includes("ewallet.html") || href.includes("pulsa.html") || href.includes("wd.html")) return "proses";
  return null;
}

const brandMap = {
  tiara: {
    sections: [
      { label: "Dashboard", items: [{ text:"Dashboard", href:`index.html?brand=tiara`, icon:"home", active:false }]},
      { label: "TIARATOTO", items: [
        { text:"Global Sheets", href:`global.html?brand=tiara`, icon:"sheets", active:false },
        { text:"Brand Meta",   href:`brand.html?brand=tiara`,  icon:"brand",  active:false },
        { text:"Profiles", href:null, icon:"profile", active:false, submenu:[
          { text:"Edit Bank", channel:"bank" },
          { text:"Edit Ewallet", channel:"ewallet" },
          { text:"Edit Pulsa", channel:"pulsa" },
          { text:"Edit Withdraw", channel:"wd" },
        ]},
      ]},
      { label:"Proses", items:[
        { text:"Catat Bank", href:`bank.html?brand=tiara`, icon:"chat", active:false },
        { text:"Catat Ewallet", href:`ewallet.html?brand=tiara`, icon:"chat", active:false },
        { text:"Catat Pulsa", href:`pulsa.html?brand=tiara`, icon:"chat", active:false },
        { text:"Catat Withdraw", href:`wd.html?brand=tiara`, icon:"chat", active:false },
      ]},
      { label:"Notifikasi", items:[
        { text:"Bank", href:`notifbank.html?brand=tiara`, icon:"bank", active:true },
        { text:"Ewallet", href:`notifewallet.html?brand=tiara`, icon:"wallet", active:false },
      ]},
      { label:"Laporan", items:[
        { text:"Pendingan", href:`pendingan.html?brand=tiara`, icon:"report", active:false },
        { text:"Mistake", href:`mistake.html?brand=tiara`, icon:"report", active:false },
      ]},
      { label:"Auto Withdraw", items:[
        { text:"Withdraw", href:`withdraw-awd.html?brand=tiara`, icon:"zap", active:false },
      ]},
      { label:"Admin", items:[
        { text:"Users", href:`users.html?brand=tiara`, icon:"users", active:false, requireSuperadmin:true },
      ]},
    ],
  },
  tts: { sections: [
    { label: "Dashboard", items: [{ text:"Dashboard", href:`index.html?brand=tts`, icon:"home", active:false }]},
    { label: "TOTOSAKTI", items: [
      { text:"Global Sheets", href:`global.html?brand=tts`, icon:"sheets", active:false },
      { text:"Brand Meta",   href:`brand.html?brand=tts`,  icon:"brand",  active:false },
      { text:"Profiles", href:null, icon:"profile", active:false, submenu:[
        { text:"Edit Bank", channel:"bank" },
        { text:"Edit Ewallet", channel:"ewallet" },
        { text:"Edit Pulsa", channel:"pulsa" },
        { text:"Edit Withdraw", channel:"wd" },
      ]},
    ]},
    { label:"Proses", items:[
      { text:"Catat Bank", href:`bank.html?brand=tts`, icon:"chat", active:false },
      { text:"Catat Ewallet", href:`ewallet.html?brand=tts`, icon:"chat", active:false },
      { text:"Catat Pulsa", href:`pulsa.html?brand=tts`, icon:"chat", active:false },
      { text:"Catat Withdraw", href:`wd.html?brand=tts`, icon:"chat", active:false },
    ]},
    { label:"Notifikasi", items:[
      { text:"Bank", href:`notifbank.html?brand=tts`, icon:"bank", active:true },
      { text:"Ewallet", href:`notifewallet.html?brand=tts`, icon:"wallet", active:false },
    ]},
    { label:"Laporan", items:[
      { text:"Pendingan", href:`pendingan.html?brand=tts`, icon:"report", active:false },
      { text:"Mistake", href:`mistake.html?brand=tts`, icon:"report", active:false },
    ]},
    { label:"Auto Withdraw", items:[
      { text:"Withdraw", href:`withdraw-awd.html?brand=tts`, icon:"zap", active:false },
    ]},
    { label:"Admin", items:[
      { text:"Users", href:`users.html?brand=tts`, icon:"users", active:false, requireSuperadmin:true },
    ]},
  ]},
  ttm: { sections: [
    { label: "Dashboard", items: [{ text:"Dashboard", href:`index.html?brand=ttm`, icon:"home", active:false }]},
    { label: "TOTOMANTAP", items: [
      { text:"Global Sheets", href:`global.html?brand=ttm`, icon:"sheets", active:false },
      { text:"Brand Meta",   href:`brand.html?brand=ttm`,  icon:"brand",  active:false },
      { text:"Profiles", href:null, icon:"profile", active:false, submenu:[
        { text:"Edit Bank", channel:"bank" },
        { text:"Edit Ewallet", channel:"ewallet" },
        { text:"Edit Pulsa", channel:"pulsa" },
        { text:"Edit Withdraw", channel:"wd" },
      ]},
    ]},
    { label:"Proses", items:[
      { text:"Catat Bank", href:`bank.html?brand=ttm`, icon:"chat", active:false },
      { text:"Catat Ewallet", href:`ewallet.html?brand=ttm`, icon:"chat", active:false },
      { text:"Catat Pulsa", href:`pulsa.html?brand=ttm`, icon:"chat", active:false },
      { text:"Catat Withdraw", href:`wd.html?brand=ttm`, icon:"chat", active:false },
    ]},
    { label:"Notifikasi", items:[
      { text:"Bank", href:`notifbank.html?brand=ttm`, icon:"bank", active:true },
      { text:"Ewallet", href:`notifewallet.html?brand=ttm`, icon:"wallet", active:false },
    ]},
    { label:"Laporan", items:[
      { text:"Pendingan", href:`pendingan.html?brand=ttm`, icon:"report", active:false },
      { text:"Mistake", href:`mistake.html?brand=ttm`, icon:"report", active:false },
    ]},
    { label:"Auto Withdraw", items:[
      { text:"Withdraw", href:`withdraw-awd.html?brand=ttm`, icon:"zap", active:false },
    ]},
    { label:"Admin", items:[
      { text:"Users", href:`users.html?brand=ttm`, icon:"users", active:false, requireSuperadmin:true },
    ]},
  ]},
};

function renderSidebar(brandKey, prefix, opts = {}) {
  const isSuperadmin = !!opts.isSuperadmin;
  const scopes = opts.scopes || [];
  const cfg = brandMap[brandKey] || brandMap.tiara;

  const parentActiveCls = "bg-blue-50 text-blue-600 border-blue-200 dark:bg-blue-950/40 dark:text-blue-400 dark:border-blue-800";
  const parentNormalCls = "hover:bg-slate-100 hover:text-blue-600 border-transparent text-slate-700 dark:text-slate-200 dark:hover:bg-[#2a2a2a] dark:hover:text-blue-400";

  let html = sidebarClockCard(prefix);

  cfg.sections.forEach(section=>{
    const visibleItems = (section.items||[]).filter(it=>{
      if (it.requireSuperadmin && !isSuperadmin) return false;
      if (it.icon === "profile") return hasScope("brand", { isSuperadmin, scopes });
      const need = scopeForHref(it.href);
      if (!need) return true;
      return hasScope(need, { isSuperadmin, scopes });
    });

    if (!visibleItems.length) return;

    html += `<div class="mb-4">
      <div class="text-[11px] uppercase font-semibold text-slate-400 dark:text-slate-500 tracking-wide mb-2">${esc(section.label || "BRAND")}</div>
      <ul class="space-y-1">`;

    visibleItems.forEach(item=>{
      if(item.icon==="profile"){
        html += `
          <li>
            <button id="profilesToggle${prefix}" class="w-full flex items-center justify-between px-3 py-2 rounded-md font-medium border ${parentNormalCls}">
              <span class="flex items-center gap-2">${iconSVG(item.icon, false)}<span>${esc(item.text)}</span></span>
              <span class="text-slate-500 dark:text-slate-400">
                <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" class="transition-transform" data-chevron>
                  <polyline points="6 9 12 15 18 9"/>
                </svg>
              </span>
            </button>
            <ul id="profilesSub${prefix}" class="mt-1 ml-6 space-y-1 hidden">
              ${(item.submenu||[]).map(sub=>`
                <li>
                  <a href="profile-edit.html?brand=${encodeURIComponent(brandKey)}&channel=${encodeURIComponent(sub.channel)}"
                    class="flex items-center gap-2 px-3 py-2 rounded-md font-medium border text-[12px] leading-tight ${parentNormalCls}">
                    <span class="w-2 h-2 rounded-full bg-current"></span>
                    <span>${esc(sub.text)}</span>
                  </a>
                </li>
              `).join("")}
            </ul>
          </li>
        `;
      } else {
        const linkCls = item.active ? parentActiveCls : parentNormalCls;
        html += `
          <li>
            <a href="${item.href}" class="flex items-center gap-2 px-3 py-2 rounded-md font-medium border ${linkCls}">
              ${iconSVG(item.icon, !!item.active)}<span>${esc(item.text)}</span>
            </a>
          </li>
        `;
      }
    });

    html += `</ul></div>`;
  });

  return html;
}

function attachSidebarToggle(prefix){
  const btn = document.getElementById("profilesToggle"+prefix);
  const sub = document.getElementById("profilesSub"+prefix);
  if(!btn || !sub) return;
  btn.addEventListener("click", ()=>{
    sub.classList.toggle("hidden");
    const chev = btn.querySelector("[data-chevron]");
    if(chev) chev.classList.toggle("rotate-180");
  }, { passive:true });
}

let _clockInt = null;
function applySidebar(isSuperadmin, scopes){
  els.sideMenuDesktop.innerHTML = renderSidebar(activeBrand, "Desktop", { isSuperadmin, scopes });
  els.sideMenuMobile.innerHTML  = renderSidebar(activeBrand, "Mobile",  { isSuperadmin, scopes });
  attachSidebarToggle("Desktop");
  attachSidebarToggle("Mobile");
  updateSidebarClock();
  if (_clockInt) clearInterval(_clockInt);
  _clockInt = setInterval(updateSidebarClock, 1000);
}

function setupQuickNav(){
  if (!els.quickNavBtn || !els.quickNavPanel) return;
  const qs = `?brand=${encodeURIComponent(activeBrand)}`;
  if (els.qnavNotifBank)   els.qnavNotifBank.href   = `notifbank.html${qs}`;
  if (els.qnavNotifEwallet)els.qnavNotifEwallet.href= `notifewallet.html${qs}`;

  if (els.qnavNotifBank){
    els.qnavNotifBank.classList.add("bg-blue-600","text-white","border-blue-600","dark:border-blue-500");
  }

  els.quickNavBtn.addEventListener("click",(e)=>{
    e.stopPropagation();
    els.quickNavPanel.classList.toggle("hidden");
  });
  els.quickNavPanel.addEventListener("click",(e)=>e.stopPropagation());

  document.addEventListener("click", ()=>{
    if (!els.quickNavPanel.classList.contains("hidden")) els.quickNavPanel.classList.add("hidden");
  }, { passive:true });

  document.addEventListener("keydown", (e)=>{
    if (e.key==="Escape") els.quickNavPanel.classList.add("hidden");
  });
}

function setQuickNavVisible(show){
  if (!els.quickNavBtn) return;
  els.quickNavBtn.classList.toggle("hidden", !show);
  els.quickNavPanel?.classList.add("hidden");
}

function applyStatic(session){
  const logoSrc = brandLogoMap[activeBrand] || brandLogoMap.tiara;

  if (els.desktopLogo) { els.desktopLogo.src = logoSrc; els.desktopLogo.alt = brandBadgeText; }
  if (els.mobileLogo)  { els.mobileLogo.src  = logoSrc; els.mobileLogo.alt  = brandBadgeText; }

  els.brandLabel.textContent = brandBadgeText;
  els.brandBadge.textContent = brandBadgeText;
  els.pageTitle.textContent  = "Notifikasi Bank";
  els.pageDesc.textContent   = `Rekonsiliasi notifikasi bank untuk brand ${brandDisplayName(activeBrand)}.`;

  els.ddThemeText.textContent  = "Tema";
  els.ddLogoutText.textContent = "Keluar";

  els.topUser.textContent = session?.username || "admin";

  els.brandBtn.addEventListener("click",(e)=>{
    e.stopPropagation();
    els.brandDropdown.classList.toggle("hidden");
    if(!els.userDropdown.classList.contains("hidden")) els.userDropdown.classList.add("hidden");
    if(els.quickNavPanel && !els.quickNavPanel.classList.contains("hidden")) els.quickNavPanel.classList.add("hidden");
  });

  els.userBtn.addEventListener("click",(e)=>{
    e.stopPropagation();
    els.userDropdown.classList.toggle("hidden");
    if(!els.brandDropdown.classList.contains("hidden")) els.brandDropdown.classList.add("hidden");
    if(els.quickNavPanel && !els.quickNavPanel.classList.contains("hidden")) els.quickNavPanel.classList.add("hidden");
  });

  els.themeToggleBtn.addEventListener("click", ()=>{
    const root=document.documentElement;
    const isDark=root.classList.toggle("dark");
    localStorage.setItem("theme", isDark?"dark":"light");
  });

  function openMobile(){
    setQuickNavVisible(false);
    els.mobileSidebar.classList.remove("-translate-x-full");
    els.sidebarOverlay.classList.remove("hidden");
  }
  function closeMobile(){
    els.mobileSidebar.classList.add("-translate-x-full");
    els.sidebarOverlay.classList.add("hidden");
    setQuickNavVisible(true);
  }
  els.hamburgerBtn.addEventListener("click",(e)=>{ e.stopPropagation(); openMobile(); });
  els.closeSidebarBtn.addEventListener("click", closeMobile);
  els.sidebarOverlay.addEventListener("click", closeMobile, { passive:true });

  document.addEventListener("click", (e)=>{
    const wrap = document.getElementById("brandPickerWrap");
    if(!els.brandDropdown.classList.contains("hidden")){
      if(wrap && !wrap.contains(e.target)) els.brandDropdown.classList.add("hidden");
    }
    if(!els.userDropdown.classList.contains("hidden")){
      if(!els.userBtn.contains(e.target) && !els.userDropdown.contains(e.target)){
        els.userDropdown.classList.add("hidden");
      }
    }
    if(els.quickNavPanel && !els.quickNavPanel.classList.contains("hidden")){
      if(!els.quickNavPanel.contains(e.target) && !els.quickNavBtn.contains(e.target)){
        els.quickNavPanel.classList.add("hidden");
      }
    }
  }, { passive:true });

  setupQuickNav();
  setQuickNavVisible(true);
}

function showForbiddenOverlay(){
  els.forbiddenOverlay.classList.remove("hidden");
  [els.uiPageSize, els.uiBankTujuan, els.uiNamaRek, els.btnRefresh].forEach(el=>{
    if (el) el.disabled = true;
  });
}

let DATA = [];
let REKLIST = [];

const AUTO_REFRESH_MS = 4000;

const STATE = {
  pageSize: 50,
  page: 1,
  status: "pending",
  search: "",
  bankTujuan: "",
  namaRek: "",
  serverTotal: null,
  _accountsReq: 0,
  _loadingNotifs: false,
  _mutatingRows: 0,
  _autoRefreshTimer: null,
};

function isSettingsModalOpen() {
  return !!els.settingsModal && !els.settingsModal.classList.contains("hidden");
}

function isEditingNotifInput() {
  const ae = document.activeElement;
  return !!(ae && ae.matches?.("input.inpUser"));
}

function canAutoRefreshNotifs() {
  if (document.hidden) return false;
  if (STATE._loadingNotifs) return false;
  if (STATE._mutatingRows > 0) return false;
  if (isSettingsModalOpen()) return false;
  if (isEditingNotifInput()) return false;
  if (!STATE.bankTujuan || !STATE.namaRek) return false;
  return true;
}

async function autoRefreshNotifsTick() {
  if (!canAutoRefreshNotifs()) return;
  await refreshNotifs({ silent: true });
}

function stopAutoRefreshNotifs() {
  if (STATE._autoRefreshTimer) {
    clearInterval(STATE._autoRefreshTimer);
    STATE._autoRefreshTimer = null;
  }
}

function startAutoRefreshNotifs() {
  stopAutoRefreshNotifs();
  STATE._autoRefreshTimer = setInterval(() => {
    void autoRefreshNotifsTick();
  }, AUTO_REFRESH_MS);
}

document.addEventListener("visibilitychange", () => {
  if (!document.hidden) {
    void autoRefreshNotifsTick();
  }
});

const LS_EWALLET_KEY = `notifbank_selected_bank:${activeBrand}`;
function loadSavedEwallet() {
  return String(localStorage.getItem(LS_EWALLET_KEY) || "").toUpperCase().trim();
}
function saveSavedEwallet(v) {
  const val = String(v || "").toUpperCase().trim();
  if (val) localStorage.setItem(LS_EWALLET_KEY, val);
  else localStorage.removeItem(LS_EWALLET_KEY);
}

const LS_ACCOUNT_KEY = (ewallet) => `notifbank_selected_account:${activeBrand}:${String(ewallet||"").toUpperCase().trim()}`;
function loadSavedAccount(ewallet){
  const e = String(ewallet||"").toUpperCase().trim();
  if (!e) return "";
  return String(localStorage.getItem(LS_ACCOUNT_KEY(e)) || "").trim();
}
function saveSavedAccount(ewallet, accountId){
  const e = String(ewallet||"").toUpperCase().trim();
  if (!e) return;
  const v = String(accountId||"").trim();
  if (v) localStorage.setItem(LS_ACCOUNT_KEY(e), v);
  else localStorage.removeItem(LS_ACCOUNT_KEY(e));
}

function filteredRows(){
  let rows = DATA.slice();

  const q = String(STATE.search||"").trim().toLowerCase();
  if (q){
    rows = rows.filter(r =>
      String(r.when).toLowerCase().includes(q) ||
      String(r.note).toLowerCase().includes(q) ||
      String(r.sender).toLowerCase().includes(q) ||
      String(r.user).toLowerCase().includes(q)
    );
  }

  if (STATE.status === "pending") rows = rows.filter(r => !r.matched);
  else if (STATE.status === "matched") rows = rows.filter(r => !!r.matched);

  return rows;
}

function renderTable(){
  const rows = filteredRows();

  const pageSize = Math.max(1, Number(STATE.pageSize||50));
  const totalDisplay = (STATE.search ? rows.length : (Number.isFinite(STATE.serverTotal) ? STATE.serverTotal : rows.length));
  const pages = Math.max(1, Math.ceil((totalDisplay || 0) / pageSize));

  STATE.page = Math.min(Math.max(1, STATE.page), pages);

  const start = (STATE.page - 1) * pageSize;
  const slice = rows.slice(start, start + pageSize);

  els.tbody.innerHTML = slice.map(r=>{
    const okBtnCls = r.matched
      ? "bg-blue-700 hover:bg-blue-800"
      : "bg-slate-300 hover:bg-slate-400 dark:bg-[#3a3a3a] dark:hover:bg-[#444]";
    const okIconCls = r.matched ? "text-white" : "text-slate-700 dark:text-slate-200";

    return `
      <tr class="hover:bg-slate-50/60 dark:hover:bg-[#1a1a1a]">
        <td class="px-3 py-2 whitespace-nowrap text-slate-700 dark:text-slate-200">${esc(r.when)}</td>
        <td class="px-3 py-2 text-slate-700 dark:text-slate-200">
          <div class="whitespace-nowrap overflow-hidden text-ellipsis max-w-[560px]">${esc(r.note)}</div>
        </td>
        <td class="px-3 py-2 whitespace-nowrap text-slate-700 dark:text-slate-200">${esc(r.sender)}</td>
        <td class="px-3 py-2 whitespace-nowrap text-right tabular-nums text-slate-700 dark:text-slate-200">${fmtMoneyDot(r.amount)}</td>
        <td class="px-3 py-2">
          <input type="text" class="inpUser w-[140px] px-2 py-1 rounded-md border border-slate-300 dark:border-[#3f3f46]
                       bg-white dark:bg-[#0f0f0f] text-slate-900 dark:text-slate-100"
            data-id="${esc(r.id)}" value="${esc(r.user)}" />
        </td>
        <td class="px-3 py-2 text-center">
          <div class="flex flex-row items-center justify-center gap-1">
            <button class="btnOk inline-flex items-center justify-center w-9 h-9 rounded-md ${okBtnCls}" data-id="${esc(r.id)}" title="Approve">
              <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"
                stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" class="${okIconCls}">
                <polyline points="20 6 9 17 4 12"/>
              </svg>
            </button>

            <button class="btnDel inline-flex items-center justify-center w-9 h-9 rounded-md bg-red-500 hover:bg-red-600 text-white"
              data-id="${esc(r.id)}" title="Hapus">
              <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2"
                stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
                <polyline points="3 6 5 6 21 6"/>
                <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
                <path d="M10 11v6"/>
                <path d="M14 11v6"/>
                <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
              </svg>
            </button>
          </div>
        </td>
      </tr>
    `;
  }).join("");

  if (slice.length === 0) {
    const bankName = STATE.bankTujuan || "bank";
    const accName = els.uiNamaRek?.options[els.uiNamaRek.selectedIndex]?.text || "";
    const isAllAcc = !STATE.namaRek;
    const desc = isAllAcc
      ? `Belum ada notifikasi masuk untuk <strong>${esc(bankName)}</strong>`
      : `Belum ada notifikasi masuk untuk <strong>${esc(accName)}</strong>`;
    els.tbody.innerHTML = `
      <tr>
        <td colspan="6" class="px-4 py-10 text-center">
          <div class="flex flex-col items-center gap-2 text-slate-400 dark:text-slate-500">
            <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z"/>
            </svg>
            <p class="text-[13px] font-medium">${desc}</p>
            <p class="text-[11px]">Notifikasi akan muncul otomatis saat ada transfer masuk</p>
          </div>
        </td>
      </tr>`;
  }

  els.footInfo.textContent = `Menampilkan ${slice.length} dari ${totalDisplay} data • Hal ${STATE.page}/${pages}`;
}

const BANK_FALLBACK = ["BCA","MANDIRI","BRI","BNI","SEABANK"];

function setEwalletDropdownLoading(text){
  if (!els.uiBankTujuan) return;
  els.uiBankTujuan.innerHTML = `<option value="" selected>${esc(text)}</option>`;
}

function fillEwalletDropdown(types){
  if (!els.uiBankTujuan) return;

  const cur = String(STATE.bankTujuan || "").toUpperCase();

  const uniq = [...new Set((types || [])
    .map(v => String(v || "").trim().toUpperCase())
    .filter(Boolean)
  )].sort();

  const opts = [];
  opts.push(`<option value="" selected>BANK TUJUAN</option>`);
  for (const t of uniq) {
    opts.push(`<option value="${esc(t)}">${esc(t)}</option>`);
  }

  els.uiBankTujuan.innerHTML = opts.join("");

  if (cur && uniq.includes(cur)) {
    els.uiBankTujuan.value = cur;
    STATE.bankTujuan = cur;
  } else {
    els.uiBankTujuan.value = "";
    STATE.bankTujuan = "";
  }
  saveSavedEwallet(STATE.bankTujuan);
}

async function refreshEwalletDropdown(){
  const token = ++STATE._accountsReq;
  setEwalletDropdownLoading("Memuat bank...");

  try {
    const data = await apiGet("/api/bank/accounts", {
      brand: activeBrand,
      active: 1,
    });

    if (token !== STATE._accountsReq) return;

    const types = (data.items || []).map(it => it.ewallet).filter(Boolean);
    fillEwalletDropdown(types);

  } catch (err) {
    if (token !== STATE._accountsReq) return;

    fillEwalletDropdown(BANK_FALLBACK);

    if (els.footHint) {
      els.footHint.textContent = `Gagal load daftar bank (fallback): ${String(err.message || err)}`;
    }
  }
}

function lockNamaDropdown(msg){
  if (!els.uiNamaRek) return;
  els.uiNamaRek.disabled = false;
  els.uiNamaRek.classList.remove("opacity-60","cursor-not-allowed");
  els.uiNamaRek.innerHTML = `<option value="">— ${esc(msg)} —</option>`;
  els.uiNamaRek.value = "";
  STATE.namaRek = "";
}

function unlockNamaDropdown(){
  if (!els.uiNamaRek) return;
  els.uiNamaRek.disabled = false;
  els.uiNamaRek.classList.remove("opacity-60","cursor-not-allowed");
}

function setNamaDropdownLoading(text){
  if (!els.uiNamaRek) return;
  els.uiNamaRek.innerHTML = `<option value="" selected>${esc(text)}</option>`;
}

function fillNamaDropdown(items){
  if (!els.uiNamaRek) return;

  const currentBank = String(STATE.bankTujuan || "").toUpperCase().trim();
  const savedId = String(loadSavedAccount(currentBank) || STATE.namaRek || "").trim();

  const opts = [];
  opts.push(`<option value="">— PILIH NAMA/NO REKENING —</option>`);

  const ids = new Set();

  for (const it of (items || [])) {
    const id = String(it.account_id || "").trim();
    if (!id) continue;
    ids.add(id);

    const phone = String(it.phone || it.phone_masked || "").trim();
    const label = `${it.nama} (${phone})`;
    opts.push(`<option value="${esc(id)}">${esc(label)}</option>`);
  }

  els.uiNamaRek.innerHTML = opts.join("");

  if (savedId && ids.has(savedId)) {
    els.uiNamaRek.value = savedId;
    STATE.namaRek = savedId;
  } else {
    els.uiNamaRek.value = "";
    STATE.namaRek = "";
    if (savedId) saveSavedAccount(currentBank, "");
  }
}

async function refreshAccountsDropdown(){
  const token = ++STATE._accountsReq;

  const bank = String(STATE.bankTujuan || "").toUpperCase().trim();

  if (!bank) {
    lockNamaDropdown("NAMA (NO REKENING)");
    return;
  }

  unlockNamaDropdown();
  setNamaDropdownLoading("Memuat akun...");

  try {
    const data = await apiGet("/api/bank/accounts", {
      brand: activeBrand,
      ewallet: bank,
      active: 1,
    });

    if (token !== STATE._accountsReq) return;
    fillNamaDropdown(data.items || []);
    if (els.footHint) els.footHint.textContent = `API OK • akun: ${(data.items||[]).length} • refresh manual`;
  } catch (err) {
    if (token !== STATE._accountsReq) return;

    if (els.uiNamaRek) {
      els.uiNamaRek.innerHTML = `<option value="" selected>NAMA (NO REKENING)</option>`;
    }
    if (els.footHint) els.footHint.textContent = `Gagal load akun (cek CORS_ORIGIN / API_BASE): ${String(err.message || err)}`;
  }
}

function setRefreshBusy(busy){
  if (!els.btnRefresh) return;
  els.btnRefresh.disabled = !!busy;
  els.btnRefresh.classList.toggle("opacity-60", !!busy);
  els.btnRefresh.classList.toggle("cursor-not-allowed", !!busy);
}

async function refreshNotifs(options = {}) {
  const silent = !!options.silent;

  if (!STATE.bankTujuan || !STATE.namaRek) {
    DATA = [];
    STATE.serverTotal = 0;

    const msg = !STATE.bankTujuan
      ? `Pilih <span class="text-blue-500">Bank Tujuan</span> terlebih dahulu`
      : `Pilih <span class="text-blue-500">Nama / No Rekening</span> terlebih dahulu`;

    els.tbody.innerHTML = `
      <tr>
        <td colspan="6" class="px-4 py-12 text-center">
          <div class="flex flex-col items-center gap-3 text-slate-400 dark:text-slate-500">
            <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/>
            </svg>
            <p class="text-[13px] font-medium">${msg}</p>
          </div>
        </td>
      </tr>`;

    if (!silent && els.footHint) {
      els.footHint.textContent = !STATE.bankTujuan ? "Pilih bank dulu" : "Pilih nama/no rekening dulu";
    }

    if (!silent) setRefreshBusy(false);
    return;
  }

  if (STATE._loadingNotifs) return;

  STATE._loadingNotifs = true;
  if (!silent) setRefreshBusy(true);

  try {
    const data = await apiGet("/api/bank/notifs", {
      brand: activeBrand,
      ewallet: String(STATE.bankTujuan || "").toUpperCase(),
      account_id: String(STATE.namaRek || "").trim(),
      status: STATE.status || "pending",
      page: 1,
      pageSize: STATE.pageSize || 50,
      q: STATE.search || "",
    });

    DATA = (data.items || []).map(it => ({
        id: it.id,
        when: it.when,
        note: it.note,
        sender: cleanSenderName(it.sender || it.note || ""),
        amount: Number(it.amount || 0),
        user: it.user || "",
        matched: !!it.matched,
    }));

    STATE.serverTotal = Number(data.total || 0);
    STATE.page = Number(data.page || 1);

    if (!silent && els.footHint) {
      const fB = String(STATE.bankTujuan || "").toUpperCase();
      const fA = String(STATE.namaRek || "");
      els.footHint.textContent = `API OK • filter: ${fB || "-"} • akun: ${fA ? "dipilih" : "-"} • refresh manual`;
    }

    renderTable();
  } catch (err) {
    if (!silent && els.footHint) {
      els.footHint.textContent = `Gagal load notifikasi (cek CORS_ORIGIN / API_BASE): ${String(err.message || err)}`;
    }
  } finally {
    STATE._loadingNotifs = false;
    if (!silent) setRefreshBusy(false);
  }
}

async function patchNotifUserOnly(id, user_id) {
  await apiAdmin(`/api/bank/notifs/${encodeURIComponent(id)}`, {
    method: "PATCH",
    body: { user_id: String(user_id || "") }
  });
}

async function panelCatatNotifApprove(id, user_id, sender, amount, sheet) {
  const operator_label = resolveOperatorLabel();

  try {
    return await panelApiFetch(`/api/catat/${activeBrand}/bank`, {
      method: "POST",
      body: JSON.stringify({
        sheet: String(sheet || "").toUpperCase(),
        idempotency_key: `notif_approve:${id}`,
        kind: "notif_approve",
        payload: {
          user_id: String(user_id || "").trim(),
          sender: String(sender || "").trim(),
          amount: Number(amount || 0),
          notif_id: String(id || "").trim(),
        },
        operator_user: String(PANEL_USER || "").trim(),
        operator_label,
      })
    });
  } catch (err) {
    const apiErr = String(err?.response?.error || err?.message || "").toLowerCase();
    if (apiErr === "duplicate" || apiErr.includes("duplicate")) {
      return { ok: true, duplicate: true };
    }
    throw err;
  }
}

async function approveNotif(id, user_id, row) {
  const rek = REKLIST.find(r => r.id === STATE.namaRek);
  const sheet = assertAllowedSheetClient(
    rek?.tujuanSheet || "",
    "Tujuan sheet rekening"
  );

  if (!row || !Number.isFinite(Number(row.amount || 0)) || Number(row.amount || 0) <= 0) {
    throw new Error("Nominal notifikasi tidak valid.");
  }

  const cleanUserId = String(user_id || "").trim();

  await panelCatatNotifApprove(id, cleanUserId, row.sender, row.amount, sheet);

  return await apiAdmin(`/api/bank/notifs/${encodeURIComponent(id)}`, {
    method: "PATCH",
    body: {
      user_id: cleanUserId,
      matched: true,
      skip_sheet: true
    }
  });
}

async function deleteNotif(id) {
  return await apiAdmin(`/api/bank/notifs/${encodeURIComponent(id)}`, {
    method: "DELETE"
  });
}

function findUserInput(id) {
  return els.tbody.querySelector(`input.inpUser[data-id="${CSS.escape(id)}"]`);
}

function findApproveButton(id) {
  return els.tbody.querySelector(`button.btnOk[data-id="${CSS.escape(id)}"]`);
}

function findDeleteButton(id) {
  return els.tbody.querySelector(`button.btnDel[data-id="${CSS.escape(id)}"]`);
}

function setRowBusy(id, busy) {
  const input = findUserInput(id);
  const btnOk = findApproveButton(id);
  const btnDel = findDeleteButton(id);

  if (input) input.disabled = !!busy;

  [btnOk, btnDel].forEach(btn => {
    if (!btn) return;
    btn.disabled = !!busy;
    btn.classList.toggle("opacity-60", !!busy);
    btn.classList.toggle("cursor-not-allowed", !!busy);
  });
}

async function runApproveAction(id) {
  const row = DATA.find(r => r.id === id);
  if (!row) return;

  const userInput = findUserInput(id);
  const user_id = String(userInput?.value || row.user || "").trim();

  try {
    STATE._mutatingRows++;
    setRowBusy(id, true);

    await approveNotif(id, user_id, row);
    await refreshNotifs({ silent: true });
  } catch (err) {
    alert(String(err.message || err));
    try { await refreshNotifs({ silent: true }); } catch (_) {}
  } finally {
    setRowBusy(id, false);
    STATE._mutatingRows = Math.max(0, STATE._mutatingRows - 1);
  }
}

async function runDeleteAction(id) {
  const row = DATA.find(r => r.id === id);
  if (!row) return;

  const ok = confirm(
    `Hapus notifikasi ini?\n\n` +
    `Pengirim: ${row.sender || "-"}\n` +
    `Jumlah: Rp${fmtMoneyDot(row.amount)}\n` +
    `Waktu: ${row.when}`
  );
  if (!ok) return;

  try {
    STATE._mutatingRows++;
    setRowBusy(id, true);

    await deleteNotif(id);
    await refreshNotifs({ silent: true });
  } catch (err) {
    alert(String(err.message || err));
    try { await refreshNotifs({ silent: true }); } catch (_) {}
  } finally {
    setRowBusy(id, false);
    STATE._mutatingRows = Math.max(0, STATE._mutatingRows - 1);
  }
}

function wireTableEvents(){
  els.tbody.addEventListener("change", async (e)=>{
    const u = e.target.closest("input.inpUser[data-id]");
    if (!u) return;

    const id = u.dataset.id;
    const val = String(u.value || "");

    try {
      u.disabled = true;
      await patchNotifUserOnly(id, val);
      DATA = DATA.map(r => r.id===id ? ({...r, user:val}) : r);
    } catch (err) {
      alert(String(err.message || err));
    } finally {
      u.disabled = false;
    }
  });

  els.tbody.addEventListener("keydown", async (e) => {
    const u = e.target.closest("input.inpUser[data-id]");
    if (!u) return;
    if (e.key !== "Enter") return;

    e.preventDefault();
    const id = String(u.dataset.id || "");
    if (!id) return;

    await runApproveAction(id);
  });

  els.tbody.addEventListener("click", async (e)=>{
    const btnOk = e.target.closest("button.btnOk[data-id]");
    if (btnOk) {
      const id = String(btnOk.dataset.id || "");
      if (!id) return;
      await runApproveAction(id);
      return;
    }

    const btnDel = e.target.closest("button.btnDel[data-id]");
    if (btnDel) {
      const id = String(btnDel.dataset.id || "");
      if (!id) return;
      await runDeleteAction(id);
    }
  });
}

function wireToolbar(){
  els.uiPageSize.addEventListener("change", ()=>{
    STATE.pageSize = Number(els.uiPageSize.value || 50);
    STATE.page = 1;
    refreshNotifs();
  });

  els.uiBankTujuan.addEventListener("change", async ()=>{
    STATE.bankTujuan = els.uiBankTujuan.value || "";
    saveSavedEwallet(STATE.bankTujuan);

    STATE.namaRek = loadSavedAccount(STATE.bankTujuan) || "";

    await refreshAccountsDropdown();
    await refreshNotifs();
  });

  els.uiNamaRek.addEventListener("change", ()=>{
    STATE.namaRek = els.uiNamaRek.value || "";
    saveSavedAccount(STATE.bankTujuan, STATE.namaRek);
    refreshNotifs();
  });

  els.btnRefresh.addEventListener("click", async ()=>{
    await refreshRekListFromApi();
    await refreshNotifs();
  });

  els.btnTableGear?.addEventListener("click", ()=> openSettingsModal());
}

async function refreshRekListFromApi() {
  if (els.setTbody) {
    els.setTbody.innerHTML = `
      <tr>
        <td colspan="5" class="px-3 py-4 text-center text-[12px] text-slate-500 dark:text-slate-400">
          Memuat rekening dari API...
        </td>
      </tr>`;
  }
  els.setEmpty?.classList.add("hidden");
  if (els.setCount) els.setCount.textContent = `...`;

  try {
    const data = await apiGet("/api/bank/accounts", { brand: activeBrand });

    REKLIST = (data.items || []).map(it => ({
      id: it.account_id,
      ewallet: it.ewallet,
      nama: it.nama,
      norek: it.phone,
      active: !!it.active,
      tujuanSheet: String(it.tujuan_sheet || "").trim(),
    }));

    renderRekTable();
  } catch (err) {
    REKLIST = [];
    if (els.setTbody) els.setTbody.innerHTML = "";
    if (els.setCount) els.setCount.textContent = `0 data`;

    if (els.setEmpty) {
      els.setEmpty.classList.remove("hidden");
      els.setEmpty.textContent = `Gagal load rekening: ${String(err.message || err)}`;
    }
  }
}

function renderRekTable() {
  const rows = REKLIST.slice();
  if (els.setCount) els.setCount.textContent = `${rows.length} data`;

  if (!rows.length) {
    els.setTbody.innerHTML = "";
    els.setEmpty.classList.remove("hidden");
    els.setEmpty.textContent = "Belum ada rekening terdaftar.";
    return;
  }
  els.setEmpty.classList.add("hidden");

  els.setTbody.innerHTML = rows.map(r => {
    const sliderTrack = r.active
      ? "background-color:#10b981;"
      : "background-color:#cbd5e1;";
    const sliderThumb = r.active
      ? "transform:translateX(20px);"
      : "transform:translateX(2px);";

    return `
      <tr class="hover:bg-slate-50/60 dark:hover:bg-[#1a1a1a]">
        <td class="px-3 py-2 whitespace-nowrap text-slate-700 dark:text-slate-200">
          <span class="font-semibold">${esc(r.ewallet)}</span>
        </td>
        <td class="px-3 py-2 whitespace-nowrap text-slate-700 dark:text-slate-200">${esc(r.nama)}</td>
        <td class="px-3 py-2 whitespace-nowrap text-slate-700 dark:text-slate-200">${esc(r.norek)}</td>
        <td class="px-3 py-2 whitespace-nowrap">
          <select
            class="selTujuanSheet w-full min-w-[180px] px-2 py-1 rounded-md border border-slate-300 dark:border-[#3f3f46]
                   bg-white dark:bg-[#0f0f0f] text-slate-900 dark:text-slate-100"
            data-id="${esc(r.id)}">
            ${buildSheetOptions(r.tujuanSheet)}
          </select>
        </td>
        <td class="px-3 py-2">
          <div class="inline-flex items-center gap-3">
            <button class="btnToggleRek relative inline-flex items-center w-11 h-6 rounded-full transition-colors duration-200 focus:outline-none"
              style="${sliderTrack}"
              data-id="${esc(r.id)}" data-active="${r.active ? "1" : "0"}"
              title="${r.active ? "Klik untuk nonaktifkan" : "Klik untuk aktifkan"}">
              <span style="position:absolute;width:20px;height:20px;background:#fff;border-radius:9999px;box-shadow:0 1px 3px rgba(0,0,0,.3);transition:transform .2s;pointer-events:none;${sliderThumb}"></span>
            </button>
            <span class="text-[12px] font-medium ${r.active ? "text-emerald-600" : "text-slate-400"}">${r.active ? "Aktif" : "Nonaktif"}</span>
            <button class="btnDeleteRek px-2.5 py-1 rounded-md text-[11px] font-semibold bg-red-100 hover:bg-red-200 text-red-600 dark:bg-red-900/30 dark:hover:bg-red-900/50 dark:text-red-400"
              data-id="${esc(r.id)}">
              Hapus
            </button>
          </div>
        </td>
      </tr>
    `;
  }).join("");
}

async function openSettingsModal() {
  els.settingsModal.classList.remove("hidden");

  els.setEwalletType.value = "";
  els.setNamaRek.value = "";
  els.setNoRek.value = "";
  fillSheetSelectOptions();

  await refreshRekListFromApi();

  setTimeout(() => { els.setEwalletType?.focus?.(); }, 0);
}

function closeSettingsModal() {
  els.settingsModal.classList.add("hidden");
}

function wireSettingsModal() {
  els.settingsCloseBtn?.addEventListener("click", closeSettingsModal);
  els.settingsCloseBtn2?.addEventListener("click", closeSettingsModal);
  els.settingsBackdrop?.addEventListener("click", closeSettingsModal, { passive: true });

  els.settingsModal?.addEventListener("click", (e) => {
    const card = e.target.closest?.(".max-w-5xl");
    if (card) e.stopPropagation();
  });

  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && !els.settingsModal.classList.contains("hidden")) {
      closeSettingsModal();
    }
  });

  els.setAddBtn?.addEventListener("click", async () => {
    try {
      const bank = String(els.setEwalletType.value || "").trim().toUpperCase();
      const nama = String(els.setNamaRek.value || "").trim();
      const phone = String(els.setNoRek.value || "").trim();
      const tujuan_sheet = assertAllowedSheetClient(
        els.setTujuanSheet?.value || "",
        "Tujuan sheet"
      );

      if (!bank) return alert("Pilih bank dulu.");
      if (!nama) return alert("Nama Rekening wajib diisi.");
      if (!phone) return alert("No Rekening wajib diisi.");

      await apiAdmin("/api/bank/accounts", {
        method: "POST",
        body: { brand: activeBrand, ewallet: bank, nama, phone, tujuan_sheet },
      });

      await refreshRekListFromApi();
      await refreshEwalletDropdown();
      await refreshAccountsDropdown();

      els.setNamaRek.value = "";
      els.setNoRek.value = "";
      els.setEwalletType.value = "";
      fillSheetSelectOptions();
      els.setEwalletType.focus();
    } catch (err) {
      alert(String(err.message || err));
    }
  });

  els.setTbody?.addEventListener("change", async (e) => {
    const sel = e.target.closest?.("select.selTujuanSheet[data-id]");
    if (!sel) return;

    const id = String(sel.dataset.id || "");
    const prev = (REKLIST.find(r => r.id === id)?.tujuanSheet || "");

    try {
      const tujuan_sheet_raw = String(sel.value || "").trim();
      const tujuan_sheet = tujuan_sheet_raw
        ? assertAllowedSheetClient(tujuan_sheet_raw, "Tujuan sheet")
        : "";

      sel.disabled = true;

      await apiAdmin(`/api/bank/accounts/${encodeURIComponent(id)}`, {
        method: "PATCH",
        body: { tujuan_sheet },
      });

      REKLIST = REKLIST.map(r => r.id === id ? ({ ...r, tujuanSheet: tujuan_sheet }) : r);
    } catch (err) {
      sel.value = prev;
      alert(String(err.message || err));
    } finally {
      sel.disabled = false;
    }
  });

  els.setTbody?.addEventListener("click", async (e) => {
    const tgl = e.target.closest?.("button.btnToggleRek[data-id]");
    const del = e.target.closest?.("button.btnDeleteRek[data-id]");

    if (tgl) {
      const id = String(tgl.dataset.id || "");
      const curActive = tgl.dataset.active === "1";
      const nextActive = curActive ? 0 : 1;

      try {
        await apiAdmin(`/api/bank/accounts/${encodeURIComponent(id)}`, {
          method: "PATCH",
          body: { active: nextActive },
        });

        await refreshRekListFromApi();
        await refreshEwalletDropdown();
        await refreshAccountsDropdown();
        await refreshNotifs();
      } catch (err) {
        alert(String(err.message || err));
      }
      return;
    }

    if (del) {
      const id = String(del.dataset.id || "");
      const row = REKLIST.find(r => r.id === id);
      const label = row ? `${row.ewallet} - ${row.nama} (${row.norek})` : id;
      const ok = confirm(`Hapus rekening berikut?\n\n${label}`);
      if (!ok) return;

      try {
        await apiAdmin(`/api/bank/accounts/${encodeURIComponent(id)}`, {
          method: "DELETE",
        });
        await refreshRekListFromApi();
        await refreshEwalletDropdown();
        await refreshAccountsDropdown();
        await refreshNotifs();
      } catch (err) {
        alert(String(err.message || err));
      }
    }
  });

  els.btnHapusSemua?.addEventListener("click", async () => {
    const aktif = REKLIST.filter(r => r.active).length;
    const total = REKLIST.length;
    if (!total) { alert("Tidak ada rekening untuk dihapus."); return; }

    const sandi = prompt(`Hapus SEMUA ${total} rekening?\n(${aktif} aktif, ${total - aktif} nonaktif)\n\nMasukkan sandi untuk konfirmasi:`);
    if (sandi === null) return;
    if (sandi !== "hapusdengansadar") {
      alert("❌ Sandi salah! Hapus dibatalkan.");
      return;
    }

    let berhasil = 0, gagal = 0;
    for (const r of REKLIST) {
      try {
        await apiAdmin(`/api/bank/accounts/${encodeURIComponent(r.id)}`, { method: "DELETE" });
        berhasil++;
      } catch { gagal++; }
    }

    await refreshRekListFromApi();
    await refreshEwalletDropdown();
    await refreshAccountsDropdown();
    await refreshNotifs();

    alert(`✅ Selesai. Berhasil dihapus: ${berhasil}${gagal ? `, Gagal: ${gagal}` : ""}`);
  });
}

(async function boot(){
  const session = await requireSession();
  PANEL_USER = session?.username || "admin";

  if (session?.token) {
    sessionStorage.setItem("panel_token_bearer", session.token);
  }

  const isSuperadmin = (session?.role === "superadmin");
  const scopes = normalizeScopes(session?.scopes ?? session?.permissions ?? session?.perms ?? session?.scope_map);

  applyStatic(session);
  applySidebar(isSuperadmin, scopes);
  wireSettingsModal();

  if (!hasScope(REQUIRED_SCOPE_PAGE, { isSuperadmin, scopes })) {
    showForbiddenOverlay();
    return;
  }

  STATE.pageSize = Number(els.uiPageSize?.value || 50);
  STATE.bankTujuan = loadSavedEwallet() || "";
  STATE.namaRek = loadSavedAccount(STATE.bankTujuan) || "";

  wireToolbar();
  wireTableEvents();

  await loadAllowedSheets();
  await refreshRekListFromApi();
  await refreshEwalletDropdown();
  await refreshAccountsDropdown();
  await refreshNotifs();
  startAutoRefreshNotifs();
})();

els.logoutBtn?.addEventListener("click", async ()=>{
  try { await apiFetch("/api/logout", { method:"POST" }); } catch(_) {}
  window.location = "login.html";
});