"""
CA Automator — server-rendered public pages (blog, careers, sitemap, robots).

Everything else public-facing (services, testimonials, the landing page
itself) is a client-rendered page in web/index.html + web/landing.js that
fetches JSON from /api/public/... — fine for content a visitor reaches by
clicking around the site, but wrong for content meant to be found via search:
a crawler needs the real title, meta description, canonical URL, Open Graph
tags and article text present in the FIRST response, not assembled by JS
afterward. Blog posts and job postings are exactly that kind of content, so
this module renders them as complete, self-contained HTML documents — no
app.js, no engines, just the page — built server-side from the database on
every request (article content changes rarely enough that this doesn't need
caching, and "always current" is worth more here than the last bit of
latency).

server/app.py's application() calls into this module for a small, fixed set
of paths that must be dynamic (sitemap.xml, robots.txt, /blog, /blog/<slug>,
/careers, /careers/<slug>) before falling through to static-file serving —
see the comment at that call site for why these can't just be ordinary
@route handlers.
"""
import html
import json as json_mod

import db
import markdown_lite

_ESC = html.escape


def _firm():
    row = db.query("SELECT * FROM firm ORDER BY id LIMIT 1", one=True)
    return dict(row) if row else {}


def _cat_tag(name):
    return '<span class="pub-card-cat">' + _ESC(name) + '</span>' if name else ""


def _base_url(env):
    scheme = "https" if env.get("HTTPS") == "on" or env.get("HTTP_X_FORWARDED_PROTO") == "https" else "http"
    host = env.get("HTTP_HOST") or env.get("SERVER_NAME", "localhost")
    return f"{scheme}://{host}"


def _footer_html(firm_name):
    # Real links, queried live — a sub-page (blog/careers/a static page) must
    # never strand a visitor with only a "back to home" link: Services,
    # Blog, Careers, Downloads and Contact stay reachable from anywhere on
    # the site, not just the landing page's own footer.
    legal_pages = db.query("SELECT slug, title FROM page WHERE is_published = 1 ORDER BY title")
    legal_links = "".join(f'<a href="/page/{_ESC(p["slug"])}">{_ESC(p["title"])}</a>' for p in legal_pages)
    tagline = _firm().get("tagline") or "Chartered Accountancy, digitised."
    year = _ESC(str(db.now_iso()[:4]))
    return f"""<footer class="pub-footer">
  <div class="pub-footer-cols">
    <div class="pub-footer-brand"><b>{firm_name}</b><p>{_ESC(tagline)}</p></div>
    <div class="pub-footer-col"><h5>Explore</h5>
      <a href="/">Home</a><a href="/services">Services</a><a href="/blog">Insights</a>
      <a href="/careers">Careers</a><a href="/downloads">Downloads</a><a href="/#contact">Contact</a>
    </div>
    <div class="pub-footer-col"><h5>Legal</h5>{legal_links or '<span class="hint">Nothing published yet</span>'}</div>
  </div>
  <div class="pub-footer-bottom">© {year} {firm_name}. <a href="/">Platform overview →</a></div>
</footer>"""


def _shell(title, meta_description, canonical, body_html, og_image=None, json_ld=None, extra_head=""):
    firm_name = _ESC(_firm().get("name") or "CA Automator")
    ld_tag = ('<script type="application/ld+json">' + json_mod.dumps(json_ld) + "</script>") if json_ld else ""
    og_image_tag = f'<meta property="og:image" content="{_ESC(og_image)}">' if og_image else ""
    return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{_ESC(title)}</title>
<meta name="description" content="{_ESC(meta_description or '')}">
<link rel="canonical" href="{_ESC(canonical)}">
<meta property="og:type" content="article">
<meta property="og:title" content="{_ESC(title)}">
<meta property="og:description" content="{_ESC(meta_description or '')}">
<meta property="og:url" content="{_ESC(canonical)}">
{og_image_tag}
<meta name="twitter:card" content="summary_large_image">
<meta name="theme-color" content="#F6F7FA">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="/style.css">
{extra_head}
{ld_tag}
</head>
<body class="pub-page">
<nav class="pub-nav"><a href="/" class="pub-nav-brand"><img class="brand-mark" src="/logo-mark.png" alt="{firm_name}">{firm_name}</a>
<a href="/#modules" class="pub-nav-link">Platform</a><a href="/blog" class="pub-nav-link">Blog</a>
<a href="/careers" class="pub-nav-link">Careers</a><a href="/downloads" class="pub-nav-link">Downloads</a>
<a href="/#contact" class="pub-nav-link">Contact</a></nav>
<main class="pub-main">
{body_html}
</main>
{_footer_html(firm_name)}
</body>
</html>"""


# ─────────────────────────────────────────────────────────────────── blog

def blog_list_page(env, qs):
    page = max(1, int((qs.get("page") or ["1"])[0] or 1))
    per_page = 12
    category_slug = (qs.get("category") or [None])[0]
    search = (qs.get("q") or [None])[0]

    where = ["status = 'published'"]
    args = []
    if category_slug:
        cat = db.query("SELECT id, name FROM blog_category WHERE slug = ?", (category_slug,), one=True)
        if cat:
            where.append("category_id = ?"); args.append(cat["id"])
    if search:
        where.append("(title LIKE ? OR meta_description LIKE ?)")
        args += [f"%{search}%", f"%{search}%"]
    where_sql = " AND ".join(where)

    total = db.query(f"SELECT COUNT(*) n FROM blog_post WHERE {where_sql}", args, one=True)["n"]
    posts = db.query(
        f"SELECT p.*, c.name category_name, c.slug category_slug FROM blog_post p"
        f" LEFT JOIN blog_category c ON c.id = p.category_id WHERE {where_sql}"
        " ORDER BY published_at DESC LIMIT ? OFFSET ?", args + [per_page, (page - 1) * per_page])
    categories = db.query("SELECT * FROM blog_category ORDER BY name")

    cards = "".join(
        f'<a class="pub-card" href="/blog/{_ESC(p["slug"])}">'
        + (f'<img class="pub-card-img" src="{_ESC(p["og_image_path"])}" alt="" loading="lazy">' if p["og_image_path"] else "")
        + f'{_cat_tag(p["category_name"])}'
        f'<h3>{_ESC(p["h1"] or p["title"])}</h3>'
        f'<p>{_ESC(markdown_lite.excerpt(p["body_md"]))}</p>'
        f'<span class="pub-card-meta">{_ESC((p["published_at"] or "")[:10])}'
        f'{" · " + str(p["reading_minutes"]) + " min read" if p["reading_minutes"] else ""}</span></a>'
        for p in posts) or '<p class="pub-empty">No articles published yet.</p>'

    cat_links = "".join(
        f'<a href="/blog?category={_ESC(c["slug"])}" class="pub-chip{" on" if c["slug"] == category_slug else ""}">{_ESC(c["name"])}</a>'
        for c in categories)

    total_pages = max(1, (total + per_page - 1) // per_page)
    pager = ""
    if total_pages > 1:
        links = []
        for pnum in range(1, total_pages + 1):
            qp = f"?page={pnum}" + (f"&category={_ESC(category_slug)}" if category_slug else "")
            links.append(f'<a href="/blog{qp}" class="pub-page-link{" on" if pnum == page else ""}">{pnum}</a>')
        pager = '<div class="pub-pager">' + "".join(links) + "</div>"

    body = (
        '<div class="pub-head"><h1>Insights</h1>'
        '<p>Mechanisms, not rate lookups — practical explanations that stay true across Finance Acts.</p></div>'
        f'<div class="pub-chips">{cat_links}</div>'
        f'<div class="pub-grid">{cards}</div>{pager}'
    )
    canonical = _base_url(env) + "/blog" + (f"?category={category_slug}" if category_slug else "")
    return _shell("Insights — " + (_firm().get("name") or "CA Automator"),
                  "Practical explanations of tax, audit and accounting mechanisms in Nepal.",
                  canonical, body).encode("utf-8")


def blog_detail_page(env, slug):
    post = db.query(
        "SELECT p.*, c.name category_name, c.slug category_slug FROM blog_post p"
        " LEFT JOIN blog_category c ON c.id = p.category_id WHERE p.slug = ? AND p.status = 'published'",
        (slug,), one=True)
    if not post:
        return None
    post = dict(post)
    db.execute("UPDATE blog_post SET views = views + 1 WHERE id = ?", (post["id"],))

    faqs = db.query("SELECT question, answer FROM blog_faq WHERE post_id = ? ORDER BY sort_order", (post["id"],))
    related = db.query(
        "SELECT p2.slug, p2.title FROM blog_internal_link l JOIN blog_post p2 ON p2.id = l.to_post_id"
        " WHERE l.from_post_id = ? AND p2.status = 'published'", (post["id"],))
    if not related:
        related = db.query(
            "SELECT slug, title FROM blog_post WHERE category_id = ? AND id != ? AND status = 'published'"
            " ORDER BY published_at DESC LIMIT 4", (post["category_id"], post["id"]))

    article_html = markdown_lite.render(markdown_lite.strip_faq_section(post["body_md"]))
    faq_html = ""
    if faqs:
        faq_html = '<section class="pub-faq"><h2>Frequently asked</h2>' + "".join(
            f'<details><summary>{_ESC(f["question"])}</summary><p>{_ESC(f["answer"])}</p></details>' for f in faqs
        ) + "</section>"
    related_html = ""
    if related:
        related_html = '<section class="pub-related"><h2>Related</h2><ul>' + "".join(
            f'<li><a href="/blog/{_ESC(r["slug"])}">{_ESC(r["title"])}</a></li>' for r in related
        ) + "</ul></section>"

    canonical = post["canonical_url"] or (_base_url(env) + "/blog/" + post["slug"])
    hero_img = f'<img class="pub-article-img" src="{_ESC(post["og_image_path"])}" alt="{_ESC(post["h1"] or post["title"])}">' if post.get("og_image_path") else ""
    body = (
        f'<article class="pub-article">'
        f'{_cat_tag(post["category_name"])}'
        f'<h1>{_ESC(post["h1"] or post["title"])}</h1>'
        f'<p class="pub-article-meta">{_ESC((post["published_at"] or "")[:10])}'
        f'{" · " + str(post["reading_minutes"]) + " min read" if post["reading_minutes"] else ""}</p>'
        f'{hero_img}{article_html}{faq_html}</article>{related_html}'
    )

    json_ld = {
        "@context": "https://schema.org", "@type": post.get("schema_type") or "Article",
        "headline": post["h1"] or post["title"], "description": post["meta_description"] or "",
        "datePublished": post["published_at"], "dateModified": post["updated_at"] or post["published_at"],
        "author": {"@type": "Organization", "name": _firm().get("name") or "CA Automator"},
        "publisher": {"@type": "Organization", "name": _firm().get("name") or "CA Automator"},
        "mainEntityOfPage": canonical,
    }
    if faqs:
        json_ld = [json_ld, {
            "@context": "https://schema.org", "@type": "FAQPage",
            "mainEntity": [{"@type": "Question", "name": f["question"],
                           "acceptedAnswer": {"@type": "Answer", "text": f["answer"]}} for f in faqs]
        }]

    return _shell(post["title"], post["meta_description"], canonical, body,
                  og_image=post.get("og_image_path"), json_ld=json_ld).encode("utf-8")


# ────────────────────────────────────────────────────────────────── careers

def careers_list_page(env):
    # Vacancies are the one piece of tenant-firm content that shares this
    # single public landing page across every firm on the platform (by the
    # firm's own explicit request) — so each card names its actual hiring
    # firm rather than reading as CA-Automator's own opening.
    jobs = db.query(
        "SELECT j.*, f.name firm_name FROM job_posting j JOIN firm f ON f.id = j.firm_id"
        " WHERE j.is_published = 1 AND (j.deadline IS NULL OR j.deadline >= date('now'))"
        " ORDER BY j.created_at DESC")
    cards = "".join(
        f'<a class="pub-card" href="/careers/{_ESC(j["slug"])}"><h3>{_ESC(j["title"])}</h3>'
        f'<p class="pub-card-firm">{_ESC(j["firm_name"])}</p>'
        f'<p>{_ESC(j["department"] or "")}{" · " + _ESC(j["location"]) if j["location"] else ""}</p>'
        f'<span class="pub-card-meta">{_ESC((j["employment_type"] or "").replace("_", " ").title())}'
        f'{" · Apply by " + _ESC(j["deadline"]) if j["deadline"] else ""}</span></a>'
        for j in jobs) or '<p class="pub-empty">No open positions right now — check back soon.</p>'

    body = ('<div class="pub-head"><h1>Careers</h1>'
           '<p>We are a practice that builds its own tools. If connected professional software and getting the numbers right on the first pass sounds like your kind of work, we would like to hear from you.</p></div>'
           f'<div class="pub-grid">{cards}</div>')
    canonical = _base_url(env) + "/careers"
    return _shell("Careers — " + (_firm().get("name") or "CA Automator"),
                  "Open positions at " + (_firm().get("name") or "CA Automator"), canonical, body).encode("utf-8")


def careers_detail_page(env, slug):
    job = db.query(
        "SELECT j.*, f.name firm_name FROM job_posting j JOIN firm f ON f.id = j.firm_id"
        " WHERE j.slug = ? AND j.is_published = 1", (slug,), one=True)
    if not job:
        return None
    job = dict(job)
    closed = job["deadline"] and job["deadline"] < db.now_iso()[:10]

    facts = [
        ("Hiring firm", job.get("firm_name")),
        ("Department", job.get("department")), ("Location", job.get("location")),
        ("Type", (job.get("employment_type") or "").replace("_", " ").title()),
        ("Experience", job.get("experience_required")), ("Education", job.get("education_required")),
        ("Salary", job.get("salary_range")), ("Apply by", job.get("deadline")),
    ]
    facts_html = "".join(f'<div class="pub-fact"><b>{_ESC(l)}</b><span>{_ESC(v)}</span></div>' for l, v in facts if v)

    # Plain text/Markdown from Admin's editor, same as services — see
    # db.py's _NEW_COLUMNS comment on why the columns keep their "_html" name.
    description_md = markdown_lite.render(job["description_html"]) if job.get("description_html") else ""
    resp_md = ("<h2>Responsibilities</h2>" + markdown_lite.render(job["responsibilities_html"])) if job.get("responsibilities_html") else ""
    reqs_md = ("<h2>Requirements</h2>" + markdown_lite.render(job["requirements_html"])) if job.get("requirements_html") else ""
    body = (
        f'<article class="pub-article"><h1>{_ESC(job["title"])}</h1>'
        f'<div class="pub-facts">{facts_html}</div>'
        f'{description_md}{resp_md}{reqs_md}'
        + (
            '<div id="apply-root" data-job-slug="' + _ESC(job["slug"]) + '"><p>Loading application form…</p></div>'
            '<script src="/careers_apply.js" defer></script>'
            if not closed else
            '<p class="pub-empty">Applications for this position are now closed.</p>'
        ) +
        '</article>'
    )
    canonical = _base_url(env) + "/careers/" + job["slug"]
    json_ld = {
        "@context": "https://schema.org", "@type": "JobPosting",
        "title": job["title"], "description": job.get("description_html") or job["title"],
        "datePosted": job["created_at"], "validThrough": job.get("deadline"),
        "employmentType": (job.get("employment_type") or "FULL_TIME").upper(),
        "hiringOrganization": {"@type": "Organization", "name": job.get("firm_name") or "CA Automator"},
    }
    return _shell(job["title"] + " — Careers", job.get("description_html") and job["title"], canonical, body,
                  json_ld=json_ld).encode("utf-8")


# ────────────────────────────────────────────────────────────────── team
# The About section's team grid links each card to its own /team/<slug>
# page instead of showing the full bio inline — the front page only needs
# enough to make someone want to click through (see landing.js renderTeam).

def team_detail_page(env, slug):
    member = db.query(
        "SELECT * FROM team_member WHERE slug = ? AND is_published = 1 AND firm_id = ?",
        (slug, _firm().get("id")), one=True)
    if not member:
        return None
    member = dict(member)
    expertise = [e.strip() for e in (member.get("expertise") or "").split(",") if e.strip()]

    facts_html = ""
    if member.get("designation"):
        facts_html += f'<div class="pub-fact"><b>Role</b><span>{_ESC(member["designation"])}</span></div>'
    if expertise:
        facts_html += f'<div class="pub-fact"><b>Expertise</b><span>{_ESC(", ".join(expertise))}</span></div>'

    photo_html = (f'<img class="pub-article-img" src="{_ESC(member["photo_path"])}" alt="{_ESC(member["name"])}">'
                 if member.get("photo_path") else "")
    bio_html = f'<p>{_ESC(member["bio"])}</p>' if member.get("bio") else ""
    linkedin_html = (f'<p><a class="btn btn-primary" href="{_ESC(member["linkedin_url"])}" target="_blank"'
                     f' rel="noopener">Connect on LinkedIn →</a></p>' if member.get("linkedin_url") else "")

    body = (
        f'<article class="pub-article">{photo_html}<h1>{_ESC(member["name"])}</h1>'
        f'<div class="pub-facts">{facts_html}</div>{bio_html}{linkedin_html}</article>'
    )
    canonical = _base_url(env) + "/team/" + member["slug"]
    json_ld = {
        "@context": "https://schema.org", "@type": "Person",
        "name": member["name"], "jobTitle": member.get("designation") or "",
        "worksFor": {"@type": "Organization", "name": _firm().get("name") or "CA Automator"},
    }
    return _shell(member["name"] + (" — " + member["designation"] if member.get("designation") else ""),
                  member.get("bio"), canonical, body, og_image=member.get("photo_path"),
                  json_ld=json_ld).encode("utf-8")


# ─────────────────────────────────────────────────────────────── downloads
# A public document library, real HTML on the first response like every
# other page in this module — so a search engine (or an AI crawler) can
# actually find and cite a specific document by its own dedicated URL, not
# just see a JS-rendered list. File bytes themselves are served by
# server/app.py's dispatcher (see /downloads/file/<id> and
# /downloads/view/<id>), never through this module.

_EXT_LABELS = {".pdf": "PDF", ".doc": "Word", ".docx": "Word", ".xls": "Excel", ".xlsx": "Excel",
              ".ppt": "PowerPoint", ".pptx": "PowerPoint"}


def _file_ext(file_path):
    return "." + file_path.rsplit(".", 1)[-1] if "." in file_path else ""


def _file_size_label(n):
    if not n:
        return ""
    for unit in ("B", "KB", "MB"):
        if n < 1024:
            return f"{n:.0f} {unit}" if unit == "B" else f"{n:.1f} {unit}"
        n /= 1024
    return f"{n:.1f} GB"


def downloads_list_page(env):
    files = db.query(
        "SELECT * FROM download_file WHERE is_published = 1 AND firm_id = ? ORDER BY uploaded_at DESC",
        (_firm().get("id"),))
    cards = "".join(
        f'<a class="pub-card" href="/downloads/{_ESC(f["slug"])}">'
        f'<span class="pub-card-cat">{_ESC(_EXT_LABELS.get(_file_ext(f["file_path"]), "File"))}</span>'
        f'<h3>{_ESC(f["title"])}</h3>'
        f'<p>{_ESC((f["description"] or "")[:180])}</p>'
        f'<span class="pub-card-meta">{_ESC((f["uploaded_at"] or "")[:10])}'
        f'{" · " + _file_size_label(f["file_size"]) if f["file_size"] else ""}</span></a>'
        for f in files) or '<p class="pub-empty">No downloads published yet.</p>'

    body = ('<div class="pub-head"><h1>Downloads</h1>'
           '<p>Brochures, forms and other documents we\'ve made available — sorted newest first.</p></div>'
           f'<div class="pub-grid">{cards}</div>')
    canonical = _base_url(env) + "/downloads"
    return _shell("Downloads — " + (_firm().get("name") or "CA Automator"),
                  "Public documents and downloads from " + (_firm().get("name") or "CA Automator"),
                  canonical, body).encode("utf-8")


def downloads_detail_page(env, slug):
    f = db.query("SELECT * FROM download_file WHERE slug = ? AND is_published = 1 AND firm_id = ?",
                (slug, _firm().get("id")), one=True)
    if not f:
        return None
    f = dict(f)
    ext = _file_ext(f["file_path"])
    label = _EXT_LABELS.get(ext, "File")
    facts = [("Type", label), ("Size", _file_size_label(f["file_size"])), ("Published", (f["uploaded_at"] or "")[:10])]
    facts_html = "".join(f'<div class="pub-fact"><b>{_ESC(l)}</b><span>{_ESC(v)}</span></div>' for l, v in facts if v)
    description_html = f'<p>{_ESC(f["description"])}</p>' if f.get("description") else ""

    body = (
        f'<article class="pub-article"><h1>{_ESC(f["title"])}</h1>'
        f'<div class="pub-facts">{facts_html}</div>'
        f'{description_html}'
        f'<div style="display:flex;gap:10px;margin-top:20px">'
        f'<a class="btn btn-primary" href="/downloads/view/{f["id"]}" target="_blank" rel="noopener">View</a>'
        f'<a class="btn" href="/downloads/file/{f["id"]}">Download</a>'
        f'</div></article>'
    )
    canonical = _base_url(env) + "/downloads/" + f["slug"]
    json_ld = {
        "@context": "https://schema.org", "@type": "DigitalDocument",
        "name": f["title"], "description": f.get("description") or f["title"],
        "datePublished": f["uploaded_at"], "encodingFormat": f.get("mime_type") or "",
        "url": canonical, "publisher": {"@type": "Organization", "name": _firm().get("name") or "CA Automator"},
    }
    return _shell(f["title"] + " — Downloads", f.get("description") or f["title"], canonical, body,
                  json_ld=json_ld).encode("utf-8")


# ───────────────────────────────────────────────────────────────── services

def _price_label(svc):
    if svc["price_type"] == "enquire" or svc["price"] is None:
        return "Price on enquiry"
    amount = f'{svc["currency"] or "NPR"} {svc["price"]:,.0f}'
    return f"From {amount}" if svc["price_type"] == "starting_from" else amount


def services_list_page(env):
    services = db.query(
        "SELECT s.*, sc.name category_name, sc.slug category_slug FROM service s"
        " LEFT JOIN service_category sc ON sc.id = s.category_id"
        " WHERE s.is_published = 1 AND s.firm_id = ? ORDER BY sc.sort_order, s.sort_order, s.name",
        (_firm().get("id"),))
    cards = "".join(
        f'<a class="pub-card" href="/services/{_ESC(s["slug"])}">'
        f'{_cat_tag(s["category_name"])}'
        f'<h3>{_ESC(s["name"])}</h3>'
        f'<p>{_ESC(s["summary"] or "")}</p>'
        f'<span class="pub-card-meta">{_ESC(_price_label(s))}</span></a>'
        for s in services) or '<p class="pub-empty">Services will appear here once the firm publishes them.</p>'

    body = ('<div class="pub-head"><h1>Services</h1>'
           '<p>Every engagement we take on, in one place — what it covers, who it helps, and how to start.</p></div>'
           f'<div class="pub-grid">{cards}</div>')
    canonical = _base_url(env) + "/services"
    return _shell("Services — " + (_firm().get("name") or "CA Automator"),
                  "Audit, tax, advisory and compliance services offered by " + (_firm().get("name") or "CA Automator"),
                  canonical, body).encode("utf-8")


def services_detail_page(env, slug):
    svc = db.query(
        "SELECT s.*, sc.name category_name, sc.slug category_slug FROM service s"
        " LEFT JOIN service_category sc ON sc.id = s.category_id"
        " WHERE s.slug = ? AND s.is_published = 1 AND s.firm_id = ?", (slug, _firm().get("id")), one=True)
    if not svc:
        return None
    svc = dict(svc)
    faqs = db.query("SELECT question, answer FROM service_faq WHERE service_id = ? ORDER BY sort_order", (svc["id"],))
    related = db.query(
        "SELECT slug, name, summary FROM service WHERE category_id = ? AND id != ? AND is_published = 1 AND firm_id = ? LIMIT 4",
        (svc["category_id"], svc["id"], svc["firm_id"])) if svc["category_id"] else []

    # Every one of these is plain text/Markdown from Admin's editor (the
    # "_html" suffix is legacy naming only — see db.py's _NEW_COLUMNS
    # comment), rendered the same way a blog post body is.
    sections = []
    if svc.get("problem_html"):
        sections.append(f'<h2>The problem</h2>{markdown_lite.render(svc["problem_html"])}')
    if svc.get("description_html"):
        sections.append(f'<h2>What we deliver</h2>{markdown_lite.render(svc["description_html"])}')
    if svc.get("process_html"):
        sections.append(f'<h2>How we work</h2>{markdown_lite.render(svc["process_html"])}')
    if svc.get("who_for_html"):
        sections.append(f'<h2>Who this is for</h2>{markdown_lite.render(svc["who_for_html"])}')
    if svc.get("benefits_html"):
        sections.append(f'<h2>Key benefits</h2>{markdown_lite.render(svc["benefits_html"])}')
    if svc.get("documents_html"):
        sections.append(f'<h2>Documents required</h2>{markdown_lite.render(svc["documents_html"])}')

    faq_html = ""
    if faqs:
        faq_html = '<section class="pub-faq"><h2>Frequently asked</h2>' + "".join(
            f'<details><summary>{_ESC(f["question"])}</summary><p>{_ESC(f["answer"])}</p></details>' for f in faqs
        ) + "</section>"
    related_html = ""
    if related:
        related_html = '<section class="pub-related"><h2>Related services</h2><ul>' + "".join(
            f'<li><a href="/services/{_ESC(r["slug"])}">{_ESC(r["name"])}</a></li>' for r in related
        ) + "</ul></section>"

    if svc["is_buyable"] and svc["price"]:
        cta_html = f'<a class="btn btn-primary" href="/?buy={_ESC(svc["slug"])}#services">Buy this service — {_ESC(_price_label(svc))}</a>'
    elif svc["requires_appointment"]:
        cta_html = '<a class="btn btn-primary" href="/#book-appointment">Book a consultation</a>'
    else:
        cta_html = '<a class="btn btn-primary" href="/#contact">Request a consultation</a>'

    facts = [("Category", svc.get("category_name")), ("Price", _price_label(svc)), ("Duration", svc.get("duration_label"))]
    facts_html = "".join(f'<div class="pub-fact"><b>{_ESC(l)}</b><span>{_ESC(v)}</span></div>' for l, v in facts if v)

    summary_html = ('<p class="pub-article-meta">' + _ESC(svc["summary"]) + '</p>') if svc.get("summary") else ""
    body = (
        f'<article class="pub-article">'
        f'{_cat_tag(svc["category_name"])}'
        f'<h1>{_ESC(svc["name"])}</h1>'
        f'{summary_html}'
        f'<div class="pub-facts">{facts_html}</div>'
        f'<div style="margin:20px 0">{cta_html}</div>'
        f'{"".join(sections)}'
        f'{faq_html}</article>{related_html}'
    )
    canonical = _base_url(env) + "/services/" + svc["slug"]
    json_ld = {
        "@context": "https://schema.org", "@type": "Service",
        "name": svc["name"], "description": svc.get("summary") or svc["name"],
        "provider": {"@type": "Organization", "name": _firm().get("name") or "CA Automator"},
    }
    if faqs:
        json_ld = [json_ld, {
            "@context": "https://schema.org", "@type": "FAQPage",
            "mainEntity": [{"@type": "Question", "name": f["question"],
                           "acceptedAnswer": {"@type": "Answer", "text": f["answer"]}} for f in faqs]
        }]
    return _shell(svc["meta_title"] or svc["name"], svc.get("meta_description") or svc.get("summary"), canonical,
                  body, og_image=svc.get("image_path"), json_ld=json_ld).encode("utf-8")


# ─────────────────────────────────────────────────────── static pages (CMS)

def static_page(env, slug):
    """About Us, Privacy Policy, Terms, Refund Policy — whatever a super
    admin has created in Admin > Site Settings > Pages. content_md is plain
    text/Markdown (the admin editor is a plain textarea, never raw HTML),
    rendered the same way a blog post body is. content_html only exists for
    a page saved before content_md did — rendered as-is, untouched, since
    reinterpreting real markup as Markdown would escape and mangle it
    rather than help."""
    page = db.query("SELECT * FROM page WHERE slug = ? AND is_published = 1 AND firm_id = ?",
                    (slug, _firm().get("id")), one=True)
    if not page:
        return None
    page = dict(page)
    content = markdown_lite.render(page["content_md"]) if page.get("content_md") else (page.get("content_html") or "")
    body = f'<article class="pub-article"><h1>{_ESC(page["title"])}</h1>{content}</article>'
    canonical = _base_url(env) + "/page/" + page["slug"]
    return _shell(page.get("meta_title") or page["title"], page.get("meta_description"), canonical, body).encode("utf-8")


# ───────────────────────────────────────────────────────────── sitemap/robots

def sitemap_xml(env):
    base = _base_url(env)
    fid = _firm().get("id")
    urls = [(base + "/", "1.0"), (base + "/services", "0.9"), (base + "/blog", "0.8"),
            (base + "/careers", "0.6"), (base + "/downloads", "0.6")]
    for p in db.query("SELECT slug, updated_at FROM blog_post WHERE status = 'published'"):
        urls.append((base + "/blog/" + p["slug"], "0.7"))
    # Job postings are the deliberate exception — every tenant firm's
    # vacancies share this one sitemap/landing page (see public_list_careers).
    for j in db.query("SELECT slug FROM job_posting WHERE is_published = 1"):
        urls.append((base + "/careers/" + j["slug"], "0.5"))
    for s in db.query("SELECT slug FROM service WHERE is_published = 1 AND firm_id = ?", (fid,)):
        urls.append((base + "/services/" + s["slug"], "0.8"))
    for p in db.query("SELECT slug FROM page WHERE is_published = 1 AND firm_id = ?", (fid,)):
        urls.append((base + "/page/" + p["slug"], "0.4"))
    for t in db.query("SELECT slug FROM team_member WHERE is_published = 1 AND firm_id = ?", (fid,)):
        urls.append((base + "/team/" + t["slug"], "0.4"))
    for d in db.query("SELECT slug FROM download_file WHERE is_published = 1 AND firm_id = ?", (fid,)):
        urls.append((base + "/downloads/" + d["slug"], "0.5"))
    body = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
    seen = set()
    for url, priority in urls:
        if url in seen:
            continue
        seen.add(url)
        body += f"  <url><loc>{_ESC(url)}</loc><priority>{priority}</priority></url>\n"
    body += "</urlset>\n"
    return body.encode("utf-8")


def robots_txt(env):
    base = _base_url(env)
    return f"User-agent: *\nAllow: /\nDisallow: /api/\nSitemap: {base}/sitemap.xml\n".encode("utf-8")
