import DomPurify, { Config } from 'dompurify';

const SANITIZE_OPTIONS: Config = {
    ALLOWED_ATTR: ['href'],
    ALLOWED_TAGS: ['a', 'b', 'div', 'em', 'i', 'p', 'span', 'strong', 'ruby', 'rt', 'rp'],
    // allow http://, https://, and // (mapped to https)
    ALLOWED_URI_REGEXP: /^(http(s?):)?\/\/.+/i,
};

const regex = /(url\(["'](?!data:))/gim;

const addStyles = (output: string[], styles: CSSStyleDeclaration) => {
    for (let prop = styles.length - 1; prop >= 0; prop -= 1) {
        const key = styles[prop] as string;
        if (key !== 'content' && styles[key]) {
            const value = styles[key];
            const priority = styles.getPropertyPriority(key as string);
            const priorityString = priority === 'important' ? ` !important` : '';
            if (typeof value === 'string') {
                if (!value.match(regex)) {
                    output.push(`${key}:${value}${priorityString};`);
                }
            } else if (typeof value === 'number') {
                output.push(`${key}:${value}${priorityString};`);
            }
        } else if (styles.getPropertyValue(key)) {
            // These will not override the value unless not declared !important
            output.push(`${key}: ${styles.getPropertyValue(key)} !important;`);
        }
    }
};

const addCssRules = (rules: CSSRuleList, output: string[]) => {
    for (let index = rules.length - 1; index >= 0; index -= 1) {
        const rule = rules[index];
        if (rule.constructor.name === 'CSSStyleRule') {
            const cssRule = rule as CSSStyleRule;
            output.push(`${cssRule.selectorText} {`);
            if (cssRule.style) {
                addStyles(output, cssRule.style);
            }
            output.push('}');
        } else if (rule.constructor.name === 'CSSMediaRule') {
            const mediaRule = rule as CSSMediaRule;
            output.push(`@media ${mediaRule.media.mediaText}{`);
            addCssRules(mediaRule.cssRules, output);
            output.push('}');
        } else if (rule.constructor.name === 'CSSKeyframesRule') {
            const keyFrameRule = rule as CSSKeyframesRule;
            for (let i = keyFrameRule.cssRules.length - 1; i >= 0; i -= 1) {
                const frame = keyFrameRule.cssRules[i];
                if (frame.constructor.name === 'CSSKeyframeRule') {
                    const keyframeRule = frame as CSSKeyframeRule;
                    if (keyframeRule.keyText) {
                        output.push(`${keyframeRule.keyText}{`);
                        if (keyframeRule.style) {
                            addStyles(output, keyframeRule.style);
                        }
                        output.push('}');
                    }
                }
            }
            output.push('}');
        }
    }
};

DomPurify.addHook('afterSanitizeAttributes', (node: Element) => {
    if (node.tagName === 'A') {
        if (node.getAttribute('href')?.startsWith('//')) {
            node.setAttribute('href', `https:${node.getAttribute('href')}`);
        }
        node.setAttribute('target', '_blank');
        node.setAttribute('rel', 'noopener noreferrer');
    }
});

(DomPurify as any).addHook('uponSanitizeElement', (node: Element) => {
    if (node.tagName === 'STYLE') {
        const rules = (node as HTMLStyleElement).sheet?.cssRules;
        if (rules) {
            const output: string[] = [];
            addCssRules(rules, output);
            node.textContent = output.join('\n');
        }
    }
});

export const sanitize = (text: string): string => {
    return DomPurify.sanitize(text, SANITIZE_OPTIONS);
};

export const sanitizeCss = (text: string): string => {
    return (DomPurify as any).sanitize(text, {
        ALLOWED_ATTR: [],
        ALLOWED_TAGS: ['style'],
        RETURN_DOM: true,
        WHOLE_DOCUMENT: true,
    }).innerText;
};
