/**
 * Antara — admin panel JS shared by users / config / logs pages.
 * Reads window.ADMIN_CTX = { apiUrl, csrf, user }.
 */

(function () {
  "use strict";

  const CTX = window.ADMIN_CTX || { apiUrl: "/core/auth/api.php", csrf: "", user: null, needsFirstAdminWizard: false, activeUserCount: 0, loginRequiredGlobally: false, deploymentKind: "personal", branches: [], projectBase: "" };
  let CSRF = CTX.csrf || "";
  let pendingProfileMediaFrame = null;
  /** Tras subir avatar, la ruta en disco es la misma; ?v= fuerza recarga en <img>. */
  const profileImageBust = Object.create(null);

  function bumpProfileImageCache(userId) {
    if (userId) profileImageBust[userId] = String(Date.now());
  }

  function adminResolveImgSrc(rel, userId) {
    if (!rel) return "";
    const base = CTX.projectBase || "";
    let url;
    if (/^https?:\/\//i.test(rel)) url = rel;
    else if (rel.charAt(0) === "/") url = base + rel;
    else url = base + "/" + rel.replace(/^\//, "");
    if (userId && profileImageBust[userId]) {
      url += (url.indexOf("?") >= 0 ? "&" : "?") + "v=" + encodeURIComponent(profileImageBust[userId]);
    }
    return url;
  }

  function userInitial(u) {
    const s = (u && (u.username || u.email)) || "?";
    const c = String(s).trim().charAt(0);
    return c ? c.toUpperCase() : "?";
  }

  function makeUserAvatarWrap(u) {
    const uid = (u && u.id) || "";
    const wrap = el("div", { class: "admin-avatar-wrap", "data-antara-profile-wrap": uid });
    const img = el("img", {
      class: "admin-avatar",
      "data-antara-profile-img": "",
      alt: "",
      loading: "lazy",
      decoding: "async",
      hidden: true,
    });
    const ph = el("span", {
      class: "admin-avatar admin-avatar--placeholder",
      "data-antara-profile-ph": "",
      "aria-hidden": "true",
    }, [userInitial(u)]);
    wrap.appendChild(img);
    wrap.appendChild(ph);
    return wrap;
  }

  function applyProfileImageEverywhere(userId, user) {
    if (!userId || !user) return;
    bumpProfileImageCache(userId);
    const rel = user.profileImageRel || "";
    const frame = user.profileMediaFrame || null;
    const sync = typeof window !== "undefined" ? window.AntaraProfileImageSync : null;
    if (sync && typeof sync.apply === "function") {
      sync.apply(userId, rel, {
        projectBase: CTX.projectBase || "",
        initial: userInitial(user),
        mediaFrame: frame,
      });
    }
    if (CTX.user && CTX.user.id === userId) {
      CTX.user.profileImageRel = rel;
      CTX.user.profileMediaFrame = frame;
    }
  }

  async function api(action, body, method) {
    const url = CTX.apiUrl + (CTX.apiUrl.indexOf("?") >= 0 ? "&" : "?") + "action=" + encodeURIComponent(action);
    const headers = { "Accept": "application/json", "X-CSRF-Token": CSRF };
    if (body !== undefined) headers["Content-Type"] = "application/json";
    const res = await fetch(url, {
      method: method || (body !== undefined ? "POST" : "GET"),
      credentials: "same-origin",
      headers,
      body: body !== undefined ? JSON.stringify(body) : undefined,
    });
    let data; try { data = await res.json(); } catch (_) { data = { success: false, error: "Bad JSON" }; }
    if (data && data.data && typeof data.data.csrf === "string") CSRF = data.data.csrf;
    if (!data || data.success !== true) {
      const e = new Error((data && data.error) || ("HTTP " + res.status));
      e.code = data && data.code;
      throw e;
    }
    return data.data;
  }

  async function apiMultipart(action, formData) {
    const url = CTX.apiUrl + (CTX.apiUrl.indexOf("?") >= 0 ? "&" : "?") + "action=" + encodeURIComponent(action);
    const res = await fetch(url, {
      method: "POST",
      credentials: "same-origin",
      headers: { Accept: "application/json", "X-CSRF-Token": CSRF },
      body: formData,
    });
    let data;
    try { data = await res.json(); } catch (_) { data = { success: false, error: "Bad JSON" }; }
    if (data && data.data && typeof data.data.csrf === "string") CSRF = data.data.csrf;
    if (!data || data.success !== true) {
      const e = new Error((data && data.error) || ("HTTP " + res.status));
      e.code = data && data.code;
      throw e;
    }
    return data.data;
  }

  function applyClientLogoEverywhere(logoData) {
    const rel = logoData.clientLogoRel || "";
    const base = CTX.projectBase || "";
    CTX.clientLogo = {
      rel,
      url: rel ? (base + "/" + rel.replace(/^\//, "")) : "",
      kind: logoData.clientLogoKind || null,
      mediaFrame: logoData.clientLogoMediaFrame || null,
    };
    document.querySelectorAll("[data-antara-client-logo-wrap]").forEach((wrap) => {
      if (!window.AntaraProfileImageSync) return;
      if (!CTX.clientLogo.url) {
        window.AntaraProfileImageSync.applyToWrap(wrap, { initial: "F" });
        return;
      }
      if (CTX.clientLogo.mediaFrame) {
        wrap.setAttribute("data-client-logo-frame", JSON.stringify(CTX.clientLogo.mediaFrame));
      } else {
        wrap.removeAttribute("data-client-logo-frame");
      }
      window.AntaraProfileImageSync.applyToWrap(wrap, {
        src: CTX.clientLogo.url,
        kind: CTX.clientLogo.kind,
        mediaFrame: CTX.clientLogo.mediaFrame,
        initial: "F",
      });
    });
  }

  if (window.AntaraProfileImageSync && typeof window.AntaraProfileImageSync.initClientLogoWraps === "function") {
    window.AntaraProfileImageSync.initClientLogoWraps();
  }

  function $(id) { return document.getElementById(id); }
  function el(tag, attrs, children) {
    const e = document.createElement(tag);
    if (attrs) Object.keys(attrs).forEach(k => {
      if (k === "class") e.className = attrs[k];
      else if (k === "html") e.innerHTML = attrs[k];
      else if (k.startsWith("on") && typeof attrs[k] === "function") e.addEventListener(k.slice(2), attrs[k]);
      else e.setAttribute(k, attrs[k]);
    });
    (children || []).forEach(c => { if (c == null) return; e.appendChild(typeof c === "string" ? document.createTextNode(c) : c); });
    return e;
  }
  function fmtDate(iso) { if (!iso) return "—"; try { return new Date(iso).toLocaleString("es-ES"); } catch (_) { return iso; } }

  /**
   * Fortaleza estimada (0–5) y etiqueta en español. El servidor exige mín. 8 caracteres.
   */
  function firstAdminPasswordStrength(pwd) {
    if (!pwd) return { tier: 0, label: "Sin indicar" };
    const len = pwd.length;
    const hasLower = /[a-z]/.test(pwd);
    const hasUpper = /[A-Z]/.test(pwd);
    const hasDigit = /\d/.test(pwd);
    const hasSym = /[^A-Za-z0-9]/.test(pwd);
    const classes = [hasLower, hasUpper, hasDigit, hasSym].filter(Boolean).length;
    let score = 0;
    if (len >= 8) score += 22;
    else score += Math.min(10, len * 1.2);
    if (len >= 12) score += 18;
    if (len >= 16) score += 10;
    if (classes >= 2) score += 18;
    if (classes >= 3) score += 14;
    if (classes >= 4) score += 18;
    score = Math.min(100, Math.round(score));
    let tier = 1;
    let label = "Muy débil";
    if (len < 8) {
      tier = Math.max(1, len <= 3 ? 1 : 2);
      label = "Insuficiente — mínimo 8 caracteres";
    } else if (score < 42) { tier = 2; label = "Débil"; }
    else if (score < 62) { tier = 3; label = "Aceptable"; }
    else if (score < 82) { tier = 4; label = "Fuerte"; }
    else { tier = 5; label = "Muy fuerte"; }
    return { tier, label, score };
  }

  function updateFirstAdminPasswordLive() {
    const pwEl = $("bf-password");
    const pw2El = $("bf-password2");
    const meter = document.querySelector(".first-admin-wizard .pw-meter");
    const strengthEl = $("bf-strength-label");
    const matchEl = $("bf-match-label");
    if (!pwEl || !strengthEl || !matchEl) return;
    const pwd = pwEl.value || "";
    const pwd2 = pw2El ? pw2El.value || "" : "";
    const s = firstAdminPasswordStrength(pwd);
    if (meter) {
      meter.className = "pw-meter" + (s.tier > 0 ? " pw-meter--" + s.tier : "");
    }
    strengthEl.className = "pw-live pw-live--strength" + (pwd && s.tier <= 2 ? " is-warn" : pwd && s.tier >= 4 ? " is-ok" : "");
    strengthEl.innerHTML = pwd
      ? "<strong>Seguridad:</strong> " + s.label + " <span class=\"muted\">(" + s.score + "/100)</span>"
      : "<strong>Seguridad:</strong> " + s.label;

    const minLen = 8;
    const meetsMin = pwd.length >= minLen;
    matchEl.className = "pw-live pw-live--match";
    if (!pwd2) {
      matchEl.textContent = "Escribe la confirmación para comprobar coincidencia y el mínimo de " + minLen + " caracteres.";
      matchEl.classList.add("is-warn");
    } else if (pwd !== pwd2) {
      matchEl.textContent = "No coinciden — revisa ambos campos.";
      matchEl.classList.add("is-bad");
    } else if (!meetsMin) {
      matchEl.textContent = "Coinciden, pero aún no cumplen el mínimo de " + minLen + " caracteres.";
      matchEl.classList.add("is-warn");
    } else {
      matchEl.textContent = "Coinciden y cumplen el mínimo de longitud (" + minLen + "+ caracteres).";
      matchEl.classList.add("is-ok");
    }
  }

  /* ---------------- LOGOUT ---------------- */
  document.addEventListener("click", async (e) => {
    const t = e.target.closest("[data-logout]");
    if (!t) return;
    e.preventDefault();
    try { await api("authLogout", {}); } catch (_) {}
    const logoutTarget = t.dataset.logout || "../../login/";
    if (window.AntaraAuthTabSync && typeof window.AntaraAuthTabSync.notifyLogout === "function") {
      window.AntaraAuthTabSync.notifyLogout(logoutTarget);
    }
    location.replace(logoutTarget);
  });

  /* ============================================================
     USERS
     ============================================================ */
  const usersPage = $("users-page");
  if (usersPage) {
    const tbody = $("users-tbody");
    const filterRole = $("filter-role");
    const filterState = $("filter-state");
    const filterSearch = $("filter-search");
    const btnNew = $("btn-new-user");
    let allUsers = [];

    function branchLabelForId(bid) {
      if (!bid || !Array.isArray(CTX.branches)) return "";
      const b = CTX.branches.find(x => x && x.id === bid);
      return b ? (b.name || b.id) : "";
    }

    function branchById(bid) {
      if (!bid || !Array.isArray(CTX.branches)) return null;
      return CTX.branches.find(x => x && x.id === bid) || null;
    }

    function departmentLabelForUser(u) {
      const br = branchById(u.branchId);
      if (!br || !u.departmentId) return "";
      const d = (br.departments || []).find(x => x && x.id === u.departmentId);
      return d ? (d.name || u.departmentId) : "";
    }

    function positionLabelForUser(u) {
      const br = branchById(u.branchId);
      if (!br || !u.departmentId || !u.positionId) return "";
      const d = (br.departments || []).find(x => x && x.id === u.departmentId);
      if (!d) return "";
      const p = (d.positions || []).find(x => x && x.id === u.positionId);
      return p ? (p.name || u.positionId) : "";
    }

    function orgLineForUser(u) {
      const parts = [];
      const dept = departmentLabelForUser(u);
      const pos = positionLabelForUser(u);
      const branch = u.branchId ? branchLabelForId(u.branchId) : "";
      if (dept) parts.push(dept);
      if (pos) parts.push(pos);
      if (branch && CTX.deploymentKind === "company") parts.push(branch);
      return parts.join(" · ");
    }

    function populateOrgSelects(branchId, selectedDept, selectedPos) {
      const deptEl = $("um-department");
      const posEl = $("um-position");
      if (!deptEl || !posEl) return;
      const br = branchById(branchId);
      deptEl.innerHTML = "";
      deptEl.appendChild(el("option", { value: "" }, ["— Sin departamento —"]));
      posEl.innerHTML = "";
      posEl.appendChild(el("option", { value: "" }, ["— Sin puesto —"]));
      if (!br) return;
      (br.departments || []).forEach(d => {
        if (!d || !d.id) return;
        deptEl.appendChild(el("option", { value: d.id }, [d.name || d.id]));
      });
      deptEl.value = selectedDept || "";
      const dept = (br.departments || []).find(x => x && x.id === (selectedDept || ""));
      if (dept) {
        (dept.positions || []).forEach(p => {
          if (!p || !p.id) return;
          posEl.appendChild(el("option", { value: p.id }, [p.name || p.id]));
        });
      }
      posEl.value = selectedPos || "";
    }

    function onBranchChangeForUserForm() {
      const branchId = $("um-branch") ? $("um-branch").value : "";
      populateOrgSelects(branchId, "", "");
    }

    function onDepartmentChangeForUserForm() {
      const branchId = $("um-branch") ? $("um-branch").value : "";
      const deptId = $("um-department") ? $("um-department").value : "";
      populateOrgSelects(branchId, deptId, "");
    }

    function applyFilters() {
      const r = filterRole.value;
      const s = filterState.value;
      const q = (filterSearch.value || "").trim().toLowerCase();
      return allUsers.filter(u => {
        if (r && u.role !== r) return false;
        if (s === "active" && (!u.active || u.blocked)) return false;
        if (s === "inactive" && u.active) return false;
        if (s === "blocked" && !u.blocked) return false;
        if (s === "passkey" && !u.hasPasskey) return false;
        if (q && !((u.email || "").toLowerCase().includes(q)
                 || (u.username || "").toLowerCase().includes(q)
                 || (u.phone || "").includes(q)
                 || (u.branchId && (branchLabelForId(u.branchId) || "").toLowerCase().includes(q))
                 || (departmentLabelForUser(u) || "").toLowerCase().includes(q)
                 || (positionLabelForUser(u) || "").toLowerCase().includes(q))) return false;
        return true;
      });
    }

    function badge(text, klass) { return el("span", { class: "badge " + (klass || "") }, [text]); }

    function statusBadgesFor(u) {
      const items = [];
      if (u.blocked) items.push(badge("Bloqueado", "is-blocked"));
      else if (!u.active) items.push(badge("Inactivo", "is-inactive"));
      else items.push(badge("Activo", ""));
      if (u.hasPasskey) items.push(badge("Passkey", "is-passkey"));
      if (u.requirePasswordReset) items.push(badge("Resetear pwd", "is-inactive"));
      return items;
    }

    function usersDetailItem(label, valueNodes) {
      const val = el("span", { class: "users-detail-item__value" });
      const nodes = Array.isArray(valueNodes) ? valueNodes : [valueNodes];
      nodes.forEach((n, i) => {
        if (n == null) return;
        if (i > 0) val.appendChild(document.createTextNode(" "));
        val.appendChild(typeof n === "string" ? document.createTextNode(n) : n);
      });
      return el("div", { class: "users-detail-item" }, [
        el("span", { class: "users-detail-item__label" }, [label]),
        val,
      ]);
    }

    function rowFor(u) {
      const branchTxt = u.branchId ? branchLabelForId(u.branchId) || u.branchId.slice(0, 10) + "…" : "";
      const orgTxt = orgLineForUser(u);
      const userCellChildren = [
        el("strong", null, [u.username || u.email || "—"]),
        el("br"),
        el("span", { class: "muted" }, [u.email || ""]),
      ];
      if (orgTxt) {
        userCellChildren.push(el("br"));
        userCellChildren.push(el("span", { class: "muted", style: "font-size:0.75rem" }, [orgTxt]));
      } else if (branchTxt) {
        userCellChildren.push(el("br"));
        userCellChildren.push(el("span", { class: "muted", style: "font-size:0.75rem" }, ["Sucursal: " + branchTxt]));
      }
      const userCol = el("div", { class: "user-cell" }, [
        makeUserAvatarWrap(u),
        el("div", { class: "user-cell__body" }, userCellChildren),
      ]);
      const statusNodes = statusBadgesFor(u);
      const statusNodesMobile = statusBadgesFor(u);
      const tr = el("tr", { class: "users-row" }, [
        el("td", { class: "users-row__user" }, [userCol]),
        el("td", { class: "users-row__phone col-hide-mobile" }, [u.phone || "—"]),
        el("td", { class: "users-row__role" }, [badge(u.role, "role-" + u.role)]),
        el("td", { class: "col-hide-sm" }, statusNodes),
        el("td", { class: "col-hide-sm muted" }, [fmtDate(u.createdAt)]),
        el("td", { class: "col-hide-sm muted" }, [fmtDate(u.lastLoginAt)]),
        el("td", { class: "col-actions users-row__actions" }, [
          el("button", { class: "btn", onclick: () => editUser(u) }, ["Editar"]),
          el("button", { class: "btn", onclick: () => toggleActive(u) }, [u.active ? "Desactivar" : "Activar"]),
          el("button", { class: "btn btn-warn", onclick: () => toggleBlock(u) }, [u.blocked ? "Desbloquear" : "Bloquear"]),
          el("button", { class: "btn", onclick: () => changePwd(u) }, ["Pwd"]),
          el("button", { class: "btn", onclick: () => resetPasskey(u) }, ["Reset passkey"]),
          el("button", { class: "btn btn-danger", onclick: () => deleteUser(u) }, ["Eliminar"]),
        ]),
      ]);

      const morePanel = el("div", { class: "users-row__more", hidden: true });
      const morePanelId = "users-more-" + (u.id || Math.random().toString(36).slice(2, 9));
      morePanel.id = morePanelId;
      [
        usersDetailItem("Teléfono", u.phone || "—"),
        usersDetailItem("Organización", orgLineForUser(u) || "—"),
        usersDetailItem("Estado", statusNodesMobile),
        usersDetailItem("Creado", fmtDate(u.createdAt)),
        usersDetailItem("Último login", fmtDate(u.lastLoginAt)),
      ].forEach(item => morePanel.appendChild(item));

      const toggleBtn = el("button", {
        type: "button",
        class: "btn users-row__toggle",
        "aria-expanded": "false",
        "aria-controls": morePanelId,
      }, ["Mostrar más"]);
      toggleBtn.addEventListener("click", () => {
        const expanded = tr.classList.toggle("is-expanded");
        morePanel.hidden = !expanded;
        toggleBtn.setAttribute("aria-expanded", expanded ? "true" : "false");
        toggleBtn.textContent = expanded ? "Mostrar menos" : "Mostrar más";
      });

      const detailsTr = el("tr", { class: "users-row__details-row" }, [
        el("td", { colspan: "7", class: "users-row__details-cell" }, [toggleBtn, morePanel]),
      ]);

      return { main: tr, details: detailsTr };
    }

    function appendUserRow(u) {
      const pair = rowFor(u);
      tbody.appendChild(pair.main);
      tbody.appendChild(pair.details);
    }

    function render() {
      tbody.innerHTML = "";
      const list = applyFilters();
      if (!list.length) {
        tbody.appendChild(el("tr", null, [el("td", { colspan: 7, class: "empty" }, ["Sin usuarios."])]));
        return;
      }
      list.forEach(u => appendUserRow(u));
      const sync = typeof window !== "undefined" ? window.AntaraProfileImageSync : null;
      if (sync && typeof sync.apply === "function") {
        list.forEach((u) => {
          sync.apply(u.id, u.profileImageRel || "", {
            projectBase: CTX.projectBase || "",
            initial: userInitial(u),
            mediaFrame: u.profileMediaFrame || null,
            skipPublish: true,
          });
        });
      }
    }

    async function refresh() {
      if (!CTX.user) {
        tbody.innerHTML = "";
        const msg = CTX.needsFirstAdminWizard
          ? "Completa el asistente de arriba. Después podrás ver y editar usuarios en esta tabla."
          : "Inicia sesión como administrador (formulario en la barra lateral) para ver y editar usuarios.";
        tbody.appendChild(el("tr", null, [el("td", { colspan: 7, class: "empty" }, [msg])]));
        return;
      }
      try {
        const r = await api("usersList");
        allUsers = r.users || [];
        render();
      } catch (e) {
        tbody.innerHTML = "";
        tbody.appendChild(el("tr", null, [el("td", { colspan: 7, class: "empty" }, ["Error: " + e.message])]));
      }
    }

    /* Modal helpers */
    const modal = $("user-modal");
    let modalAvatarUser = null;
    let pendingProfileFile = null;
    let pendingAvatarBlobUrl = null;
    let umProfileBinding = null;

    function syncModalProfileWrapIds() {
      const uid = $("um-id") ? $("um-id").value : "";
      ["um-avatar-wrap", "um-photo-preview"].forEach((id) => {
        const node = $(id);
        if (node) node.setAttribute("data-antara-profile-wrap", uid || "");
      });
    }

    function revokePendingAvatarUrl() {
      if (pendingAvatarBlobUrl) {
        try { URL.revokeObjectURL(pendingAvatarBlobUrl); } catch (_) {}
        pendingAvatarBlobUrl = null;
      }
    }

    function resetUmPhotoFileInput() {
      const fi = $("um-photo-file");
      const src = $("um-photo-source");
      if (fi) fi.value = "";
      if (src) src.value = "original";
      if (umProfileBinding) umProfileBinding.cropReady = false;
    }

    function syncModalAvatarFromState() {
      const sync = typeof window !== "undefined" ? window.AntaraProfileImageSync : null;
      const email = $("um-email") ? $("um-email").value : "";
      const username = $("um-username") ? $("um-username").value : "";
      const uForInitial = Object.assign({}, modalAvatarUser || {}, { email, username });
      const initial = userInitial(uForInitial);
      const uid = (modalAvatarUser && modalAvatarUser.id) || ($("um-id") ? $("um-id").value : "") || "";

      function paintWrap(wrapEl) {
        if (!wrapEl || !sync || typeof sync.applyToWrap !== "function") return;

        if (pendingAvatarBlobUrl && pendingProfileFile) {
          const kind = typeof sync.mediaKindFromFile === "function"
            ? sync.mediaKindFromFile(pendingProfileFile)
            : "image";
          sync.applyToWrap(wrapEl, {
            src: pendingAvatarBlobUrl,
            kind,
            mediaFrame: pendingProfileMediaFrame,
            initial,
            noCacheBust: true,
          });
          return;
        }

        const rel = modalAvatarUser && modalAvatarUser.profileImageRel;
        if (rel) {
          sync.applyToWrap(wrapEl, {
            src: adminResolveImgSrc(rel, uid),
            kind: typeof sync.mediaKindFromRel === "function" ? sync.mediaKindFromRel(rel) : null,
            mediaFrame: (modalAvatarUser && modalAvatarUser.profileMediaFrame) || null,
            initial,
          });
          return;
        }

        sync.applyToWrap(wrapEl, { src: "", initial });
      }

      paintWrap($("um-avatar-wrap"));
      paintWrap($("um-photo-preview"));
    }

    function openModal(title) {
      modal.querySelector("h2").textContent = title;
      syncModalProfileWrapIds();
      syncModalAvatarFromState();
      modal.classList.add("is-open");
      requestAnimationFrame(() => syncModalAvatarFromState());
    }
    function closeModal() {
      modal.classList.remove("is-open");
      revokePendingAvatarUrl();
      pendingProfileFile = null;
      pendingProfileMediaFrame = null;
      resetUmPhotoFileInput();
    }
    modal.querySelector(".close").addEventListener("click", closeModal);
    modal.addEventListener("click", (e) => { if (e.target === modal) closeModal(); });

    async function uploadProfileImage(userId, file, mediaFrame) {
      const url = CTX.apiUrl + (CTX.apiUrl.indexOf("?") >= 0 ? "&" : "?") + "action=" + encodeURIComponent("usersProfileImage");
      const fd = new FormData();
      fd.append("userId", userId);
      fd.append("file", file);
      if (mediaFrame) {
        fd.append("mediaFrame", JSON.stringify(mediaFrame));
      }
      const headers = { "Accept": "application/json", "X-CSRF-Token": CSRF };
      const res = await fetch(url, {
        method: "POST",
        credentials: "same-origin",
        headers,
        body: fd,
      });
      let data;
      try { data = await res.json(); } catch (_) { data = { success: false, error: "Bad JSON" }; }
      if (data && data.data && typeof data.data.csrf === "string") CSRF = data.data.csrf;
      if (!data || data.success !== true) {
        const e = new Error((data && data.error) || ("HTTP " + res.status));
        e.code = data && data.code;
        throw e;
      }
      return data.data;
    }

    function editUser(u) {
      modalAvatarUser = Object.assign({}, u);
      revokePendingAvatarUrl();
      pendingProfileFile = null;
      pendingProfileMediaFrame = null;
      resetUmPhotoFileInput();
      $("um-id").value = u.id;
      $("um-email").value = u.email || "";
      $("um-username").value = u.username || "";
      $("um-phone").value = u.phone || "";
      $("um-role").value = u.role;
      if ($("um-branch")) {
        $("um-branch").value = u.branchId || "";
        populateOrgSelects(u.branchId || "", u.departmentId || "", u.positionId || "");
      }
      $("um-password").value = "";
      $("um-feedback").textContent = "";
      openModal("Editar usuario");
    }

    btnNew.addEventListener("click", () => {
      if (!CTX.user) return;
      modalAvatarUser = null;
      revokePendingAvatarUrl();
      pendingProfileFile = null;
      pendingProfileMediaFrame = null;
      resetUmPhotoFileInput();
      $("um-id").value = "";
      $("um-email").value = ""; $("um-username").value = ""; $("um-phone").value = "";
      $("um-role").value = "usuario"; $("um-password").value = "";
      if ($("um-branch")) {
        $("um-branch").value = "";
        populateOrgSelects("", "", "");
      }
      $("um-feedback").textContent = "";
      openModal("Nuevo usuario");
    });

    const umPickGallery = $("um-pick-gallery");
    const umPickCamera = $("um-pick-camera");
    const umPhotoFile = $("um-photo-file");
    const umPhotoClear = $("um-photo-clear");
    const IIP = typeof window !== "undefined" ? window.InstallImagePicker : null;

    function onUmProfileAccepted(binding) {
      const fb = $("um-feedback");
      const f = binding && binding.fileInput && binding.fileInput.files && binding.fileInput.files[0];
      if (!f) return;
      revokePendingAvatarUrl();
      pendingProfileFile = f;
      pendingProfileMediaFrame = binding.mediaFrame || null;
      pendingAvatarBlobUrl = URL.createObjectURL(f);
      if (fb) {
        fb.classList.add("is-ok");
        fb.textContent = "Listo. Pulsa Guardar para aplicar la foto de perfil.";
      }
      syncModalAvatarFromState();
      requestAnimationFrame(() => syncModalAvatarFromState());
    }

    if (IIP && umPhotoFile) {
      IIP.ensureDialog();
      umProfileBinding = IIP.bind({
        fileInput: umPhotoFile,
        sourceHidden: $("um-photo-source"),
        dialogTitle: "Foto de perfil",
        onAccept: onUmProfileAccepted,
      });
    } else if ($("um-feedback")) {
      const fbMiss = $("um-feedback");
      if (umPickGallery || umPickCamera) {
        const warn = () => {
          fbMiss.classList.remove("is-ok");
          fbMiss.textContent = "No se cargó el editor de imagen (install_image_picker.js / Cropper).";
        };
        if (umPickGallery) umPickGallery.addEventListener("click", warn);
        if (umPickCamera) umPickCamera.addEventListener("click", warn);
      }
    }

    if (umPickGallery && umPhotoFile) {
      umPickGallery.addEventListener("click", () => {
        umPhotoFile.removeAttribute("capture");
        umPhotoFile.click();
      });
    }
    if (umPickCamera && umPhotoFile) {
      umPickCamera.addEventListener("click", () => {
        umPhotoFile.setAttribute("capture", "environment");
        umPhotoFile.click();
      });
    }

    if (umPhotoClear) {
      umPhotoClear.addEventListener("click", async () => {
        const id = $("um-id").value;
        const fb = $("um-feedback");
        fb.classList.remove("is-ok");
        revokePendingAvatarUrl();
        pendingProfileFile = null;
        resetUmPhotoFileInput();
        if (!id) {
          syncModalAvatarFromState();
          fb.textContent = "";
          return;
        }
        fb.textContent = "Quitando imagen…";
        try {
          const r = await api("usersUpdate", { id, clearProfileImage: true });
          modalAvatarUser = r.user || Object.assign({}, modalAvatarUser, { profileImageRel: "" });
          const cleared = modalAvatarUser;
          applyProfileImageEverywhere(id, cleared);
          const idx = allUsers.findIndex((x) => x.id === id);
          if (idx >= 0) allUsers[idx] = Object.assign({}, allUsers[idx], cleared);
          syncModalAvatarFromState();
          fb.classList.add("is-ok");
          fb.textContent = "Imagen quitada.";
        } catch (e) {
          fb.textContent = e.message || "Error.";
        }
      });
    }
    const umCancel = $("um-cancel");
    if (umCancel) umCancel.addEventListener("click", closeModal);
    ["um-email", "um-username"].forEach(id => {
      const node = $(id);
      if (node) node.addEventListener("input", () => { if (modal.classList.contains("is-open")) syncModalAvatarFromState(); });
    });

    $("um-save").addEventListener("click", async () => {
      if (umProfileBinding && umPhotoFile && umPhotoFile.files && umPhotoFile.files[0]) {
        if (IIP && !IIP.validateSubmit(umProfileBinding)) {
          const fb0 = $("um-feedback");
          if (fb0) {
            fb0.classList.remove("is-ok");
            fb0.textContent = "Confirma la imagen en el editor (Aceptar) antes de guardar.";
          }
          return;
        }
      }
      const id = $("um-id").value;
      const body = {
        email: $("um-email").value.trim(),
        username: $("um-username").value.trim(),
        phone: $("um-phone").value.trim(),
        role: $("um-role").value,
        password: $("um-password").value,
      };
      const fb = $("um-feedback");
      fb.classList.remove("is-ok"); fb.textContent = "Guardando…";
      try {
        let targetId = id;
        let savedUser = null;
        if (id) {
          const up = { id, email: body.email, username: body.username, phone: body.phone, role: body.role };
          if ($("um-branch")) {
            up.branchId = $("um-branch").value;
            if ($("um-department")) up.departmentId = $("um-department").value;
            if ($("um-position")) up.positionId = $("um-position").value;
          }
          const rUp = await api("usersUpdate", up);
          savedUser = rUp.user || null;
          if (body.password) await api("usersChangePassword", { id, password: body.password });
        } else {
          if (!body.password) throw new Error("La contraseña es obligatoria para usuarios nuevos.");
          const cr = { ...body };
          if ($("um-branch")) {
            cr.branchId = $("um-branch").value;
            if ($("um-department")) cr.departmentId = $("um-department").value;
            if ($("um-position")) cr.positionId = $("um-position").value;
          }
          const created = await api("usersCreate", cr);
          savedUser = created.user || null;
          targetId = savedUser && savedUser.id;
        }
        if (pendingProfileFile && targetId) {
          fb.textContent = "Subiendo imagen…";
          const imgData = await uploadProfileImage(targetId, pendingProfileFile, pendingProfileMediaFrame);
          savedUser = imgData.user || savedUser;
          pendingProfileFile = null;
          pendingProfileMediaFrame = null;
          revokePendingAvatarUrl();
        }
        if (savedUser && targetId) {
          modalAvatarUser = Object.assign({}, modalAvatarUser || {}, savedUser);
          const idx = allUsers.findIndex((x) => x.id === targetId);
          if (idx >= 0) {
            allUsers[idx] = Object.assign({}, allUsers[idx], savedUser);
          }
        }
        syncModalProfileWrapIds();
        fb.classList.add("is-ok"); fb.textContent = "Guardado.";
        await refresh();
        if (savedUser && targetId) {
          applyProfileImageEverywhere(targetId, savedUser);
          if (modal.classList.contains("is-open")) syncModalAvatarFromState();
        }
        setTimeout(closeModal, 350);
      } catch (e) { fb.textContent = e.message; }
    });

    async function toggleActive(u) {
      try { await api("usersToggleActive", { id: u.id, active: !u.active }); await refresh(); }
      catch (e) { await appDialog.alert(e.message); }
    }
    async function toggleBlock(u) {
      try { await api("usersBlock", { id: u.id, blocked: !u.blocked }); await refresh(); }
      catch (e) { await appDialog.alert(e.message); }
    }
    async function resetPasskey(u) {
      if (!(await appDialog.confirm("¿Eliminar todas las passkeys de " + (u.email || u.username) + "?"))) return;
      try { await api("usersResetPasskey", { id: u.id }); await refresh(); }
      catch (e) { await appDialog.alert(e.message); }
    }
    async function deleteUser(u) {
      if (!(await appDialog.confirm("¿Eliminar lógicamente a " + (u.email || u.username) + "?"))) return;
      try { await api("usersDelete", { id: u.id }); await refresh(); }
      catch (e) { await appDialog.alert(e.message); }
    }
    async function changePwd(u) {
      const pwd = await appDialog.prompt("Nueva contraseña para " + (u.email || u.username) + " (mín 8 caracteres):", "", { inputType: "password" });
      if (!pwd) return;
      try { await api("usersChangePassword", { id: u.id, password: pwd }); await appDialog.alert("Actualizada."); }
      catch (e) { await appDialog.alert(e.message); }
    }

    if ($("um-branch")) {
      $("um-branch").addEventListener("change", onBranchChangeForUserForm);
    }
    if ($("um-department")) {
      $("um-department").addEventListener("change", onDepartmentChangeForUserForm);
    }

    [filterRole, filterState, filterSearch].forEach(el => el.addEventListener("input", render));

    const bfForm = $("first-admin-wizard-form");
    if (bfForm) {
      const pwIn = $("bf-password");
      const pw2In = $("bf-password2");
      if (pwIn) pwIn.addEventListener("input", updateFirstAdminPasswordLive);
      if (pw2In) pw2In.addEventListener("input", updateFirstAdminPasswordLive);
      updateFirstAdminPasswordLive();

      bfForm.addEventListener("submit", async (e) => {
        e.preventDefault();
        const fb = $("bf-feedback");
        const pw = ($("bf-password") && $("bf-password").value) || "";
        const pw2 = ($("bf-password2") && $("bf-password2").value) || "";
        if (pw.length < 8) {
          if (fb) { fb.textContent = "La contraseña debe tener al menos 8 caracteres."; fb.classList.remove("is-ok"); }
          return;
        }
        if (pw !== pw2) {
          if (fb) { fb.textContent = "Las contraseñas no coinciden."; fb.classList.remove("is-ok"); }
          return;
        }
        const email = $("bf-email") ? $("bf-email").value.trim() : "";
        const username = $("bf-username") ? $("bf-username").value.trim() : "";
        if (!email && !username) {
          if (fb) { fb.textContent = "Indica al menos un correo o un nombre de usuario."; fb.classList.remove("is-ok"); }
          return;
        }
        if (fb) { fb.textContent = "Creando cuenta…"; fb.classList.remove("is-ok"); }
        try {
          await api("authBootstrapFirstAdmin", {
            email,
            username,
            phone: $("bf-phone") ? $("bf-phone").value.trim() : "",
            password: pw,
          });
          if (fb) { fb.classList.add("is-ok"); fb.textContent = "Listo. Recargando…"; }
          location.reload();
        } catch (err) {
          if (fb) { fb.textContent = err.message || "Error"; fb.classList.remove("is-ok"); }
        }
      });
    }

    refresh();
  }

  /* ============================================================
     CONFIG (super_admin only)
     ============================================================ */
  const cfgPage = $("config-page");
  if (cfgPage) {
    const fb = $("cfg-feedback");
    const btnSave = $("cfg-save");
    btnSave.addEventListener("click", async () => {
      const rpIdEl = $("cfg-rp_id");
      const rpIdRaw = rpIdEl ? rpIdEl.value.trim() : "";
      const patch = {
        login_required:          $("cfg-login_required").checked,
        allow_user_registration: $("cfg-allow_user_registration").checked,
        allow_webauthn:          $("cfg-allow_webauthn").checked,
        allow_password_login:    $("cfg-allow_password_login").checked,
        session_timeout_minutes: parseInt($("cfg-session_timeout_minutes").value, 10),
        max_failed_attempts:     parseInt($("cfg-max_failed_attempts").value, 10),
        lockout_minutes:         parseInt($("cfg-lockout_minutes").value, 10),
        rp_name:                 $("cfg-rp_name").value.trim(),
        rp_id:                   rpIdRaw === "" ? null : rpIdRaw,
      };
      fb.classList.remove("is-ok"); fb.textContent = "Guardando…";
      try {
        await api("authConfigSave", patch);
        fb.classList.add("is-ok"); fb.textContent = "Configuración guardada.";
      } catch (e) { fb.textContent = e.message; }
    });
  }

  const cfgBranding = $("config-branding");
  if (cfgBranding && window.InstallImagePicker) {
    const logoFb = $("cfg-logo-feedback");
    const logoFile = $("cfg-client-logo-file");
    const logoSource = $("cfg-client-logo-source");
    const logoPreview = $("cfgClientLogoPreview");
    let logoBinding = null;

    async function uploadClientLogo(binding) {
      const f = binding && binding.fileInput && binding.fileInput.files && binding.fileInput.files[0];
      if (!f) return;
      const fd = new FormData();
      fd.append("file", f);
      fd.append("source", binding.sourceHidden ? binding.sourceHidden.value : "original");
      if (binding.mediaFrame) fd.append("mediaFrame", JSON.stringify(binding.mediaFrame));
      if (logoFb) { logoFb.classList.remove("is-ok"); logoFb.textContent = "Subiendo logo…"; }
      try {
        const data = await apiMultipart("clientLogoUpload", fd);
        applyClientLogoEverywhere(data);
        if (logoFb) { logoFb.classList.add("is-ok"); logoFb.textContent = "Logo actualizado."; }
        setTimeout(() => location.reload(), 450);
      } catch (e) {
        if (logoFb) logoFb.textContent = e.message;
      }
    }

    window.InstallImagePicker.ensureDialog();
    const initLogo = CTX.clientLogo || {};
    logoBinding = window.InstallImagePicker.bind({
      fileInput: logoFile,
      sourceHidden: logoSource,
      previewWrap: logoPreview,
      dialogTitle: "Logo de la empresa",
      existingUrl: initLogo.url || "",
      existingFrame: initLogo.mediaFrame || null,
      existingKind: initLogo.kind || null,
      previewInitial: "L",
      onAccept: (b) => { void uploadClientLogo(b); },
    });

    const btnClearLogo = $("cfg-client-logo-clear");
    if (btnClearLogo) {
      btnClearLogo.addEventListener("click", async () => {
        if (logoFb) { logoFb.classList.remove("is-ok"); logoFb.textContent = "Quitando…"; }
        try {
          const data = await api("clientLogoClear", {});
          applyClientLogoEverywhere(data);
          if (logoFile) logoFile.value = "";
          if (logoBinding) {
            logoBinding.existingUrl = "";
            logoBinding.existingFrame = null;
            logoBinding.existingKind = null;
            logoBinding.mediaFrame = null;
            if (window.InstallImagePicker.refreshBindingPreview) {
              window.InstallImagePicker.refreshBindingPreview(logoBinding);
            }
          }
          if (logoPreview) logoPreview.classList.remove("is-on");
          if (logoFb) { logoFb.classList.add("is-ok"); logoFb.textContent = "Logo eliminado."; }
          setTimeout(() => location.reload(), 450);
        } catch (e) {
          if (logoFb) logoFb.textContent = e.message;
        }
      });
    }
  }

  /* ============================================================
     ORG STRUCTURE (company mode)
     ============================================================ */
  const orgPage = $("org-page");
  if (orgPage) {
    const orgBranch = $("org-branch");
    const orgDepts = $("org-departments");
    const orgFb = $("org-feedback");
    const orgSave = $("org-save");
    const orgAddDept = $("org-add-dept");
    let orgBranches = Array.isArray(CTX.branches) ? CTX.branches.slice() : [];
    let activeBranchId = orgBranch ? orgBranch.value : "";

    function orgNewId(prefix) {
      return prefix + "_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
    }

    function currentBranchData() {
      const bid = activeBranchId || (orgBranch ? orgBranch.value : "");
      return orgBranches.find(b => b && b.id === bid) || null;
    }

    function collectDepartmentsFromDom() {
      const out = [];
      orgDepts.querySelectorAll(".org-dept").forEach(deptEl => {
        const nameIn = deptEl.querySelector(".org-dept-name");
        const name = nameIn ? nameIn.value.trim() : "";
        if (!name) return;
        const deptId = deptEl.dataset.deptId || orgNewId("dept");
        const positions = [];
        deptEl.querySelectorAll(".org-pos").forEach(posEl => {
          const posNameIn = posEl.querySelector(".org-pos-name");
          const posName = posNameIn ? posNameIn.value.trim() : "";
          if (!posName) return;
          positions.push({
            id: posEl.dataset.posId || orgNewId("pos"),
            name: posName,
          });
        });
        out.push({ id: deptId, name, positions });
      });
      return out;
    }

    function renderOrgDepartments() {
      orgDepts.innerHTML = "";
      const br = currentBranchData();
      const depts = br && Array.isArray(br.departments) ? br.departments : [];
      if (!depts.length) {
        orgDepts.appendChild(el("p", { class: "muted org-empty" }, ["Sin departamentos. Añade el primero con el botón inferior."]));
        return;
      }
      depts.forEach(dept => renderOrgDepartment(dept));
    }

    function renderOrgDepartment(dept) {
      const deptId = dept.id || orgNewId("dept");
      const card = el("div", { class: "org-dept card-soft", "data-dept-id": deptId });
      const head = el("div", { class: "org-dept__head" });
      head.appendChild(el("label", null, ["Departamento"]));
      const nameIn = el("input", { type: "text", class: "org-dept-name", value: dept.name || "", placeholder: "Ej. Ventas", maxlength: "120" });
      head.appendChild(nameIn);
      head.appendChild(el("button", { type: "button", class: "btn btn-danger org-dept-remove" }, ["Quitar"]));
      card.appendChild(head);

      const posList = el("div", { class: "org-positions" });
      (dept.positions || []).forEach(pos => {
        posList.appendChild(renderOrgPosition(pos));
      });
      card.appendChild(posList);
      card.appendChild(el("button", { type: "button", class: "btn org-add-pos" }, ["+ Añadir puesto"]));
      orgDepts.appendChild(card);

      head.querySelector(".org-dept-remove").addEventListener("click", () => {
        card.remove();
        if (!orgDepts.querySelector(".org-dept")) {
          orgDepts.appendChild(el("p", { class: "muted org-empty" }, ["Sin departamentos. Añade el primero con el botón inferior."]));
        }
      });
      card.querySelector(".org-add-pos").addEventListener("click", () => {
        const empty = orgDepts.querySelector(".org-empty");
        if (empty) empty.remove();
        posList.appendChild(renderOrgPosition({ id: orgNewId("pos"), name: "" }));
      });
    }

    function renderOrgPosition(pos) {
      const posId = pos.id || orgNewId("pos");
      const row = el("div", { class: "org-pos", "data-pos-id": posId });
      row.appendChild(el("input", { type: "text", class: "org-pos-name", value: pos.name || "", placeholder: "Ej. Gerente comercial", maxlength: "120" }));
      row.appendChild(el("button", { type: "button", class: "btn org-pos-remove" }, ["×"]));
      row.querySelector(".org-pos-remove").addEventListener("click", () => row.remove());
      return row;
    }

    function syncBranchDepartmentsToState() {
      const br = orgBranches.find(b => b && b.id === activeBranchId);
      if (!br) return;
      br.departments = collectDepartmentsFromDom();
    }

    if (orgBranch) {
      orgBranch.addEventListener("change", () => {
        syncBranchDepartmentsToState();
        activeBranchId = orgBranch.value;
        renderOrgDepartments();
      });
    }

    if (orgAddDept) {
      orgAddDept.addEventListener("click", () => {
        const empty = orgDepts.querySelector(".org-empty");
        if (empty) empty.remove();
        renderOrgDepartment({ id: orgNewId("dept"), name: "", positions: [] });
      });
    }

    if (orgSave) {
      orgSave.addEventListener("click", async () => {
        if (!orgBranch || !orgBranch.value) return;
        syncBranchDepartmentsToState();
        if (orgFb) { orgFb.classList.remove("is-ok"); orgFb.textContent = "Guardando…"; }
        try {
          const r = await api("orgStructureSave", {
            branchId: orgBranch.value,
            departments: collectDepartmentsFromDom(),
          });
          if (r.branches) {
            orgBranches = r.branches;
            CTX.branches = r.branches;
          }
          renderOrgDepartments();
          if (orgFb) { orgFb.classList.add("is-ok"); orgFb.textContent = "Estructura guardada."; }
        } catch (e) {
          if (orgFb) orgFb.textContent = e.message || "Error al guardar.";
        }
      });
    }

    renderOrgDepartments();
  }

  /* ============================================================
     LOGS
     ============================================================ */
  const logsPage = $("logs-page");
  if (logsPage) {
    const list = $("log-list");
    const btn = $("log-refresh");
    async function refresh() {
      list.innerHTML = "Cargando…";
      try {
        const r = await api("securityLogTail");
        list.innerHTML = "";
        if (!r.lines || !r.lines.length) {
          list.appendChild(el("div", { class: "empty" }, ["Sin eventos registrados."]));
          return;
        }
        r.lines.slice().reverse().forEach(line => {
          const ctx = line.ctx ? JSON.stringify(line.ctx) : "";
          list.appendChild(el("div", { class: "row" }, [
            el("span", { class: "ts" }, [line.ts || ""]),
            el("span", { class: "ev" }, [line.event || ""]),
            el("span", { class: "ctx" }, [ctx]),
          ]));
        });
      } catch (e) { list.innerHTML = ""; list.appendChild(el("div", { class: "empty" }, ["Error: " + e.message])); }
    }
    btn.addEventListener("click", refresh);
    refresh();
  }

  const adminAnonForm = $("admin-anon-login");
  if (adminAnonForm) {
    const fbAnon = $("admin-anon-login-feedback");
    adminAnonForm.addEventListener("submit", async (e) => {
      e.preventDefault();
      const uEl = $("admin-anon-user");
      const pEl = $("admin-anon-pass");
      const u = uEl ? uEl.value.trim() : "";
      const p = pEl ? pEl.value : "";
      if (fbAnon) { fbAnon.textContent = "Entrando…"; fbAnon.classList.remove("is-ok"); }
      try {
        const base = (CTX.apiUrl || "").split("?")[0];
        const url = base + "?action=authLogin";
        const res = await fetch(url, {
          method: "POST",
          credentials: "same-origin",
          headers: { "Content-Type": "application/json", Accept: "application/json" },
          body: JSON.stringify({ username: u, password: p }),
        });
        let data = {};
        try { data = await res.json(); } catch (_) {}
        if (!data || data.success !== true) {
          if (fbAnon) fbAnon.textContent = (data && data.error) ? data.error : "No se pudo iniciar sesión.";
          return;
        }
        if (window.AntaraAuthTabSync && typeof window.AntaraAuthTabSync.notifyLogin === "function") {
          window.AntaraAuthTabSync.notifyLogin(location.href);
        }
        location.reload();
      } catch (_) {
        if (fbAnon) fbAnon.textContent = "Error de red.";
      }
    });
  }
})();
