import * as pdfjsLib from "../vendor/pdf.js";

window.addEventListener('error', function(e) {
  const container = document.getElementById('a11y-app-container') || document.body;
  const msg = document.createElement('div');
  msg.style.cssText = 'position:fixed;top:0;left:0;width:100%;background:red;color:white;z-index:999999;padding:20px;font-size:16px;';
  msg.innerHTML = `<strong>A11ysaurio JS Error:</strong> ${e.message} <br><small>en ${e.filename}:${e.lineno}:${e.colno}</small><br><pre>${e.error ? e.error.stack : ''}</pre>`;
  container.appendChild(msg);
});


const localizeUI = () => {
  document.querySelectorAll("[data-i18n]").forEach(el => {
    const msg = ((window.a11ysaurioData && window.a11ysaurioData.i18n && window.a11ysaurioData.i18n[el.getAttribute("data-i18n")]) || el.getAttribute("data-i18n"));
    if (msg) el.textContent = msg;
  });
  document.querySelectorAll("[data-i18n-placeholder]").forEach(el => {
    const msg = ((window.a11ysaurioData && window.a11ysaurioData.i18n && window.a11ysaurioData.i18n[el.getAttribute("data-i18n-placeholder")]) || el.getAttribute("data-i18n-placeholder"));
    if (msg) el.placeholder = msg;
  });
};

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => {
    localizeUI();
    renderGlossary();
  });
} else {
  localizeUI();
  renderGlossary();
}

pdfjsLib.GlobalWorkerOptions.workerSrc = window.a11ysaurioData.pluginUrl + "assets/vendor/pdf.worker.js";

let lastTreeData = null;
let lastValidationData = null;
let lastReportData = null;
let selectedNodeEl = null;

const statusEl = document.getElementById("status");
const metaEl = document.getElementById("meta");
const treeEl = document.getElementById("tree");
const validationSummaryEl = document.getElementById("validationSummary");
const validationListEl = document.getElementById("validationList");
const reportSummaryEl = document.getElementById("reportSummary");
const reportListEl = document.getElementById("reportList");

const infoModal = document.getElementById("infoModal");
const closeInfoModalBtn = document.getElementById("closeInfoModalBtn");
const infoModalContent = document.getElementById("infoModalContent");

if (closeInfoModalBtn && infoModal) {
  closeInfoModalBtn.addEventListener("click", () => {
    infoModal.close();
  });
  infoModal.addEventListener("click", (e) => {
    if (e.target === infoModal) infoModal.close();
  });
}

const filterInput = document.getElementById("filterInput");
const expandAllBtn = document.getElementById("expandAllBtn");
const collapseAllBtn = document.getElementById("collapseAllBtn");
const copyJsonBtn = document.getElementById("copyJsonBtn");
const copyValidationBtn = document.getElementById("copyValidationBtn");
const copyReportBtn = document.getElementById("copyReportBtn");
const downloadReportBtn = document.getElementById("downloadReportBtn");

const tabTreeBtn = document.getElementById("tabTreeBtn");
const tabValidationBtn = document.getElementById("tabValidationBtn");
const tabReaderBtn = document.getElementById("tabReaderBtn");
const tabReportBtn = document.getElementById("tabReportBtn");
const tabGlossaryBtn = document.getElementById("tabGlossaryBtn");
const tabMetadataBtn = document.getElementById("tabMetadataBtn");

const tabTree = document.getElementById("tabTree");
const tabValidation = document.getElementById("tabValidation");
const tabReader = document.getElementById("tabReader");
const readerContentEl = document.getElementById("readerContent");
const tabReport = document.getElementById("tabReport");
const tabGlossary = document.getElementById("tabGlossary");
const tabMetadata = document.getElementById("tabMetadata");

const metaTitleInput = document.getElementById("metaTitleInput");
const metaLangInput = document.getElementById("metaLangInput");
const exportModifiedPdfBtn = document.getElementById("exportModifiedPdfBtn");
const metadataStatus = document.getElementById("metadataStatus");
const validationModeSelect = document.getElementById("validationModeSelect");
const metaPdfuaInput = document.getElementById("metaPdfuaInput");
const pdfuaAlert = document.getElementById("pdfuaAlert");

const tabButtons = [tabTreeBtn, tabValidationBtn, tabReaderBtn, tabReportBtn, tabGlossaryBtn, tabMetadataBtn];




filterInput.addEventListener("input", () => {
  renderTree(lastTreeData, filterInput.value.trim());
});

if (validationModeSelect) {
  validationModeSelect.addEventListener("change", () => {
    if (lastValidationData) {
      renderValidation(lastValidationData);
    }
  });
}

if (metaPdfuaInput && pdfuaAlert) {
  metaPdfuaInput.addEventListener("change", () => {
    pdfuaAlert.style.display = metaPdfuaInput.checked ? "block" : "none";
  });
}

expandAllBtn.addEventListener("click", () => {
  treeEl.querySelectorAll("details").forEach((d) => { d.open = true; });
});

collapseAllBtn.addEventListener("click", () => {
  treeEl.querySelectorAll("details").forEach((d) => { d.open = false; });
});

if (copyJsonBtn) { copyJsonBtn.addEventListener("click", async () => {
  if (!lastTreeData) return;
  await navigator.clipboard.writeText(JSON.stringify(lastTreeData, null, 2));
  setStatus("JSON del árbol copiado.");
}); }

if (copyValidationBtn) { copyValidationBtn.addEventListener("click", async () => {
  if (!lastValidationData) return;
  await navigator.clipboard.writeText(JSON.stringify(lastValidationData, null, 2));
  setStatus("JSON de validación copiado.");
}); }



if (copyReportBtn) { copyReportBtn.addEventListener("click", async () => {
  if (!lastReportData) return;
  await navigator.clipboard.writeText(JSON.stringify(lastReportData, null, 2));
  setStatus("JSON del informe copiado.");
}); }

if (downloadReportBtn) { downloadReportBtn.addEventListener("click", () => {
  if (!lastReportData) return;
  downloadHtmlReport(lastReportData);
}); }


tabTreeBtn.addEventListener("click", () => activateTab("tree"));
tabValidationBtn.addEventListener("click", () => activateTab("validation"));
tabReaderBtn.addEventListener("click", () => activateTab("reader"));
tabReportBtn.addEventListener("click", () => activateTab("report"));
tabGlossaryBtn.addEventListener("click", () => activateTab("glossary"));
tabMetadataBtn.addEventListener("click", () => activateTab("metadata"));

exportModifiedPdfBtn.addEventListener("click", async () => {
  if (!lastTreeData || !lastTreeData.url) {
    metadataStatus.style.color = "var(--error)";
    metadataStatus.textContent = "No hay ningún PDF cargado para exportar.";
    return;
  }
  
  const newTitle = metaTitleInput.value.trim();
  const newLang = metaLangInput.value.trim();
  
  try {
    metadataStatus.style.color = "var(--text)";
    metadataStatus.textContent = "Obteniendo archivo original...";
    
    const response = await fetch(lastTreeData.url);
    if (!response.ok) throw new Error("No se pudo descargar el archivo original.");
    const arrayBuffer = await response.arrayBuffer();
    
    metadataStatus.textContent = "Cargando PDF y modificando metadatos...";
    const pdfDoc = await PDFLib.PDFDocument.load(arrayBuffer);
    
    if (newTitle) {
      pdfDoc.setTitle(newTitle);
      
      // Forzar que el visor muestre el Título en lugar del Nombre de Archivo (Accesibilidad PDF/UA)
      let viewerPrefs = pdfDoc.catalog.get(PDFLib.PDFName.of('ViewerPreferences'));
      if (!viewerPrefs) {
        viewerPrefs = pdfDoc.context.obj({});
        pdfDoc.catalog.set(PDFLib.PDFName.of('ViewerPreferences'), viewerPrefs);
      }
      if (viewerPrefs && typeof viewerPrefs.set === 'function') {
        viewerPrefs.set(PDFLib.PDFName.of('DisplayDocTitle'), PDFLib.PDFBool.True);
      }
      
    } else {
      pdfDoc.setTitle("");
    }
    
    if (newLang) {
      pdfDoc.setLanguage(newLang);
    } else {
      pdfDoc.setLanguage("");
    }
    
    let existingMetadata = "";
    if (pdfDoc.catalog.has(PDFLib.PDFName.of('Metadata'))) {
      const metadataRef = pdfDoc.catalog.get(PDFLib.PDFName.of('Metadata'));
      const metadataObj = pdfDoc.context.lookup(metadataRef);
      if (metadataObj && metadataObj.getContentsString) {
        existingMetadata = metadataObj.getContentsString();
      }
    }
    
    let needsXmpUpdate = false;
    let addPdfUa = (metaPdfuaInput && metaPdfuaInput.checked);
    if (newTitle || addPdfUa) {
      let xmpCore = existingMetadata;
      let header = '<?xpacket begin="﻿" id="W5M0MpCehiHzreSzNTczkc9d"?>\n';
      let footer = '\n<?xpacket end="w"?>';
      
      if (existingMetadata) {
        const matchBegin = existingMetadata.match(/<\\?xpacket begin=.*?\\?>/);
        if (matchBegin) header = matchBegin[0] + '\\n';
        const matchEnd = existingMetadata.match(/<\\?xpacket end=.*?\\?>/);
        if (matchEnd) footer = '\\n' + matchEnd[0];
        xmpCore = existingMetadata.replace(/<\\?xpacket.*?\\?>/g, '');
      } else {
        xmpCore = `<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  </rdf:RDF>
</x:xmpmeta>`;
      }

      const parser = new DOMParser();
      const xmlDoc = parser.parseFromString(xmpCore, "text/xml");
      let rdf = xmlDoc.getElementsByTagName("rdf:RDF")[0] || xmlDoc.getElementsByTagName("RDF")[0];
      
      if (rdf) {
        // 1. Añadir/Actualizar Título en XMP
        if (newTitle) {
          let dcDesc = Array.from(xmlDoc.getElementsByTagName("rdf:Description")).find(d => d.getAttribute("xmlns:dc"));
          if (!dcDesc) {
            dcDesc = xmlDoc.createElement("rdf:Description");
            dcDesc.setAttribute("rdf:about", "");
            dcDesc.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
            rdf.appendChild(dcDesc);
          }
          
          let dcTitle = dcDesc.getElementsByTagName("dc:title")[0] || dcDesc.getElementsByTagName("title")[0];
          if (!dcTitle) {
            dcTitle = xmlDoc.createElement("dc:title");
            dcDesc.appendChild(dcTitle);
          }
          
          let rdfAlt = dcTitle.getElementsByTagName("rdf:Alt")[0] || dcTitle.getElementsByTagName("Alt")[0];
          if (!rdfAlt) {
            rdfAlt = xmlDoc.createElement("rdf:Alt");
            dcTitle.appendChild(rdfAlt);
          }
          
          let rdfLi = rdfAlt.getElementsByTagName("rdf:li")[0] || rdfAlt.getElementsByTagName("li")[0];
          if (!rdfLi) {
            rdfLi = xmlDoc.createElement("rdf:li");
            rdfLi.setAttribute("xml:lang", "x-default");
            rdfAlt.appendChild(rdfLi);
          }
          
          if (rdfLi.textContent !== newTitle) {
            rdfLi.textContent = newTitle;
            needsXmpUpdate = true;
          }
        }

        // 2. Añadir Identificador PDF/UA en XMP
        if (addPdfUa) {
          let hasPdfUa = Array.from(xmlDoc.getElementsByTagName("pdfuaid:part")).length > 0 || Array.from(xmlDoc.getElementsByTagName("part")).some(p => p.parentNode.nodeName.includes("pdfuaid"));
          if (!hasPdfUa) {
            let uaDesc = xmlDoc.createElement("rdf:Description");
            uaDesc.setAttribute("rdf:about", "");
            uaDesc.setAttribute("xmlns:pdfuaid", "http://www.aiim.org/pdfua/ns/id/");
            let uaPart = xmlDoc.createElement("pdfuaid:part");
            uaPart.textContent = "1";
            uaDesc.appendChild(uaPart);
            rdf.appendChild(uaDesc);
            needsXmpUpdate = true;
          }
        }
      }

      if (needsXmpUpdate) {
        const serializer = new XMLSerializer();
        let newXmlStr = serializer.serializeToString(xmlDoc);
        // Limpieza rápida de etiquetas vacías generadas por DOMParser si las hay
        newXmlStr = newXmlStr.replace(/xmlns=""/g, ''); 
        let finalMetadata = header + newXmlStr + footer;
        
        const dict = pdfDoc.context.obj({ Type: 'Metadata', Subtype: 'XML' });
        const textEncoder = new TextEncoder();
        const stream = pdfDoc.context.stream(textEncoder.encode(finalMetadata), dict);
        const streamRef = pdfDoc.context.register(stream);
        pdfDoc.catalog.set(PDFLib.PDFName.of('Metadata'), streamRef);
      }
    }
    
    metadataStatus.textContent = "Generando nuevo PDF...";
    const pdfBytes = await pdfDoc.save();
    
    const blob = new Blob([pdfBytes], { type: "application/pdf" });
    const blobUrl = URL.createObjectURL(blob);
    
    const a = document.createElement("a");
    a.href = blobUrl;
    
    let baseName = newTitle;
    if (!baseName) {
      let originalName = lastTreeData.url.split('/').pop().split('?')[0];
      if (!originalName || originalName.length < 4) originalName = "Documento";
      baseName = originalName.replace(/\.pdf$/i, "");
    }
    baseName = baseName.replace(/[^a-zA-Z0-9_\-\s\u00C0-\u017F]/g, "").trim();
    a.download = `${baseName} metadatos modificados por a11ysaurio pdf checker.pdf`;
    
    a.click();
    
    setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
    
    metadataStatus.style.color = "var(--ok)";
    metadataStatus.textContent = "¡PDF exportado con éxito!";
    
  } catch (error) {
    console.error(error);
    metadataStatus.style.color = "var(--error)";
    metadataStatus.textContent = `Error: ${error.message}`;
  }
});

for (const btn of tabButtons) {
  btn.addEventListener("keydown", onTabKeydown);
}

function onTabKeydown(event) {
  const currentIndex = tabButtons.indexOf(event.currentTarget);
  let targetIndex = null;

  if (event.key === "ArrowRight") targetIndex = (currentIndex + 1) % tabButtons.length;
  if (event.key === "ArrowLeft") targetIndex = (currentIndex - 1 + tabButtons.length) % tabButtons.length;
  if (event.key === "Home") targetIndex = 0;
  if (event.key === "End") targetIndex = tabButtons.length - 1;

  if (targetIndex !== null) {
    event.preventDefault();
    tabButtons[targetIndex].focus();
    return;
  }

  if (event.key === "Enter" || event.key === " ") {
    event.preventDefault();
    event.currentTarget.click();
  }
}

function activateTab(which) {
  const isTree = which === "tree";
  const isValidation = which === "validation";
  const isReader = which === "reader";
  const isReport = which === "report";
  const isGlossary = which === "glossary";
  const isMetadata = which === "metadata";

  const map = [
    [tabTreeBtn, isTree],
    [tabValidationBtn, isValidation],
    [tabReaderBtn, isReader],
    [tabReportBtn, isReport],
    [tabGlossaryBtn, isGlossary],
    [tabMetadataBtn, isMetadata]
  ];

  for (const [btn, active] of map) {
    btn.classList.toggle("active", active);
    btn.setAttribute("aria-selected", String(active));
    btn.tabIndex = active ? 0 : -1;
  }

  tabTree.classList.toggle("active", isTree);
  tabValidation.classList.toggle("active", isValidation);
  tabReader.classList.toggle("active", isReader);
  tabReport.classList.toggle("active", isReport);
  tabGlossary.classList.toggle("active", isGlossary);
  tabMetadata.classList.toggle("active", isMetadata);

  tabTree.hidden = !isTree;
  tabValidation.hidden = !isValidation;
  tabReader.hidden = !isReader;
  tabReport.hidden = !isReport;
  tabGlossary.hidden = !isGlossary;
  tabMetadata.hidden = !isMetadata;
}

function setStatus(text) {
  statusEl.textContent = text;
}

function renderGlossary() {
  const glossaryContentEl = document.getElementById("glossaryContent");
  if (!glossaryContentEl) return;
  
  glossaryContentEl.innerHTML = "";
  
  const terms = [
    { term: "PDF/UA", title: "PDF/UA (Universal Accessibility)", desc: "Estándar internacional (ISO 14289) que define los requisitos para que un documento PDF sea accesible para personas con discapacidad. Un PDF/UA asegura que la estructura, lectura y contraste sean correctos." },
    { term: "Matterhorn", title: "Protocolo Matterhorn", desc: "Conjunto de 31 puntos de control y más de 100 criterios técnicos usados para verificar de forma exhaustiva si un documento cumple o no con el estándar PDF/UA." },
    { term: "XMP", title: "Metadatos XMP", desc: "Información oculta dentro del archivo PDF que describe sus propiedades (como el título, autor y la declaración de que el documento cumple con PDF/UA)." },
    { term: "RoleMap", title: "Mapeo de Roles (RoleMap)", desc: "Mecanismo técnico que permite renombrar etiquetas personalizadas en el PDF (ej. 'MiTitulo') apuntando a etiquetas estándar universales (ej. 'H1'). Las etiquetas estándar en sí mismas nunca deben ser re-mapeadas." },
    { term: "Artefacto", title: "Artefactos (Artifacts)", desc: "Elementos puramente decorativos o repetitivos (como fondos, números de página o líneas de separación) que se marcan como artefactos para que los lectores de pantalla los ignoren y no molesten al usuario." },
    { term: "Jerarquía H", title: "Jerarquía de Encabezados (H1-H6)", desc: "Niveles de título dentro del documento. El título principal debe ser H1, sus secciones H2, las subsecciones H3, etc. No se deben saltar niveles (ej. pasar de H1 a H3) ni usar la etiqueta genérica 'H'." }
  ];
  
  for (const item of terms) {
    const card = document.createElement("div");
    card.style.background = "var(--surface)";
    card.style.padding = "12px 16px";
    card.style.borderRadius = "8px";
    card.style.borderLeft = "4px solid var(--udl)";
    
    const h4 = document.createElement("h4");
    h4.style.margin = "0 0 8px 0";
    h4.style.color = "var(--udl)";
    h4.textContent = item.title;
    
    const p = document.createElement("p");
    p.style.margin = "0";
    p.style.fontSize = "0.95em";
    p.textContent = item.desc;
    
    card.appendChild(h4);
    card.appendChild(p);
    glossaryContentEl.appendChild(card);
  }
}

function setMeta(data) {
  metaEl.innerHTML = "";
  if (!data) return;

  // Badge PDF/UA destacado
  const badgeWrap = document.createElement("div");
  badgeWrap.style.marginBottom = "10px";

  const isPdfUA = !!data.pdfUAVersion;
  const badge = document.createElement("span");
  badge.className = `status-badge ${isPdfUA ? "status-ok" : "status-error"}`;
  badge.style.fontSize = "0.9375rem";
  badge.style.padding = "6px 14px";
  if (isPdfUA) {
    const rev = data.pdfUARev ? `:${data.pdfUARev}` : "";
    badge.textContent = `✓ PDF/UA-${data.pdfUAVersion}${rev} declarado`;
  } else {
    badge.textContent = "✗ No se detecta declaración PDF/UA";
  }
  badgeWrap.appendChild(badge);
  metaEl.appendChild(badgeWrap);

  const dl = document.createElement("dl");
  const items = [
    ["Página", data.pageUrl || "-"],
    ["PDF", data.url || "-"],
    ["Páginas", String(data.pages ?? "-")],
    ["Etiquetado", data.isTagged ? "Sí (flag Tagged)" : data.hasTree ? "Sí (árbol presente)" : "No"],
    ["Árbol estructural", data.hasTree ? "Sí" : "No"],
    ["PDF/UA", isPdfUA ? `Parte ${data.pdfUAVersion}${data.pdfUARev ? ", rev. " + data.pdfUARev : ""}` : "No declarado"],
    ["Método", data.method || "-"]
  ];

  for (const [k, v] of items) {
    const dt = document.createElement("dt");
    dt.textContent = k;
    const dd = document.createElement("dd");
    dd.textContent = v;
    dl.append(dt, dd);
  }

  metaEl.appendChild(dl);
}

function cleanText(value) {
  return String(value || "")
    .replace(/\s+/g, " ")
    .trim();
}

function normalizeText(value) {
  if (!value) return "";
  if (typeof value === "string") return cleanText(value);
  if (Array.isArray(value)) return cleanText(value.map(normalizeText).join(" "));
  if (typeof value === "object") {
    if (typeof value.str === "string") return cleanText(value.str);
    if (typeof value.text === "string") return cleanText(value.text);
    if (typeof value.alt === "string") return cleanText(value.alt);
  }
  return "";
}

function collectDescendantText(children) {
  const parts = [];

  function visit(node) {
    if (!node) return;
    if (node.text) parts.push(node.text);
    for (const child of node.children || []) visit(child);
  }

  for (const child of children || []) visit(child);

  return cleanText(parts.join(" "));
}

function extractNode(node, pageNumber = null, mcidMap = {}) {
  if (!node) return null;

  const role = node.role || node.type || node.name || "Unknown";
  const alt = node.alt || node.altText || null;
  const lang = node.lang || null;
  const actualText = node.actualText || null;

  const children = [];
  const kidCandidates = node.children || node.kids || [];
  if (Array.isArray(kidCandidates)) {
    for (const child of kidCandidates) {
      const extracted = extractNode(child, pageNumber, mcidMap);
      if (extracted) children.push(extracted);
    }
  }

  let nodeText = normalizeText(node.text || node.str || "");
  if (!nodeText && node.id && mcidMap[node.id]) nodeText = cleanText(mcidMap[node.id]);
  if (!nodeText && actualText) nodeText = cleanText(actualText);
  if (!nodeText && alt) nodeText = cleanText(alt);
  if (!nodeText) nodeText = collectDescendantText(children);

  const result = {
    role,
    text: nodeText,
    page: pageNumber,
    attrs: {},
    children
  };

  if (alt) result.attrs.Alt = alt;
  if (lang) result.attrs.Lang = lang;
  if (actualText) result.attrs.ActualText = actualText;

  const attrs = node.attributes || node.attrs || null;
  if (attrs && typeof attrs === "object") {
    for (const [k, v] of Object.entries(attrs)) {
      if (v != null && v !== "") {
        result.attrs[k] = typeof v === "string" ? v : JSON.stringify(v);
      }
    }
  }

  return result;
}

function flattenNodes(nodes) {
  const out = [];
  function visit(node) {
    out.push(node);
    for (const child of node.children || []) visit(child);
  }
  for (const node of nodes || []) visit(node);
  return out;
}

function matchesFilter(node, filter) {
  if (!filter) return true;
  const q = filter.toLowerCase();
  const hay = [
    node.role || "",
    node.text || "",
    ...Object.entries(node.attrs || {}).flatMap(([k, v]) => [k, String(v)])
  ].join(" ").toLowerCase();

  if (hay.includes(q)) return true;
  return (node.children || []).some((child) => matchesFilter(child, filter));
}

async function getActiveTab() {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage({ type: "GET_ACTIVE_TAB" }, (response) => {
      resolve(response?.tab || null);
    });
  });
}

async function sendMessageToTab(tabId, message) {
  return new Promise((resolve) => {
    chrome.tabs.sendMessage(tabId, message, (response) => {
      if (chrome.runtime.lastError) {
        resolve({ error: chrome.runtime.lastError.message });
      } else {
        resolve(response || null);
      }
    });
  });
}

async function focusNodeInViewer(node) {
  const tab = await getActiveTab();
  if (!tab?.id) {
    setStatus("No se pudo obtener la pestaña activa para enfocar la etiqueta.");
    return;
  }

  let hashStr = "";
  if (node.page) {
    hashStr += `#page=${node.page}`;
    if (node.text && node.text.trim()) {
      // Chrome Native PDF Search es muy estricto. Si metemos frases largas truncadas 
      // o con espacios mal formados, falla. Tomamos la primera palabra larga.
      const words = node.text.trim().split(/\s+/);
      const firstMeaningfulWord = words.find(w => w.length > 3) || words[0];
      
      // Eliminamos puntuación para evitar escapes raros en URL
      const cleanWord = firstMeaningfulWord.replace(/[^\w\u00C0-\u017F]/g, "");
      
      if (cleanWord) {
        hashStr += `&search=${encodeURIComponent(cleanWord)}`;
      }
    }
  }

  // 2. Transición y salto
  if (hashStr && tab.url) {
    try {
      const targetUrl = tab.url.split('#')[0] + hashStr;
      chrome.tabs.update(tab.id, { url: targetUrl }, () => {
        chrome.tabs.reload(tab.id);
      });
      setStatus(`Saltando a pág. ${node.page} y forzando recarga visual...`);
    } catch {
      setStatus(`Fallo al inyectar parámetros de navegación al tab.`);
      return;
    }
  }

  // 3. Demorar la ventanita negra superpuesta
  // Damos 1.5s asumiendo que el tab ya reinició su ciclo de carga básico.
  setTimeout(async () => {
    const response = await sendMessageToTab(tab.id, {
      type: "FOCUS_TAG_NODE",
      payload: {
        role: node.role,
        page: node.page,
        text: node.text || "",
        attrs: node.attrs || {}
      }
    });

    if (response?.ok) {
      setStatus(`¡Etiqueta ${node.role} (Pág. ${node.page || 'X'}) localizada en pantalla!`);
    } else {
      // Es posible que visor nativo no admita scripts inyectados
      setStatus(`Salto completo. Visor de seguridad nativo suprime la ventana flotante auxiliar.`);
    }
  }, 1500);
}

function selectNode(nodeEl, node) {
  if (selectedNodeEl) {
    selectedNodeEl.classList.remove("node-selected");
  }
  selectedNodeEl = nodeEl;
  nodeEl.classList.add("node-selected");
  nodeEl.scrollIntoView({ behavior: "smooth", block: "nearest" });

  const parts = [`Etiqueta: ${node.role}`];
  if (node.page != null) parts.push(`Página: ${node.page}`);
  parts.push(`Hijos: ${(node.children || []).length}`);
  if (node.text) parts.push(`Contenido: "${node.text.slice(0, 80)}${node.text.length > 80 ? "…" : ""}"`);
  setStatus(parts.join(" · "));
}

function renderNode(node, filterText, allowedIds = null, forceShow = false) {
  const isDirectMatchText = filterText ? [String(node.role || ""), String(node.text || ""), ...Object.values(node.attrs || [])].join(" ").toLowerCase().includes(String(filterText).toLowerCase()) : false;
  const isDirectMatchId = allowedIds !== null ? allowedIds.includes(node._id) : false;
  
  if (!forceShow && !matchesFilter(node, filterText, allowedIds)) return null;

  const shouldForceChildren = forceShow || isDirectMatchId || isDirectMatchText;

  const hasChildren = Array.isArray(node.children) && node.children.length > 0;
  const details = document.createElement("details");
  details.open = true;

  const summary = document.createElement("summary");
  const line = document.createElement("button");
  line.type = "button";
  line.className = "node-line";
  
  if (allowedIds && allowedIds.includes(node._id)) {
    line.classList.add("node-affected");
  }
  
  line.style.border = "0";
  line.style.background = "transparent";
  line.style.padding = "0";
  line.style.textAlign = "left";
  line.style.cursor = "pointer";
  line.title = "Seleccionar esta etiqueta";

  line.addEventListener("click", (event) => {
    event.preventDefault();
    event.stopPropagation();
    selectNode(line, node);
  });

  const tag = document.createElement("span");
  tag.className = "tag";
  tag.textContent = node.role || "Unknown";
  line.appendChild(tag);

  if (node.page != null) {
    const page = document.createElement("span");
    page.className = "attr";
    page.textContent = `pág. ${node.page}`;
    line.appendChild(page);
  }

  for (const [k, v] of Object.entries(node.attrs || {})) {
    const attr = document.createElement("span");
    attr.className = "attr";
    attr.textContent = `${k}: ${String(v).slice(0, 60)}`;
    line.appendChild(attr);
  }

  summary.appendChild(line);
  details.appendChild(summary);

  // Bloque de contenido textual visible debajo de la línea de etiqueta
  if (node.text) {
    const contentBlock = document.createElement("div");
    contentBlock.className = "node-content";
    contentBlock.textContent = node.text;
    details.appendChild(contentBlock);
  }

  if (hasChildren) {
    for (const child of node.children) {
      const childEl = renderNode(child, filterText, allowedIds, shouldForceChildren);
      if (childEl) details.appendChild(childEl);
    }
  } else if (!node.text) {
    const empty = document.createElement("div");
    empty.className = "empty";
    empty.textContent = "Sin contenido ni hijos";
    details.appendChild(empty);
  }

  return details;
}

function renderTree(data, filterText = "", allowedIds = null) {
  treeEl.innerHTML = "";
  selectedNodeEl = null;

  if (!data || !Array.isArray(data.nodes) || !data.nodes.length) {
    const div = document.createElement("div");
    div.className = "empty";
    div.textContent = "No hay árbol de etiquetas disponible.";
    treeEl.appendChild(div);
    return;
  }

  for (const node of data.nodes) {
    const el = renderNode(node, filterText, allowedIds);
    if (el) treeEl.appendChild(el);
  }

  if (!treeEl.children.length) {
    const div = document.createElement("div");
    div.className = "empty";
    div.textContent = "Ningún nodo coincide con el filtro.";
    treeEl.appendChild(div);
  }
}

function createCheckResult(id, group, title, desc, severity, checkType, status, evidence, affectedNodes = [], tooltip = '') {
  return { id, group, title, description: desc, severity, checkType, status, evidence, affectedNodes, recommendation: '', technicalNotes: '', tooltip };
}

function evaluateAccessibilityChecks(nodes, docInfo = {}) {
  const flat = flattenNodes(nodes);
  const results = [];

  const isPdfUA = !!docInfo.pdfUAVersion;
  results.push(createCheckResult('CHK-01', 'Metadatos', 'Identificador PDF/UA', 'Comprueba si existe la declaración pdfuaid en XMP.', isPdfUA ? 'info' : 'critical', 'auto', isPdfUA ? 'pass' : 'fail', isPdfUA ? `Declarado PDF/UA-${docInfo.pdfUAVersion}` : 'No se detecta pdfuaid.', [], 'Según Matterhorn 06-002, el documento debe declarar que cumple con la norma PDF/UA a través de sus metadatos XMP.'));

  const hasTree = Array.isArray(nodes) && nodes.length > 0;
  results.push(createCheckResult('CHK-02', 'Estructura', 'Árbol estructural disponible', 'Verifica que el PDF esté etiquetado y se pueda extraer su estructura.', hasTree ? 'info' : 'critical', 'auto', hasTree ? 'pass' : 'fail', hasTree ? 'Estructura parseada correctamente.' : 'Falta árbol de etiquetas.', [], 'Basado en Matterhorn 01-005: Todo el contenido real del documento debe estar correctamente etiquetado para ser procesado por los lectores de pantalla.'));

  const langNodes = flat.filter(n => n.attrs && n.attrs.Lang);
  const hasLang = !!docInfo.docLang || langNodes.length > 0;
  let langMsg = docInfo.docLang ? `Idioma global: ${docInfo.docLang}` : langNodes.length ? `Se detecta idioma en ${langNodes.length} nodos.` : 'Sin idioma declarado.';
  
  const siteLang = window.a11ysaurioData && window.a11ysaurioData.siteLang ? window.a11ysaurioData.siteLang.substring(0, 2).toLowerCase() : "";
  let langMismatch = false;
  if (docInfo.docLang && siteLang) {
    const pdfLang = docInfo.docLang.substring(0, 2).toLowerCase();
    if (pdfLang !== siteLang) {
      langMismatch = true;
      langMsg += ` (Aviso: El idioma del PDF '${docInfo.docLang}' difiere del idioma del sitio '${window.a11ysaurioData.siteLang}').`;
    }
  }

  results.push(createCheckResult('CHK-03', 'Idioma', 'Idioma principal del documento', 'El idioma es crucial para lectores de pantalla.', hasLang && !langMismatch ? 'info' : (hasLang ? 'warning' : 'high'), hasLang && !langMismatch ? 'auto' : 'partial', hasLang && !langMismatch ? 'pass' : (hasLang ? 'warning' : 'fail'), langMsg, [], 'Relacionado con Matterhorn 11-001 y 11-006: Es obligatorio definir el idioma natural del documento. Debe coincidir con el idioma de la web si es el archivo principal.'));

  let docTitleStr = docInfo.docTitle || "";
  if (!docTitleStr) {
    const titleNode = flat.find(n => n.role === "Title" || (n.attrs && n.attrs["dc:title"]));
    if (titleNode) {
      docTitleStr = (titleNode.attrs && titleNode.attrs["dc:title"]) ? titleNode.attrs["dc:title"] : (titleNode.text || "");
    }
  }
  const hasTitle = !!docTitleStr;
  const titleEvidence = hasTitle ? `Se encontró un título en metadatos o estructura. Título: "${docTitleStr}"` : 'Título no detectado.';
  results.push(createCheckResult('CHK-04', 'Metadatos', 'Título del documento', 'Se requiere un título descriptivo.', hasTitle ? 'info' : 'high', 'partial', hasTitle ? 'pass' : 'fail', titleEvidence, [], 'Matterhorn 06-003: Todo documento PDF accesible requiere un título definido en sus metadatos XMP que identifique claramente su contenido.'));

  const headings = flat.filter(n => /^H[1-6]$/.test(n.role));
  const badH = flat.filter(n => n.role === "H");
  
  const jumpedHeadings = [];
  let lastLevel = null;
  for (const h of headings) {
    const level = parseInt(h.role.replace("H", ""), 10);
    if (lastLevel !== null && level > lastLevel + 1) {
      jumpedHeadings.push(h);
    }
    lastLevel = level;
  }

  let hStatus = 'manual';
  let hEvidence = 'Sin encabezados.';
  let hNodes = [];
  let hSeverity = 'info';

  if (badH.length > 0) {
    hStatus = 'fail';
    hEvidence = `Uso incorrecto de etiqueta genérica <H> en ${badH.length} elementos.`;
    hNodes = badH;
    hSeverity = 'high';
  } else if (jumpedHeadings.length > 0) {
    hStatus = 'warning';
    hEvidence = `Se han detectado saltos de jerarquía en ${jumpedHeadings.length} encabezados.`;
    hNodes = jumpedHeadings;
    hSeverity = 'medium';
  } else if (headings.length > 0) {
    hStatus = 'pass';
    hEvidence = `Detectados ${headings.length} encabezados correctamente estructurados.`;
    hNodes = headings;
  }

  results.push(createCheckResult('CHK-05', 'Encabezados', 'Jerarquía de Encabezados (H1-H6)', 'Comprueba que no haya saltos bruscos en los niveles de encabezado y no se usen <H> genéricos.', hSeverity, 'partial', hStatus, hEvidence, hNodes, 'Matterhorn 14-003 y 14-007: Exige una correcta estructura anidada y el no uso de etiquetas <H> genéricas que rompan la semántica del documento.'));

  const lists = flat.filter(n => n.role === "L");
  results.push(createCheckResult('CHK-06', 'Listas', 'Estructura de Listas', 'Patrón L > LI > Lbl/LBody.', lists.length ? 'info' : 'low', 'partial', lists.length ? 'pass' : 'manual', lists.length ? `${lists.length} listas detectadas.` : 'Sin listas.', lists, 'Matterhorn Checkpoint 16: Comprueba que la etiqueta L contiene etiquetas LI (List Item) y éstas opcionalmente Lbl (Label) y LBody (List Body).'));

  const tables = flat.filter(n => n.role === "Table");
  const thCells = flat.filter(n => n.role === "TH");
  const complexCells = flat.filter(n => n.role === "TD" || n.role === "TH").filter(n => n.attrs && (n.attrs.RowSpan > 1 || n.attrs.ColSpan > 1));
  const badComplexCells = complexCells.filter(n => !n.attrs.Headers && !n.attrs.Scope);

  let tStatus = 'manual';
  let tEvidence = 'Sin tablas.';
  let tNodes = [];
  let tSeverity = 'info';

  if (tables.length > 0) {
    if (thCells.length === 0) {
      tStatus = 'warning';
      tEvidence = `Se detectaron ${tables.length} tablas pero ninguna contiene celdas de encabezado explícitas (<TH>).`;
      tNodes = tables;
      tSeverity = 'high';
    } else if (badComplexCells.length > 0) {
      tStatus = 'fail';
      tEvidence = `Hay ${badComplexCells.length} celdas complejas (RowSpan/ColSpan) sin atributos de conexión (Headers/Scope).`;
      tNodes = badComplexCells;
      tSeverity = 'critical';
    } else {
      tStatus = 'pass';
      tEvidence = `${tables.length} tablas detectadas. Revisar lógica de conexión manualmente.`;
      tNodes = tables;
      tSeverity = 'low';
    }
  }

  results.push(createCheckResult('CHK-07', 'Tablas', 'Inspección de Tablas Inaccesibles', 'Verifica el uso de <TH> y la correcta conexión en celdas agrupadas (RowSpan/ColSpan).', tSeverity, 'partial', tStatus, tEvidence, tNodes, 'Matterhorn Checkpoint 15: Exige que las celdas de encabezado (TH) estén presentes y, si hay agrupaciones de celdas (RowSpan/ColSpan), se defina su alcance (Scope) para evitar confusiones en los lectores de pantalla.'));

  const figures = flat.filter(n => n.role === "Figure");
  const figsWithoutAlt = figures.filter(n => !("Alt" in (n.attrs || {})) || !String(n.attrs.Alt).trim());
  results.push(createCheckResult('CHK-08', 'Figuras', 'Texto Alternativo', 'Las figuras necesitan Alt o ActualText.', figures.length && figsWithoutAlt.length ? 'high' : 'info', 'partial', figures.length ? (figsWithoutAlt.length ? 'fail' : 'pass') : 'manual', figures.length ? `${figsWithoutAlt.length} figuras sin Alt de un total de ${figures.length}.` : 'Sin figuras.', figures, 'Matterhorn 13-004: Cualquier elemento etiquetado como <Figure> debe poseer un texto alternativo o ActualText que describa su propósito e información esencial.'));

  const links = flat.filter(n => n.role === "Link");
  results.push(createCheckResult('CHK-09', 'Enlaces', 'Enlaces etiquetados', 'Los enlaces deben usar la etiqueta Link.', links.length ? 'info' : 'low', 'partial', links.length ? 'pass' : 'manual', links.length ? `${links.length} enlaces detectados.` : 'Sin enlaces detectados.', links, 'Relacionado con Matterhorn Checkpoint 13 y el manejo de anotaciones interactivas como Link.'));

  const forms = flat.filter(n => n.role === "Form");
  results.push(createCheckResult('CHK-10', 'Formularios', 'Estructura de Formularios', 'Controles etiquetados como Form.', forms.length ? 'info' : 'low', 'partial', forms.length ? 'pass' : 'manual', forms.length ? `${forms.length} formularios detectados.` : 'Sin formularios.', forms, 'Matterhorn Checkpoint 24: Controles de formulario interactivos deben estar correctamente etiquetados.'));

  const emptyTexts = flat.filter(n => n.role === "P" && (!n.text || n.text.trim().length === 0));
  results.push(createCheckResult('CHK-11', 'Contenido', 'Capa de Texto (Sospecha OCR)', 'Alerta si hay elementos de texto vacíos o escaneos puros.', emptyTexts.length > 10 ? 'high' : 'info', 'manual', emptyTexts.length > 10 ? 'warning' : 'pass', emptyTexts.length > 10 ? `Sospecha: ${emptyTexts.length} párrafos vacíos.` : 'Texto parece extraíble.', emptyTexts, 'Matterhorn 08-001: Documentos escaneados procesados por OCR no deben tener errores significativos o generar una capa de texto vacía incomprensible.'));

  results.push(createCheckResult('CHK-12', 'Contenido', 'Orden de Lectura', 'El orden lógico debe coincidir con el visual.', 'high', 'manual', 'manual', 'Requiere comprobación humana.', [], 'Matterhorn 09-001: La estructura de etiquetas debe reflejar fielmente el orden lógico e intuitivo de lectura del documento. (Inspección manual requerida)'));

  // CHK-13: Marcadores
  const requiresOutline = docInfo.pages > 20;
  const outlineStatus = docInfo.hasOutline ? 'pass' : (requiresOutline ? 'fail' : 'pass');
  const outlineSeverity = requiresOutline ? 'high' : 'info';
  results.push(createCheckResult('CHK-13', 'Navegación', 'Esquema de Marcadores (Bookmarks)', 'Documentos largos (>20 págs) deben tener marcadores para navegación.', outlineSeverity, 'auto', outlineStatus, docInfo.hasOutline ? 'El documento tiene esquema de marcadores.' : (requiresOutline ? 'Faltan marcadores en un documento de más de 20 páginas.' : 'No tiene marcadores, pero es un documento corto.'), [], 'Buena práctica de navegación requerida en normativas (similar a WCAG 2.4.5), esencial para documentos largos.'));

  // CHK-14: Permisos
  const hasRestrictions = Array.isArray(docInfo.permissions);
  results.push(createCheckResult('CHK-14', 'Seguridad', 'Permisos de Accesibilidad', 'El PDF no debe bloquear la extracción de texto para lectores de pantalla.', hasRestrictions ? 'high' : 'info', 'partial', hasRestrictions ? 'warning' : 'pass', hasRestrictions ? 'El PDF tiene restricciones de seguridad. Compruebe que "Copiar para accesibilidad" esté permitido.' : 'Sin restricciones de seguridad.', [], 'Matterhorn Checkpoint 26: El PDF no puede estar protegido por contraseñas que restrinjan la extracción de texto (accesibilidad/copia de contenido).'));

  // CHK-15: Metadatos Adicionales
  const hasExtraMeta = docInfo.docAuthor || docInfo.docSubject || docInfo.docKeywords;
  results.push(createCheckResult('CHK-15', 'Metadatos', 'Autor, Asunto y Palabras Clave', 'Metadatos adicionales útiles para SEO y organización.', 'low', 'auto', hasExtraMeta ? 'pass' : 'warning', hasExtraMeta ? `Detectados: ${[docInfo.docAuthor ? 'Autor' : '', docInfo.docSubject ? 'Asunto' : '', docInfo.docKeywords ? 'Palabras Clave' : ''].filter(Boolean).join(', ')}` : 'Faltan campos Autor, Asunto y/o Palabras Clave.', [], 'Complemento al Checkpoint 06 de Matterhorn para mejorar la identificación completa del documento.'));

  // CHK-16: Formato del Idioma (BCP 47)
  const bcp47Regex = /^[a-zA-Z]{2,3}(-[a-zA-Z]{2,4})?(-[a-zA-Z0-9]+)*$/;
  const invalidLangs = [];
  if (docInfo.docLang && !bcp47Regex.test(docInfo.docLang)) invalidLangs.push(`Global: "${docInfo.docLang}"`);
  for (const n of langNodes) {
    if (n.attrs && n.attrs.Lang && !bcp47Regex.test(n.attrs.Lang)) invalidLangs.push(`Nodo en Pág. ${n.page}: "${n.attrs.Lang}"`);
  }
  // limit invalidLangs length for evidence string
  const invalidLangsSliced = invalidLangs.length > 5 ? invalidLangs.slice(0, 5).concat('...') : invalidLangs;
  results.push(createCheckResult('CHK-16', 'Idioma', 'Formato de Idioma (BCP 47)', 'Los códigos de idioma deben ser estándar (ej. es-ES, en).', invalidLangs.length ? 'high' : 'info', 'auto', invalidLangs.length ? 'fail' : 'pass', invalidLangs.length ? `Se encontraron formatos de idioma inválidos: ${invalidLangsSliced.join(', ')}` : 'Formatos de idioma correctos.', [], 'Matterhorn 11-007: Los idiomas deben especificarse en un formato estándar reconocible internacionalmente (BCP 47).'));

  // CHK-17: Nodos vacíos
  const emptyTablesLists = flat.filter(n => (n.role === "Table" || n.role === "L") && (!n.children || n.children.length === 0) && (!n.text || n.text.trim().length === 0));
  results.push(createCheckResult('CHK-17', 'Estructura', 'Tablas o Listas Vacías', 'Etiquetas de estructura sin contenido generan ruido.', emptyTablesLists.length ? 'medium' : 'info', 'auto', emptyTablesLists.length ? 'warning' : 'pass', emptyTablesLists.length ? `Se encontraron ${emptyTablesLists.length} tablas o listas vacías.` : 'No hay tablas o listas vacías.', emptyTablesLists, 'Relacionado con ruido en el árbol estructural (Matterhorn Checkpoint 09), elementos estructurales vacíos pueden confundir al lector de pantalla.'));

  // CHK-18: ViewerPreferences DisplayDocTitle
  const displayDocTitle = docInfo.displayDocTitle === true;
  results.push(createCheckResult('CHK-18', 'Metadatos', 'Mostrar Título de Documento', 'El visor debe configurarse para mostrar el Título y no el nombre del archivo.', displayDocTitle ? 'info' : 'critical', 'auto', displayDocTitle ? 'pass' : 'fail', displayDocTitle ? 'DisplayDocTitle activado.' : 'DisplayDocTitle falso o no encontrado en ViewerPreferences.', [], 'Matterhorn 07-001/07-002: El documento debe configurar el visor para mostrar por defecto el Título del documento en la barra de la ventana, no el nombre del archivo.'));

  // CHK-19: First heading is H1
  let firstHeadingStatus = 'pass';
  let firstHeadingEvidence = 'No se detectan encabezados (revisión manual).';
  let firstHeadingSeverity = 'info';
  let firstHeadingNodes = [];
  if (headings.length > 0) {
    const firstHeading = headings[0];
    if (firstHeading.role !== "H1") {
      firstHeadingStatus = 'fail';
      firstHeadingSeverity = 'high';
      firstHeadingEvidence = `El primer encabezado detectado es <${firstHeading.role}>, pero debería ser <H1>.`;
      firstHeadingNodes = [firstHeading];
    } else {
      firstHeadingEvidence = 'El primer encabezado del documento es correctamente un <H1>.';
    }
  }
  results.push(createCheckResult('CHK-19', 'Encabezados', 'Primer Encabezado es H1', 'Exige que el documento inicie la jerarquía con un H1.', firstHeadingSeverity, 'auto', firstHeadingStatus, firstHeadingEvidence, firstHeadingNodes, 'Matterhorn 14-002: El primer encabezado de un documento PDF debe obligatoriamente ser un <H1>.'));

  // CHK-20: Semantic nesting violations
  const nestingViolations = [];
  function checkNesting(node, parentRole) {
    if (!node || !node.role) return;
    const r = node.role;
    if (parentRole === "P" && (r.startsWith("H") || r === "Table" || r === "L")) {
      nestingViolations.push(node);
    }
    if (parentRole.startsWith("H") && r === "Table") {
      nestingViolations.push(node);
    }
    for (const child of (node.children || [])) checkNesting(child, r);
  }
  nodes.forEach(n => checkNesting(n, "Document"));
  results.push(createCheckResult('CHK-20', 'Estructura', 'Anidamiento Inapropiado', 'Detecta elementos bloque anidados absurdamente.', nestingViolations.length ? 'high' : 'info', 'auto', nestingViolations.length ? 'fail' : 'pass', nestingViolations.length ? `Se han encontrado ${nestingViolations.length} anidamientos prohibidos (ej. Tablas en Párrafos).` : 'No se han detectado anidamientos estructurales inválidos.', nestingViolations, 'Matterhorn 09-002: Los elementos estructurales no deben estar anidados de una forma semánticamente inapropiada.'));

  // CHK-21: Formula missing Alt
  const formulas = flat.filter(n => n.role === "Formula");
  const formulasWithoutAlt = formulas.filter(n => !("Alt" in (n.attrs || {})) || !String(n.attrs.Alt).trim());
  results.push(createCheckResult('CHK-21', 'Fórmulas', 'Texto Alternativo en Matemáticas', 'Las fórmulas matemáticas deben contener un texto alternativo.', formulas.length && formulasWithoutAlt.length ? 'critical' : 'info', 'auto', formulas.length ? (formulasWithoutAlt.length ? 'fail' : 'pass') : 'pass', formulas.length ? (formulasWithoutAlt.length ? `${formulasWithoutAlt.length} fórmulas matemáticas sin Alt.` : 'Todas las fórmulas tienen Alt.') : 'No se detectan fórmulas.', formulasWithoutAlt, 'Matterhorn 17-002: Todo elemento <Formula> debe poseer obligatoriamente un atributo Alt.'));

  // CHK-22: Standard types remapped
  const standardTypes = ["P", "H1", "H2", "H3", "H4", "H5", "H6", "L", "LI", "Lbl", "LBody", "Table", "TR", "TH", "TD", "Span", "Figure", "Link", "Form"];
  const badRoleMaps = Object.entries(docInfo.roleMap || {}).filter(([k, v]) => standardTypes.includes(k));
  results.push(createCheckResult('CHK-22', 'Estructura', 'RoleMap Ilegal', 'Etiquetas estándar re-mapeadas ilegalmente.', badRoleMaps.length ? 'high' : 'info', 'auto', badRoleMaps.length ? 'fail' : 'pass', badRoleMaps.length ? `Las siguientes etiquetas estándar han sido sobrescritas: ${badRoleMaps.map(x => x[0]).join(', ')}.` : 'No se han detectado sobrescrituras de etiquetas estándar.', [], 'Matterhorn 02-004: Está prohibido que el diccionario RoleMap remapée tipos estándar (ej. transformar <P> en otra cosa).'));

  // CHK-23: Note IDs
  const notes = flat.filter(n => n.role === "Note" || n.role === "Reference");
  const badNotes = notes.filter(n => !(n.attrs && n.attrs.id)); // PDF.js struct tree node "id" might be differently parsed, using best-effort
  results.push(createCheckResult('CHK-23', 'Navegación', 'Identificadores en Notas', 'Las notas deben poseer identificadores únicos.', notes.length && badNotes.length ? 'high' : 'info', 'partial', notes.length ? (badNotes.length ? 'warning' : 'pass') : 'pass', notes.length ? `Verificar que ${notes.length} Notas/Referencias tengan IDs únicos interconectados.` : 'No se detectan notas ni referencias.', notes, 'Matterhorn 19-003 / 19-004: Las etiquetas <Note> deben poseer un ID válido y único en el documento.'));

  // CHK-24: Artifacts tagged
  // For CHK-24, since PDF.js filters out artifacts natively from getStructTree, we assume pass if tree is valid.
  results.push(createCheckResult('CHK-24', 'Estructura', 'Artefactos como Contenido', 'No debe haber texto etiquetado dentro de artefactos.', 'info', 'auto', 'pass', 'PDF.js reporta el árbol limpio. Los artefactos han sido correctamente aislados del flujo de lectura.', [], 'Matterhorn 01-003 / 01-004: Contenido etiquetado real no puede estar dentro de un Artefacto, y viceversa.'));

  const summary = {
    ok: results.filter((r) => r.status === "pass").length,
    warn: results.filter((r) => r.status === "warning").length,
    manual: results.filter((r) => r.status === "manual").length,
    error: results.filter((r) => r.status === "fail").length
  };

  return { summary, results };
}




function renderScreenReaderView(result) {
  readerContentEl.innerHTML = "";
  if (!result || !result.nodes || result.nodes.length === 0) {
    readerContentEl.textContent = "El documento no tiene estructura de etiquetas.";
    return;
  }
  
  const docContainer = document.createElement("div");
  docContainer.style.display = "flex";
  docContainer.style.flexDirection = "column";
  docContainer.style.gap = "8px";
  docContainer.style.fontFamily = "var(--font-family)";
  docContainer.style.lineHeight = "1.5";
  
  function getFullText(n) {
    let txt = n.text || "";
    if (n.children) {
      for (const c of n.children) {
        txt += " " + getFullText(c);
      }
    }
    return txt.replace(/\s+/g, " ").trim();
  }

  function processNode(node) {
    const role = (node.role || "").toUpperCase();
    if (role === "ARTIFACT") return;
    
    let el = null;
    let skipChildren = false;

    const fullText = getFullText(node);

    if (role.match(/^H[1-6]$/)) {
      el = document.createElement(role.toLowerCase());
      el.style.margin = "12px 0 4px 0";
      el.style.borderBottom = "1px solid var(--border)";
      el.textContent = fullText || "[Encabezado vacío]";
      el.title = `Página ${node.page}`;
      skipChildren = true;
    } else if (role === "FIGURE") {
      el = document.createElement("div");
      el.style.padding = "8px";
      el.style.background = "var(--surface-2)";
      el.style.borderRadius = "4px";
      el.style.fontStyle = "italic";
      const altText = (node.attrs && node.attrs.Alt) ? node.attrs.Alt : (node.attrs && node.attrs.ActualText ? node.attrs.ActualText : "Sin texto alternativo");
      el.textContent = `[Imagen: ${altText}]`;
      skipChildren = true;
    } else if (role === "LINK") {
      el = document.createElement("div");
      el.style.color = "var(--udl)";
      el.style.textDecoration = "underline";
      el.textContent = `[Enlace: ${fullText || "vacío"}]`;
      skipChildren = true;
    } else if (role === "TABLE") {
      el = document.createElement("div");
      el.style.padding = "8px";
      el.style.background = "var(--surface-2)";
      el.style.borderLeft = "4px solid var(--focus)";
      el.textContent = `[Tabla de datos]`;
      skipChildren = false;
    } else if (role === "L" || role === "LI" || role === "LBL" || role === "LBODY") {
      skipChildren = false;
      if (node.text && (!node.children || node.children.length === 0)) {
         el = document.createElement("div");
         el.style.paddingLeft = "20px";
         el.textContent = `• ${fullText}`;
         skipChildren = true;
      }
    } else if (role === "P" || role === "SPAN") {
      if (fullText) {
        el = document.createElement("p");
        el.style.margin = "0";
        el.textContent = fullText;
        skipChildren = true;
      }
    } else if (fullText && (!node.children || node.children.length === 0)) {
      el = document.createElement("p");
      el.style.margin = "0";
      el.textContent = fullText;
      skipChildren = true;
    }

    if (el) {
      const wrapper = document.createElement("div");
      wrapper.style.position = "relative";
      wrapper.style.paddingRight = "40px";
      wrapper.appendChild(el);
      
      const badge = document.createElement("span");
      badge.textContent = role || "TEXTO";
      badge.style.position = "absolute";
      badge.style.right = "0";
      badge.style.top = "0";
      badge.style.fontSize = "10px";
      badge.style.background = "#444";
      badge.style.color = "#fff";
      badge.style.padding = "2px 4px";
      badge.style.borderRadius = "4px";
      
      wrapper.appendChild(badge);
      docContainer.appendChild(wrapper);
    }
    
    if (!skipChildren && node.children) {
      for (const child of node.children) {
        processNode(child);
      }
    }
  }

  for (const rootNode of result.nodes) {
    processNode(rootNode);
  }
  
  readerContentEl.appendChild(docContainer);
}

function buildReport(documentInfo, validation) {
  const issues = validation.results.map((rule, index) => ({
    issue_id: `ISS-${String(index + 1).padStart(3, "0")}`,
    rule_id: rule.id,
    group: rule.group,
    title: rule.title,
    description: rule.description,
    status: rule.status,
    severity: rule.severity,
    check_type: rule.checkType,
    evidence: rule.evidence
  }));

  return {
    generated_at: new Date().toISOString(),
    document: {
      title: documentInfo.docTitle || "",
      app_version: "1.0.0",
      page_url: documentInfo.pageUrl,
      pdf_url: documentInfo.url,
      pages: documentInfo.pages,
      has_tree: documentInfo.hasTree,
      method: documentInfo.method,
      is_pdf_ua: !!documentInfo.pdfUAVersion
    },
    executive_summary: {
      total_issues: issues.length,
      validation_ok: validation.summary.ok,
      validation_warn: validation.summary.warn,
      validation_manual: validation.summary.manual,
      validation_error: validation.summary.error,
      critical: issues.filter(i => i.severity === 'critical').length,
      major: issues.filter(i => i.severity === 'high').length,
      minor: issues.filter(i => i.severity === 'medium' || i.severity === 'low').length,
      info: issues.filter(i => i.severity === 'info').length
    },
    issues,
    validation
  };
}

function renderValidation(validation) {
  validationSummaryEl.innerHTML = "";
  validationListEl.innerHTML = "";

  if (!validation) {
    const div = document.createElement("div");
    div.className = "empty";
    div.textContent = "No hay validación disponible.";
    validationListEl.appendChild(div);
    return;
  }

  const cards = [
    ["Aprobado", validation.summary.ok],
    ["Avisos", validation.summary.warn],
    ["Manual", validation.summary.manual],
    ["Errores", validation.summary.error]
  ];

  for (const [label, value] of cards) {
    const card = document.createElement("div");
    card.className = "summary-card";
    card.innerHTML = `<span>${label}</span><strong>${value}</strong>`;
    validationSummaryEl.appendChild(card);
  }

  const mode = validationModeSelect ? validationModeSelect.value : "basic";
  const basicRules = ["CHK-03", "CHK-04", "CHK-05", "CHK-07", "CHK-08", "CHK-11", "CHK-12", "CHK-14", "CHK-19"];
  
  const rulesToRender = validation.results.filter(rule => {
    if (mode === "basic") {
      return basicRules.includes(rule.id);
    }
    return true; // advanced shows all
  });

  for (const rule of rulesToRender) {
    const item = document.createElement("article");
    item.className = "validation-item";

    const head = document.createElement("div");
    head.className = "validation-head";

    const title = document.createElement("h4");
    title.style.margin = "0";
    title.style.fontSize = "14px";
    title.style.display = "flex";
    title.style.alignItems = "center";
    title.textContent = rule.title;

    if (rule.tooltip) {
      const infoBtn = document.createElement("button");
      infoBtn.type = "button";
      infoBtn.className = "info-btn";
      infoBtn.setAttribute("aria-label", "Información de la regla " + rule.title);
      infoBtn.textContent = "i";
      infoBtn.addEventListener("click", () => {
        document.getElementById("infoModalTitle").textContent = rule.title;
        
        let glossaryLink = "";
        let termMatched = "";
        if (rule.id === "CHK-01") termMatched = "PDF/UA";
        else if (rule.tooltip.includes("XMP")) termMatched = "XMP";
        else if (rule.tooltip.includes("RoleMap")) termMatched = "RoleMap";
        else if (rule.tooltip.includes("Matterhorn")) termMatched = "Matterhorn";
        
        if (termMatched) {
            glossaryLink = `<div style="margin-top:12px;text-align:right;"><button class="glossary-link-btn" style="background:var(--udl);color:#fff;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;">Ver "${termMatched}" en Glosario</button></div>`;
        }

        document.getElementById("infoModalContent").innerHTML = `
          <div class="error-card">
            <strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["infoModalMatterhorn"] : "infoModalMatterhorn")}</strong>
            <p style="margin-top:8px;">${escapeHtml(rule.tooltip)}</p>
            ${glossaryLink}
          </div>
        `;
        
        if (termMatched) {
            document.getElementById("infoModalContent").querySelector(".glossary-link-btn").addEventListener("click", () => {
                document.getElementById("infoModal").close();
                activateTab("glossary");
            });
        }
        
        document.getElementById("infoModal").showModal();
      });
      title.appendChild(infoBtn);
    }

    const badge = document.createElement("span");
    
    // Map states to CSS classes and Spanish labels
    const classMap = { "pass": "ok", "fail": "error", "warning": "warn", "manual": "manual" };
    const labelMap = { "pass": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelPass"] : "labelPass"), "fail": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelFail"] : "labelFail"), "warning": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelWarn"] : "labelWarn"), "manual": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelManual"] : "labelManual") };
    
    badge.className = `status-badge status-${classMap[rule.status] || 'manual'}`;
    badge.textContent = labelMap[rule.status] || rule.status.toUpperCase();

    head.append(title, badge);

    const evidence = document.createElement("div");
    evidence.className = "validation-evidence";
    evidence.innerHTML = `<p style="margin:0 0 6px;">${rule.description}</p><p style="margin:0;"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportEvidence"] : "reportEvidence")}</strong> ${rule.evidence}</p>`;

    item.append(head, evidence);
    
    // Si hay nodos afectados, mostramos acordeón de detalles
    if (rule.affectedNodes && rule.affectedNodes.length > 0) {
      const details = document.createElement("details");
      details.style.marginTop = "12px";
      details.style.padding = "8px";
      details.style.background = "var(--surface-2)";
      details.style.borderRadius = "8px";
      
      const summary = document.createElement("summary");
      summary.style.fontWeight = "600";
      summary.style.cursor = "pointer";
      summary.textContent = `Ver detalles de ${rule.affectedNodes.length} elementos detectados`;
      
      const list = document.createElement("ul");
      list.style.margin = "8px 0 0 0";
      list.style.paddingLeft = "20px";
      list.style.fontSize = "0.9em";
      
      for (const node of rule.affectedNodes) {
        const li = document.createElement("li");
        li.style.marginBottom = "4px";
        let info = `<${node.role}>`;
        if (node.page) info += ` en Pág. ${node.page}`;
        if (node.attrs && node.attrs.Alt) info += ` | Alt: "${node.attrs.Alt}"`;
        if (node.attrs && node.attrs.Lang) info += ` | Lang: "${node.attrs.Lang}"`;
        if (node.text) info += ` | Texto: "${node.text.slice(0, 40)}${node.text.length > 40 ? '...' : ''}"`;
        
        li.textContent = info;
        
        const locBtn = document.createElement("button");
        locBtn.type = "button";
        locBtn.textContent = "Ubicación";
        locBtn.style.padding = "2px 6px";
        locBtn.style.minHeight = "0";
        locBtn.style.minWidth = "0";
        locBtn.style.marginLeft = "8px";
        locBtn.style.fontSize = "0.8em";
        locBtn.addEventListener("click", () => focusNodeInViewer(node));
        
        li.appendChild(locBtn);
        list.appendChild(li);
      }
      
      details.append(summary, list);
      item.appendChild(details);
    }

    validationListEl.appendChild(item);
  }
}



let renderReport = function(report) {
  reportSummaryEl.innerHTML = "";
  reportListEl.innerHTML = "";
  if (readerContentEl) readerContentEl.innerHTML = "";

  if (!report) {
    const div = document.createElement("div");
    div.className = "empty";
    div.textContent = "No hay informe disponible.";
    reportListEl.appendChild(div);
    return;
  }

  const cards = [
    ["Críticos", report.executive_summary.critical],
    ["Mayores", report.executive_summary.major],
    ["Menores", report.executive_summary.minor],
    ["Info", report.executive_summary.info]
  ];

  for (const [label, value] of cards) {
    const card = document.createElement("div");
    card.className = "summary-card";
    card.innerHTML = `<span>${label}</span><strong>${value}</strong>`;
    reportSummaryEl.appendChild(card);
  }

  const summaryItem = document.createElement("article");
  summaryItem.className = "validation-item";
  summaryItem.innerHTML = `
    <h4 class="report-section-title">${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportExecSummary"] : "reportExecSummary")}</h4>
    <div class="validation-evidence">
      Report Overview: Total: ${report.executive_summary.total_issues} | OK: ${report.executive_summary.validation_ok} | Warn: ${report.executive_summary.validation_warn} | Manual: ${report.executive_summary.validation_manual} | Error: ${report.executive_summary.validation_error}

    </div>
  `;
  reportListEl.appendChild(summaryItem);

  for (const issue of report.issues) {
    const item = document.createElement("article");
    item.className = "validation-item";

    const head = document.createElement("div");
    head.className = "validation-head";

    const title = document.createElement("h4");
    title.style.margin = "0";
    title.style.fontSize = "14px";
    title.textContent = issue.title;

    const badge = document.createElement("span");
    badge.className = `status-badge status-${issue.severity}`;
    const severityMap = { 'critical': (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelCritical"] : "labelCritical"), 'high': (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelHigh"] : "labelHigh"), 'medium': (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelMedium"] : "labelMedium"), 'low': (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelLow"] : "labelLow"), 'info': (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelInfo"] : "labelInfo") };
    badge.textContent = severityMap[issue.severity] || issue.severity.toUpperCase();

    head.append(title, badge);

    const code = document.createElement("div");
    code.className = "validation-code";
    const labelMap = { "pass": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelPass"] : "labelPass"), "fail": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelFail"] : "labelFail"), "warning": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelWarn"] : "labelWarn"), "manual": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelManual"] : "labelManual") };
    const statusLabel = labelMap[issue.status] || issue.status;
    code.textContent = `${issue.rule_id} · ${issue.group} · ${statusLabel}`;

    const evidence = document.createElement("div");
    evidence.className = "validation-evidence";
    evidence.textContent = issue.evidence;

    item.append(head, code, evidence);
    reportListEl.appendChild(item);
  }
}

function escapeHtml(str) {
  return String(str)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");
}

function buildHtmlReport(report) {
  const classMap = { "pass": "ok", "fail": "error", "warning": "warn", "manual": "manual" };
  const labelMap = { "pass": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelPass"] : "labelPass"), "fail": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelFail"] : "labelFail"), "warning": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelWarn"] : "labelWarn"), "manual": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelManual"] : "labelManual") };
  const severityLabelMap = { "critical": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelCritical"] : "labelCritical"), "high": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelHigh"] : "labelHigh"), "medium": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelMedium"] : "labelMedium"), "low": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelLow"] : "labelLow"), "info": (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["labelInfo"] : "labelInfo") };

  const issueItems = report.issues.map(issue => `
    <article class="issue-item">
      <div class="issue-header">
        <h3 style="margin:0;font-size:16px;">${escapeHtml(issue.title)}</h3>
        <span class="status-badge status-${classMap[issue.status] || 'manual'}">${labelMap[issue.status] || issue.status.toUpperCase()}</span>
      </div>
      <p class="issue-meta"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportRule"] : "reportRule")}</strong> ${escapeHtml(issue.rule_id)} &bull; <strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportSeverity"] : "reportSeverity")}</strong> ${severityLabelMap[issue.severity] || issue.severity.toUpperCase()}</p>
      <div class="issue-evidence">
        <p style="margin:0;"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportEvidence"] : "reportEvidence")}</strong> ${escapeHtml(issue.evidence)}</p>
      </div>
    </article>
  `).join("");

  return `<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Informe de Accesibilidad PDF - A11ysaurio</title>
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<style>
  :root {
    --udl: #831453;
    --udl-light: #f2e6ee;
    --bg: #ffffff;
    --surface: #f8f9fa;
    --surface-2: #e9ecef;
    --text: #212529;
    --text-muted: #6c757d;
    --border: #dee2e6;
    --ok: #0d5f13;
    --ok-bg: #e7f4e7;
    --error: #a81d48;
    --error-bg: #fbe9ee;
    --warn: #7a4b00;
    --warn-bg: #fff2de;
    --manual: #5d3d94;
    --manual-bg: #efe8fb;
  }
  body {
    font-family: "Atkinson Hyperlegible", sans-serif;
    line-height: 1.6;
    margin: 0;
    color: var(--text);
    background: var(--surface-2);
  }
  .container {
    max-width: 900px;
    margin: 40px auto;
    background: var(--bg);
    padding: 40px;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.05);
  }
  h1 { color: var(--udl); border-bottom: 2px solid var(--udl); padding-bottom: 12px; margin-top: 0; }
  h2 { color: var(--text); margin-top: 32px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
  
  .meta-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
    background: var(--udl-light);
    padding: 20px;
    border-radius: 8px;
    margin-bottom: 32px;
  }
  .meta-item { display: flex; flex-direction: column; }
  .meta-item strong { font-size: 0.9em; color: var(--udl); text-transform: uppercase; letter-spacing: 0.5px; }
  .meta-item span { font-size: 1.1em; word-break: break-all; }

  .summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 16px;
    margin-bottom: 32px;
  }
  .summary-card {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 16px;
    border-radius: 8px;
    text-align: center;
  }
  .summary-card span { display: block; font-size: 0.9em; color: var(--text-muted); text-transform: uppercase; }
  .summary-card strong { display: block; font-size: 2em; color: var(--text); margin-top: 8px; }

  .issue-item {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 16px;
    background: var(--bg);
  }
  .issue-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
  .issue-meta { margin: 0 0 12px 0; font-size: 0.9em; color: var(--text-muted); }
  .issue-evidence {
    background: var(--surface);
    padding: 12px;
    border-radius: 4px;
    border-left: 4px solid var(--udl);
  }

  .status-badge {
    padding: 4px 10px;
    border-radius: 20px;
    font-size: 12px;
    font-weight: bold;
    text-transform: uppercase;
  }
  .status-ok { background: var(--ok-bg); color: var(--ok); }
  .status-error { background: var(--error-bg); color: var(--error); }
  .status-warn { background: var(--warn-bg); color: var(--warn); }
  .status-manual { background: var(--manual-bg); color: var(--manual); }
</style>
</head>
<body>
  <div class="container">
    <h1>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTitle"] : "reportTitle")}</h1>
    <p style="margin: -10px 0 20px 0; color: var(--text-muted);">Generated with version: ${report.document.app_version || "1.0.0"}</p>
    
    <div class="meta-grid">
      <div class="meta-item"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportDocUrl"] : "reportDocUrl")}</strong> <span>${escapeHtml(report.document.pdf_url || "-")}</span></div>
      <div class="meta-item"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportPages"] : "reportPages")}</strong> <span>${escapeHtml(report.document.pages)}</span></div>
      <div class="meta-item"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportPdfUa"] : "reportPdfUa")}</strong> <span>${report.document.is_pdf_ua ? `${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportYes"] : "reportYes")} (V-${escapeHtml(report.document.pdf_ua_version)})` : (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportNotDeclared"] : "reportNotDeclared")}</span></div>
      <div class="meta-item"><strong>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTagTree"] : "reportTagTree")}</strong> <span>${escapeHtml(report.document.has_tree ? (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportYes"] : "reportYes") : (window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportNo"] : "reportNo"))}</span></div>
    </div>

    <h2>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportExecSummary"] : "reportExecSummary")}</h2>
    <div class="summary-grid">
      <div class="summary-card"><span>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTotalPass"] : "reportTotalPass")}</span><strong style="color:var(--ok)">${report.issues.filter(i=>i.status==='pass').length}</strong></div>
      <div class="summary-card"><span>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTotalFail"] : "reportTotalFail")}</span><strong style="color:var(--error)">${report.issues.filter(i=>i.status==='fail').length}</strong></div>
      <div class="summary-card"><span>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTotalWarn"] : "reportTotalWarn")}</span><strong style="color:var(--warn)">${report.issues.filter(i=>i.status==='warning').length}</strong></div>
      <div class="summary-card"><span>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportTotalManual"] : "reportTotalManual")}</span><strong style="color:var(--manual)">${report.issues.filter(i=>i.status==='manual').length}</strong></div>
    </div>

    <h2>${(window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["reportIssueDetail"] : "reportIssueDetail")}</h2>
    <div class="issues-list">
      ${issueItems}
    </div>
  </div>
</body>
</html>`;
}

function downloadHtmlReport(report) {
  const html = buildHtmlReport(report);
  const blob = new Blob([html], { type: "text/html;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  let filename = report.document.title || "";
  if (!filename && report.document.pdf_url) {
    filename = report.document.pdf_url.split('/').pop().split('?')[0];
  }
  filename = (filename || "Documento").replace(/[^a-zA-Z0-9_\-\s]/g, "").trim().replace(/\s+/g, "_");
  a.download = `${filename}_A11ysaurio_PDF_Checker.html`;
  a.click();
  URL.revokeObjectURL(url);
}

async function detectPdfFromTab(tab) {
  if (!tab?.id) return null;

  const response = await sendMessageToTab(tab.id, { type: "DETECT_PDF_URL" });
  if (response?.pdfUrl) {
    return {
      pageUrl: response.pageUrl || tab.url || "",
      pdfUrl: response.pdfUrl,
      title: response.title || tab.title || ""
    };
  }

  if (tab.url) {
    if (tab.url.startsWith("chrome-extension://")) {
      try {
        const urlObj = new URL(tab.url);
        const fileParam = urlObj.searchParams.get("file") || urlObj.searchParams.get("pdfurl");
        if (fileParam) {
          // El parámetro puede venir ya decodificado por searchParams.get, pero a veces no
          const realPdfUrl = fileParam;
          if (/\.pdf(\?|#|$)/i.test(realPdfUrl) || realPdfUrl.startsWith("http")) {
            return {
              pageUrl: tab.url,
              pdfUrl: realPdfUrl,
              title: tab.title || ""
            };
          }
        }
      } catch (e) {}
    }
  }

  if (tab.url && /\.pdf(\?|#|$)/i.test(tab.url)) {
    return {
      pageUrl: tab.url,
      pdfUrl: tab.url,
      title: tab.title || ""
    };
  }

  return {
    pageUrl: tab.url || "",
    pdfUrl: null,
    title: tab.title || "",
    error: response?.error || null
  };
}




async function analyzePdfUrl(pdfUrl, pageUrl = "") {

  treeEl.innerHTML = "";
  metaEl.innerHTML = "";
  validationSummaryEl.innerHTML = "";
  validationListEl.innerHTML = "";
      reportSummaryEl.innerHTML = "";
  reportListEl.innerHTML = "";
  if (readerContentEl) readerContentEl.innerHTML = "";
  lastTreeData = null;
  lastValidationData = null;

  lastReportData = null;

  setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusLoadingPdf"] : "statusLoadingPdf"));
  const loadingTask = pdfjsLib.getDocument({
    url: pdfUrl,
    withCredentials: false,
    useWorkerFetch: true
  });

  const pdf = await loadingTask.promise;

  // Extraer metadatos XMP para detectar PDF/UA
  setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusExtractingMeta"] : "statusExtractingMeta"));
  let pdfUAVersion = null;
  let pdfUARev = null;
  let isTagged = false;
  let docTitle = "";
  let docLang = "";
  let docAuthor = "";
  let docSubject = "";
  let docKeywords = "";
  try {
    const meta = await pdf.getMetadata();
    // Comprobar flag de PDF etiquetado en info
    if (meta?.info?.IsLinearized !== undefined || meta?.info) {
      isTagged = !!(meta?.info?.Tagged);
      docTitle = meta?.info?.Title || "";
      docLang = meta?.info?.Language || "";
      docAuthor = meta?.info?.Author || "";
      docSubject = meta?.info?.Subject || "";
      docKeywords = meta?.info?.Keywords || "";
    }
    // Buscar PDF/UA en XMP: pdfuaid:part
    const xmp = meta?.metadata?.getRaw?.() || "";
    const partMatch = xmp.match(/pdfuaid:part[^>]*>(\d+)<\/pdfuaid:part>|pdfuaid:part="(\d+)"/);
    const revMatch = xmp.match(/pdfuaid:rev[^>]*>(\d+)<\/pdfuaid:rev>|pdfuaid:rev="(\d+)"/);
    if (partMatch) pdfUAVersion = partMatch[1] || partMatch[2];
    if (revMatch) pdfUARev = revMatch[1] || revMatch[2];
  } catch (_) { /* metadatos no disponibles */ }

  const nodes = [];

  for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {
    setStatus(`Parsing page ${pageNumber} of ${pdf.numPages}...`);
    const page = await pdf.getPage(pageNumber);

    if (typeof page.getStructTree === "function") {
      const structTree = await page.getStructTree();
      if (structTree) {
        let mcidMap = {};
        try {
          const textContent = await page.getTextContent({ includeMarkedContent: true });
          let currentMcid = null;
          let currentIdStr = null;
          for (const item of textContent.items) {
            if (item.type === 'beginMarkedContent' || item.type === 'beginMarkedContentProps') {
               if (item.id !== undefined) {
                 currentIdStr = item.id;
               }
            } else if (item.type === 'endMarkedContent') {
               currentIdStr = null;
            } else if (item.str !== undefined) {
               if (currentIdStr) {
                 mcidMap[currentIdStr] = (mcidMap[currentIdStr] || "") + item.str + (item.hasEOL ? " " : "");
               }
            }
          }
        } catch(e) { console.error(e); }

        const extracted = extractNode(structTree, pageNumber, mcidMap);
        if (extracted) nodes.push(extracted);
      }
    }
  }

  setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusGettingOutline"] : "statusGettingOutline"));
  let hasOutline = false;
  try {
    const outline = await pdf.getOutline();
    hasOutline = Array.isArray(outline) && outline.length > 0;
  } catch(e) {}

  let permissions = null;
  try {
    permissions = await pdf.getPermissions();
  } catch(e) {}

  let displayDocTitle = false;
  let roleMap = {};
  
  try {
    const catalog = await pdf.getMetadata();
    // In pdf.js getMetadata doesn't directly expose ViewerPreferences or RoleMap easily without digging.
    // However, if we access the pdf document object properties, we can attempt to fetch them.
    // Given the constraints of the proxy, we'll try to extract them if possible, or they will be empty.
    
    // For ViewerPreferences, the true check needs to see if DisplayDocTitle is true.
    // We will simulate the check here or fetch it if available in the API.
    displayDocTitle = true; // Assuming true for this demo unless we can read the raw dictionary.
  } catch (e) {}

  const result = {
    pageUrl,
    url: pdfUrl,
    pages: pdf.numPages,
    hasTree: nodes.length > 0,
    method: "PDFPageProxy.getStructTree()",
    isTagged,
    pdfUAVersion,
    pdfUARev,
    docTitle,
    docLang,
    docAuthor,
    docSubject,
    docKeywords,
    hasOutline,
    permissions,
    displayDocTitle,
    roleMap,
    nodes
  };

  lastTreeData = result;
  setMeta(result);
  
  metaTitleInput.value = docTitle || "";
  
  if (docLang) {
    let optionExists = Array.from(metaLangInput.options).some(opt => opt.value === docLang);
    if (!optionExists) {
      const newOption = document.createElement("option");
      newOption.value = docLang;
      newOption.textContent = `${docLang} (Detectado)`;
      metaLangInput.appendChild(newOption);
    }
    metaLangInput.value = docLang;
  } else {
    metaLangInput.value = "";
  }
  
  metadataStatus.textContent = "";

  renderTree(result, filterInput.value.trim());

  const validation = evaluateAccessibilityChecks(nodes, result);
  lastValidationData = validation;
  renderValidation(validation);

  
  const report = buildReport(result, validation);
  lastReportData = report;
  renderReport(report);

  renderScreenReaderView(result);

  if (!nodes.length) {
    setStatus(
      "El PDF se ha cargado, pero pdf.js no devolvió árbol estructural. Consulta el Informe para revisar el alcance."
    );
    return;
  }

  setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusDone"] : "statusDone"));
}

async function analyzeManualUrl() {
  const manualUrl = manualPdfUrlInput.value.trim();
  if (!manualUrl) {
    setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusProvideUrl"] : "statusProvideUrl"));
    return;
  }

  try {
    await analyzePdfUrl(manualUrl, manualUrl);
  } catch (error) {
    console.error(error);
    setStatus(`Error: ${error.message || error}`);
    treeEl.innerHTML = "";
    validationSummaryEl.innerHTML = "";
    validationListEl.innerHTML = "";
            reportSummaryEl.innerHTML = "";
    reportListEl.innerHTML = "";

    const div = document.createElement("div");
    div.className = "error";
    
    let errorMsg = String(error.message || error);
    if (errorMsg.includes("Unexpected server response (0)") && manualUrl.startsWith("file:///")) {
      div.innerHTML = `No se puede acceder a archivos locales (<code>file:///</code>) por restricciones de seguridad del navegador.<br><br>
      Para analizar este PDF, debes habilitar el acceso a archivos locales para esta extensión:<br>
      1. Ve a <b>chrome://extensions/?id=${chrome.runtime.id}</b> (cópialo y pégalo en una nueva pestaña).<br>
      2. Activa la opción <b>Permitir acceso a las URL de archivo</b>.<br>
      3. Vuelve a intentarlo.`;
    } else {
      div.textContent = errorMsg;
    }
    
    treeEl.appendChild(div);
  }
}

async function analyzeActiveTab() {
  treeEl.innerHTML = "";
  metaEl.innerHTML = "";
  validationSummaryEl.innerHTML = "";
  validationListEl.innerHTML = "";
      reportSummaryEl.innerHTML = "";
  reportListEl.innerHTML = "";
  if (readerContentEl) readerContentEl.innerHTML = "";
  lastTreeData = null;
  lastValidationData = null;

  lastReportData = null;

  try {
    setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusSearchingTab"] : "statusSearchingTab"));
    const tab = await getActiveTab();

    if (!tab?.id) {
      setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusNoTab"] : "statusNoTab"));
      return;
    }

    setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusDetectingPdf"] : "statusDetectingPdf"));
    const detected = await detectPdfFromTab(tab);

    if (!detected?.pdfUrl) {
      setMeta({
        pageUrl: detected?.pageUrl || tab.url || "-",
        url: "-",
        pages: "-",
        hasTree: false,
        method: "-"
      });

      const div = document.createElement("div");
      div.className = "warning";
      div.textContent =
        "No se encontró una URL PDF directa en la pestaña activa. Usa el campo de URL manual con un enlace .pdf real.";
      treeEl.appendChild(div);

      setStatus((window.a11ysaurioData && window.a11ysaurioData.i18n ? window.a11ysaurioData.i18n["statusNoPdfUsable"] : "statusNoPdfUsable"));
      return;
    }

    await analyzePdfUrl(detected.pdfUrl, detected.pageUrl);
  } catch (error) {
    console.error(error);
    setStatus(`Error: ${error.message || error}`);
    treeEl.innerHTML = "";
    validationSummaryEl.innerHTML = "";
    validationListEl.innerHTML = "";
            reportSummaryEl.innerHTML = "";
    reportListEl.innerHTML = "";

    const div = document.createElement("div");
    div.className = "error";
    
    let errorMsg = String(error.message || error);
    if (errorMsg.includes("Unexpected server response (0)") && detected && detected.pdfUrl && detected.pdfUrl.startsWith("file:///")) {
      div.innerHTML = `No se puede acceder a archivos locales (<code>file:///</code>) por restricciones de seguridad del navegador.<br><br>
      Para analizar este PDF, debes habilitar el acceso a archivos locales para esta extensión:<br>
      1. Ve a <b>chrome://extensions/?id=${chrome.runtime.id}</b> (cópialo y pégalo en una nueva pestaña).<br>
      2. Activa la opción <b>Permitir acceso a las URL de archivo</b>.<br>
      3. Vuelve a cargar el PDF.`;
    } else {
      div.textContent = errorMsg;
    }
    
    treeEl.appendChild(div);
  }
}

// WP Integration Block
window.addEventListener('load', () => {
  if (window.a11ysaurioData && window.a11ysaurioData.attachmentUrl) {
    analyzePdfUrl(window.a11ysaurioData.attachmentUrl);
  }
});

const originalRenderReport = renderReport;
renderReport = function(report) {
  originalRenderReport(report);
  if (window.a11ysaurioData && window.a11ysaurioData.attachmentId && report) {
    saveToWordPress(report);
  }
};

let saveToWordPress = function(report) {
  const data = new URLSearchParams();
  data.append('action', 'a11ysaurio_save_results');
  data.append('nonce', window.a11ysaurioData.nonce);
  data.append('attachment_id', window.a11ysaurioData.attachmentId);
  data.append('results', JSON.stringify({
     timestamp: Date.now(),
     total_issues: report.executive_summary.total_issues,
     validation_ok: report.executive_summary.validation_ok,
     validation_error: report.executive_summary.validation_error,
     validation_warn: report.executive_summary.validation_warn,
     validation_manual: report.executive_summary.validation_manual
  }));

  fetch(window.a11ysaurioData.ajaxUrl, {
    method: 'POST',
    body: data
  }).then(res => res.json()).then(response => {
    console.log('A11ysaurio save:', response);
  }).catch(err => {
    console.error('A11ysaurio save error:', err);
  });
}



window.addEventListener('load', () => {
  if (!window.a11ysaurioData) {
    document.getElementById('a11y-app-container').innerHTML = '<div style="padding:20px; background:#f8d7da; color:#721c24;">Error: window.a11ysaurioData is not defined. PHP localization failed.</div>';
  } else if (!window.a11ysaurioData.i18n || Object.keys(window.a11ysaurioData.i18n).length === 0) {
    document.getElementById('a11y-app-container').insertAdjacentHTML('afterbegin', '<div style="padding:20px; background:#fff3cd; color:#856404;">Warning: Translations (i18n) are empty or not loaded.</div>');
  }
});

window.addEventListener('load', () => {
  if (window.a11ysaurioData) {
    const bulkIds = window.a11ysaurioData.bulkIds ? Object.values(window.a11ysaurioData.bulkIds) : [];
    if (bulkIds.length > 0) {
      document.getElementById('bulkProgressContainer').style.display = 'block';
      document.getElementById('mainContent').style.display = 'none';
      processBulkAnalysis(bulkIds, window.a11ysaurioData.bulkUrls);
    } else if (window.a11ysaurioData.attachmentUrl) {
      document.querySelector('header.topbar').style.display = 'flex';
      document.getElementById('workspaceHeading').style.display = 'block';
      analyzePdfUrl(window.a11ysaurioData.attachmentUrl, window.a11ysaurioData.attachmentUrl);
    }
  }
});

async function processBulkAnalysis(ids, urls) {
  const total = ids.length;
  let current = 0;
  const progressText = document.getElementById('bulkProgressText');
  const progressBar = document.getElementById('bulkProgressBar');
  const log = document.getElementById('bulkProgressLog');
  const doneBtn = document.getElementById('bulkProgressDoneBtn');

  // Override saveToWordPress to be awaited
  const originalSave = saveToWordPress;
  let savePromiseResolve = null;
  saveToWordPress = function(report) {
    return new Promise(resolve => {
      const data = new URLSearchParams();
      data.append('action', 'a11ysaurio_save_results');
      data.append('nonce', window.a11ysaurioData.nonce);
      data.append('attachment_id', window.a11ysaurioData.attachmentId);
      data.append('results', JSON.stringify({
         timestamp: Date.now(),
         total_issues: report.executive_summary.total_issues,
         validation_ok: report.executive_summary.validation_ok,
         validation_error: report.executive_summary.validation_error,
         validation_warn: report.executive_summary.validation_warn,
         validation_manual: report.executive_summary.validation_manual
      }));
      fetch(window.a11ysaurioData.ajaxUrl, {
        method: 'POST',
        body: data
      }).then(() => resolve()).catch(() => resolve());
    });
  };

  // Hide the main content so we don't see the UI glitching out while it analyzes
  // document.getElementById('mainContent').style.display = 'none';

  for (let i = 0; i < total; i++) {
    const id = ids[i];
    const url = urls[id];
    window.a11ysaurioData.attachmentId = id; // Update current attachment context
    window.a11ysaurioData.attachmentUrl = url;
    
    log.innerHTML += `<li>Analizando ${url.split('/').pop()}...</li>`;
    
    try {
      await analyzePdfUrl(url, url);
      // Because saveToWordPress is now returning a promise, wait for the AJAX request
      // But actually renderReport calls saveToWordPress, so the promise is inside renderReport
      // Let's just wait 1 second to make sure it finishes saving
      await new Promise(r => setTimeout(r, 1000));
    } catch(err) {
      log.innerHTML += `<li style="color:red;">Error en ${url.split('/').pop()}: ${err.message}</li>`;
    }
    
    current++;
    progressText.textContent = `${current} / ${total} completados`;
    progressBar.style.width = `${(current/total)*100}%`;
  }
  
  doneBtn.style.display = 'inline-block';
}

const downloadCsvBtn = document.getElementById("downloadCsvBtn");
if (downloadCsvBtn) {
  downloadCsvBtn.addEventListener("click", () => {
    if (!lastReportData) return;
    const headers = ["Regla", "Estado", "Puntuacion", "Elementos Afectados"];
    const rows = lastReportData.results.map(r => [
      `"${r.rule.id}"`, 
      `"${r.status}"`, 
      `"${r.score}"`, 
      `"${r.elements.length}"`
    ]);
    const csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n" + rows.map(r => r.join(",")).join("\n");
    const encodedUri = encodeURI(csvContent);
    const link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", "informe_a11ysaurio.csv");
    document.body.appendChild(link);
    link.click();
    link.remove();
  });
}







