const ALLOWED_TAGS = new Set([
    "a",
    "b",
    "blockquote",
    "br",
    "code",
    "del",
    "em",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    "hr",
    "i",
    "li",
    "ol",
    "p",
    "pre",
    "span",
    "strong",
    "table",
    "tbody",
    "td",
    "th",
    "thead",
    "tr",
    "ul",
]);

const DROP_CONTENT_TAGS = new Set(["script", "style", "iframe", "object", "embed", "link", "meta"]);

const GLOBAL_ALLOWED_ATTRS = new Set(["class"]);
const TAG_ALLOWED_ATTRS: Record<string, Set<string>> = {
    a: new Set(["href", "title", "target", "rel"]),
    code: new Set(["class"]),
    span: new Set(["class"]),
};

function isSafeUrl(value: string): boolean {
    const normalized = value.trim().toLowerCase();
    if (!normalized) return false;

    if (
        normalized.startsWith("http://") ||
        normalized.startsWith("https://") ||
        normalized.startsWith("mailto:") ||
        normalized.startsWith("tel:") ||
        normalized.startsWith("/") ||
        normalized.startsWith("#")
    ) {
        return true;
    }

    return false;
}

function sanitizeAttributes(element: Element): void {
    const tagName = element.tagName.toLowerCase();
    const allowedForTag = TAG_ALLOWED_ATTRS[tagName] || new Set<string>();

    const attrs = Array.from(element.attributes);
    for (const attr of attrs) {
        const name = attr.name.toLowerCase();
        const value = attr.value;

        const allowed = GLOBAL_ALLOWED_ATTRS.has(name) || allowedForTag.has(name);
        if (!allowed) {
            element.removeAttribute(attr.name);
            continue;
        }

        if ((name === "href" || name === "src") && !isSafeUrl(value)) {
            element.removeAttribute(attr.name);
        }
    }

    if (tagName === "a") {
        if (element.getAttribute("target") === "_blank") {
            element.setAttribute("rel", "noopener noreferrer nofollow");
        } else {
            element.removeAttribute("target");
        }
    }
}

function sanitizeNode(node: Node): void {
    if (node.nodeType === Node.TEXT_NODE) return;

    if (node.nodeType !== Node.ELEMENT_NODE) {
        node.parentNode?.removeChild(node);
        return;
    }

    const element = node as Element;
    const tagName = element.tagName.toLowerCase();

    if (!ALLOWED_TAGS.has(tagName)) {
        if (DROP_CONTENT_TAGS.has(tagName)) {
            element.remove();
            return;
        }

        const parent = element.parentNode;
        if (!parent) return;
        while (element.firstChild) {
            parent.insertBefore(element.firstChild, element);
        }
        parent.removeChild(element);
        return;
    }

    sanitizeAttributes(element);
    const children = Array.from(element.childNodes);
    for (const child of children) {
        sanitizeNode(child);
    }
}

// 服务端降级：使用正则剥离危险标签（无 DOMParser 时）
function serverSideSanitize(input: string): string {
    let result = input;
    // 移除 script、style、iframe 等危险标签及其内容
    for (const tag of DROP_CONTENT_TAGS) {
        const regex = new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi");
        result = result.replace(regex, "");
        // 移除自闭合形式
        result = result.replace(new RegExp(`<${tag}[^>]*/?>`, "gi"), "");
    }
    // 移除事件处理器属性（on* 属性）
    result = result.replace(/\s+on\w+\s*=\s*"[^"]*"/gi, "");
    result = result.replace(/\s+on\w+\s*=\s*'[^']*'/gi, "");
    // 移除 javascript: 协议
    result = result.replace(/href\s*=\s*"javascript:[^"]*"/gi, 'href=""');
    result = result.replace(/href\s*=\s*'javascript:[^']*'/gi, "href=''");
    return result;
}

export function sanitizeHtml(input: string): string {
    if (typeof window === "undefined") return serverSideSanitize(input);
    const parser = new DOMParser();
    const doc = parser.parseFromString(input, "text/html");
    const nodes = Array.from(doc.body.childNodes);
    for (const node of nodes) {
        sanitizeNode(node);
    }
    return doc.body.innerHTML;
}

