import type { ReactNode } from "react";

/**
 * Renders a ticket message body with the inline markdown the composer toolbar
 * produces — `**bold**`, `*italic*` / `_italic_`, `` `code` ``, `[text](url)` —
 * plus bare-URL autolinking and `@mention` highlighting.
 *
 * Output is built from React elements only (never `dangerouslySetInnerHTML`), and
 * link targets are restricted to http(s)/mailto, so message text can't inject markup
 * or `javascript:` URLs. Line breaks are preserved by the parent's `whitespace-pre-wrap`.
 */
export function MessageBody({ body }: { body: string }) {
  return <>{renderInline(body, 0)}</>;
}

const MAX_DEPTH = 3;
const SAFE_HREF = /^(https?:\/\/|mailto:)/i;

// One pass, ordered so that code spans and `**` win over single `*`, and links
// over bare URLs. Emphasis content must start and end on a non-space character
// (CommonMark-style), so "a * b * c" stays literal.
const TOKEN =
  /(`[^`\n]+`)|(\*\*(?:\S|\S[\s\S]*?\S)\*\*)|(__(?:\S|\S[^\n]*?\S)__)|(\*(?:[^\s*]|[^\s*][^*\n]*?[^\s*])\*)|(_(?:[^\s_]|[^\s_][^_\n]*?[^\s_])_)|(\[[^\]\n]+\]\([^)\s]+\))|(https?:\/\/[^\s<>]+)|(@\w+(?:\.\w+)*)/g;

function renderInline(text: string, depth: number): ReactNode[] {
  const out: ReactNode[] = [];
  if (!text) return out;

  const re = new RegExp(TOKEN.source, "g");
  let last = 0;
  let key = 0;
  let m: RegExpExecArray | null;

  while ((m = re.exec(text)) !== null) {
    const [raw, code, bold, boldU, italic, italicU, link, url, mention] = m;
    const start = m.index;

    // `_` emphasis must not fire inside identifiers (snake_case_name), and an
    // `@handle` must not fire inside an email address (john@acme.com).
    if ((boldU || italicU || mention) && start > 0 && /\w/.test(text[start - 1])) {
      re.lastIndex = start + 1;
      continue;
    }

    let node: ReactNode = null;

    if (code) {
      node = (
        <code key={key++} className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]">
          {raw.slice(1, -1)}
        </code>
      );
    } else if (bold || boldU) {
      node = (
        <strong key={key++} className="font-semibold">
          {nested(raw.slice(2, -2), depth)}
        </strong>
      );
    } else if (italic || italicU) {
      node = <em key={key++}>{nested(raw.slice(1, -1), depth)}</em>;
    } else if (link) {
      const close = raw.indexOf("](");
      const label = raw.slice(1, close);
      const href = raw.slice(close + 2, -1);
      node = SAFE_HREF.test(href) ? (
        <a key={key++} href={href} {...linkProps}>
          {nested(label, depth)}
        </a>
      ) : (
        <span key={key++}>{raw}</span>
      );
    } else if (url) {
      const [href, rest] = splitTrailingPunctuation(raw);
      node = (
        <span key={key++}>
          <a href={href} {...linkProps}>
            {href}
          </a>
          {rest}
        </span>
      );
    } else if (mention) {
      node = (
        <span key={key++} className="rounded bg-primary/10 px-1 text-primary font-medium">
          {raw}
        </span>
      );
    }

    if (start > last) out.push(text.slice(last, start));
    out.push(node);
    last = start + raw.length;
  }

  if (last < text.length) out.push(text.slice(last));
  return out;
}

function nested(text: string, depth: number): ReactNode {
  return depth + 1 >= MAX_DEPTH ? text : renderInline(text, depth + 1);
}

/**
 * Trailing punctuation is almost never part of a pasted URL ("see https://x.y/z."),
 * but a closing paren that balances one inside the URL (wikipedia-style) is kept.
 */
function splitTrailingPunctuation(url: string): [string, string] {
  let end = url.length;
  while (end > 0) {
    const ch = url[end - 1];
    if (!".,;:!?'\")]".includes(ch)) break;
    if (ch === ")") {
      const head = url.slice(0, end);
      const opens = (head.match(/\(/g) ?? []).length;
      const closes = (head.match(/\)/g) ?? []).length;
      if (opens >= closes) break;
    }
    end--;
  }
  return [url.slice(0, end), url.slice(end)];
}

const linkProps = {
  target: "_blank",
  rel: "noopener noreferrer",
  className: "text-primary underline underline-offset-2 break-all",
} as const;
