"""Keep only safe, simple formatting in staff-written biographies.

Bios are written in a small rich-text box and rendered as HTML, so the input is
filtered against an allowlist rather than trusted. Staff are not the threat
model here — a compromised staff account is, and stored HTML that renders in
every owner's browser is exactly the shape of the leak this portal exists to
prevent.
"""
from html import escape
from html.parser import HTMLParser

ALLOWED_TAGS = {"p", "br", "strong", "b", "em", "i", "u", "ul", "ol", "li", "a"}
SELF_CLOSING = {"br"}
ALLOWED_ATTRS = {"a": {"href", "title"}}
SAFE_SCHEMES = ("http://", "https://", "mailto:")


class _Cleaner(HTMLParser):
    def __init__(self):
        super().__init__(convert_charrefs=True)
        self.out = []
        self.open_tags = []

    def handle_starttag(self, tag, attrs):
        if tag not in ALLOWED_TAGS:
            return
        kept = []
        for name, value in attrs:
            if name not in ALLOWED_ATTRS.get(tag, set()):
                continue
            if name == "href":
                lowered = (value or "").strip().lower()
                if not lowered.startswith(SAFE_SCHEMES):
                    continue  # drops javascript:, data:, and anything odd
            kept.append(f' {name}="{escape(value or "", quote=True)}"')
        if tag in SELF_CLOSING:
            self.out.append(f"<{tag}>")
            return
        self.out.append(f"<{tag}{''.join(kept)}>")
        self.open_tags.append(tag)

    def handle_endtag(self, tag):
        if tag not in ALLOWED_TAGS or tag in SELF_CLOSING:
            return
        if tag in self.open_tags:
            # Close anything left dangling inside, so the page can't be broken
            # by unbalanced markup.
            while self.open_tags:
                open_tag = self.open_tags.pop()
                self.out.append(f"</{open_tag}>")
                if open_tag == tag:
                    break

    def handle_data(self, data):
        self.out.append(escape(data))

    def close_all(self):
        while self.open_tags:
            self.out.append(f"</{self.open_tags.pop()}>")


def clean_html(value, limit=20000):
    """Return `value` with only the allowed tags and attributes left."""
    if not value:
        return ""
    cleaner = _Cleaner()
    cleaner.feed(value[:limit])
    cleaner.close()
    cleaner.close_all()
    return "".join(cleaner.out).strip()
