/* global window */
/* ============================================================
   Report formatting — one dictated body, typeset on the way out.
   Dr. Canapp dictates into Talkatoo and pastes the whole report
   into a single field. This turns that plain text into a properly
   set document: headings, paragraphs, lists and labelled regions,
   without her having to fill in separate boxes.

   Recognised, in order:
     · a short line ending in ":"  or in CAPITALS  → heading
     · "Left shoulder: the biceps…"                → labelled paragraph
     · "-", "•", "*" or "1." at line start         → list
     · anything else                               → paragraph
   Blank lines separate blocks; single newlines inside a paragraph
   are kept as line breaks.
   ============================================================ */
(function () {
  const esc = (s) => String(s == null ? '' : s)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

  const HEADING_WORDS = /^(findings?|impressions?|diagnosis|diagnoses|assessment|recommendations?|plan|technique|history|clinical history|comparison|comments?|conclusions?|summary|discussion|limitations?|sites? (evaluated|examined|imaged)|study|studies|indication|signalment)\b/i;

  const isBullet = (l) => /^([-–—•*·]|\d+[.)])\s+/.test(l);
  const bulletText = (l) => l.replace(/^([-–—•*·]|\d+[.)])\s+/, '');
  const isOrdered = (l) => /^\d+[.)]\s+/.test(l);

  function isHeading(l) {
    if (l.length > 72) return false;
    if (/:$/.test(l)) return true;                                   // "Findings:"
    const letters = l.replace(/[^A-Za-z]/g, '');
    if (letters.length >= 3 && l === l.toUpperCase() && /[A-Z]/.test(l)) return true; // "FINDINGS"
    if (HEADING_WORDS.test(l) && l.split(/\s+/).length <= 4) return true;
    return false;
  }
  const ACRONYMS = /^(mri|ct|us|dicom|stat|roi|rom|ccl|cbc|pt|iv|ia|ap|l|r)$/i;
  function titleCase(s) {
    return s.toLowerCase().split(/(\s+|\/|-)/).map(w => {
      if (!w.trim() || /^[\s/-]+$/.test(w)) return w;
      if (ACRONYMS.test(w)) return w.toUpperCase();
      return w.charAt(0).toUpperCase() + w.slice(1);
    }).join('');
  }
  function headingText(l) {
    const t = l.replace(/\s*:\s*$/, '');
    const letters = t.replace(/[^A-Za-z]/g, '');
    // dictated headings often arrive shouting — set them like a document
    return (letters.length >= 3 && t === t.toUpperCase()) ? titleCase(t) : t;
  }

  /* "Supraspinatus: no significant findings" — a label, then prose.
     The label is set in bold and the colon kept, exactly as dictated.
     Deliberately permissive: labels in these reports carry quotes, slashes,
     parenthesised units and abbreviations — e.g.
       Right “Joint” Capsule Measurements (cm): 0.17
       Right Supraspinatus Tendon Cross-Section Measurements (cm2): 0.59
     The guards below exist only to stop an ordinary sentence that happens to
     contain a colon from being bolded ("...effect on tendon: glucocorticoids
     suppress..."): the label must be reasonably short, at most ten words,
     and contain no sentence-ending punctuation. */
  const LABELLED = /^([^:\n]{1,80}):[ \t]+(\S.*)$/;
  function labelled(line) {
    const m = line.match(LABELLED);
    if (!m) return null;
    const label = m[1].trim();
    if (!label) return null;
    if (!/[A-Za-z]/.test(label)) return null;
    const words = label.split(/\s+/);
    if (words.length > 8) return null;
    if (/[.!?;](\s|$)/.test(label)) return null;   // allows "e.g" style dots, blocks sentences
    /* Labels in these reports are written like titles ("Right Biceps Tendon
       Sheath Diagnosis"). A run of lowercase words before a colon is prose,
       not a label — "…catabolic effect on tendon: glucocorticoids suppress…"
       must stay unbolded. */
    if (words.length > 3) {
      const alpha = words.filter(w => /[A-Za-z]/.test(w));
      const capped = alpha.filter(w => /^[^A-Za-z]*[A-Z]/.test(w));
      if (capped.length * 2 < alpha.length) return null;
    }
    return { label, rest: m[2] };
  }
  const labelHtml = (lab) =>
    `<strong class="rf-label">${esc(lab.label)}:</strong> ${esc(lab.rest)}`;

  function formatReportBody(text) {
    const raw = String(text || '').replace(/\r\n?/g, '\n').replace(/[ \t]+$/gm, '').trim();
    if (!raw) return '';
    const out = [];
    /* Dictated numbered sections are usually interrupted by bullet lists,
       which used to start a fresh <ol> each time — so every section came out
       numbered "1.". The counter carries across those interruptions and
       resets at each heading. */
    let ordinal = 0;
    raw.split(/\n\s*\n+/).forEach(block => {
      const lines = block.split('\n').map(l => l.trim()).filter(Boolean);
      let para = [];
      let list = null;      // { ordered, items[] }
      /* Every dictated line becomes its own paragraph. Joining them with <br>
         made spacing look arbitrary — some blocks tight, some open, depending
         on where she happened to leave a blank line. */
      const flushPara = () => {
        if (!para.length) return;
        para.forEach(line => {
          const lab = labelled(line);
          out.push(lab ? `<p>${labelHtml(lab)}</p>` : `<p>${esc(line)}</p>`);
        });
        para = [];
      };
      const flushList = () => {
        if (!list) return;
        /* A list item that is itself a label — "1. Working diagnoses:" heading
           the bullets beneath it — is set bold, number included. The weight
           sits on the <li> so the list marker picks it up too. */
        const li = (i) => {
          if (/:$/.test(i)) return `<li class="rf-step">${esc(i)}</li>`;
          const lab = labelled(i);
          return `<li>${lab ? labelHtml(lab) : esc(i)}</li>`;
        };
        if (list.ordered) {
          const items = list.items.map(i => li(i)).join('');
          out.push(`<ol start="${ordinal + 1}">${items}</ol>`);
          ordinal += list.items.length;
        } else {
          out.push(`<ul>${list.items.map(i => li(i)).join('')}</ul>`);
        }
        list = null;
      };
      lines.forEach(line => {
        if (isBullet(line)) {
          flushPara();
          const ordered = isOrdered(line);
          if (!list || list.ordered !== ordered) { flushList(); list = { ordered, items: [] }; }
          list.items.push(bulletText(line));
          return;
        }
        flushList();
        if (isHeading(line)) { flushPara(); ordinal = 0; out.push(`<h2>${esc(headingText(line))}</h2>`); return; }
        para.push(line);
      });
      flushList();
      flushPara();
    });
    return out.join('\n');
  }

  /* Older reports were three separate boxes. Fold them into one body so
     nothing written before the change is lost or displayed differently. */
  function reportBody(r) {
    if (!r) return '';
    if (r.body && r.body.trim()) return r.body;
    const parts = [];
    if (r.findings && r.findings.trim()) parts.push('Findings:\n' + r.findings.trim());
    if (r.impression && r.impression.trim()) parts.push('Impression / Diagnosis:\n' + r.impression.trim());
    if (r.recommendations && r.recommendations.trim()) parts.push('Recommendations:\n' + r.recommendations.trim());
    return parts.join('\n\n');
  }

  window.formatReportBody = formatReportBody;
  window.reportBody = reportBody;
})();
