const $ = (id) => document.getElementById(id);
const STORAGE_KEY = 'ssa_last_session_id';
const state = {
  sessionId: null,
  current: null,
  drafts: [],
};

const stageLabels = {
  intro: 'تمهيد',
  exploration: 'استكشاف',
  usecase: 'حالات الاستخدام',
  evaluation: 'تقييم',
  done: 'مكتمل',
};

document.addEventListener('DOMContentLoaded', init);

function init() {
  $('start-btn').addEventListener('click', startSession);
  $('project-title').addEventListener('keydown', (e) => { if (e.key === 'Enter') startSession(); });
  $('send-btn').addEventListener('click', sendMessage);
  $('input').addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  });
  $('input').addEventListener('input', autoGrow);
  $('srs-text').addEventListener('input', autoGrow);
  $('actors-editor').addEventListener('input', autoGrow);
  $('usecases-editor').addEventListener('input', autoGrow);
  $('io-editor').addEventListener('input', autoGrow);
  $('scope-editor').addEventListener('input', autoGrow);
  $('analyze-srs-btn').addEventListener('click', analyzeSrs);
  $('confirm-srs-btn').addEventListener('click', confirmStructure);
  $('save-planning-btn').addEventListener('click', savePlanning);
  $('export-btn').addEventListener('click', () => {
    if (state.sessionId) window.open(`/report/${state.sessionId}`, '_blank');
  });
  $('srs-file').addEventListener('change', () => {
    const file = $('srs-file').files?.[0];
    $('srs-file-name').textContent = file ? `الملف المختار: ${file.name}` : 'لم يتم اختيار ملف بعد.';
  });

  loadDrafts();
}

function autoGrow(event) {
  const target = event.target;
  if (!target || target.tagName !== 'TEXTAREA') return;
  target.style.height = 'auto';
  target.style.height = Math.min(target.scrollHeight, target.classList.contains('editor-textarea') ? 220 : 160) + 'px';
}

async function loadDrafts() {
  try {
    const res = await fetch('/api/sessions');
    const data = await res.json();
    state.drafts = Array.isArray(data.sessions) ? data.sessions : [];
    renderDraftLists();
  } catch (error) {
    console.error('loadDrafts error', error);
  }
}

function renderDraftLists() {
  renderDraftList('welcome-drafts', true);
  renderDraftList('sidebar-drafts', false);
}

function renderDraftList(containerId, compact = false) {
  const container = $(containerId);
  const drafts = state.drafts || [];
  const lastId = localStorage.getItem(STORAGE_KEY);

  if (!drafts.length) {
    container.innerHTML = '<div class="draft-empty">لا توجد مسودات محفوظة بعد.</div>';
    return;
  }

  container.innerHTML = drafts.map((draft) => {
    const active = draft.id === state.sessionId;
    const last = draft.id === lastId;
    const counts = draft.counts || {};
    return `
      <button class="draft-card${active ? ' active' : ''}${last ? ' last-opened' : ''}" data-session-id="${esc(draft.id)}">
        <div class="draft-card-top">
          <div class="draft-title">${esc(draft.title || 'مسودة بدون عنوان')}</div>
          ${last && !active ? '<span class="tiny-badge">آخر جلسة</span>' : ''}
        </div>
        <div class="draft-meta">${esc(draft.checkpointLabel || draft.stage || '—')}</div>
        <div class="draft-stats">
          <span>${counts.useCases || 0} UC</span>
          <span>${counts.functionPoints || 0} FP</span>
          <span>${counts.inputOutputs || 0} IO</span>
        </div>
        <div class="draft-date">${formatDate(draft.updatedAt, compact)}</div>
      </button>
    `;
  }).join('');

  container.querySelectorAll('.draft-card').forEach((btn) => {
    btn.addEventListener('click', () => resumeSession(btn.dataset.sessionId));
  });
}

function formatDate(value, compact = false) {
  if (!value) return 'بدون تاريخ';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return 'بدون تاريخ';
  const formatter = new Intl.DateTimeFormat('ar-EG', {
    year: 'numeric',
    month: compact ? 'short' : 'long',
    day: 'numeric',
    hour: compact ? undefined : 'numeric',
    minute: compact ? undefined : '2-digit',
  });
  return formatter.format(date);
}

function showApp(title) {
  $('welcome').classList.add('hidden');
  $('app').classList.remove('hidden');
  $('project-name').textContent = title || '—';
}

function showResumeBanner(text) {
  const banner = $('resume-banner');
  banner.textContent = text;
  banner.classList.remove('hidden');
}

function hideResumeBanner() {
  $('resume-banner').classList.add('hidden');
  $('resume-banner').textContent = '';
}

async function startSession() {
  const title = $('project-title').value.trim();
  if (!title) {
    $('project-title').focus();
    return;
  }

  $('start-btn').disabled = true;
  try {
    const res = await fetch('/api/start', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'فشل بدء الجلسة');

    state.sessionId = data.sessionId;
    localStorage.setItem(STORAGE_KEY, data.sessionId);
    state.current = data;
    clearMessages();
    hideResumeBanner();
    showApp(title);
    addAgentMessage(data.reply || 'تم بدء الجلسة.', true);
    updateState(data);
    await loadDrafts();
    $('input').focus();
  } catch (error) {
    alert(`خطأ: ${error.message}`);
  } finally {
    $('start-btn').disabled = false;
  }
}

async function resumeSession(sessionId) {
  try {
    const res = await fetch(`/api/session/${encodeURIComponent(sessionId)}`);
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'تعذر استئناف الجلسة');

    state.sessionId = data.sessionId;
    state.current = data;
    localStorage.setItem(STORAGE_KEY, data.sessionId);
    showApp(data.title);
    renderHistory(data.history || []);
    updateState(data);
    showResumeBanner(data.resumeMessage || 'أهلاً بك مجدداً، يمكننا متابعة العمل من آخر نقطة توقفنا عندها.');
    $('input').focus();
    await loadDrafts();
  } catch (error) {
    alert(`خطأ: ${error.message}`);
    if (String(error.message).includes('غير موجودة')) {
      localStorage.removeItem(STORAGE_KEY);
    }
  }
}

async function sendMessage() {
  const input = $('input');
  const text = input.value.trim();
  if (!text || !state.sessionId) return;

  hideResumeBanner();
  addUserMessage(text);
  input.value = '';
  input.style.height = 'auto';
  $('send-btn').disabled = true;
  const thinkingEl = addAgentMessage('يفكّر...', false, true);

  try {
    const res = await fetch('/api/message', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId: state.sessionId, message: text }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'فشل الإرسال');
    thinkingEl.remove();
    addAgentMessage(data.reply || 'تم تحديث التحليل.', true);
    updateState(data);
    await loadDrafts();
  } catch (error) {
    thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
    thinkingEl.classList.remove('thinking');
  } finally {
    $('send-btn').disabled = false;
    $('input').focus();
  }
}

async function analyzeSrs() {
  if (!state.sessionId) {
    alert('ابدأ جلسة أولاً ثم ارفع ملف SRS أو الصق النص.');
    return;
  }

  const file = $('srs-file').files?.[0];
  let content = $('srs-text').value.trim();
  let filename = '';

  if (!content && file) {
    filename = file.name;
    content = await file.text();
  } else if (file) {
    filename = file.name;
  }

  if (!content.trim()) {
    alert('أرفق ملفاً نصياً بسيطاً أو الصق نص المتطلبات أولاً.');
    return;
  }

  if (looksBinary(content)) {
    alert('الملف يبدو غير نصي. في هذه النسخة يُفضَّل رفع ملف نصي بسيط أو لصق محتوى SRS يدوياً.');
    return;
  }

  addUserMessage(filename ? `📄 تم إرسال ملف SRS للتحليل: ${filename}` : '📄 تم إرسال نص SRS للتحليل');
  const thinkingEl = addAgentMessage('أحلّل المستند وأستخرج Actors وUse Cases وInputs/Outputs...', false, true);

  try {
    const res = await fetch('/api/analyze-srs', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId: state.sessionId, content, filename }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'فشل تحليل المستند');
    thinkingEl.remove();
    addAgentMessage(data.reply || 'تم تحليل المستند.', true);
    updateState(data);
    await loadDrafts();
  } catch (error) {
    thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
    thinkingEl.classList.remove('thinking');
  }
}

async function confirmStructure() {
  if (!state.sessionId) return;

  const actors = parseSimpleList($('actors-editor').value);
  const useCases = parseUseCases($('usecases-editor').value, state.current?.useCases || []);
  const inputOutputs = parseInputOutputs($('io-editor').value, state.current?.inputOutputs || []);
  const scope = $('scope-editor').value.trim();

  if (!actors.length && !useCases.length && !inputOutputs.length && !scope) {
    alert('لا توجد بيانات لاعتمادها بعد. حلّل SRS أولاً أو أدخل القوائم يدوياً.');
    return;
  }

  addUserMessage('✅ تم اعتماد/تعديل الهيكل الأولي للمشروع.');
  const thinkingEl = addAgentMessage('أثبّت القوائم وأعيد حساب التقدير...', false, true);

  try {
    const res = await fetch('/api/confirm-structure', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId: state.sessionId, scope, actors, useCases, inputOutputs }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'فشل اعتماد الاقتراح');
    thinkingEl.remove();
    addAgentMessage(data.reply || 'تم اعتماد الهيكل الأولي.', true);
    updateState(data);
    await loadDrafts();
  } catch (error) {
    thinkingEl.querySelector('.bubble').textContent = `⚠️ ${error.message}`;
    thinkingEl.classList.remove('thinking');
  }
}

async function savePlanning() {
  if (!state.sessionId) return;

  $('planning-status').textContent = 'جارٍ تحديث خطة التنفيذ...';
  try {
    const res = await fetch(`/api/session/${encodeURIComponent(state.sessionId)}/planning`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        developers: Number($('team-size').value || 3),
        sprintWeeks: Number($('sprint-weeks').value || 2),
      }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'فشل تحديث التخطيط');
    updateState(data);
    $('planning-status').textContent = 'تم تحديث الخطة والمدة المتوقعة بنجاح.';
    await loadDrafts();
  } catch (error) {
    $('planning-status').textContent = `⚠️ ${error.message}`;
  }
}

function clearMessages() {
  $('messages').innerHTML = '';
}

function renderHistory(history) {
  clearMessages();
  history.forEach((entry) => {
    if (entry.role === 'user') addUserMessage(entry.text || '', false);
    else if (entry.role === 'assistant') addAgentMessage(entry.payload?.reply || '', false, false, false);
  });
  scrollMsgs();
}

function addUserMessage(text, scroll = true) {
  const el = document.createElement('div');
  el.className = 'msg user';
  el.innerHTML = `<div class="avatar">أنا</div><div class="bubble"></div>`;
  el.querySelector('.bubble').textContent = text;
  $('messages').appendChild(el);
  if (scroll) scrollMsgs();
  return el;
}

function addAgentMessage(text, typewriter = false, thinking = false, scroll = true) {
  document.querySelectorAll('.msg.agent.fresh').forEach((node) => node.classList.remove('fresh'));
  const el = document.createElement('div');
  el.className = `msg agent fresh${thinking ? ' thinking' : ''}`;
  el.innerHTML = `<div class="avatar">AI</div><div class="bubble${thinking ? ' typing' : ''}"></div>`;
  $('messages').appendChild(el);
  const bubble = el.querySelector('.bubble');
  if (typewriter) typeText(bubble, text);
  else bubble.textContent = text;
  if (scroll) scrollMsgs();
  return el;
}

function typeText(el, text) {
  el.textContent = '';
  const cursor = document.createElement('span');
  cursor.className = 'cursor';
  el.appendChild(cursor);
  let i = 0;
  const speed = Math.max(8, Math.min(28, 1200 / Math.max(text.length, 12)));

  const tick = () => {
    if (i < text.length) {
      cursor.insertAdjacentText('beforebegin', text[i]);
      i += 1;
      scrollMsgs();
      setTimeout(tick, speed);
    } else {
      cursor.remove();
    }
  };
  tick();
}

function scrollMsgs() {
  const messages = $('messages');
  messages.scrollTop = messages.scrollHeight;
}

function updateState(data) {
  state.current = data;
  state.sessionId = data.sessionId || data.id || state.sessionId;
  localStorage.setItem(STORAGE_KEY, state.sessionId);

  const fps = data.functionPoints || [];
  const ucs = data.useCases || [];
  const ios = data.inputOutputs || [];
  const actors = data.actors || [];
  const analytics = data.analytics || {};
  const planning = data.planning || {};

  $('project-name').textContent = data.title || $('project-name').textContent;
  $('fp-count').textContent = fps.length;
  $('uc-count').textContent = ucs.length;
  $('io-count').textContent = ios.length;
  $('fp-total').textContent = analytics.fp?.totalFP ?? fps.reduce((sum, item) => sum + (Number(item.fpScore) || 0), 0);
  $('stage-pill').textContent = stageLabels[data.stage] || data.stage || '—';

  $('team-size').value = planning.developers || 3;
  $('sprint-weeks').value = planning.sprintWeeks || 2;

  fillProposalEditors(data);
  renderTree(actors, fps, ios);
  renderPreview(data);

  if (data.isComplete || ucs.length >= 1 || fps.length >= 2) $('export-btn').classList.remove('hidden');
}

function fillProposalEditors(data) {
  const source = data.srsDraft || data;
  $('scope-editor').value = source.summary || source.scope || data.scope || '';
  $('actors-editor').value = (source.actors || []).join('\n');
  $('usecases-editor').value = (source.useCases || []).map((uc) => `${uc.title || ''} | ${uc.actor || 'المستخدم'}`).join('\n');
  $('io-editor').value = (source.inputOutputs || []).map((item) => [
    item.type === 'output' ? 'Output' : 'Input',
    item.name || '',
    item.source || '',
    item.destination || '',
    item.description || '',
  ].join(' | ')).join('\n');

  const counts = source.counts || {
    actors: (source.actors || []).length,
    useCases: (source.useCases || []).length,
    inputs: (source.inputOutputs || []).filter((item) => item.type === 'input').length,
    outputs: (source.inputOutputs || []).filter((item) => item.type === 'output').length,
  };

  if (counts.actors || counts.useCases || counts.inputs || counts.outputs) {
    $('proposal-summary').textContent = `Actors: ${counts.actors || 0} · Use Cases: ${counts.useCases || 0} · Inputs: ${counts.inputs || 0} · Outputs: ${counts.outputs || 0}`;
  } else {
    $('proposal-summary').textContent = 'ستظهر هنا أعداد Actors وUse Cases وInputs/Outputs بعد تحليل المستند.';
  }

  const status = $('proposal-status');
  if (data.needsStructureConfirmation) {
    status.textContent = 'بانتظار الاعتماد';
    status.classList.remove('dim');
  } else if ((source.actors || []).length || (source.useCases || []).length) {
    status.textContent = 'قابل للتعديل';
    status.classList.remove('dim');
  } else {
    status.textContent = 'بانتظار تحليل';
    status.classList.add('dim');
  }
}

function renderTree(actors, fps, ios) {
  const tree = $('tree');
  if (!actors.length && !fps.length && !ios.length) {
    tree.innerHTML = '<div class="tree-empty">ستظهر الـ Actors والوظائف وهياكل الـ IO هنا تلقائياً.</div>';
    return;
  }

  let html = '';
  actors.forEach((actor) => {
    html += `<div class="tree-actor">${esc(actor)}</div>`;
  });
  fps.forEach((fp) => {
    html += `<div class="tree-fp">${esc(fp.id || '')} · ${esc(fp.name || '')}</div>`;
  });
  ios.slice(0, 8).forEach((item) => {
    html += `<div class="tree-io ${item.type === 'output' ? 'out' : 'in'}">${item.type === 'output' ? 'Output' : 'Input'} · ${esc(item.name || '')}</div>`;
  });
  tree.innerHTML = html;
}

function renderPreview(data) {
  const prev = $('preview-content');
  const actors = data.actors || [];
  const fps = data.functionPoints || [];
  const ucs = data.useCases || [];
  const ios = data.inputOutputs || [];
  const analytics = data.analytics || {};
  const fp = analytics.fp || {};
  const ucp = analytics.ucp || {};
  const discrepancy = analytics.discrepancy || {};
  const plan = analytics.plan || {};

  let html = '';
  html += `<div class="summary-strip">
    ${previewMetric(fp.totalFP ?? 0, 'Total FP', `~ ${fp.effortHours ?? 0} ساعة`) }
    ${previewMetric(ucp.UCP ?? 0, 'UCP', `~ ${ucp.effortHours ?? 0} ساعة`) }
    ${previewMetric(discrepancy.deltaPercent ?? 0, 'Gap', `${discrepancy.dominantMethod || '—'} الأعلى`) }
    ${previewMetric(plan.totalSprints ?? 0, 'Sprints', `${plan.calendarWeeks ?? 0} أسبوع`) }
  </div>`;

  if (data.scope) {
    html += `<h3>نطاق النظام</h3><div class="scope">${esc(data.scope)}</div>`;
  }

  if (actors.length) {
    html += `<h3>Actors (${actors.length})</h3><div class="tag-row">${actors.map((actor) => `<span class="tag-chip">${esc(actor)}</span>`).join('')}</div>`;
  }

  if (ios.length) {
    html += `<h3>Inputs / Outputs (${ios.length})</h3>`;
    ios.forEach((item) => {
      html += `<div class="io-prev ${item.type === 'output' ? 'out' : 'in'}"><b>${item.type === 'output' ? 'Output' : 'Input'}</b> ${esc(item.name || '')}<br><span>${esc(item.source || '')} → ${esc(item.destination || '')}</span></div>`;
    });
  }

  if (fps.length) {
    html += `<h3>المتطلبات الوظيفية (${fps.length})</h3>`;
    fps.forEach((fpItem) => {
      html += `<div class="uc-prev"><b>${esc(fpItem.id || '')}</b> ${esc(fpItem.name || '')} <span style="color:#888">— ${esc(fpItem.complexity || '')} (FP ${esc(String(fpItem.fpScore || ''))})</span></div>`;
    });
  }

  if (ucs.length) {
    html += `<h3>حالات الاستخدام (${ucs.length})</h3>`;
    ucs.forEach((uc) => {
      html += `<div class="uc-prev"><b>${esc(uc.id || '')}</b> ${esc(uc.title || '')}<br><span style="color:#888">الفاعل: ${esc(uc.actor || '')}</span></div>`;
    });
  }

  if (discrepancy.narrative) {
    html += `<h3>تحليل الفرق بين FP وUCP</h3>`;
    html += `<div class="analysis-card">
      <div class="analysis-line"><strong>الفرق:</strong> ${esc(String(discrepancy.deltaPercent ?? 0))}%</div>
      <div class="analysis-line"><strong>الطريقة الأعلى:</strong> ${esc(discrepancy.dominantMethod || '—')}</div>
      <div class="analysis-line">${esc(discrepancy.narrative || '')}</div>
      ${Array.isArray(discrepancy.evidence) && discrepancy.evidence.length ? `<ul>${discrepancy.evidence.map((item) => `<li>${esc(item)}</li>`).join('')}</ul>` : ''}
      <div class="analysis-line"><strong>التوصية:</strong> ${esc(discrepancy.recommendation || '')}</div>
    </div>`;
  }

  if (plan.milestones?.length) {
    html += `<h3>WBS / Milestones</h3>`;
    plan.milestones.forEach((item) => {
      html += `<div class="milestone-item"><b>${esc(item.name || '')}</b><span>${esc(String(item.percent || 0))}% · ${esc(String(item.hours || 0))} ساعة · ${esc(item.owner || '')}</span></div>`;
    });
  }

  if (plan.sprintPlan?.length) {
    html += `<h3>Sprint Breakdown</h3>`;
    plan.sprintPlan.forEach((sprint) => {
      html += `<div class="sprint-card"><b>${esc(sprint.name || '')}</b><span>${esc(sprint.goal || '')}</span><ul>${(sprint.focus || []).map((item) => `<li>${esc(item)}</li>`).join('')}</ul></div>`;
    });
  }

  if (plan.staffing?.length) {
    html += `<h3>توزيع الفريق</h3>`;
    plan.staffing.forEach((member) => {
      html += `<div class="milestone-item"><b>${esc(member.name || '')}</b><span>${esc(member.focus || '')} · ${esc(String(member.loadHours || 0))} ساعة</span></div>`;
    });
  }

  prev.innerHTML = html || '<div class="preview-empty">سيتشكل هنا ملخص النطاق، Catalog الـ IO، تحليل الفجوة بين FP/UCP، وخطة التنفيذ الأولية.</div>';
}

function previewMetric(value, label, meta) {
  return `<div class="preview-metric"><div class="metric-label">${esc(label)}</div><div class="metric-value">${esc(String(value))}</div><div class="metric-meta">${esc(meta || '')}</div></div>`;
}

function parseSimpleList(text) {
  return text
    .split(/\n+/)
    .map((line) => line.trim())
    .filter(Boolean);
}

function parseUseCases(text, previous = []) {
  return parseSimpleList(text).map((line, index) => {
    const prev = previous[index] || {};
    const parts = line.split('|').map((part) => part.trim());
    const title = parts[0] || prev.title || `حالة استخدام ${index + 1}`;
    const actor = parts[1] || prev.actor || 'المستخدم';
    const mainFlow = parts[2]
      ? parts[2].split('>').map((step) => step.trim()).filter(Boolean)
      : (Array.isArray(prev.mainFlow) && prev.mainFlow.length ? prev.mainFlow : defaultMainFlow(title, actor));
    const alternateFlow = parts[3]
      ? parts[3].split('>').map((step) => step.trim()).filter(Boolean)
      : (Array.isArray(prev.alternateFlow) ? prev.alternateFlow : []);
    return {
      id: prev.id || `UC-${String(index + 1).padStart(2, '0')}`,
      title,
      actor,
      preconditions: prev.preconditions || `يملك ${actor} الصلاحية اللازمة.`,
      mainFlow,
      alternateFlow,
    };
  });
}

function parseInputOutputs(text, previous = []) {
  return parseSimpleList(text).map((line, index) => {
    const prev = previous[index] || {};
    const parts = line.split('|').map((part) => part.trim());
    const typeRaw = (parts[0] || prev.type || 'input').toLowerCase();
    const type = /output|out|مخرج|إخراج/.test(typeRaw) ? 'output' : 'input';
    return {
      id: prev.id || `IO-${String(index + 1).padStart(2, '0')}`,
      type,
      name: parts[1] || prev.name || `${type === 'output' ? 'مخرج' : 'مدخل'} ${index + 1}`,
      source: parts[2] || prev.source || (type === 'input' ? 'المستخدم' : 'النظام'),
      destination: parts[3] || prev.destination || (type === 'input' ? 'النظام' : 'المستخدم'),
      description: parts[4] || prev.description || '',
    };
  });
}

function defaultMainFlow(title, actor) {
  return [
    `${actor} يبدأ مسار ${title}.`,
    'يدخل البيانات أو يحدد الخيارات المطلوبة.',
    'يعالج النظام الطلب ويعرض النتيجة أو التأكيد.',
  ];
}

function looksBinary(text) {
  if (!text) return false;
  let suspicious = 0;
  const sample = text.slice(0, 600);
  for (let i = 0; i < sample.length; i += 1) {
    const code = sample.charCodeAt(i);
    if (code === 0 || (code < 9) || (code > 13 && code < 32)) suspicious += 1;
  }
  return suspicious / Math.max(sample.length, 1) > 0.12;
}

function esc(value) {
  return String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
}
