/* ============================================================
   CIEN — Renderer genérico Markdown → artículo de informe
   Carga `contenido-informes/<slug>.md` en runtime y lo convierte
   al mismo HTML que producen los componentes hand-crafted
   (informe-quince-meses.jsx / informe-mercado-trabajo.jsx).
   ============================================================ */

(function(){
  const FIG_PFX_RE = /^(Gráfico|Tabla|Figura)\s*\d+\s*[:.\-–—]\s*/i;

  function parseInline(s, keyBase){
    if(!s) return s;
    const out = [];
    let key = 0;
    let i = 0;
    const push = (n)=>{ out.push(n); };
    // Combined inline regex: bold, italic, link
    const RX = /\*\*([^*]+?)\*\*|\*([^*\n]+?)\*|\[([^\]]+?)\]\(([^)]+?)\)/g;
    let m;
    let last = 0;
    while((m = RX.exec(s))!==null){
      if(m.index > last) push(s.slice(last, m.index));
      if(m[1]!==undefined){
        push(<strong key={keyBase+":b"+(key++)}>{parseInline(m[1], keyBase+":b"+key)}</strong>);
      } else if(m[2]!==undefined){
        push(<em key={keyBase+":i"+(key++)}>{parseInline(m[2], keyBase+":i"+key)}</em>);
      } else if(m[3]!==undefined){
        const href = m[4];
        const ext = /^(https?:)?\/\//i.test(href);
        push(
          <a key={keyBase+":a"+(key++)} href={href}
             target={ext?"_blank":undefined} rel={ext?"noreferrer":undefined}>
            {m[3]}
          </a>
        );
      }
      last = m.index + m[0].length;
    }
    if(last < s.length) push(s.slice(last));
    if(out.length === 0) return s;
    if(out.length === 1) return out[0];
    return out;
  }

  // Heurística para detectar bloques de "preámbulo" que repiten título/subtítulo/fecha
  // (los .md vienen con ese contenido entre `---` y el cuerpo real). Saltamos hasta
  // que aparece un heading o un párrafo lo bastante largo para ser cuerpo real.
  function isPreambleBlock(block){
    if(/^\s*$/.test(block)) return true;
    if(/^#/.test(block)) return false;          // un heading rompe el preámbulo
    if(/^!\[/.test(block)) return false;        // una figura rompe el preámbulo
    if(/^- /.test(block)) return false;         // una lista rompe el preámbulo
    if(/^<small>/i.test(block)) return true;    // sueltos: skip
    const txt = block.replace(/\s+/g, " ").trim();
    // Si el "bloque" es corto y no tiene puntuación de oración → probablemente título/subtítulo/fecha
    if(txt.length < 140 && !/[.!?]\s/.test(txt)) return true;
    return false;
  }

  // normaliza a solo-letras (para comparar el título del .md con su duplicado)
  function normLetters(s){
    // NFD descompone acentos; [^a-z] luego barre marcas y todo lo no-letra.
    return (s||"").toLowerCase().normalize("NFD").replace(/[^a-z]/g,"");
  }

  // ¿una línea suelta parece un título de sección? (corta, capitalizada, sin
  // puntuación de oración al final). Se usa SOLO en informes que no traen
  // encabezados markdown (## / ###), para no tocar los que ya renderan bien.
  function looksLikeSectionTitle(t){
    if(!t || t.indexOf("\n") >= 0) return false;
    if(t.length < 3 || t.length > 48) return false;
    if(/^[!\-*<#|>0-9]/.test(t)) return false;        // no md-especial ni empieza con dígito
    if(/[.,:;)]$/.test(t)) return false;               // sin puntuación de oración al final
    if(!/^[A-ZÁÉÍÓÚÜÑ¿¡]/.test(t)) return false;       // arranca en mayúscula
    if(/\d/.test(t)) return false;                     // los títulos no traen números (sí las filas de tabla)
    const words = t.split(/\s+/);
    if(words.length > 6) return false;                 // los títulos son cortos
    // Filas de tabla del tipo "Valor Precio Cantidad" / "Salud Promoción ...":
    // varias palabras Capitalizadas (mixtas, no siglas) → no es un título.
    let capsAfter = 0;
    for(let i=1;i<words.length;i++){
      const w = words[i];
      if(/^[A-ZÁÉÍÓÚÜÑ]/.test(w) && w !== w.toUpperCase()) capsAfter++;
    }
    if(capsAfter >= 2) return false;
    return true;
  }

  // ¿la línea es en realidad datos/eje de gráfico (no un título)? Algunos .md
  // traen "### $ 750.000 USD 110.000" por una mala extracción del PDF.
  function isDataLikeLine(s){
    if(/^\s*\$/.test(s)) return true;
    if(/\bUSD\b/.test(s) && /\d/.test(s)) return true;
    if(/\d[.,]\d{3}/.test(s)) return true;                 // 750.000
    if(/\d/.test(s) && (s.match(/\d+(?:[.,]\d+)?%?/g)||[]).length >= 2) return true;
    return false;
  }

  // ¿la línea está DOMINADA por números (eje de gráfico / fila de datos), con casi
  // nada de texto? — p.ej. "$ 750.000 USD 110.000". Un título que solo MENCIONA
  // números ("...los artículos 60 y 76") NO cae acá.
  function isPureData(s){
    const letters = (s.match(/[a-záéíóúüñ]/gi)||[]).length;
    const digits = (s.match(/\d/g)||[]).length;
    return digits >= 3 && letters < digits;
  }

  // Línea marcada como título por el .md pero que NO lo es: pie de gráfico/tabla,
  // una oración entera, o una cabecera de tabla.
  function headingLooksBogus(t){
    if(/^(gráfico|grafico|tabla|figura|cuadro|fuente)\b/i.test(t)) return true; // pie/fuente de gráfico/tabla
    if(/\.$/.test(t)) return true;                                          // termina en punto → oración, no título
    if(t.indexOf(", ") >= 0) return true;                                   // es una oración
    if(t.length > 70) return true;                                          // demasiado largo
    const w = t.split(/\s+/);
    if(/^[A-ZÁÉÍÓÚÜÑ0-9 .%\-]+$/.test(t) && w.length >= 2) return true;     // CABECERA EN MAYÚSCULAS
    let caps = 0;
    for(let i=1;i<w.length;i++){ if(/^[A-ZÁÉÍÓÚÜÑ]/.test(w[i]) && w[i] !== w[i].toUpperCase()) caps++; }
    if(caps >= 2) return true;                                              // fila tipo Title Case
    return false;
  }

  // Bloque de tabla de datos separada por espacios (mala extracción del PDF):
  // 2+ líneas con muchos números → se muestra monoespaciado.
  function isTableBlock(b){
    const ls = b.split("\n").filter(x=>x.trim());
    if(ls.length < 2) return false;
    let rich = 0;
    for(const l of ls){ if((l.match(/-?\d+(?:[.,]\d+)?%?/g)||[]).length >= 4) rich++; }
    return rich >= 2;
  }

  // Encabezado que abre un resumen ejecutivo. No es una sección del informe:
  // se muestra aparte, arriba del cuerpo, y no consume número ni entra al índice.
  const SUMMARY_RE = /^(resumen(\s+ejecutivo)?|s[ií]ntesis(\s+ejecutiva)?)\s*:?\s*$/i;

  // Boilerplate institucional que los informes de coyuntura traen pegado arriba de
  // todo (viene de la contratapa del PDF). Son cinco párrafos fijos: presentación
  // del centro y llamada al mailing. No es cuerpo del informe. En algunos .md
  // vienen como un solo bloque y en otros como cinco, así que van uno por uno.
  const BOILERPLATE_RE = [
    /^Somos el Centro de Investigación de Economía Nacional\s*\(CIEN\)/i,
    /^Motivados por las recurrentes dificultades económicas/i,
    /^Los tiempos que corren exigen cuadros técnicos/i,
    /^No tenemos todas las respuestas, pero buscamos participar/i,
    /^¡?\s*Te invitamos a que nos sigas/i
  ];

  // Varios .md traen las viñetas como carácter suelto (●, ○, •…) porque salieron
  // de la extracción del PDF, no como listas markdown. Sin esto el bloque entero
  // cae en isTableBlock() y termina renderizado como <pre> monoespaciado.
  function normalizeBullets(text){
    return text
      .replace(/^[ \t]*[●•▪■]\s+/gm, "- ")       // ● • ▪ ■  → nivel 1
      .replace(/^[ \t]*[○◦□]\s+/gm, "  - ")      // ○ ◦ □    → nivel 2
      .replace(/^[ \t]{2,}-\s+/gm, "  - ");      // dash ya indentado → nivel 2
  }

  function parseInformeMarkdown(rawText, slug){
    let text = normalizeBullets(rawText.replace(/\r\n/g, "\n"));

    // Título del .md ("# ..."), para descartar su duplicado dentro del cuerpo.
    const titleM = text.match(/^#\s+(.+)$/m);
    const titleKey = titleM ? normLetters(titleM[1]) : "";

    // 1. Cortar todo hasta el primer "---" (separador después del título/meta)
    const firstHrIdx = text.search(/\n---\s*\n/);
    if(firstHrIdx >= 0) text = text.slice(text.indexOf("\n", firstHrIdx+1)+1);

    // 2. Cortar la sección final del download "**[⬇ Descargar... ](pdfs/...)**"
    text = text.replace(/\n---\s*\n\s*\*\*\[⬇[\s\S]*$/m, "");
    text = text.replace(/\n\s*\*\*\[⬇[\s\S]*$/m, "");

    // 3. Bloques separados por línea(s) en blanco
    const blocks = text.split(/\n\s*\n+/).map(b=>b.trim()).filter(Boolean);

    // ¿El informe trae encabezados markdown propios (## / ###)? Si sí, NO
    // activamos la heurística de "títulos sueltos": esos informes ya renderan
    // bien y no queremos inventar secciones de más.
    let mdHeadingCount = 0;
    for(const b of blocks){
      if(b.indexOf("\n") < 0 && /^#{2,3}\s+/.test(b)) mdHeadingCount++;
    }
    const detectBareTitles = mdHeadingCount < 2;

    const nodes = [];
    const summaryNodes = [];
    const toc = [];
    let figCount = 0;
    let inPreamble = true;

    // Todo lo que se emite va a `sink`. Vale `nodes` (el cuerpo) salvo mientras
    // estamos dentro del resumen ejecutivo de apertura, donde vale `summaryNodes`.
    let sink = nodes;
    let inSummary = false;

    const SUBSEC_RE = /^(\d+(?:\.\d+)+\.?)\s+(\S.+)$/;

    let headingSeq = 0, h2count = 0;
    // hashes: 2|3 = sección principal (h2, numerada, en índice);
    //         4 = subsección (h3, en índice, indentada); 5+ = sub-sub (h4, fuera del índice).
    const pushHeading = (title, bi, hashes)=>{
      const h = hashes || 3;

      // "Resumen" / "Resumen ejecutivo" de apertura: no es la sección 01 del
      // informe. Se desvía a su propio bloque, sin número y fuera del índice.
      if(!inSummary && h2count === 0 && summaryNodes.length === 0 && SUMMARY_RE.test(title)){
        inSummary = true;
        sink = summaryNodes;
        return;
      }
      // Cualquier otro encabezado cierra el resumen y devuelve el flujo al cuerpo.
      if(inSummary){ inSummary = false; sink = nodes; }

      const id = "sec" + (++headingSeq);
      if(h >= 5){
        sink.push(<h4 id={id} key={"h-"+bi}>{title}</h4>);
        return;
      }
      if(h === 4){
        toc.push({ id, label: title, sub: true });
        sink.push(<h3 id={id} key={"h-"+bi}>{title}</h3>);
        return;
      }
      h2count++;
      toc.push({ id, label: title });
      sink.push(
        <h2 id={id} key={"h-"+bi}>
          <span className="num">{String(h2count).padStart(2,"0")}</span>{title}
        </h2>
      );
    };

    for(let bi=0; bi<blocks.length; bi++){
      const block = blocks[bi];
      const firstLine = block.split("\n")[0];
      const single = block.indexOf("\n") < 0;
      const collapsed = block.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
      const nextIsImg = (bi+1 < blocks.length) && /^!\[/.test(blocks[bi+1]);

      // Duplicado del título del .md (exacto o por inclusión) → descartar.
      const ck = single ? normLetters(collapsed) : "";
      if(titleKey && ck && (ck === titleKey || (titleKey.length >= 8 && (ck.indexOf(titleKey) >= 0 || titleKey.indexOf(ck) >= 0)))) continue;

      // Presentación institucional y llamada al mailing de la portada del PDF.
      // Solo se barre mientras seguimos en el preámbulo: más abajo, un párrafo
      // que empiece igual es texto del informe y no se toca.
      if(inPreamble && BOILERPLATE_RE.some(re=> re.test(collapsed))) continue;

      // Tabla markdown con pipes ( | a | b | ) → <table className="data">
      if(firstLine.indexOf("|") >= 0 && (block.match(/\|/g)||[]).length >= 3){
        const rws = block.split("\n").map(l=>l.trim()).filter(Boolean);
        const cells = (r)=> r.replace(/^\|/,"").replace(/\|$/,"").split("|").map(c=>c.trim());
        let header = null; const body = [];
        for(const r of rws){
          const c = cells(r);
          if(c.length && c.every(x=> x==="" || /^:?-{2,}:?$/.test(x))) continue;  // fila separadora ---
          if(!header) header = c; else body.push(c);
        }
        if(header && body.length){
          inPreamble = false;
          sink.push(
            <div className="tbl-wrap" key={"pt-"+bi}>
              <table className="data">
                <thead><tr>{header.map((h,j)=>(<th key={j}>{parseInline(h, "th-"+bi+"-"+j)}</th>))}</tr></thead>
                <tbody>
                  {body.map((row,ri)=>(
                    <tr key={ri}>{row.map((cc,cj)=>(
                      <td key={cj} className={/^[-$(\d]/.test(cc) ? "num" : undefined}>{parseInline(cc, "td-"+bi+"-"+ri+"-"+cj)}</td>
                    ))}</tr>
                  ))}
                </tbody>
              </table>
            </div>
          );
          continue;
        }
      }

      // Heading markdown ## / ###
      const hMatch = firstLine.match(/^(#{2,5})\s+(.+)$/);
      if(hMatch && single){
        inPreamble = false;
        let title = hMatch[2].trim();
        const numMatch = title.match(/^(\d+)[.)]\s+(.+)$/);
        if(numMatch) title = numMatch[2];
        // En un encabezado markdown EXPLÍCITO confiamos en el autor. Solo descartamos:
        // líneas de PUROS datos/eje (dominadas por números), pies de gráfico/tabla, y
        // oraciones muy largas. NO usamos isDataLikeLine (un título legítimo puede citar
        // números, ej. "...los artículos 60 y 76") ni el chequeo agresivo de mayúsculas
        // (volteaba "Parte 2. El Fondo de Asistencia Laboral: ahora despedir es gratis").
        if(isPureData(title)
           || /^(gráfico|grafico|tabla|figura|cuadro|fuente)\b/i.test(title)
           || title.length > 90){
          sink.push(<p key={"p-"+bi}>{parseInline(title, "p-"+bi)}</p>);
          continue;
        }
        pushHeading(title, bi, hMatch[1].length);
        continue;
      }

      // Figura: empieza con ![alt](path)
      if(/^!\[/.test(firstLine)){
        const imgM = firstLine.match(/^!\[([^\]]*)\]\(([^)]+)\)\s*$/);
        if(imgM){
          inPreamble = false;
          figCount++;
          const src = imgM[2];
          let cap = "", source = "";
          const lines = block.split("\n");
          let capLine = "";
          if(lines.length > 1) capLine = lines.slice(1).join(" ").trim();
          else if(bi+1 < blocks.length && /^<small>/i.test(blocks[bi+1].split("\n")[0])){
            capLine = blocks[bi+1].trim();
            bi++;
          }
          if(capLine){
            const sM = capLine.match(/^<small>([\s\S]*?)<\/small>\s*$/i);
            if(sM){
              const inner = sM[1].trim();
              const fM = inner.match(/^Fuente:\s*(.+?)\.?\s*$/i);
              if(fM){ source = fM[1]; }
              else {
                let rest = inner.replace(FIG_PFX_RE, "");
                const inlineF = rest.match(/^([\s\S]*?)\.?\s*Fuente:\s*(.+?)\.?\s*$/i);
                if(inlineF){ cap = inlineF[1].trim(); source = inlineF[2].trim(); }
                else { cap = rest.trim(); }
              }
            }
          }
          sink.push(<Figure key={"fig-"+bi} src={src} num={String(figCount)} cap={cap} source={source} />);
          continue;
        }
      }

      // Lista. Dos niveles: "- " es nivel 1 y "  - " (lo que normalizeBullets
      // produce a partir de ○/◦ o de un dash ya indentado) cuelga del anterior.
      if(/^- /.test(firstLine)){
        inPreamble = false;
        const items = [];
        let cur = null;
        for(const line of block.split("\n")){
          const top = line.match(/^- (.*)$/);
          const sub = line.match(/^ {2,}- (.*)$/);
          if(top){ cur = { text: top[1].trim(), kids: [] }; items.push(cur); }
          else if(sub && cur){ cur.kids.push(sub[1].trim()); }
          else if(cur){
            // línea suelta: continúa el último ítem abierto (nivel 2 si lo hay)
            const t = line.trim();
            if(!t) continue;
            if(cur.kids.length) cur.kids[cur.kids.length-1] += " " + t;
            else cur.text += " " + t;
          }
        }
        // Un único bullet corto, tipo título y sin puntuación de oración suele ser
        // un apartado mal extraído del PDF (ej. "- Conclusión") → lo tratamos como sección.
        if(items.length === 1 && items[0].kids.length === 0){
          const it = items[0].text;
          if(it.length >= 3 && it.length <= 60 && /^[A-ZÁÉÍÓÚÜÑ¿¡]/.test(it) && !/[.;:]$/.test(it)
             && it.split(/\s+/).length <= 9 && !isDataLikeLine(it) && !headingLooksBogus(it)){
            pushHeading(it, bi);
            continue;
          }
        }
        if(!items.length) continue;
        sink.push(
          <ul key={"ul-"+bi}>
            {items.map((it,i)=>(
              <li key={i}>
                {parseInline(it.text, "ul-"+bi+"-"+i)}
                {it.kids.length > 0 && (
                  <ul>
                    {it.kids.map((k,j)=>(<li key={j}>{parseInline(k, "ul-"+bi+"-"+i+"-"+j)}</li>))}
                  </ul>
                )}
              </li>
            ))}
          </ul>
        );
        continue;
      }

      // Subtítulo enteramente en negrita: **Texto** → sección (con índice).
      // Excluimos oraciones completas en negrita (con coma o punto final), que
      // son énfasis de párrafo y no títulos.
      const boldOnly = firstLine.match(/^\*\*(.+?)\*\*[.\s]*$/);
      if(boldOnly && single){
        const bt = boldOnly[1].trim();
        if(bt.length < 70 && bt.indexOf(", ") < 0 && !/[.]$/.test(bt) && !isDataLikeLine(bt) && !headingLooksBogus(bt)){
          inPreamble = false;
          pushHeading(bt, bi);
          continue;
        }
      }

      // <small> suelto (caption huérfana) → skip
      if(/^<small>/i.test(firstLine)) continue;

      // Sub-sub-heading "2.1 Etapa ..." (numeración con punto + texto debajo)
      const subM = firstLine.match(SUBSEC_RE);
      if(subM && !single){
        inPreamble = false;
        const rest = block.split("\n").slice(1).join(" ").trim();
        sink.push(<h3 key={"sh-"+bi}>{firstLine}</h3>);
        if(rest) sink.push(<p key={"p-"+bi}>{parseInline(rest, "p-"+bi)}</p>);
        continue;
      }

      // Título de sección como línea suelta (solo en informes sin ###, y nunca
      // si la línea siguiente es una imagen — ahí suele ser el pie del gráfico).
      if(detectBareTitles && single && !nextIsImg && looksLikeSectionTitle(collapsed)){
        inPreamble = false;
        pushHeading(collapsed, bi);
        continue;
      }

      // Tabla de datos separada por espacios (extracción de PDF): se muestra
      // monoespaciada con scroll, en vez de una sopa de números en un párrafo.
      if(isTableBlock(block)){
        inPreamble = false;
        sink.push(<pre className="data-table" key={"tb-"+bi}>{block}</pre>);
        continue;
      }

      // Preámbulo: subtítulo / fecha / bajada corta de portada antes del cuerpo.
      if(inPreamble){
        if(collapsed.length < 70) continue;   // línea corta de portada → descartar
        // Línea suelta, media y SIN puntuación de oración: es el subtítulo de la
        // portada, no un párrafo. Un párrafo de cuerpo siempre trae un punto.
        if(single && collapsed.length < 140 && !/[.!?](\s|$)/.test(collapsed)) continue;
        inPreamble = false;                    // primer párrafo real → arranca el cuerpo
      }

      // Párrafo
      if(!collapsed) continue;
      sink.push(<p key={"p-"+bi}>{parseInline(collapsed, "p-"+bi)}</p>);
    }

    // Red de seguridad: si el informe era SOLO un resumen y no llegó a tener
    // secciones, ese texto es el cuerpo — no lo escondemos en el bloque.
    if(!nodes.length && summaryNodes.length){
      return { body: summaryNodes, toc, summary: [] };
    }
    return { body: nodes, toc, summary: summaryNodes };
  }

  /* El resumen ejecutivo se pinta acá adentro, no se pasa a InformeDetalle:
     esa página advierte (con razón) que envolver el cuerpo en un componente
     nuevo por render remonta InformeMarkdown y dispara un loop de "Cargando…".
     Manteniéndolo interno, la interfaz con pages.jsx no cambia. */
  function InformeMarkdown({ slug, onReady }){
    const [state, setState] = React.useState({ loading: true, nodes: null, summary: null, error: false });
    React.useEffect(()=>{
      let alive = true;
      setState({ loading: true, nodes: null, summary: null, error: false });
      fetch("contenido-informes/"+slug+".md")
        .then(r=> r.ok ? r.text() : Promise.reject(new Error("HTTP "+r.status)))
        .then(text=>{
          if(!alive) return;
          let body, toc, summary;
          try {
            const parsed = parseInformeMarkdown(text, slug);
            body = parsed.body; toc = parsed.toc; summary = parsed.summary;
          } catch(e){
            console.error("[InformeMarkdown] parser falló para", slug, e);
            setState({ loading: false, nodes: null, summary: null, error: true });
            if(onReady) onReady([]);
            return;
          }
          setState({ loading: false, nodes: body, summary: summary, error: false });
          if(onReady) onReady(toc);
        })
        .catch((e)=>{
          if(!alive) return;
          console.error("[InformeMarkdown] fetch falló para", slug, e);
          setState({ loading: false, nodes: null, summary: null, error: true });
          if(onReady) onReady([]);
        });
      return ()=>{ alive = false; };
    }, [slug]);

    if(state.loading){
      return <div className="prose"><p style={{color:"var(--muted)"}}>Cargando informe…</p></div>;
    }
    if(state.error){
      return (
        <div className="prose">
          <p>No se pudo cargar el contenido del informe.</p>
          <p style={{color:"var(--muted)", fontSize:"14px"}}>
            Revisá que el archivo <code>contenido-informes/{slug}.md</code> exista y se sirva
            con el contenido del sitio.
          </p>
        </div>
      );
    }
    const hasSummary = state.summary && state.summary.length > 0;
    return (
      <React.Fragment>
        {hasSummary && (
          <aside className="resumen-ej">
            <div className="k">Resumen ejecutivo</div>
            <div className="c">{state.summary}</div>
          </aside>
        )}
        <div className="prose">{state.nodes}</div>
      </React.Fragment>
    );
  }

  Object.assign(window, { InformeMarkdown, parseInformeMarkdown });
})();
