import { h, Host } from "@stencil/core";
import { markdownToHTML } from "./markdown-parser";
import { globalConfig } from "../../global/config";
import { ImageIntersectionObserver } from "./image-intersection-observer";
import { hydrateCustomElements } from "./hydrate-custom-elements";
import { DEFAULT_MARKDOWN_WHITELIST } from "./default-whitelist";
/**
 * The Markdown component receives markdown syntax
 * and renders it as HTML.
 *
 * A built-in set of lime-elements components is whitelisted by default
 * and can be used directly in markdown content without any configuration.
 * Consumers can extend this list via the `whitelist` prop or `limel-config`.
 *
 * When custom elements use JSON attribute values, any URL-bearing
 * properties (`href`, `src`, `cite`, `longDesc`) are automatically
 * sanitized using the same protocol allowlists as rehype-sanitize.
 * URLs with dangerous schemes (e.g. `javascript:`, `data:`) are
 * removed (with a console warning) to prevent script injection.
 *
 * @exampleComponent limel-example-markdown-headings
 * @exampleComponent limel-example-markdown-emphasis
 * @exampleComponent limel-example-markdown-lists
 * @exampleComponent limel-example-markdown-links
 * @exampleComponent limel-example-markdown-images
 * @exampleComponent limel-example-markdown-code
 * @exampleComponent limel-example-markdown-footnotes
 * @exampleComponent limel-example-markdown-tables
 * @exampleComponent limel-example-markdown-html
 * @exampleComponent limel-example-markdown-keys
 * @exampleComponent limel-example-markdown-blockquotes
 * @exampleComponent limel-example-markdown-horizontal-rule
 * @exampleComponent limel-example-markdown-custom-component
 * @exampleComponent limel-example-markdown-custom-component-with-json-props
 * @exampleComponent limel-example-markdown-remove-empty-paragraphs
 * @exampleComponent limel-example-markdown-composite
 */
export class Markdown {
    constructor() {
        /**
         * The input text. Treated as GitHub Flavored Markdown, with the addition
         * that any included HTML will be parsed and rendered as HTML, rather than
         * as text.
         */
        this.value = '';
        /**
         * Additional whitelisted custom elements to render inside markdown.
         *
         * A built-in set of lime-elements components (such as `limel-chip`,
         * `limel-icon`, `limel-badge`, `limel-callout`, etc.) is always
         * allowed by default. Any entries provided here are **merged** with
         * those defaults — if both define the same `tagName`, their
         * attributes are combined.
         *
         * Can also be set via `limel-config`. Setting this property will
         * override the global config.
         *
         * JSON attribute values that contain URL-bearing properties
         * (`href`, `src`, `cite`, `longDesc`) are automatically sanitized
         * using the same protocol allowlists as rehype-sanitize. URLs with
         * dangerous schemes (e.g. `javascript:`, `data:`) are removed
         * (with a console warning).
         *
         * @alpha
         */
        this.whitelist = globalConfig.markdownWhitelist;
        /**
         * Enable lazy loading for images
         */
        this.lazyLoadImages = false;
        /**
         * Set to `false` to preserve empty paragraphs before rendering.
         * Empty paragraphs are paragraphs that do not contain
         * any meaningful content (text, images, etc.), or only contain
         * whitespace (`<br />` or `&nbsp;`).
         */
        this.removeEmptyParagraphs = true;
        this.imageIntersectionObserver = null;
    }
    async textChanged() {
        try {
            this.cleanupImageIntersectionObserver();
            // The whitelist merge and default import live here (not in
            // markdown-parser.ts) because this component orchestrates both
            // the parser and hydration, which both need the combined list.
            if (!this.cachedCombinedWhitelist ||
                this.whitelist !== this.cachedConsumerWhitelist) {
                this.cachedConsumerWhitelist = this.whitelist;
                this.cachedCombinedWhitelist = mergeWhitelists(DEFAULT_MARKDOWN_WHITELIST, this.whitelist);
            }
            const combinedWhitelist = this.cachedCombinedWhitelist;
            const html = await markdownToHTML(this.value, {
                forceHardLineBreaks: true,
                whitelist: combinedWhitelist,
                lazyLoadImages: this.lazyLoadImages,
                removeEmptyParagraphs: this.removeEmptyParagraphs,
            });
            this.rootElement.innerHTML = html;
            // Hydration parses JSON attribute values (e.g. link='{"href":"..."}')
            // into JS properties. URL sanitization happens here because
            // rehype-sanitize can't inspect values inside JSON strings.
            hydrateCustomElements(this.rootElement, combinedWhitelist);
            this.setupImageIntersectionObserver();
        }
        catch (error) {
            console.error(error);
        }
    }
    handleWhitelistChange() {
        return this.textChanged();
    }
    handleRemoveEmptyParagraphsChange() {
        return this.textChanged();
    }
    async componentDidLoad() {
        this.textChanged();
    }
    disconnectedCallback() {
        this.cleanupImageIntersectionObserver();
    }
    render() {
        return (h(Host, { key: 'd3c5e71466ad7fa2723a0a44bc6ba6742e597ca1' }, h("div", { key: 'ff45056e1a3ad465bdea9026b0c9674d911607a2', id: "markdown", ref: (el) => (this.rootElement = el) })));
    }
    setupImageIntersectionObserver() {
        if (this.lazyLoadImages) {
            this.imageIntersectionObserver = new ImageIntersectionObserver(this.rootElement);
        }
    }
    cleanupImageIntersectionObserver() {
        if (this.imageIntersectionObserver) {
            this.imageIntersectionObserver.disconnect();
            this.imageIntersectionObserver = null;
        }
    }
    static get is() { return "limel-markdown"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["markdown.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["markdown.css"]
        };
    }
    static get properties() {
        return {
            "value": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The input text. Treated as GitHub Flavored Markdown, with the addition\nthat any included HTML will be parsed and rendered as HTML, rather than\nas text."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "value",
                "defaultValue": "''"
            },
            "whitelist": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "CustomElementDefinition[]",
                    "resolved": "CustomElementDefinition[]",
                    "references": {
                        "CustomElementDefinition": {
                            "location": "import",
                            "path": "../../global/shared-types/custom-element.types",
                            "id": "src/global/shared-types/custom-element.types.ts::CustomElementDefinition",
                            "referenceLocation": "CustomElementDefinition"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "alpha",
                            "text": undefined
                        }],
                    "text": "Additional whitelisted custom elements to render inside markdown.\n\nA built-in set of lime-elements components (such as `limel-chip`,\n`limel-icon`, `limel-badge`, `limel-callout`, etc.) is always\nallowed by default. Any entries provided here are **merged** with\nthose defaults \u2014 if both define the same `tagName`, their\nattributes are combined.\n\nCan also be set via `limel-config`. Setting this property will\noverride the global config.\n\nJSON attribute values that contain URL-bearing properties\n(`href`, `src`, `cite`, `longDesc`) are automatically sanitized\nusing the same protocol allowlists as rehype-sanitize. URLs with\ndangerous schemes (e.g. `javascript:`, `data:`) are removed\n(with a console warning)."
                },
                "getter": false,
                "setter": false,
                "defaultValue": "globalConfig.markdownWhitelist"
            },
            "lazyLoadImages": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Enable lazy loading for images"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "lazy-load-images",
                "defaultValue": "false"
            },
            "removeEmptyParagraphs": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `false` to preserve empty paragraphs before rendering.\nEmpty paragraphs are paragraphs that do not contain\nany meaningful content (text, images, etc.), or only contain\nwhitespace (`<br />` or `&nbsp;`)."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "remove-empty-paragraphs",
                "defaultValue": "true"
            }
        };
    }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "textChanged"
            }, {
                "propName": "whitelist",
                "methodName": "handleWhitelistChange"
            }, {
                "propName": "removeEmptyParagraphs",
                "methodName": "handleRemoveEmptyParagraphsChange"
            }];
    }
}
/**
 * Merge the default whitelist with a consumer-provided one.
 * If both define the same tagName, their attributes are combined.
 * @param defaults
 * @param consumer
 */
function mergeWhitelists(defaults, consumer) {
    if (!(consumer === null || consumer === void 0 ? void 0 : consumer.length)) {
        return defaults.map((def) => (Object.assign(Object.assign({}, def), { attributes: [...def.attributes] })));
    }
    const merged = new Map();
    for (const def of [...defaults, ...consumer]) {
        const existing = merged.get(def.tagName);
        if (existing) {
            for (const attr of def.attributes) {
                existing.add(attr);
            }
        }
        else {
            merged.set(def.tagName, new Set(def.attributes));
        }
    }
    return [...merged.entries()].map(([tagName, attrs]) => ({
        tagName,
        attributes: [...attrs],
    }));
}
