/*!
 * BLR Backline – front-end helpers
 */
(function () {
  "use strict";

  var l10n = window.blrBaL10n || {};

  function t(key, fallback) {
    return Object.prototype.hasOwnProperty.call(l10n, key) ? String(l10n[key]) : String(fallback);
  }

  function ready(fn) {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", fn);
    } else {
      fn();
    }
  }

  function runSafe(name, fn) {
    try {
      fn();
    } catch (err) {
      console.error("BLR BA init failed:", name, err);
    }
  }

  function parseJsonAttr(value, fallback) {
    if (!value) return fallback;
    try {
      return JSON.parse(value);
    } catch (e) {
      return fallback;
    }
  }

  function pad2(n) {
    return (n < 10 ? "0" : "") + String(n);
  }

  function localYMD(d) {
    return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
  }

  function escapeHtml(str) {
    return String(str == null ? "" : str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }

  function initFeather() {
    if (window.feather && typeof window.feather.replace === "function") {
      window.feather.replace();
    }
  }






var THEME_STORAGE_KEY = "blrBaThemeMode";

function normalizeThemeMode(mode) {
  return mode === "dark" ? "dark" : "light";
}

function getAppliedThemeMode() {
  var docEl = document.documentElement;
  var body = document.body;

  return normalizeThemeMode(
    (docEl && docEl.getAttribute("data-blr-theme")) ||
    (body && body.getAttribute("data-blr-theme")) ||
    window.blrBaInitialThemeMode ||
    l10n.themeMode ||
    "light"
  );
}

function persistThemeModeLocally(mode) {
  try {
    window.localStorage.setItem(THEME_STORAGE_KEY, mode);
  } catch (err) {}
}

function updateThemeToggleIcon(button, mode) {
  if (!button) return;

  var icon =
    button.querySelector("[data-blr-theme-icon]") ||
    button.querySelector("#blr-theme-toggle-icon") ||
    button.querySelector("i[data-feather]") ||
    button.querySelector("svg[data-feather]");

  if (!icon) {
    return;
  }

  var iconName = mode === "dark" ? "sun" : "moon";

  icon.setAttribute("data-feather", iconName);

  if (icon.tagName && icon.tagName.toLowerCase() === "svg") {
    var replacement = document.createElement("i");

    Array.prototype.slice.call(icon.attributes).forEach(function (attr) {
      if (attr && attr.name !== "data-feather") {
        replacement.setAttribute(attr.name, attr.value);
      }
    });

    replacement.setAttribute("data-feather", iconName);
    icon.parentNode.replaceChild(replacement, icon);
  }
}

function initContentLoader() {
  function markLoaded() {
    document.documentElement.classList.add("blr-page-loaded");
  }

  if (document.readyState === "complete") {
    markLoaded();
    return;
  }

  window.addEventListener("load", markLoaded, { once: true });
}

function updateThemeToggleButtons(mode) {
  var nextMode = mode === "dark" ? "light" : "dark";
  var nextLabel = nextMode === "dark" ? t("darkMode", "Dark mode") : t("lightMode", "Light mode");
  var nextAria = nextMode === "dark"
    ? t("switchToDarkMode", "Switch to dark mode")
    : t("switchToLightMode", "Switch to light mode");

  document.querySelectorAll("[data-blr-theme-toggle]").forEach(function (button) {
    button.setAttribute("data-theme-current", mode);
    button.setAttribute("aria-pressed", mode === "dark" ? "true" : "false");
    button.setAttribute("aria-label", nextAria);
    button.setAttribute("title", nextAria);

    var label = button.querySelector("[data-blr-theme-label]");
    if (label) {
      label.textContent = nextLabel;
    }

    updateThemeToggleIcon(button, mode);
  });

  if (window.feather && typeof window.feather.replace === "function") {
    window.feather.replace();
  }
}

function applyThemeMode(mode) {
  mode = normalizeThemeMode(mode);

  var docEl = document.documentElement;
  if (docEl) {
    docEl.setAttribute("data-blr-theme", mode);
    docEl.setAttribute("data-bs-theme", mode);
  }

  if (document.body) {
    document.body.setAttribute("data-blr-theme", mode);
    document.body.setAttribute("data-bs-theme", mode);
  }

  window.blrBaInitialThemeMode = mode;
  updateThemeToggleButtons(mode);
  return mode;
}

function saveThemeMode(mode) {
  if (!l10n.ajaxUrl || !l10n.themeModeNonce || !window.fetch) {
    return;
  }

  var formData = new window.FormData();
  formData.append("action", "blr_ba_set_theme_mode");
  formData.append("nonce", String(l10n.themeModeNonce));
  formData.append("theme_mode", mode);

  window.fetch(String(l10n.ajaxUrl), {
    method: "POST",
    credentials: "same-origin",
    body: formData
  }).catch(function () {});
}

function initThemeToggle() {
  var buttons = document.querySelectorAll("[data-blr-theme-toggle]");
  if (!buttons.length) {
    return;
  }

  applyThemeMode(getAppliedThemeMode());

  buttons.forEach(function (button) {
    if (button._blrThemeBound) return;
    button._blrThemeBound = true;

    button.addEventListener("click", function (event) {
      event.preventDefault();

      var currentMode = getAppliedThemeMode();
      var nextMode = currentMode === "dark" ? "light" : "dark";

      applyThemeMode(nextMode);
      persistThemeModeLocally(nextMode);
      saveThemeMode(nextMode);
    });
  });
}






  function initDashboardBulkUploadModal() {
  var openBtn = document.getElementById("blr-ba-open-bulk-upload-modal");
  var modalEl = document.getElementById("blr-ba-bulk-upload-modal");

  if (!openBtn || !modalEl) return;
  if (!window.bootstrap || typeof window.bootstrap.Modal !== "function") return;

  var modal = window.bootstrap.Modal.getOrCreateInstance(modalEl);

  openBtn.addEventListener("click", function (e) {
    e.preventDefault();
    modal.show();
  });
}

  function initSelect2() {
    if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.select2) return;

    var $ = window.jQuery;

    $(".blr-ba-select2").each(function () {
      var $el = $(this);
      if ($el.data("select2")) return;

      var ajaxUrl = $el.data("ajax-url") || undefined;
      var config = {
        width: "100%",
        placeholder: $el.data("placeholder") || undefined,
        allowClear: !!$el.data("allow-clear")
      };

      if (ajaxUrl) {
        config.minimumInputLength = 1;
        config.ajax = {
          url: ajaxUrl,
          dataType: "json",
          delay: 250,
          data: function (params) {
            return { q: params && params.term ? params.term : "" };
          },
          processResults: function (response) {
            if (
              !response ||
              !response.success ||
              !response.data ||
              !Array.isArray(response.data.results)
            ) {
              return { results: [] };
            }

            return { results: response.data.results };
          },
          cache: true
        };
      }

      $el.select2(config);
    });
  }

  function initFlatpickr() {
    if (!window.flatpickr) return;

    document.querySelectorAll("input.blr-ba-date").forEach(function (el) {
      if (el._blrFlatpickr) return;
      el._blrFlatpickr = window.flatpickr(el, {
        dateFormat: "Y-m-d",
        allowInput: true
      });
    });

    document.querySelectorAll("input.blr-ba-datetime").forEach(function (el) {
      if (el._blrFlatpickr) return;
      el._blrFlatpickr = window.flatpickr(el, {
        enableTime: true,
        time_24hr: true,
        dateFormat: "Y-m-d H:i",
        allowInput: true
      });
    });

    document.querySelectorAll("input.blr-ba-time").forEach(function (el) {
      if (el._blrFlatpickr) return;
      el._blrFlatpickr = window.flatpickr(el, {
        enableTime: true,
        noCalendar: true,
        time_24hr: true,
        dateFormat: "H:i",
        allowInput: true
      });
    });
  }

  function setPreviewContent(previewEl, kind, url, label) {
    if (!previewEl) return;

    var previewKind = kind === "audio" ? "audio" : "image";
    var tagName = previewEl.tagName ? previewEl.tagName.toUpperCase() : "";

    if (tagName === "IMG" && previewKind === "image") {
      if (!url) {
        var originalSrc = previewEl.getAttribute("data-original");
        if (originalSrc) {
          previewEl.src = originalSrc;
        }
        return;
      }

      previewEl.src = url;
      if (typeof label === "string") {
        previewEl.alt = label;
      }
      return;
    }

    if (!url) {
      var originalHtml = previewEl.getAttribute("data-original-html");
      if (originalHtml !== null) {
        previewEl.innerHTML = originalHtml;
      } else {
        previewEl.innerHTML =
          '<div class="text-muted">' +
          escapeHtml(
            previewKind === "audio"
              ? t("noAudioSet", "No audio file selected.")
              : t("noImageSet", "No image set.")
          ) +
          "</div>";
      }
      return;
    }

    if (previewKind === "audio") {
      previewEl.innerHTML =
        '<div class="blr-ba-audio-preview-card"><div class="blr-ba-audio-preview-title">' +
        escapeHtml(label || "") +
        '</div><audio class="w-100" controls preload="none" src="' +
        escapeHtml(url) +
        '"></audio></div>';
      return;
    }

    previewEl.innerHTML =
      '<img class="img-fluid rounded border blr-ba-preview-image" src="' +
      escapeHtml(url) +
      '" alt="' +
      escapeHtml(label || "") +
      '">';
  }

  function bindFilePreviewInputs(selector, kind) {
    document.querySelectorAll(selector).forEach(function (input) {
      if (input._blrPreviewBound) return;
      input._blrPreviewBound = true;

      var sel = input.getAttribute("data-preview");
      if (!sel) return;

      var previewEl = document.querySelector(sel);
      if (!previewEl) return;

      var tagName = previewEl.tagName ? previewEl.tagName.toUpperCase() : "";
      if (tagName === "IMG" && kind === "image") {
        if (!previewEl.getAttribute("data-original")) {
          previewEl.setAttribute("data-original", previewEl.getAttribute("src") || "");
        }
      } else if (previewEl.getAttribute("data-original-html") === null) {
        previewEl.setAttribute("data-original-html", previewEl.innerHTML);
      }

      input.addEventListener("change", function () {
        if (!input.files || !input.files[0]) {
          setPreviewContent(previewEl, kind, "", "");
          return;
        }

        try {
          var objectUrl = URL.createObjectURL(input.files[0]);
          setPreviewContent(previewEl, kind, objectUrl, input.files[0].name || "");
        } catch (e) {}
      });
    });
  }

  function initImagePreviews() {
    bindFilePreviewInputs('input.blr-ba-image-input[type="file"]', "image");
  }

  function initAudioPreviews() {
    bindFilePreviewInputs('input.blr-ba-audio-input[type="file"]', "audio");
  }

  function initVisualPickers() {
    document.querySelectorAll(".blr-ba-visual-picker").forEach(function (picker) {
      if (picker._blrPickerBound) return;
      picker._blrPickerBound = true;

      var search = picker.querySelector(".blr-ba-visual-picker-search");
      var choices = Array.prototype.slice.call(
        picker.querySelectorAll(".blr-ba-visual-choice")
      );
      var inputs = Array.prototype.slice.call(
        picker.querySelectorAll(".blr-ba-visual-choice-input")
      );
      var empty = picker.querySelector(".blr-ba-visual-picker-empty");
      var previewTarget = picker.getAttribute("data-preview-target") || "";
      var previewKind = picker.getAttribute("data-preview-kind") || "image";
      var modalEl = picker.closest(".modal");

      function applyFilter() {
        var q = search ? String(search.value || "").toLowerCase().trim() : "";
        var visibleCount = 0;

        choices.forEach(function (choice) {
          var hay = String(choice.getAttribute("data-search-text") || "").toLowerCase();
          var match = !q || hay.indexOf(q) !== -1;
          choice.hidden = !match;
          if (match) visibleCount += 1;
        });

        if (empty) empty.hidden = visibleCount > 0;
      }

      if (search) {
        search.addEventListener("input", applyFilter);
      }

      inputs.forEach(function (input) {
        input.addEventListener("change", function () {
          if (!input.checked) return;

          var choice = input.closest(".blr-ba-visual-choice");
          if (choice) {
            setPreviewContent(
              previewTarget ? document.querySelector(previewTarget) : null,
              choice.getAttribute("data-preview-kind") || previewKind,
              choice.getAttribute("data-preview-url") || "",
              choice.getAttribute("data-preview-label") || ""
            );
          }

          if (
            modalEl &&
            window.bootstrap &&
            typeof window.bootstrap.Modal === "function"
          ) {
            window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
            cleanupModalArtifacts();
          }
        });
      });

      if (modalEl && search) {
        modalEl.addEventListener("shown.bs.modal", function () {
          window.setTimeout(function () {
            search.focus();
            search.select();
          }, 50);
        });

        modalEl.addEventListener("hidden.bs.modal", function () {
          search.value = "";
          applyFilter();
          cleanupModalArtifacts();
        });
      }

      applyFilter();
    });
  }

  function cleanupModalArtifacts() {
    window.setTimeout(function () {
      var openModals = document.querySelectorAll(".modal.show");
      if (openModals.length > 0) {
        return;
      }

      document.querySelectorAll(".modal-backdrop").forEach(function (backdrop) {
        backdrop.parentNode && backdrop.parentNode.removeChild(backdrop);
      });

      if (document.body) {
        document.body.classList.remove("modal-open");
        document.body.style.removeProperty("overflow");
        document.body.style.removeProperty("padding-right");
        document.body.style.removeProperty("padding-left");
      }
    }, 25);
  }

  function initModalBackdropCleanup() {
    if (document._blrModalCleanupBound) {
      return;
    }

    document._blrModalCleanupBound = true;

    document.addEventListener("hidden.bs.modal", function () {
      cleanupModalArtifacts();
    });
  }

  function initTrackOrder() {
    var list = document.getElementById("blr-ba-track-order");
    var input = document.getElementById("blr-ba-track-order-input");
    var sel = document.getElementById("blr-ba-track-select");
    if (!list || !input || !sel) return;

    function liIds() {
      return Array.prototype.slice.call(list.querySelectorAll("[data-id]"))
        .map(function (li) {
          return String(li.getAttribute("data-id") || "");
        })
        .filter(Boolean);
    }

    function selectedIds() {
      var out = [];
      Array.prototype.slice.call(sel.options).forEach(function (opt) {
        if (opt && opt.selected) out.push(String(opt.value));
      });
      return out.filter(Boolean);
    }

    function updateInput() {
      input.value = liIds().join(",");
    }

    function removeId(id) {
      var safeId = String(id).replace(/"/g, "");
      var li = list.querySelector('li[data-id="' + safeId + '"]');
      if (li) li.remove();

      Array.prototype.slice.call(sel.options).forEach(function (opt) {
        if (String(opt.value) === String(id)) opt.selected = false;
      });

      if (window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
        window.jQuery(sel).trigger("change");
      } else {
        sel.dispatchEvent(new Event("change", { bubbles: true }));
      }

      updateInput();
    }

    function bindDnD(li) {
      if (li._blrDnDBound) return;
      li._blrDnDBound = true;

      li.addEventListener("dragstart", function (e) {
        li.classList.add("opacity-50");
        if (e.dataTransfer) {
          e.dataTransfer.effectAllowed = "move";
          e.dataTransfer.setData("text/plain", String(li.getAttribute("data-id") || ""));
        }
      });

      li.addEventListener("dragend", function () {
        li.classList.remove("opacity-50");
        updateInput();
      });

      li.addEventListener("dragover", function (e) {
        e.preventDefault();

        var dragging = list.querySelector(".opacity-50");
        if (!dragging || dragging === li) return;

        var rect = li.getBoundingClientRect();
        var after = (e.clientY - rect.top) > (rect.height / 2);

        if (after) {
          if (li.nextSibling !== dragging) list.insertBefore(dragging, li.nextSibling);
        } else {
          if (li !== dragging.nextSibling) list.insertBefore(dragging, li);
        }
      });

      li.addEventListener("drop", function (e) {
        e.preventDefault();
        updateInput();
      });
    }

    function makeItem(id, label, editUrl) {
      var li = document.createElement("li");
      li.className = "list-group-item d-flex align-items-center justify-content-between";
      li.setAttribute("data-id", String(id));
      li.setAttribute("draggable", "true");

      var left = document.createElement("div");
      left.className = "d-flex align-items-center gap-2";

      var handle = document.createElement("span");
      handle.className = "blr-ba-drag-handle text-muted";
      handle.style.cursor = "grab";
      handle.title = t("dragToReorder", "Drag to reorder");
      handle.textContent = "\u2630";

      var txt = document.createElement("span");
      txt.className = "blr-ba-track-label";
      txt.textContent = label || ("Track #" + id);

      left.appendChild(handle);
      left.appendChild(txt);

      var right = document.createElement("div");
      right.className = "d-flex align-items-center gap-2";

      if (editUrl) {
        var a = document.createElement("a");
        a.className = "btn btn-sm btn-outline-secondary";
        a.href = editUrl;
        a.textContent = t("edit", "Edit");
        right.appendChild(a);
      }

      var btn = document.createElement("button");
      btn.type = "button";
      btn.className = "btn btn-sm btn-outline-danger blr-ba-track-remove";
      btn.textContent = t("remove", "Remove");
      btn.addEventListener("click", function () {
        removeId(id);
      });

      right.appendChild(btn);
      li.appendChild(left);
      li.appendChild(right);

      bindDnD(li);
      return li;
    }

    Array.prototype.slice.call(list.querySelectorAll("li[data-id]")).forEach(bindDnD);

    Array.prototype.slice.call(list.querySelectorAll(".blr-ba-track-remove")).forEach(function (btn) {
      if (btn._blrBound) return;
      btn._blrBound = true;

      btn.addEventListener("click", function () {
        var li = btn.closest("li[data-id]");
        if (!li) return;

        var id = li.getAttribute("data-id") || "";
        if (!id) return;

        removeId(id);
      });
    });

    function syncFromSelect() {
      var selected = selectedIds();
      var current = liIds();

      current.forEach(function (id) {
        if (selected.indexOf(id) === -1) {
          var safeId = id.replace(/"/g, "");
          var li = list.querySelector('li[data-id="' + safeId + '"]');
          if (li) li.remove();
        }
      });

      selected.forEach(function (id) {
        if (liIds().indexOf(id) !== -1) return;

        var safeId = id.replace(/"/g, "");
        var opt = sel.querySelector('option[value="' + safeId + '"]');
        var label = opt ? (opt.textContent || opt.innerText || id) : id;
        var editUrl = opt ? (opt.getAttribute("data-edit-url") || "") : "";

        list.appendChild(makeItem(id, label, editUrl));
      });

      updateInput();
    }

    sel.addEventListener("change", syncFromSelect);

    if (window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
      window.jQuery(sel).on("change.blrTrackOrder", syncFromSelect);
    }

    syncFromSelect();
  }

  function initDashboardCharts() {
    if (typeof window.Chart === "undefined") return;

    var charts = document.querySelectorAll("canvas.blr-ba-donut-chart");
    if (!charts.length) return;

    charts.forEach(function (canvas) {
      if (canvas._blrChartBound) return;
      canvas._blrChartBound = true;

      var labels = parseJsonAttr(canvas.getAttribute("data-chart-labels"), []);
      var values = parseJsonAttr(canvas.getAttribute("data-chart-values"), []);
      var colors = parseJsonAttr(canvas.getAttribute("data-chart-colors"), []);

      if (!labels.length || !values.length) return;

      var total = values.reduce(function (sum, val) {
        var n = parseFloat(val);
        return sum + (isNaN(n) ? 0 : n);
      }, 0);

      if (total <= 0) return;

      new window.Chart(canvas, {
        type: "doughnut",
        data: {
          labels: labels,
          datasets: [{
            data: values,
            backgroundColor: colors.length ? colors : ["#3b7ddd", "#6c757d", "#1cbb8c", "#dc3545", "#fd7e14", "#6f42c1"],
            borderWidth: 0
          }]
        },
        options: {
          maintainAspectRatio: false,
          cutout: "68%",
          plugins: {
            legend: { display: false }
          }
        }
      });
    });
  }

  function initHorizontalStackCharts() {
    if (typeof window.Chart === "undefined") return;

    var charts = document.querySelectorAll("canvas.blr-ba-horizontal-stack-chart");
    if (!charts.length) return;

    charts.forEach(function (canvas) {
      if (canvas._blrStackChartBound) return;
      canvas._blrStackChartBound = true;

      var labels = parseJsonAttr(canvas.getAttribute("data-chart-labels"), []);
      var datasets = parseJsonAttr(canvas.getAttribute("data-chart-datasets"), []);
      if (!labels.length || !datasets.length) return;

      new window.Chart(canvas, {
        type: "bar",
        data: {
          labels: labels,
          datasets: datasets
        },
        options: {
          maintainAspectRatio: false,
          indexAxis: "y",
          scales: {
            x: { stacked: true, beginAtZero: true },
            y: { stacked: true }
          },
          plugins: {
            legend: { display: true, position: "bottom" }
          }
        }
      });
    });
  }


  function initBarCharts() {
    if (typeof window.Chart === "undefined") return;

    var charts = document.querySelectorAll("canvas.blr-ba-bar-chart");
    if (!charts.length) return;

    charts.forEach(function (canvas) {
      if (canvas._blrBarChartBound) return;
      canvas._blrBarChartBound = true;

      var labels = parseJsonAttr(canvas.getAttribute("data-chart-labels"), []);
      var values = parseJsonAttr(canvas.getAttribute("data-chart-values"), []);
      var datasetLabel = canvas.getAttribute("data-chart-label") || "";
      var color = canvas.getAttribute("data-chart-color") || "#229ae0";

      if (!labels.length || !values.length) return;

      new window.Chart(canvas, {
        type: "bar",
        data: {
          labels: labels,
          datasets: [{
            label: datasetLabel,
            data: values,
            backgroundColor: color,
            borderWidth: 0
          }]
        },
        options: {
          maintainAspectRatio: false,
          scales: {
            y: {
              beginAtZero: true,
              ticks: {
                precision: 0
              }
            }
          },
          plugins: {
            legend: {
              display: !!datasetLabel
            }
          }
        }
      });
    });
  }



  function initCalendar() {
    var root = document.getElementById("blr-ba-calendar");
    if (!root) return;

    var events = (window.BLR_BA_CAL_EVENTS && Array.isArray(window.BLR_BA_CAL_EVENTS))
      ? window.BLR_BA_CAL_EVENTS
      : [];

    var byDay = Object.create(null);

    events.forEach(function (ev) {
      if (!ev || !ev.start) return;

      var dt = new Date(String(ev.start).replace(" ", "T"));
      if (isNaN(dt.getTime())) return;

      var key = localYMD(dt);
      if (!byDay[key]) byDay[key] = [];
      byDay[key].push(ev);
    });

    function monthLabel(y, m) {
      var names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
      return names[m] + " " + y;
    }

    var today = new Date();
    var state = {
      y: today.getFullYear(),
      m: today.getMonth()
    };

    var elLabel = document.getElementById("blr-ba-cal-label");
    var btnPrev = document.getElementById("blr-ba-cal-prev");
    var btnNext = document.getElementById("blr-ba-cal-next");
    var btnToday = document.getElementById("blr-ba-cal-today");

    function render() {
      if (elLabel) elLabel.textContent = monthLabel(state.y, state.m);

      var first = new Date(state.y, state.m, 1);
      var startDow = first.getDay();
      var daysInMonth = new Date(state.y, state.m + 1, 0).getDate();
      var prevMonthDays = new Date(state.y, state.m, 0).getDate();

      var html = "";
      var dows = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

      html += '<div class="blr-ba-cal-grid">';
      for (var i = 0; i < 7; i++) {
        html += '<div class="blr-ba-cal-dow">' + dows[i] + "</div>";
      }
      html += "</div>";

      html += '<div class="blr-ba-cal-grid">';

      for (var cell = 0; cell < 42; cell++) {
        var dayNum = cell - startDow + 1;
        var cellDate = new Date(state.y, state.m, dayNum);
        var inMonth = (dayNum >= 1 && dayNum <= daysInMonth);

        if (!inMonth) {
          if (dayNum <= 0) {
            dayNum = prevMonthDays + dayNum;
          } else {
            dayNum = dayNum - daysInMonth;
          }
        }

        var key = localYMD(cellDate);
        var dayEvents = byDay[key] || [];

        dayEvents.sort(function (a, b) {
          return String(a.start || "").localeCompare(String(b.start || ""));
        });

        var cls = "blr-ba-cal-cell" + (inMonth ? "" : " blr-ba-cal-muted");
        html += '<div class="' + cls + '">';
        html += '<div class="blr-ba-cal-daynum">' + String(dayNum) + "</div>";

        for (var e = 0; e < Math.min(3, dayEvents.length); e++) {
          var ev = dayEvents[e];
          var title = escapeHtml(String(ev.title || "(Untitled)"));
          var url = ev.url ? String(ev.url) : "#";

          html += '<a class="blr-ba-cal-event" href="' + escapeHtml(url) + '" title="' + title + '">' + title + "</a>";
        }

        if (dayEvents.length > 3) {
          html += '<div class="text-muted blr-ba-calendar-more">+' + String(dayEvents.length - 3) + " more</div>";
        }

        html += "</div>";
      }

      html += "</div>";
      root.innerHTML = html;
    }

    function bindOnce(el, fn) {
      if (!el || el._blrBound) return;
      el._blrBound = true;
      el.addEventListener("click", fn);
    }

    bindOnce(btnPrev, function (e) {
      e.preventDefault();
      state.m -= 1;
      if (state.m < 0) {
        state.m = 11;
        state.y -= 1;
      }
      render();
    });

    bindOnce(btnNext, function (e) {
      e.preventDefault();
      state.m += 1;
      if (state.m > 11) {
        state.m = 0;
        state.y += 1;
      }
      render();
    });

    bindOnce(btnToday, function (e) {
      e.preventDefault();
      var t = new Date();
      state.y = t.getFullYear();
      state.m = t.getMonth();
      render();
    });

    render();
  }

  function initDeleteConfirmationModal() {
    var modalEl = document.getElementById("blr-ba-delete-modal");
    var textEl = document.getElementById("blr-ba-delete-text");
    var confirmBtn = document.getElementById("blr-ba-delete-confirm");

    if (!modalEl || !confirmBtn) return;

    var pendingForm = null;
    var pendingMessage = "";
    var bsModal = null;
    var defaultMessage = "This permanently deletes the record. Some deletes also remove plugin-owned media and linked attachments.";

    if (window.bootstrap && typeof window.bootstrap.Modal === "function") {
      bsModal = window.bootstrap.Modal.getOrCreateInstance(modalEl);
    }

    function getAttr(el, name) {
      return el && typeof el.getAttribute === "function" ? el.getAttribute(name) : "";
    }

    function buildMessage(form, submitter) {
      var explicitText =
        getAttr(submitter, "data-delete-text") ||
        getAttr(form, "data-delete-text") ||
        "";

      var itemLabel =
        getAttr(submitter, "data-item-label") ||
        getAttr(submitter, "data-delete-label") ||
        getAttr(form, "data-item-label") ||
        getAttr(form, "data-delete-label") ||
        "";

      itemLabel = String(itemLabel || "").trim();

      if (explicitText) {
        return explicitText
          .replace(/\{item\}/g, itemLabel)
          .replace(/%s/g, itemLabel);
      }

      if (itemLabel) {
        return 'Delete "' + itemLabel + '"? ' + defaultMessage;
      }

      return defaultMessage;
    }

    function setModalMessage(message) {
      pendingMessage = String(message || defaultMessage);
      if (textEl) {
        textEl.textContent = pendingMessage;
      }
    }

    function openModal() {
      if (bsModal) {
        bsModal.show();
        return;
      }

      var ok = window.confirm(pendingMessage || defaultMessage);
      if (ok && pendingForm) {
        var form = pendingForm;
        pendingForm = null;
        form.submit();
      }
    }

    function closeModal() {
      if (bsModal) {
        bsModal.hide();
      }
    }

    document.addEventListener("submit", function (e) {
      var form = e.target;
      if (!form || !form.matches || !form.matches("form.blr-ba-delete-form")) return;

      if (form._blrDeleteConfirmed) {
        form._blrDeleteConfirmed = false;
        return;
      }

      e.preventDefault();

      var submitter = e.submitter || document.activeElement || null;
      pendingForm = form;
      setModalMessage(buildMessage(form, submitter));
      openModal();
    });

    confirmBtn.addEventListener("click", function () {
      if (!pendingForm) return;

      var form = pendingForm;
      pendingForm = null;
      form._blrDeleteConfirmed = true;
      closeModal();
      form.submit();
    });

    modalEl.addEventListener("hidden.bs.modal", function () {
      pendingForm = null;
      confirmBtn.blur();
    });
  }


  function initAudioPlayers() {
    document.querySelectorAll(".blr-ba-audio-player").forEach(function (root) {
      if (!root || root._blrAudioBound) return;
      root._blrAudioBound = true;

      var dataEl = root.querySelector(".blr-ba-player-data");
      var dataAttr = root.getAttribute("data-player-tracks") || "";
      var tracks = [];

      if (dataAttr) {
        try {
          tracks = JSON.parse(dataAttr);
        } catch (e) {
          tracks = [];
        }
      } else if (dataEl) {
        try {
          tracks = JSON.parse(dataEl.textContent || "[]");
        } catch (e) {
          tracks = [];
        }
      }

      if (!Array.isArray(tracks)) tracks = [];

      var audio = root.querySelector(".blr-ba-player-audio");
      var btnPlay = root.querySelector(".blr-ba-player-toggle");
      var btnPrev = root.querySelector(".blr-ba-player-prev");
      var btnNext = root.querySelector(".blr-ba-player-next");
      var progress = root.querySelector(".blr-ba-player-progress");
      var nowTitle = root.querySelector(".blr-ba-player-now-title");
      var nowMeta = root.querySelector(".blr-ba-player-now-meta");
      var status = root.querySelector(".blr-ba-player-status");
      var timeCurrent = root.querySelector(".blr-ba-player-time-current");
      var timeDuration = root.querySelector(".blr-ba-player-time-duration");
      var trackButtons = Array.prototype.slice.call(root.querySelectorAll(".blr-ba-player-track"));

      if (!audio || !btnPlay || !btnPrev || !btnNext || !progress) return;

      function formatTime(value) {
        var seconds = Number(value || 0);
        if (!isFinite(seconds) || seconds < 0) return "0:00";

        var hours = Math.floor(seconds / 3600);
        var minutes = Math.floor((seconds % 3600) / 60);
        var secs = Math.floor(seconds % 60);

        if (hours > 0) {
          return hours + ":" + pad2(minutes) + ":" + pad2(secs);
        }

        return minutes + ":" + pad2(secs);
      }

      function playableIndexes() {
        var out = [];
        tracks.forEach(function (track, index) {
          if (track && track.audio_url) out.push(index);
        });
        return out;
      }

      function findFirstPlayable() {
        for (var i = 0; i < tracks.length; i += 1) {
          if (tracks[i] && tracks[i].audio_url) return i;
        }
        return tracks.length ? 0 : -1;
      }

      function findAdjacentPlayable(startIndex, step) {
        if (!tracks.length) return -1;
        var total = tracks.length;
        var current = Number(startIndex);
        for (var i = 0; i < total; i += 1) {
          current += step;
          if (current < 0) current = total - 1;
          if (current >= total) current = 0;
          if (tracks[current] && tracks[current].audio_url) return current;
        }
        return -1;
      }

      function updatePlayButton() {
        btnPlay.textContent = audio.paused ? t("play", "Play") : t("pause", "Pause");
        btnPlay.setAttribute("aria-label", audio.paused ? t("playCurrentTrack", "Play current track") : t("pauseCurrentTrack", "Pause current track"));
        root.classList.toggle("is-playing", !audio.paused);
      }

      function updateProgress() {
        var duration = Number(audio.duration || 0);
        var current = Number(audio.currentTime || 0);
        var ratio = duration > 0 ? Math.min(1000, Math.max(0, Math.round((current / duration) * 1000))) : 0;
        progress.value = String(ratio);
        if (timeCurrent) timeCurrent.textContent = formatTime(current);
        if (timeDuration) timeDuration.textContent = formatTime(duration || 0);
      }

      function updatePlaylist() {
        trackButtons.forEach(function (button) {
          var index = Number(button.getAttribute("data-index") || -1);
          var isActive = index === state.index;
          var track = tracks[index] || {};
          var unavailable = !track.audio_url;
          button.classList.toggle("is-active", isActive);
          button.classList.toggle("is-unavailable", unavailable);
          button.setAttribute("aria-current", isActive ? "true" : "false");
        });
      }

      function setStatus(message) {
        if (status) status.textContent = String(message || "");
      }

      function describeTrack(track, index) {
        var bits = [];
        if (track.artist) bits.push(String(track.artist));
        if (track.duration) bits.push(String(track.duration));
        if (tracks.length > 1) bits.push("Track " + String(index + 1) + " of " + String(tracks.length));
        return bits.join(" • ") || "—";
      }

      var state = {
        index: findFirstPlayable()
      };

      function loadTrack(index, autoplay) {
        if (!tracks.length || index < 0 || index >= tracks.length) {
          btnPlay.disabled = true;
          btnPrev.disabled = true;
          btnNext.disabled = true;
          setStatus(t("noTracksAvailable", "No tracks available."));
          if (nowTitle) nowTitle.textContent = "—";
          if (nowMeta) nowMeta.textContent = t("noTracksAvailable", "No tracks available.");
          return;
        }

        state.index = index;
        var track = tracks[index] || {};
        var hasAudio = !!track.audio_url;
        var nextSrc = hasAudio ? String(track.audio_url) : "";

        audio.pause();
        if (nextSrc) {
          if (audio.getAttribute("src") !== nextSrc) {
            audio.setAttribute("src", nextSrc);
            audio.load();
          } else {
            audio.currentTime = 0;
          }
        } else {
          audio.removeAttribute("src");
          audio.load();
        }

        if (nowTitle) {
          nowTitle.textContent = String(track.display_title || track.title || t("untitledTrack", "Untitled Track"));
        }
        if (nowMeta) {
          nowMeta.textContent = hasAudio ? describeTrack(track, index) : t("noAudioAttachedForTrack", "No audio file attached for this track.");
        }

        btnPlay.disabled = !hasAudio;
        var playable = playableIndexes();
        btnPrev.disabled = playable.length <= 1;
        btnNext.disabled = playable.length <= 1;

        if (!hasAudio) {
          progress.value = "0";
          if (timeCurrent) timeCurrent.textContent = "0:00";
          if (timeDuration) timeDuration.textContent = track.duration ? String(track.duration) : "0:00";
          setStatus(t("selectedTrackNoAudio", "Selected track has no audio file attached."));
          updatePlayButton();
          updatePlaylist();
          return;
        }

        setStatus(t("readyToPlay", "Ready to play."));
        updatePlaylist();
        updatePlayButton();
        updateProgress();

        if (autoplay) {
          var playPromise = audio.play();
          if (playPromise && typeof playPromise.catch === "function") {
            playPromise.catch(function () {
              updatePlayButton();
            });
          }
        }
      }

      btnPlay.addEventListener("click", function () {
        if (btnPlay.disabled) return;
        if (audio.paused) {
          var playPromise = audio.play();
          if (playPromise && typeof playPromise.catch === "function") {
            playPromise.catch(function () {
              updatePlayButton();
            });
          }
        } else {
          audio.pause();
        }
      });

      btnPrev.addEventListener("click", function () {
        var target = findAdjacentPlayable(state.index, -1);
        if (target >= 0) loadTrack(target, true);
      });

      btnNext.addEventListener("click", function () {
        var target = findAdjacentPlayable(state.index, 1);
        if (target >= 0) loadTrack(target, true);
      });

      progress.addEventListener("input", function () {
        var duration = Number(audio.duration || 0);
        if (!isFinite(duration) || duration <= 0) return;
        var ratio = Number(progress.value || 0) / 1000;
        audio.currentTime = Math.max(0, Math.min(duration, duration * ratio));
        updateProgress();
      });

      trackButtons.forEach(function (button) {
        button.addEventListener("click", function () {
          var index = Number(button.getAttribute("data-index") || -1);
          if (index < 0) return;
          var track = tracks[index] || {};
          loadTrack(index, !!track.audio_url);
        });
      });

      audio.addEventListener("loadedmetadata", function () {
        updateProgress();
      });

      audio.addEventListener("timeupdate", function () {
        updateProgress();
      });

      audio.addEventListener("play", function () {
        setStatus(t("playing", "Playing."));
        updatePlayButton();
      });

      audio.addEventListener("pause", function () {
        if (audio.ended) return;
        setStatus(btnPlay.disabled ? t("selectedTrackNoAudio", "Selected track has no audio file attached.") : t("paused", "Paused."));
        updatePlayButton();
      });

      audio.addEventListener("ended", function () {
        var target = findAdjacentPlayable(state.index, 1);
        if (target >= 0 && target !== state.index) {
          loadTrack(target, true);
          return;
        }
        updatePlayButton();
        setStatus(t("playbackFinished", "Playback finished."));
      });

      loadTrack(findFirstPlayable(), false);
    });
  }

    function initSidebarToggle() {
    var sidebar = document.querySelector(".js-sidebar");
    var toggles = document.querySelectorAll(".js-sidebar-toggle");

    if (!sidebar || !toggles.length) {
      return;
    }

    toggles.forEach(function (toggle) {
      if (toggle._blrSidebarBound) return;
      toggle._blrSidebarBound = true;

      toggle.addEventListener("click", function (event) {
        event.preventDefault();

        sidebar.classList.toggle("collapsed");

        var onTransitionEnd = function () {
          window.dispatchEvent(new Event("resize"));
          sidebar.removeEventListener("transitionend", onTransitionEnd);
        };

        sidebar.addEventListener("transitionend", onTransitionEnd);
      });
    });
  }

  ready(function () {
    runSafe("initThemeToggle", initThemeToggle);
    runSafe("contentLoader", initContentLoader);
    runSafe("initFeather", initFeather);
    runSafe("initSidebarToggle", initSidebarToggle);
    runSafe("initSelect2", initSelect2);
    runSafe("initFlatpickr", initFlatpickr);
    runSafe("initImagePreviews", initImagePreviews);
    runSafe("initAudioPreviews", initAudioPreviews);
    runSafe("initVisualPickers", initVisualPickers);
    runSafe("initModalBackdropCleanup", initModalBackdropCleanup);
    runSafe("initTrackOrder", initTrackOrder);
    runSafe("initDashboardCharts", initDashboardCharts);
    runSafe("initHorizontalStackCharts", initHorizontalStackCharts);
    runSafe("initBarCharts", initBarCharts);
    runSafe("initCalendar", initCalendar);
    runSafe("initDeleteConfirmationModal", initDeleteConfirmationModal);
    runSafe("initAudioPlayers", initAudioPlayers);
    runSafe("initDashboardBulkUploadModal", initDashboardBulkUploadModal);
  });




document.addEventListener('DOMContentLoaded', function () {
  const toggleGroups = document.querySelectorAll('[aria-label$="view mode"]');

  if (!toggleGroups.length) {
    return;
  }

  const url = new URL(window.location.href);
  const currentView = url.searchParams.get('view');
  const path = window.location.pathname.replace(/\/+$/, '');
  const storageKey = 'blr_backline_view:' + path;

  toggleGroups.forEach(function (group) {
    const links = group.querySelectorAll('a[href]');
    if (!links.length) {
      return;
    }

    links.forEach(function (link) {
      try {
        const linkUrl = new URL(link.href, window.location.origin);
        const linkView = linkUrl.searchParams.get('view');

        if (linkView === 'list' || linkView === 'grid') {
          link.addEventListener('click', function () {
            window.localStorage.setItem(storageKey, linkView);
          });
        }
      } catch (err) {
        // Ignore malformed URLs.
      }
    });
  });

  if (currentView === 'list' || currentView === 'grid') {
    window.localStorage.setItem(storageKey, currentView);
    return;
  }

  const savedView = window.localStorage.getItem(storageKey);

  if (savedView !== 'list' && savedView !== 'grid') {
    return;
  }

  url.searchParams.set('view', savedView);
  window.location.replace(url.toString());
});




})();