/** Wrap the selected text in ** (toggles bold). */
export function toggleBold(text: string, start: number, end: number): { text: string; start: number; end: number } {
  const before = text.slice(0, start);
  const sel = text.slice(start, end);
  const after = text.slice(end);
  if (before.endsWith('**') && after.startsWith('**')) {
    const nb = before.slice(0, -2);
    const na = after.slice(2);
    return { text: nb + sel + na, start: nb.length, end: nb.length + sel.length };
  }
  const wrapped = `**${sel || 'bold text'}**`;
  return { text: before + wrapped + after, start: before.length + 2, end: before.length + 2 + (sel || 'bold text').length };
}
/** Wrap the selected text in __ (toggles underline). */
export function toggleUnderline(text: string, start: number, end: number): { text: string; start: number; end: number } {
  const before = text.slice(0, start);
  const sel = text.slice(start, end);
  const after = text.slice(end);
  if (before.endsWith('__') && after.startsWith('__')) {
    const nb = before.slice(0, -2);
    const na = after.slice(2);
    return { text: nb + sel + na, start: nb.length, end: nb.length + sel.length };
  }
  const label = sel || 'underlined text';
  return { text: before + `__${label}__` + after, start: before.length + 2, end: before.length + 2 + label.length };
}
/** Put "• " at the start of every selected line (toggles off if already there). */
export function toggleBullets(text: string, start: number, end: number): { text: string; start: number; end: number } {
  const lineStart = text.lastIndexOf('\n', start - 1) + 1;
  const lineEndIdx = text.indexOf('\n', end);
  const lineEnd = lineEndIdx === -1 ? text.length : lineEndIdx;
  const lines = text.slice(lineStart, lineEnd).split('\n');
  const allBulleted = lines.every((l) => l.trim() === '' || l.startsWith('• '));
  const next = lines.map((l) => {
    if (l.trim() === '') return l;
    return allBulleted ? l.replace(/^• /, '') : `• ${l}`;
  }).join('\n');
  const out = text.slice(0, lineStart) + next + text.slice(lineEnd);
  return { text: out, start: lineStart, end: lineStart + next.length };
}
/**
 * Wrap the selected text in single * (toggles italic).
 * "**" is bold, so an existing italic wrap is an ODD number of "*" on both sides (*x* or ***x***).
 */
export function toggleItalic(text: string, start: number, end: number): { text: string; start: number; end: number } {
  const before = text.slice(0, start);
  const sel = text.slice(start, end);
  const after = text.slice(end);
  const lead = (before.match(/\*+$/)?.[0].length ?? 0);
  const trail = (after.match(/^\*+/)?.[0].length ?? 0);
  if (lead % 2 === 1 && trail % 2 === 1) {
    const nb = before.slice(0, -1);
    const na = after.slice(1);
    return { text: nb + sel + na, start: nb.length, end: nb.length + sel.length };
  }
  const label = sel || 'italic text';
  return { text: before + `*${label}*` + after, start: before.length + 1, end: before.length + 1 + label.length };
}

/** Remove **, __ and * markers from the selection (or the whole text when nothing is selected). */
export function clearFormatting(text: string, start: number, end: number): { text: string; start: number; end: number } {
  const a = start === end ? 0 : start;
  const b = start === end ? text.length : end;
  // line by line through the same parser the report uses, so bullets and literal "*" are kept
  const sel = text.slice(a, b).split('\n').map((ln) => {
    const blk = parseRich(ln)[0];
    if (!blk) return ln;
    const lead = ln.match(/^\s*(?:[•●▪‣]|[-*])\s+/)?.[0] ?? (ln.match(/^\s*/)?.[0] ?? '');
    return (blk.bullet ? lead : ln.match(/^\s*/)?.[0] ?? '') + blk.runs.map((r) => r.t).join('');
  }).join('\n');
  const out = text.slice(0, a) + sel + text.slice(b);
  return { text: out, start: a, end: a + sel.length };
}

/** Same rules as the backend (rich-text.util.ts parseRich) — used for the live "how it prints" preview. */
export interface RichRun { t: string; b?: boolean; i?: boolean; u?: boolean }
export interface RichBlock { bullet: boolean; heading?: boolean; runs: RichRun[] }
export function parseRich(text: string | null | undefined): RichBlock[] {
  if (!text) return [];
  const out: RichBlock[] = [];
  for (const raw of String(text).replace(/\r/g, '').split('\n')) {
    let line = raw.trim();
    if (!line) continue;
    let bullet = false;
    const m = line.match(/^(?:[•●▪‣]|[-*])\s+(.*)$/);
    if (m) { bullet = true; line = m[1].trim(); }
    const toks: { at: number; len: number; kind: 'b' | 'u' | 'i' }[] = [];
    const re = /(\*\*|__|\*)/g;
    let mk: RegExpExecArray | null;
    while ((mk = re.exec(line)) !== null) toks.push({ at: mk.index, len: mk[0].length, kind: mk[0] === '**' ? 'b' : mk[0] === '__' ? 'u' : 'i' });
    const italics = toks.filter((k) => k.kind === 'i');
    const literal = new Set(italics.length % 2 ? [italics[italics.length - 1]] : []);
    const runs: RichRun[] = [];
    let bold = false, ital = false, under = false, last = 0;
    const push = (t: string) => {
      if (!t) return;
      const prev = runs[runs.length - 1];
      if (prev && !!prev.b === bold && !!prev.i === ital && !!prev.u === under) { prev.t += t; return; }
      runs.push({ t, ...(bold ? { b: true } : {}), ...(ital ? { i: true } : {}), ...(under ? { u: true } : {}) });
    };
    for (const k of toks) {
      if (literal.has(k)) continue;
      push(line.slice(last, k.at));
      if (k.kind === 'b') bold = !bold; else if (k.kind === 'u') under = !under; else ital = !ital;
      last = k.at + k.len;
    }
    push(line.slice(last));
    if (!runs.length) continue;
    out.push({ bullet, heading: !bullet && runs.every((r) => r.b) && runs.some((r) => r.t.trim() !== ''), runs });
  }
  return out;
}
/** true when the text uses any formatting marker (decides whether to show the preview) */
export const hasRichMarkers = (text: string | null | undefined) => /\*\*|__|\*[^\s*][^*]*\*/.test(String(text ?? ''));
