var CourseNav = (function (COURSE_CONFIG) {
  "use strict";

  /* ==========================================================================
   * === 1. Inyección y ejecución de scripts de contenido dinámico ============
   * ========================================================================== */
  const loadedScriptSrcs = new Set();

  function executeInjectedScripts(container) {
    // 1) Scripts externos (src)
    container.querySelectorAll("script[src]").forEach((old) => {
      const src = old.src;
      if (!loadedScriptSrcs.has(src)) {
        loadedScriptSrcs.add(src);
        const s = document.createElement("script");
        s.src = src;
        s.async = false;
        document.body.appendChild(s);
        s.addEventListener("load", () => s.remove());
      }
      old.remove();
    });

    // 2) Scripts inline
    container.querySelectorAll("script:not([src])").forEach((old) => {
      try {
        (0, eval)(old.textContent);
      } catch (e) {
        console.error("Error al ejecutar script inline:", e);
      }
      old.remove();
    });
  }

  /* ==========================================================================
   * === 2. Extensión de Howl para eventos de tiempo ===========================
   * ========================================================================== */
  class ExtendedHowl extends Howl {
    constructor(options) {
      super(options);
      this._timeupdateListeners = [];
      this._interval = null;
      this._startTimeUpdate();
    }

    _startTimeUpdate() {
      this._interval = setInterval(() => {
        if (this.playing()) this._emitTimeUpdate(this.seek());
      }, 250);
    }

    _emitTimeUpdate(currentTime) {
      this._timeupdateListeners.forEach((cb) => cb(currentTime));
    }

    onTimeUpdate(cb) {
      this._timeupdateListeners.push(cb);
    }

    offTimeUpdate(cb) {
      this._timeupdateListeners = this._timeupdateListeners.filter((l) => l !== cb);
    }

    play(id) {
      const result = super.play(id);
      if (!this._interval) this._startTimeUpdate();
      return result;
    }

    pause(id) {
      const result = super.pause(id);
      clearInterval(this._interval);
      this._interval = null;
      return result;
    }

    stop(id) {
      super.stop(id);
      clearInterval(this._interval);
      this._interval = null;
    }
  }

  function createSound(audioUrl) {
    return audioUrl ? new ExtendedHowl({ src: [audioUrl] }) : null;
  }

  /* ==========================================================================
   * === 3. Controlador de audio ===============================================
   * ========================================================================== */
  class AudioController {
    constructor() {
      this.audioElement = null;
      this.audioControlButton = document.getElementById("coursenav-audio-control");
      this.audioIcon = document.getElementById("coursenav-audio-icon");
      this.progressCircle = document.getElementById("coursenav-progress-circle");
      this.isMuted = false;

      if (this.progressCircle) this.progressCircle.style.display = "none";
      if (this.audioControlButton) {
        this.audioControlButton.addEventListener("click", this.toggleAudio.bind(this));
      }
    }

    stopAllSoundsAndPlay(howl) {
      Howler._howls?.forEach((sound) => sound.stop());
      this.setAudio(howl);
      this.playAudio();
    }

    loadAudio(url) {
      if (this.audioElement) this.audioElement.stop();
      if (!url) return;
      this.audioElement = createSound(url);
      this._bindAudioEvents();
    }

    playAudio() {
      this.audioElement?.play();
    }

    pauseAudio() {
      this.audioElement?.pause();
    }

    stopAudio() {
      this.audioElement?.stop();
    }

    toggleAudio() {
      if (!this.audioElement) return;
      this.audioElement.playing() ? this.pauseAudio() : this.playAudio();
    }

    toggleMute() {
      this.isMuted = !this.isMuted;
      Howler.mute(this.isMuted);
      this.updateIcon();
      document.querySelectorAll("video").forEach((v) => (v.muted = this.isMuted));
    }

    onPlay() {
      if (this.progressCircle) this.progressCircle.style.display = "block";
      const t = this.audioElement.seek();
      this.updateProgressCircle(t);
      this.updateIcon();
    }

    onEnd() {
      if (this.progressCircle) this.progressCircle.style.display = "none";
      this.updateIcon();
    }

    updateIcon() {
      if (!this.audioIcon) return;
      const playing = this.audioElement?.playing();
      this.audioIcon.className = playing ? "fa-duotone fa-solid fa-pause" : "fa-duotone fa-solid fa-play";
    }

    updateProgressCircle(currentTime) {
      if (!this.progressCircle || !this.audioElement) return;
      const r = parseFloat(this.progressCircle.getAttribute("r"));
      const circ = 2 * Math.PI * r;
      const offset = circ - (currentTime / this.audioElement.duration()) * circ;
      this.progressCircle.setAttribute("stroke-dashoffset", offset);
    }

    setAudioUrl(url) {
      this.loadAudio(url);
    }

    setAudio(howl) {
      if (!(howl instanceof ExtendedHowl)) return;
      this.audioElement?.stop();
      this.audioElement = howl;
      this._bindAudioEvents();
    }

    _bindAudioEvents() {
      this.audioElement.on("play", this.onPlay.bind(this));
      this.audioElement.on("pause", this.updateIcon.bind(this));
      this.audioElement.on("stop", this.updateIcon.bind(this));
      this.audioElement.on("end", this.onEnd.bind(this));
      this.audioElement.onTimeUpdate(this.updateProgressCircle.bind(this));
    }
  }

  const audioController = new AudioController();

  /* ==========================================================================
   * === 4. Configuración global y constantes =================================
   * ========================================================================== */
  const COURSE_CONFIG_URL = COURSE_CONFIG.COURSE_CONFIG_URL || "config.json";
  const DEBUG = COURSE_CONFIG.DEBUG || false;
  const KEY_APP = COURSE_CONFIG.KEY || Infinity;

  const MAIN_CONTENT = document.getElementById("coursenav-main-content");
  const WRAP_COURSE_CONTENT = document.getElementById("wrap-course-content");
  WRAP_COURSE_CONTENT.setAttribute("data-original-class", WRAP_COURSE_CONTENT.className);

  const LOADER_ELEMENT = document.getElementById("coursenav-loader-course");
  const CLICK_SOUND = new Audio("audio/click.mp3");
  const PREV_BTN = document.getElementById("coursenav-prev-btn");
  const NEXT_BTN = document.getElementById("coursenav-next-btn");
  const COURSE_PROGRESS_BAR = document.getElementById("coursenav-progress-bar");
  const COURSE_MENU = document.getElementById("coursenav-main-menu");

  pipwerks.SCORM.version = "1.2";
  pipwerks.debug.isActive = DEBUG;
  pipwerks.SCORM.handleExitMode = false;

  let sessionStartTime;
  let scormAPIUnloaded = false;
  let courseStructure = null;
  let courseData = { contentArray: [], maximumAdvance: 0 };
  let currentIndex = 0;

  /* ==========================================================================
   * === 5. SCORM: Inicialización y helpers de alto nivel =====================
   * ========================================================================== */
  function initializeScorm(callback) {
    const scorm = pipwerks.SCORM;
    const connected = scorm.init();
    if (connected) {
      const status = scorm.get("cmi.core.lesson_status");
      if (status === "not attempted") {
        scorm.set("cmi.core.lesson_status", "incomplete");
        scorm.save();
      }
      sessionStartTime = Date.now();
    } else {
      // SCORM API no encontrada. Usando sessionStorage para web.
    }
    callback(connected);
  }

  function buildContentArray(mods, courseTitle = "", moduleTitle = "", parentTitle = null) {
    mods.forEach((m) => {
      const isModuleLevel = !parentTitle && !moduleTitle;
      const currentModuleTitle = isModuleLevel ? m.title : moduleTitle;

      if (m.content) {
        courseData.contentArray.push({
          title: m.title,
          content: m.content,
          audio: m.audio,
          visited: false,
          courseTitle,
          moduleTitle: currentModuleTitle,
          parentTitle,
        });
      }
      if (m.topics) {
        buildContentArray(m.topics, courseTitle, currentModuleTitle, m.title);
      }
    });
  }

  function verifyContentArray(saved, curr) {
    if (!saved || !curr || saved.length !== curr.length) return false;
    return saved.every((s, i) => s.title === curr[i].title && s.content === curr[i].content);
  }

  function loadConfig() {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", `${COURSE_CONFIG_URL}?_=${Date.now()}`, true);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    xhr.withCredentials = true;
    xhr.responseType = "json";

    xhr.onload = function () {
      if (xhr.status >= 200 && xhr.status < 300) {
        courseStructure = xhr.response;
        courseData = { contentArray: [], maximumAdvance: 0 };
        if (courseStructure.title) document.title = courseStructure.title;

        buildContentArray(courseStructure.modules, courseStructure.title || "");

        const saved = getProgress();
        if (saved.contentArray && verifyContentArray(saved.contentArray, courseData.contentArray)) {
          courseData = saved;
        }

        if (courseData.maximumAdvance > 0) showStartOptions();
        else initializeCourse();

        setProgress(courseData);
      } else {
        MAIN_CONTENT?.remove();
        console.error("Error cargando config:", xhr.status, xhr.statusText);
      }
    };

    xhr.onerror = function () {
      MAIN_CONTENT?.remove();
      console.error("Error de red al cargar config");
    };

    xhr.send();
  }

  function initializeCourse() {
    if (COURSE_MENU) {
      buildMenu();
      hideDuplicateLinks();
    }
    if (courseData.contentArray.length > 0) loadContent();
    else MAIN_CONTENT.innerHTML = `<div class='alert alert-warning'>No hay contenido.</div>`;
    setupNavigation();
  }

  function showStartOptions() {
    if (typeof Swal === "undefined") {
      currentIndex = confirm("¿Retomar tu progreso?") ? courseData.maximumAdvance : 0;
      initializeCourse();
    } else {
      Swal.fire({
        title: "¿Dónde quieres empezar?",
        text: "Retomar o comenzar de nuevo",
        icon: "question",
        showCancelButton: true,
        confirmButtonText: "Retomar",
        cancelButtonText: "Comenzar",
        target: MAIN_CONTENT,
        customClass: {
          confirmButton: "btn btn-primary",
          cancelButton: "btn btn-secondary",
        },
      }).then((res) => {
        currentIndex = res.isConfirmed ? courseData.maximumAdvance : 0;
        initializeCourse();
      });
    }
  }

  /* ==========================================================================
   * === 6. Construcción y manejo del menú ====================================
   * ========================================================================== */
  function buildMenu() {
    COURSE_MENU.innerHTML = "";
    (courseStructure.modules || []).forEach((module) => {
      const ul = document.createElement("ul");
      ul.classList.add("course-menu");
      ul.appendChild(createMenuItem(module));
      COURSE_MENU.appendChild(ul);
    });
    hideDuplicateLinks();
  }

  function createMenuItem(item) {
    const li = document.createElement("li");
    li.classList.add("menu-item");

    const wdiv = document.createElement("div");
    wdiv.classList.add("witem");
    li.appendChild(wdiv);

    const link = document.createElement("a");
    link.classList.add("coursenav-link");
    link.textContent = item.title;

    const idx = courseData.contentArray.findIndex((c) => c.content === item.content && c.title === item.title);
    link.dataset.coursenavindex = idx;
    link.dataset.coursenavvisited = true;
    wdiv.appendChild(link);

    link.addEventListener("click", () => {
      CLICK_SOUND.play();
      const index = parseInt(link.dataset.coursenavindex, 10);
      if (index >= 0) {
        // Permitir navegación libre desde el menú
        currentIndex = index;
        closeSidebar();
        loadContent();
      } else {
        const toggle = wdiv.querySelector(".toggle-icon");
        toggle && toggle.click();
      }
    });

    if (item.topics?.length) {
      const toggle = document.createElement("span");
      toggle.classList.add("toggle-icon");
      toggle.innerHTML = '<i class="fa-duotone fa-solid fa-square-chevron-down"></i>';
      wdiv.appendChild(toggle);

      const subUl = document.createElement("ul");
      subUl.classList.add("sub-ul", "open");
      item.topics.forEach((sub) => subUl.appendChild(createMenuItem(sub)));
      li.appendChild(subUl);

      toggle.addEventListener("click", () => {
        CLICK_SOUND.play();
        const isOpen = subUl.classList.toggle("open");
        const icon = toggle.querySelector("i");
        icon.classList.toggle("fa-square-chevron-down", isOpen);
        icon.classList.toggle("fa-square-chevron-right", !isOpen);
      });
    }

    return li;
  }

  function hideDuplicateLinks() {
    function processUl(ul) {
      const seen = new Set();
      Array.from(ul.children)
        .filter((el) => el.tagName === "LI")
        .forEach((li) => {
          const link = li.querySelector(":scope > .witem > .coursenav-link");
          if (link) {
            const text = link.textContent.trim();
            if (seen.has(text)) li.style.display = "none";
            else seen.add(text);
          }
          li.querySelectorAll(":scope > ul").forEach(processUl);
        });
    }
    document.querySelectorAll("#coursenav-main-menu > ul.course-menu").forEach(processUl);
  }

  function closeSidebar() {
    const offEl = document.getElementById("coursenav-offcanvas");
    const bsOff = bootstrap.Offcanvas.getInstance(offEl) || new bootstrap.Offcanvas(offEl);
    bsOff.hide();
  }

  function showLockedContentWarning() {
    if (typeof Swal === "undefined") {
      alert("Debes completar el contenido actual antes de avanzar.");
    } else {
      Swal.fire({
        text: "Debes completar el contenido actual antes de avanzar.",
        icon: "warning",
        target: MAIN_CONTENT,
        customClass: {
          confirmButton: "btn btn-primary",
          cancelButton: "btn btn-warning",
        },
      });
    }
  }

  /* ==========================================================================
   * === 7. Carga de contenido y actualización de la interfaz ================
   * ========================================================================== */
  function loadContent() {
    MAIN_CONTENT.innerHTML = "";
    WRAP_COURSE_CONTENT.className = WRAP_COURSE_CONTENT.getAttribute("data-original-class");
    window.scrollTo(0, 0);
    LOADER_ELEMENT.style.display = "block";

    const item = courseData.contentArray[currentIndex];
    if (!item?.content) return console.warn("Ítem inválido:", item);

    audioController.stopAudio();
    Howler._howls?.forEach((h) => h.stop());

    if (Swal.isVisible()) {
      Swal.close();
    }
    // Al cerrar, limpiamos clases y atributos de html/body
    document.documentElement.classList.remove("swal2-shown", "swal2-height-auto");
    document.body.classList.remove("swal2-shown", "swal2-height-auto");
    document.documentElement.removeAttribute("aria-hidden");
    document.body.removeAttribute("aria-hidden");
    // Y volvemos a quitar aria-hidden de los scripts
    document.querySelectorAll("script[aria-hidden]").forEach((el) => el.removeAttribute("aria-hidden"));
    fetch(item.content, { cache: "no-store" })
      .then((r) => {
        if (!r.ok) throw new Error(r.statusText);
        return r.text();
      })
      .then((html) => {
        //MAIN_CONTENT.innerHTML = html;
        //executeInjectedScripts(MAIN_CONTENT);
        $(MAIN_CONTENT).html(html);
      })
      .catch((err) => {
        console.error("Error cargando contenido:", err);
        MAIN_CONTENT.innerHTML = `<pre>${err.message}</pre>`;
      })
      .finally(() => {
        courseData.maximumAdvance = Math.max(courseData.maximumAdvance, currentIndex);
        LOADER_ELEMENT.style.display = "none";
        updateUITemplate();
        triggerSlideChange(currentIndex, courseData.contentArray);
      });
  }

  function updateUITemplate() {
    setProgress(courseData);
    updateNavigationButtons();
    updateProgressBar();
    updateCourseNavLinks();
  }

  function updateCourseNavLinks() {
    document.querySelectorAll(".coursenav-link").forEach((link) => {
      const idx = parseInt(link.dataset.coursenavindex, 10);
      const item = courseData.contentArray[idx];
      if (item) {
        link.dataset.coursenavvisited = true;
        link.classList.add("visited");
      }
    });
  }

  function setupNavigation() {
    PREV_BTN?.addEventListener("click", () => {
      CLICK_SOUND.play();
      navigate(-1);
    });
    NEXT_BTN?.addEventListener("click", () => {
      CLICK_SOUND.play();
      navigate(1);
    });
    updateNavigationButtons();
  }

  function navigate(dir) {
    triggerBeforeSlideChange(currentIndex, courseData.contentArray);
    const newIndex = currentIndex + dir;
    if (newIndex < 0 || newIndex >= courseData.contentArray.length) return;

    if (dir === -1 || courseData.contentArray[currentIndex].visited || DEBUG) {
      currentIndex = newIndex;
      loadContent();
    } else {
      showLockedContentWarning();
    }
    updateNavigationButtons();
  }

  function updateNavigationButtons() {
    if (!courseData.contentArray.length || !courseData.contentArray[currentIndex]) {
      PREV_BTN.disabled = NEXT_BTN.disabled = true;
      return;
    }
    PREV_BTN.disabled = currentIndex === 0;
    NEXT_BTN.disabled = currentIndex >= courseData.contentArray.length - 1 || (!courseData.contentArray[currentIndex].visited && !DEBUG);
  }

  function updateProgressBar() {
    const visited = courseData.contentArray.filter((i) => i.visited).length;
    const pct = (visited / courseData.contentArray.length) * 100;
    COURSE_PROGRESS_BAR.style.width = pct + "%";
    COURSE_PROGRESS_BAR.setAttribute("aria-valuenow", pct.toFixed(2));
    COURSE_PROGRESS_BAR.textContent = `${pct.toFixed(0)}%`;
  }

  /* ==========================================================================
   * === 8. Eventos custom =====================================================
   * ========================================================================== */
  function triggerSlideChange(currentIndex, contentArray) {
    document.body.dispatchEvent(
      new CustomEvent("slideChange", {
        detail: { message: "Slide changed!", slideIndex: currentIndex, contentArray },
      })
    );
  }

  function triggerSlideCompleted(index, total, slideObj) {
    document.body.dispatchEvent(
      new CustomEvent("slideCompleted", {
        detail: { message: "Slide completed!", slideIndex: index, totalSlides: total, slide: slideObj },
      })
    );
  }

  function triggerBeforeSlideChange(currentIndex, contentArray) {
    document.body.dispatchEvent(
      new CustomEvent("beforeSlideChange", {
        detail: { message: "Before slide change!", currentIndex, contentArray },
      })
    );
  }

  /* ==========================================================================
   * === 9. API públicas y utilitarios =========================================
   * ========================================================================== */
  function setSlideVisited(state = true) {
    courseData.contentArray[currentIndex].visited = state;
    updateUITemplate();
    triggerSlideCompleted(currentIndex, courseData.contentArray.length, courseData.contentArray[currentIndex]);
  }

  function markSlidesAsVisited(indices) {
    indices
      .sort((a, b) => b - a)
      .forEach((i) => {
        currentIndex = i;
        setSlideVisited(true);
      });
  }

  function resetCourse() {
    courseData.contentArray.forEach((i) => (i.visited = false));
    courseData.maximumAdvance = 0;
    currentIndex = 0;
    updateUITemplate();
    loadContent();
  }

  function soundClick() {
    CLICK_SOUND.play();
  }

  function isDebug() {
    return DEBUG;
  }

  function gotoSlide(index) {
    const i = Math.floor(index);
    if (!isNaN(i) && i >= 0 && i < courseData.contentArray.length) {
      currentIndex = i;
      loadContent();
    } else {
      console.error("gotoSlide: índice inválido", index);
    }
  }

  /* ==========================================================================
   * === 10. SCORM: helpers de bajo nivel y progreso ============================
   * ========================================================================== */
  function getLessonLocation() {
    if (pipwerks.SCORM.connection.isActive) {
      const val = pipwerks.SCORM.get("cmi.core.lesson_location");
      return val ?? "";
    }
    return sessionStorage.getItem("cmi.core.lesson_location") ?? "";
  }

  function setLessonLocation(loc) {
    if (pipwerks.SCORM.connection.isActive) {
      const ok = pipwerks.SCORM.set("cmi.core.lesson_location", loc);
      if (ok) pipwerks.SCORM.save();
      return ok;
    }
    sessionStorage.setItem("cmi.core.lesson_location", loc);
    return true;
  }

  function getLessonStatus() {
    if (pipwerks.SCORM.connection.isActive) {
      return pipwerks.SCORM.get("cmi.core.lesson_status") ?? "";
    }
    return sessionStorage.getItem("cmi.core.lesson_status") ?? "";
  }

  function setLessonStatus(st) {
    if (pipwerks.SCORM.connection.isActive) {
      const ok = pipwerks.SCORM.set("cmi.core.lesson_status", st);
      if (ok) pipwerks.SCORM.save();
      return ok;
    }
    sessionStorage.setItem("cmi.core.lesson_status", st);
    return true;
  }

  function getScore() {
    let val = pipwerks.SCORM.connection.isActive ? pipwerks.SCORM.get("cmi.core.score.raw") : sessionStorage.getItem("cmi.core.score.raw");
    return val != null && val !== "" ? Number(val) : null;
  }

  function setScore(sc) {
    if (pipwerks.SCORM.connection.isActive) {
      const ok = pipwerks.SCORM.set("cmi.core.score.raw", sc);
      if (ok) pipwerks.SCORM.save();
      return ok;
    }
    sessionStorage.setItem("cmi.core.score.raw", sc);
    return true;
  }

  function getSuspendData() {
    let val = pipwerks.SCORM.connection.isActive ? pipwerks.SCORM.get("cmi.suspend_data") : sessionStorage.getItem("cmi.suspend_data");
    if (val) {
      try {
        return JSON.parse(val);
      } catch {
        return val;
      }
    }
    return "";
  }

  function setSuspendData(data) {
    const json = JSON.stringify(data);
    if (pipwerks.SCORM.connection.isActive) {
      const ok = pipwerks.SCORM.set("cmi.suspend_data", json);
      if (ok) pipwerks.SCORM.save();
      return ok;
    }
    sessionStorage.setItem("cmi.suspend_data", json);
    return true;
  }

  function getProgressPercent(byModule = false) {
    if (!byModule) {
      const visited = courseData.contentArray.filter((i) => i.visited).length;
      return parseFloat(((visited / courseData.contentArray.length) * 100).toFixed(2));
    }
    const currentSlide = courseData.contentArray[currentIndex];
    const moduleSlides = courseData.contentArray.filter((s) => s.moduleTitle === currentSlide.moduleTitle);
    const visited = moduleSlides.filter((i) => i.visited).length;
    return moduleSlides.length ? parseFloat(((visited / moduleSlides.length) * 100).toFixed(2)) : 0;
  }

  /**
   * Obtiene el porcentaje de avance de cada módulo.
   * @returns {Object<string, number>} Un objeto con
   *   { "Título de módulo": porcentaje (0–100), … }
   */
  function getProgressByModule() {
    // Recolectamos totales y visitados por módulo
    const stats = {};
    courseData.contentArray.forEach((slide) => {
      const mod = slide.moduleTitle || "Sin módulo";
      if (!stats[mod]) stats[mod] = { total: 0, visited: 0 };
      stats[mod].total++;
      if (slide.visited) stats[mod].visited++;
    });

    // Calculamos porcentajes
    const result = {};
    Object.entries(stats).forEach(([mod, { total, visited }]) => {
      result[mod] = parseFloat(((visited / total) * 100).toFixed(2));
    });

    return result;
  }

  function setProgress(p) {
    setSuspendData(p);
  }

  function getProgress() {
    return getSuspendData() || { contentArray: [], maximumAdvance: 0 };
  }

  function finishScorm() {
    if (pipwerks.SCORM.connection.isActive && !scormAPIUnloaded) {
      const elapsed = (Date.now() - sessionStartTime) / 1000;
      const hh = String(Math.floor(elapsed / 3600)).padStart(2, "0");
      const mm = String(Math.floor((elapsed % 3600) / 60)).padStart(2, "0");
      const ss = String(Math.floor(elapsed % 60)).padStart(2, "0");
      pipwerks.SCORM.set("cmi.core.session_time", `${hh}:${mm}:${ss}`);
      pipwerks.SCORM.save();
      pipwerks.SCORM.quit();
      scormAPIUnloaded = true;
    }
  }

  function getScormData(key) {
    return pipwerks.SCORM.connection.isActive ? pipwerks.SCORM.get(key) ?? "" : sessionStorage.getItem(key) ?? "";
  }

  function setScormData(key, value) {
    if (pipwerks.SCORM.connection.isActive) {
      const ok = pipwerks.SCORM.set(key, value);
      if (ok) pipwerks.SCORM.save();
      return ok;
    }
    sessionStorage.setItem(key, value);
    return true;
  }

  /* ==========================================================================
   * === 11. Arranque DOM y offcanvas =========================================
   * ========================================================================== */
  document.addEventListener("DOMContentLoaded", () => {
    initializeScorm(() => loadConfig());
    window.addEventListener("beforeunload", finishScorm);

    // Tooltips
    var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
    tooltipTriggerList.map((el) => new bootstrap.Tooltip(el));

    // Offcanvas backdrop dentro de custom container
    const offcanvasEl = document.getElementById("coursenav-offcanvas");
    const customContainer = document.getElementById("wrap-course-content");
    const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(offcanvasEl);

    offcanvasEl.addEventListener("shown.bs.offcanvas", () => {
      if (customContainer.querySelector(".offcanvas-backdrop")) return;
      const backdrop = document.createElement("div");
      backdrop.className = "offcanvas-backdrop fade";
      customContainer.appendChild(backdrop);
      backdrop.getBoundingClientRect();
      backdrop.classList.add("show");
      backdrop.addEventListener("click", () => bsOffcanvas.hide());
    });

    offcanvasEl.addEventListener("hidden.bs.offcanvas", () => {
      customContainer.querySelector(".offcanvas-backdrop")?.remove();
    });
  });

  /* ==========================================================================
   * === 12. API pública =======================================================
   * ========================================================================== */
  return {
    /* Audio */
    audioController,
    createSound,
    soundClick,

    /* Debug */
    isDebug,

    /* SCORM Básico */
    getStudentName: () => getScormData("cmi.core.student_name"),
    getLessonLocation,
    setLessonLocation,
    getLessonStatus,
    setLessonStatus,
    getScore,
    setScore,
    getSuspendData,
    setSuspendData,
    getScormData,
    setScormData,

    /* Navegación */
    nextSlide: () => navigate(1),
    prevSlide: () => navigate(-1),
    gotoSlide,
    isVisited: () => courseData.contentArray[currentIndex]?.visited || false,
    isCompletedSlideIndex: (idx) => (idx >= 0 && idx < courseData.contentArray.length ? courseData.contentArray[idx].visited : undefined),

    /* Estado del curso */
    getCurrentSlide: () => courseData.contentArray[currentIndex],
    getCurrentIndex: () => currentIndex,
    getCourseData: () => courseData,
    getCourseStructure: () => courseStructure,
    getCourseConfig: () => COURSE_CONFIG,
    getCourseTitle: () => courseStructure?.title || "",
    getCourseModules: () => courseStructure?.modules || [],
    getCourseContentArray: () => courseData.contentArray,

    /* Curso actual */
    resetCourse,
    markSlidesAsVisited,
    setSlideVisited,
    completeLesson: () => setLessonStatus("completed"),
    updateProgressBar,
    getProgressPercent,
    getProgressByModule,
    getCurrentModuleSlides: () => {
      const module = courseData.contentArray[currentIndex]?.moduleTitle;
      return courseData.contentArray.filter((s) => s.moduleTitle === module);
    },
    getCurrentModuleTitle: () => courseData.contentArray[currentIndex]?.moduleTitle || "",
    getCurrentCourseTitle: () => courseData.contentArray[currentIndex]?.courseTitle || "",

    /* SCORM avanzado */
    save: () => (pipwerks.SCORM.connection.isActive ? pipwerks.SCORM.save() : setProgress(courseData)),
    reload: loadContent,
    loadModule: (moduleTitle) => {
      const idx = courseData.contentArray.findIndex((s) => s.moduleTitle === moduleTitle);
      if (idx >= 0) {
        currentIndex = idx;
        loadContent();
      }
    },
  };
})(COURSE_CONFIG);

window.CourseNav = CourseNav;
