// Matcha POC – Interactive v1.6
// - Summary format (bold + italic line): 
//   **F#### – Clean label.**
//   *Level of Harm – … | Residents Affected – …*
// - Expanded view shows the initial complaint sentence (“Based on …”).
// - Hidden programmatic prompts; Generate PoC in Chat; context uploads; Enter=send.

(function(){
  // ---------- utils ----------
  const log = (...a)=>{ try{ console.log('[MatchaPOC]', ...a);}catch{} };
  const err = (...a)=>{ try{ console.error('[MatchaPOC]', ...a);}catch{} };
  const esc = s => (s||'').replace(/[&<>"']/g, m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
  const NL = '\n';

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
  else init();

  function init(){
    const els = {
      upload:        document.querySelector('#upload'),
      ribbon:        document.querySelector('#citationsRibbon'),
      chat:          document.querySelector('#chat'),
      context:       document.querySelector('#context'),
      exportCsv:     document.querySelector('#exportCsv'),
      exportJson:    document.querySelector('#exportJson'),
      contextUpload: document.querySelector('#contextUpload'),
      contextFiles:  document.querySelector('#contextFiles'),
    };
    const missing = Object.entries(els).filter(([_,v])=>!v).map(([k])=>k);
    if (missing.length){ err('Missing UI elements:', missing.join(', ')); return; }

    const state = {
      citations: [],
      threads: {},       // threadId -> { id, citationId, messages[], draft?, hasInitial?:bool }
      contextText: '',
      activeThreadId: null
    };

    const SYSTEM_ANCHOR =
`You are an SNF compliance expert using CMS SOM Appendix PP language. Produce clear, regulation-aligned Plans of Correction (PoC) tied to the specific F-tag. Keep tone professional, succinct, and compliant.
For each PoC, structure with exactly five sections:
1) Immediate Correction, 2) Staff Training, 3) Monitoring, 4) Policy Review, 5) Expected Outcomes.
Cite only operational steps the facility can actually perform. Avoid generic filler.`;

    // ---------- pdf.js extraction (non-worker for reliability) ----------
    async function extractTextFromPdf(file){
      if (!window.pdfjsLib) throw new Error('pdfjsLib not found (pdf.js not loaded).');
      const array = await file.arrayBuffer();
      const cfg = {
        data: array,
        disableWorker: true,
        cMapUrl: MATCHA_POC?.pdf?.cMapUrl || undefined,
        cMapPacked: true,
        standardFontDataUrl: MATCHA_POC?.pdf?.standardFontDataUrl || undefined
      };
      const pdf = await pdfjsLib.getDocument(cfg).promise;
      const pages = [];
      for (let i=1; i<=pdf.numPages; i++){
        const page = await pdf.getPage(i);
        const content = await page.getTextContent();
        pages.push(content.items.map(it => it.str || '').join(' '));
      }
      return pages.join('\n');
    }

    // ---------- normalize & parse F-tags ----------
    function normalizeFtags(raw){
      let t = raw;
      t = t.replace(/F\s*(\d)(?:\s*(\d)){2,3}/gi, m => { const d=m.replace(/[^0-9]/g,''); return (d.length>=3&&d.length<=4)? 'F'+d.padStart(4,'0') : m; });
      t = t.replace(/F\s*[-–]\s*(\d{3,4})/gi, (_,d)=> 'F'+String(d).padStart(4,'0'));
      t = t.replace(/\(F\s*(\d{3,4})\)/gi, (_,d)=> 'F'+String(d).padStart(4,'0'));
      t = t.replace(/F\s+(\d{3,4})/gi, (_,d)=> 'F'+String(d).padStart(4,'0'));
      return t;
    }

    function parseCitations(fullText){
      const text = normalizeFtags(fullText);
      const matches = [];
      let m; const tagRe = /\b(?:ID\s*Prefix\s*Tag|Prefix\s*Tag|Tag)?:?\s*(F\d{3,4})\b/gi;
      while ((m = tagRe.exec(text)) !== null) matches.push({ ftag:m[1], index:m.index });
      if (!matches.length) for (const lm of text.matchAll(/\bF\d{3,4}\b/g)) matches.push({ ftag:lm[0], index:lm.index });
      matches.sort((a,b)=>a.index-b.index);

      const out = [];
      for (let i=0;i<matches.length;i++){
        const cur = matches[i], next = (i<matches.length-1)?matches[i+1].index:text.length;

        const after = text.slice(cur.index, Math.min(next, cur.index+400));
        let rawLabel = '';
        const lm = after.match(new RegExp(cur.ftag+`\\s*[-–:]\\s*([^\\n]{0,200})`, 'i'));
        if (lm) rawLabel = (lm[1]||'').trim();

        const narrative = text.slice(cur.index, next)
          .replace(new RegExp(`^.*?\\b${cur.ftag}\\b[\\s\\-–:]*`, 'i'),'')
          .trim();

        const meta = extractMeta(narrative);
        const cleanLabel = deriveLabel(rawLabel, narrative, meta);

        out.push({ 
          id:cur.ftag, 
          label: cleanLabel, 
          text:narrative.slice(0, 12000), 
          meta, 
          threadId:`thr_${cur.ftag}_${i}` 
        });
      }
      const byId = new Map();
      for (const c of out){ const ex=byId.get(c.id); if(!ex || c.text.length>ex.text.length) byId.set(c.id, c); }
      return [...byId.values()];
    }

    // ---------- meta + label helpers ----------
    function extractMeta(narrative){
      const meta = { loh:'', affected:'' };

      // accept hyphen or en-dash and both “Affected/Affected:”
      const loh = narrative.match(/Level\s+of\s+Harm\s*[-–:]\s*([^\n\.]+)(?:[.\n]|$)/i);
      if (loh) meta.loh = loh[1].trim();

      const aff = narrative.match(/Residents\s+Affect(?:ed|ed:)\s*[-–:]\s*([^\n\.]+)(?:[.\n]|$)/i);
      if (aff) meta.affected = aff[1].trim();

      return meta;
    }

    function deriveLabel(rawLabel, narrative, meta){
      // If the captured label includes meta lines, discard them.
      let label = (rawLabel || '').replace(/\s{2,}/g,' ').trim();
      if (/Level\s+of\s+Harm|Residents\s+Affect/i.test(label)) label = '';

      // Prefer a sentence that sounds like the tag descriptor, e.g., “Provide and implement…”
      if (!label){
        // Take first non-meta line/sentence
        const lines = narrative.split(/\n+/).map(s=>s.trim()).filter(Boolean);
        const firstNonMeta = lines.find(s => !/^Level\s+of\s+Harm/i.test(s) && !/^Residents\s+Affect/i.test(s));
        if (firstNonMeta){
          // If it starts with “Provide”, “Ensure”, “Develop”, etc., use sentence until first period.
          const m = firstNonMeta.match(/^(.*?)(?:\.|$)/);
          if (m && m[1]) label = m[1].trim();
        }
      }

      // As a last resort, fallback to the very first sentence of narrative excluding meta
      if (!label){
        const textNoMeta = narrative.replace(/Level\s+of\s+Harm[\s\S]*?(?:\.|\n|$)/i,'')
                                    .replace(/Residents\s+Affect(?:ed|ed:)[\s\S]*?(?:\.|\n|$)/i,'')
                                    .trim();
        const m2 = textNoMeta.match(/^(.*?)(?:\.|$)/);
        if (m2 && m2[1]) label = m2[1].trim();
      }

      // Ensure it’s concise and ends with a period (if not empty)
      label = (label || '').replace(/\s{2,}/g,' ').replace(/\s+pr\s*$/i,'').trim();
      if (label && !/[.!?]$/.test(label)) label += '.';
      return label || 'Citation.';
    }

    function extractInitialComplaint(narrative){
      // Prefer a sentence that starts with “Based on …”
      const m1 = narrative.match(/(?:^|\n)\s*(Based\s+on[\s\S]*?\.)(?:\s|$)/i);
      if (m1) return m1[1].trim();

      // Else take the first substantive sentence that is not the meta lines
      const cleaned = narrative
        .replace(/Level\s+of\s+Harm[\s\S]*?(?:\.|\n|$)/i,'')
        .replace(/Residents\s+Affect(?:ed|ed:)[\s\S]*?(?:\.|\n|$)/i,'')
        .trim();
      const m2 = cleaned.match(/^(.*?\.)(?:\s|$)/);
      if (m2) return m2[1].trim();

      // Fallback: first 200 chars
      return cleaned.slice(0, 200) + (cleaned.length>200 ? '…' : '');
    }

    // ---------- hidden composite prompt ----------
    function buildUserComposite(c){
      const ctx = state.contextText?.trim();
      return `Facility Context (optional):
${ctx || '(none provided)'}

CMS-2567 Citation (${c.id} ${c.label}):
"""
${c.text}
"""

Goal:
Draft an individualized, regulation-aligned Plan of Correction for this specific F-tag using exactly these sections:
1) Immediate Correction, 2) Staff Training, 3) Monitoring, 4) Policy Review, 5) Expected Outcomes.
Ensure actions are specific, measurable, and feasible for the facility.`;
    }

    // ---------- citations ribbon (accordion) ----------
    function renderRibbon(){
      const root = els.ribbon; root.innerHTML = '';
      const head = document.createElement('div');
      head.className = 'mpoc-ribbon-head';
      head.innerHTML = `<strong>Citations</strong><span class="mpoc-count">${state.citations.length}</span>`;
      root.appendChild(head);

      const ul = document.createElement('ul'); ul.className = 'mpoc-accordion'; root.appendChild(ul);

      const items = state.citations.slice().sort((a,b)=>a.id.localeCompare(b.id));
      for (const c of items){
        const li = document.createElement('li'); li.className = 'mpoc-acc';
        const details = document.createElement('details');

        // Summary lines
        const summary = document.createElement('summary');
        const boldLine = `<strong>${esc(c.id)} – ${esc(c.label)}</strong>`;
        const italBits = [];
        if (c.meta?.loh) italBits.push(`Level of Harm – ${esc(c.meta.loh)}`);
        if (c.meta?.affected) italBits.push(`Residents Affected – ${esc(c.meta.affected)}`);
        const italLine = italBits.length ? `<em>${italBits.join(' | ')}</em>` : '';
        summary.innerHTML = italLine ? `${boldLine}<br>${italLine}` : boldLine;
        details.appendChild(summary);

        // Body: initial complaint + button
        const body = document.createElement('div'); body.className = 'mpoc-body';
        const complaint = extractInitialComplaint(c.text);
        body.innerHTML = `<div class="mpoc-snippet">${esc(complaint)}</div>`;
        const actions = document.createElement('div'); actions.className = 'mpoc-actions';
        const gen = document.createElement('button'); gen.type='button'; gen.textContent='Generate PoC in Chat';
        gen.addEventListener('click', ()=> openThread(c));
        actions.appendChild(gen);
        body.appendChild(actions);

        details.appendChild(body); li.appendChild(details); ul.appendChild(li);
      }
    }

    // ---------- thread lifecycle ----------
    async function openThread(c){
      if(!state.threads[c.threadId]){
        state.threads[c.threadId] = {
          id: c.threadId,
          citationId: c.id,
          messages: [
            { role:'system', content:SYSTEM_ANCHOR, hidden:true },
            { role:'user',   content:buildUserComposite(c), hidden:true }
          ],
          hasInitial:false
        };
      }
      state.activeThreadId = c.threadId;
      renderThread(c.threadId);
      ensureInitialDraft(state.threads[c.threadId]);
    }

    async function ensureInitialDraft(thr){
      if (thr.hasInitial) return;
      appendAssistant(thr, 'Generating initial Plan of Correction…', true);
      try{
        const resp = await callMatcha(thr.messages);
        replaceLastAssistant(thr, resp);
        thr.hasInitial = true; thr.draft = resp;
      }catch(e){
        replaceLastAssistant(thr, 'Error contacting server: '+e.message);
      }
      if (thr.id === state.activeThreadId) renderThread(thr.id);
    }

    function appendAssistant(thr, text, ephemeral=false){
      thr.messages.push({ role:'assistant', content:text, ts:Date.now(), hidden:false, ephemeral });
      if (thr.id === state.activeThreadId) renderThread(thr.id);
    }
    function replaceLastAssistant(thr, text){
      for (let i=thr.messages.length-1; i>=0; i--){
        if (thr.messages[i].role === 'assistant'){ thr.messages[i].content = text; thr.messages[i].ephemeral=false; return; }
      }
      thr.messages.push({ role:'assistant', content:text, ts:Date.now() });
    }

    function renderThread(threadId){
      const thr = state.threads[threadId]; if(!thr) return;
      els.chat.innerHTML = '';

      const history = document.createElement('div'); history.className = 'mpoc-thread';
      thr.messages.forEach(m => {
        if (m.hidden || m.role === 'system') return;
        const b = document.createElement('div');
        b.className = `mpoc-msg mpoc-${m.role}` + (m.ephemeral?' mpoc-ghost':'');
        b.textContent = m.content;
        history.appendChild(b);
      });

      const form = document.createElement('form'); form.className='mpoc-input';
      form.innerHTML = `
        <textarea id="mpocInput" rows="4" placeholder="Ask Matcha to refine, tighten to your state, make monitoring measurable, etc. (Shift+Enter for newline, Enter to send)"></textarea>
        <div class="mpoc-actions">
          <button type="submit">Send</button>
          <button type="button" id="acceptDraft">Accept Draft</button>
        </div>`;
      const ta = form.querySelector('#mpocInput');

      // Enter=send, Shift+Enter=newline
      ta.addEventListener('keydown', (e)=>{
        if (e.key === 'Enter' && !e.shiftKey){
          e.preventDefault();
          form.dispatchEvent(new Event('submit', {cancelable:true, bubbles:true}));
        }
      });

      form.addEventListener('submit', async (e)=>{
        e.preventDefault();
        const t = (ta.value||'').trim(); if(!t) return;
        thr.messages.push({ role:'user', content:t, ts:Date.now() });
        ta.value = '';
        renderThread(threadId);
        try{
          const resp = await callMatcha(thr.messages);
          thr.messages.push({ role:'assistant', content:resp, ts:Date.now() });
          thr.draft = resp;
        }catch(e2){
          thr.messages.push({ role:'assistant', content:'Error contacting server: '+e2.message, ts:Date.now() });
        }
        renderThread(threadId);
      });

      form.querySelector('#acceptDraft').addEventListener('click', ()=>{
        const last = [...thr.messages].reverse().find(m => m.role==='assistant');
        if (last){ thr.draft = last.content; alert('Draft saved for export.'); }
      });

      els.chat.appendChild(history);
      els.chat.appendChild(form);
    }

    // ---------- REST call to proxy ----------
    async function callMatcha(messages){
      const payload = { mission_id: 660, messages: messages.map(m => ({ role:m.role, content:m.content })) };
      const res = await fetch(MATCHA_POC.endpoint, {
        method: 'POST',
        headers: { 'Content-Type':'application/json', 'X-WP-Nonce': MATCHA_POC.nonce },
        body: JSON.stringify(payload)
      });
      const text = await res.text();
      let data = {};
      try { data = JSON.parse(text); } catch(_){}
      if (!res.ok){
        const msg = data?.error ? `${data.error}${data.body ? ' — '+(typeof data.body==='string'?data.body:JSON.stringify(data.body)) : ''}` : `HTTP ${res.status}`;
        throw new Error(msg);
      }
      return data.output || 'No content returned.';
    }

    // ---------- context text/Uploads ----------
    els.context.addEventListener('input', e => { state.contextText = e.target.value || ''; });

    els.contextUpload.addEventListener('change', async (e)=>{
      const files = Array.from(e.target.files || []);
      for (const f of files){
        try{
          let text='';
          if ((f.type||'').includes('pdf') || /\.pdf$/i.test(f.name)) text = await extractTextFromPdf(f);
          else text = await f.text();
          state.contextText += `${NL}${NL}---${NL}[Context: ${f.name}]${NL}${text}${NL}---${NL}`;
          els.context.value = state.contextText;
          if (els.contextFiles){
            const chip = document.createElement('div'); chip.className='mpoc-filechip'; chip.textContent=f.name; els.contextFiles.appendChild(chip);
          }
        }catch(ex){
          err('Context upload failed for', f.name, ex);
          alert('Failed to read '+f.name+': '+ex.message);
        }
      }
      e.target.value = '';
    });

    // ---------- exports ----------
    els.exportCsv.addEventListener('click', ()=>{
      const rows = [['F-Tag','Label','Draft']];
      state.citations.forEach(c=>{
        const thr = state.threads[c.threadId];
        const draft = thr?.draft || '';
        rows.push([c.id, c.label, draft]);
      });
      const csv = rows.map(r => r.map(v=>{
        const s=String(v);
        return (s.includes(',')||s.includes('"')||s.includes('\n')) ? `"${s.replace(/"/g,'""')}"` : s;
      }).join(',')).join('\n');
      downloadText(csv, 'matcha_poc_exports.csv');
    });

    els.exportJson.addEventListener('click', ()=>{
      const out = state.citations.map(c => ({
        ftag: c.id, label: c.label,
        draft: state.threads[c.threadId]?.draft || '',
        messages: state.threads[c.threadId]?.messages || []
      }));
      downloadText(JSON.stringify(out, null, 2), 'matcha_poc_exports.json');
    });

    function downloadText(text, filename){
      const blob = new Blob([text], { type:'text/plain;charset=utf-8' });
      const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
      a.download = filename; a.click(); URL.revokeObjectURL(a.href);
    }

    // ---------- 2567 upload (populate ribbon) ----------
    els.upload.addEventListener('change', async (e)=>{
      const file = e.target.files?.[0]; if (!file) return;
      els.ribbon.innerHTML = '<div class="mpoc-ribbon-head"><strong>Citations</strong><span class="mpoc-count">Extracting…</span></div>';
      try{
        const text = await extractTextFromPdf(file);
        state.citations = parseCitations(text);
        renderRibbon();
        if (state.citations.length) {
          // Optionally open the first one: details.open = true;
          // const firstAcc = els.ribbon.querySelector('details'); if (firstAcc) firstAcc.open = true;
        } else {
          els.ribbon.innerHTML = '<div class="mpoc-ribbon-head"><strong>Citations</strong><span class="mpoc-count">0</span></div><p>No F-tags found.</p>';
        }
      }catch(ex){
        err('extraction error', ex);
        els.ribbon.innerHTML = '<p>Extraction failed. See console.</p>';
      }
    });

    log('Init complete');
  }
})();
/* Tabs above chat */
.mpoc-tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.mpoc-tab {
  padding: 6px 10px;
  border: 1px solid #cbd5e1;
  background: #f8fafc;
  border-radius: 999px;
  cursor: pointer;
  font-size: 12.5px;
}
.mpoc-tab.active {
  background: #e0f2fe;
  border-color: #93c5fd;
}

/* Empty state */
.mpoc-empty {
  border: 1px dashed #cbd5e1;
  border-radius: 8px;
  padding: 12px;
  background: #fff;
  color: #64748b;
}

