"""
CA Automator — minimal Markdown-to-HTML renderer for the blog.

Deliberately not CommonMark: this only needs to handle exactly what
content/blog/*.md and the admin blog editor produce — paragraphs, ## / ###
headings, **bold**, *italic*, `code`, numbered and bulleted lists, links and
horizontal rules. Pulling in a third-party Markdown library would be the
first non-standard-library runtime dependency in this codebase outside
openpyxl/python-docx/anthropic (see requirements.txt's own comment on this);
a firm's blog content is simple enough that a small, auditable renderer here
is a better trade than a new dependency for eleven syntax forms.

Source text is HTML-escaped before any markdown transformation is applied,
so a stray "<script>" typed into the editor renders as literal text rather
than executing — this is not meant to accept raw HTML from the author.
"""
import re

_ESCAPE = {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;"}


def _esc(s):
    return re.sub(r'[&<>"]', lambda m: _ESCAPE[m.group(0)], s)


def _inline(text):
    text = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", text)
    text = re.sub(r"(?<!\*)\*([^*]+?)\*(?!\*)", r"<em>\1</em>", text)
    text = re.sub(r"`(.+?)`", r"<code>\1</code>", text)
    text = re.sub(r"\[(.+?)\]\((https?://[^\s)]+)\)",
                  r'<a href="\2" target="_blank" rel="noopener">\1</a>', text)
    return text


def render(md):
    """Renders Markdown to a string of HTML block elements."""
    if not md:
        return ""
    lines = _esc(md).replace("\r\n", "\n").split("\n")
    out = []
    para = []
    list_buf = []
    list_type = None  # 'ul' | 'ol'

    def flush_para():
        if para:
            out.append("<p>" + _inline(" ".join(para)) + "</p>")
            para.clear()

    def flush_list():
        nonlocal list_type
        if list_buf:
            out.append("<" + list_type + ">" + "".join(list_buf) + "</" + list_type + ">")
            list_buf.clear()
            list_type = None

    for raw in lines:
        line = raw.strip()
        if not line:
            flush_para(); flush_list()
            continue
        m = re.match(r"^(#{1,6})\s+(.*)$", line)
        if m:
            flush_para(); flush_list()
            level = min(len(m.group(1)) + 1, 6)  # article body starts at h2 (h1 is the page title)
            out.append(f"<h{level}>" + _inline(m.group(2)) + f"</h{level}>")
            continue
        if line == "---":
            flush_para(); flush_list()
            out.append("<hr>")
            continue
        m = re.match(r"^\d+\.\s+(.*)$", line)
        if m:
            flush_para()
            if list_type != "ol":
                flush_list(); list_type = "ol"
            list_buf.append("<li>" + _inline(m.group(1)) + "</li>")
            continue
        m = re.match(r"^[-*]\s+(.*)$", line)
        if m:
            flush_para()
            if list_type != "ul":
                flush_list(); list_type = "ul"
            list_buf.append("<li>" + _inline(m.group(1)) + "</li>")
            continue
        flush_list()
        para.append(line)

    flush_para(); flush_list()
    return "\n".join(out)


def strip_faq_section(md):
    """The blog importer extracts '## Frequently asked' into blog_faq rows
    but leaves the section in body_md too (so the raw file stays readable
    standalone) — cut it from the rendered body so the FAQ isn't shown twice
    once blog_faq is rendered as its own block."""
    return re.split(r"\n##\s+Frequently asked\b", md, maxsplit=1, flags=re.I)[0].rstrip()


def excerpt(md, max_chars=200):
    """First non-empty paragraph, plain text, for list-page cards and the
    meta description fallback."""
    for block in strip_faq_section(md).split("\n\n"):
        block = block.strip()
        if not block or block.startswith("#") or block.startswith("---"):
            continue
        text = re.sub(r"[*_`\[\]()]", "", block)
        return (text[:max_chars] + "…") if len(text) > max_chars else text
    return ""
