/**
 * HighlightCoordinator
 *
 * Manages separate highlight layers for TTS (temporary) and annotations (persistent).
 * Uses CSS Custom Highlight API - zero DOM mutation, preserves accessibility.
 *
 * Features:
 * - Separate layers prevent TTS clearing from removing student annotations
 * - CSS Custom Highlight API (no DOM mutation)
 * - Full accessibility support (screen readers see original text)
 * - Configurable colors and styles
 * - Annotation persistence via RangeSerializer
 *
 * Part of PIE Assessment Toolkit.
 *
 * Browser Support:
 * - Chrome/Edge 105+
 * - Safari 17.2+
 * - Firefox: Behind flag (not recommended for production)
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API
 */
import { RangeSerializer } from "./RangeSerializer.js";
/**
 * Highlight types
 */
export var HighlightType;
(function (HighlightType) {
    HighlightType["TTS_WORD"] = "tts-word";
    HighlightType["TTS_SENTENCE"] = "tts-sentence";
    HighlightType["ANNOTATION"] = "annotation";
})(HighlightType || (HighlightType = {}));
/**
 * Highlight colors for annotations
 */
export var HighlightColor;
(function (HighlightColor) {
    HighlightColor["YELLOW"] = "yellow";
    HighlightColor["GREEN"] = "green";
    HighlightColor["BLUE"] = "blue";
    HighlightColor["PINK"] = "pink";
    HighlightColor["ORANGE"] = "orange";
    HighlightColor["UNDERLINE"] = "underline";
})(HighlightColor || (HighlightColor = {}));
export class HighlightCoordinator {
    config;
    ttsWordHighlight = null;
    ttsSentenceHighlight = null;
    ttsWordElementHighlights = new Set();
    ttsSentenceElementHighlights = new Set();
    annotations = new Map();
    // Shared highlights per color (one Highlight object per color, contains all ranges)
    colorHighlights = new Map();
    nextAnnotationId = 1;
    supported = false;
    rangeSerializer;
    themeObserver = null;
    explicitTTSColorOverride = null;
    constructor(config = {}) {
        this.config = config;
        this.rangeSerializer = new RangeSerializer();
        // SSR guard
        if (typeof CSS === "undefined" || !("highlights" in CSS)) {
            console.warn("CSS Custom Highlight API not supported (SSR or unsupported browser)");
            return;
        }
        this.supported = true;
        this.initializeHighlights();
        this.registerStyles();
        this.applyAdaptiveTTSStyle();
        this.setupThemeObservation();
    }
    setupThemeObservation() {
        if (typeof document === "undefined")
            return;
        if (typeof MutationObserver === "undefined")
            return;
        const refresh = () => this.applyAdaptiveTTSStyle();
        this.themeObserver = new MutationObserver(refresh);
        this.themeObserver.observe(document.documentElement, {
            attributes: true,
            attributeFilter: ["style", "data-theme", "data-color-scheme", "class"],
        });
        for (const host of document.querySelectorAll("pie-theme")) {
            this.themeObserver.observe(host, {
                attributes: true,
                attributeFilter: [
                    "theme",
                    "scheme",
                    "provider",
                    "variables",
                    "style",
                    "data-theme",
                    "data-color-scheme",
                ],
            });
        }
    }
    parseColor(input) {
        if (!input)
            return null;
        const value = input.trim();
        if (!value)
            return null;
        const hexMatch = /^#([a-f\d]{3}|[a-f\d]{6})$/i.exec(value);
        if (hexMatch) {
            const hex = hexMatch[1];
            if (hex.length === 3) {
                return [
                    parseInt(hex[0] + hex[0], 16),
                    parseInt(hex[1] + hex[1], 16),
                    parseInt(hex[2] + hex[2], 16),
                ];
            }
            return [
                parseInt(hex.slice(0, 2), 16),
                parseInt(hex.slice(2, 4), 16),
                parseInt(hex.slice(4, 6), 16),
            ];
        }
        const rgbMatch = /^rgba?\((.+)\)$/i.exec(value);
        if (rgbMatch) {
            const normalized = rgbMatch[1].replace(/\//g, ",");
            const parts = normalized
                .split(/[,\s]+/)
                .map((part) => part.trim())
                .filter(Boolean);
            const r = Number(parts[0]);
            const g = Number(parts[1]);
            const b = Number(parts[2]);
            if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
                return [r, g, b];
            }
        }
        // Browser parser fallback (covers formats like oklch()).
        if (typeof document !== "undefined") {
            const parserEl = document.createElement("span");
            parserEl.style.color = value;
            if (typeof parserEl.style.color === "string" && parserEl.style.color) {
                document.body?.appendChild(parserEl);
                const resolved = typeof getComputedStyle === "function"
                    ? getComputedStyle(parserEl).color
                    : "";
                parserEl.remove();
                const normalizedResolved = resolved.trim();
                // Guard against recursive loops when computed style returns the same
                // unresolved function syntax (observed with certain color formats).
                if (normalizedResolved && normalizedResolved !== value) {
                    return this.parseColor(resolved);
                }
            }
        }
        return null;
    }
    relativeLuminance([r, g, b]) {
        const toLinear = (channel) => {
            const n = channel / 255;
            return n <= 0.03928 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
        };
        const lr = toLinear(r);
        const lg = toLinear(g);
        const lb = toLinear(b);
        return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
    }
    contrastRatio(a, b) {
        const la = this.relativeLuminance(a);
        const lb = this.relativeLuminance(b);
        const lighter = Math.max(la, lb);
        const darker = Math.min(la, lb);
        return (lighter + 0.05) / (darker + 0.05);
    }
    blend(fg, bg, alpha) {
        return [
            Math.round(fg[0] * alpha + bg[0] * (1 - alpha)),
            Math.round(fg[1] * alpha + bg[1] * (1 - alpha)),
            Math.round(fg[2] * alpha + bg[2] * (1 - alpha)),
        ];
    }
    resolveAdaptiveTTSStyle(sourceEl) {
        const fallbackMissing = [255, 235, 59];
        const fallbackText = [17, 24, 39];
        const fallbackBackground = [255, 255, 255];
        const target = sourceEl ||
            (typeof document !== "undefined" ? document.documentElement : null);
        const computed = target && typeof getComputedStyle === "function"
            ? getComputedStyle(target)
            : null;
        const background = this.parseColor(computed?.getPropertyValue("--pie-background") ||
            computed?.backgroundColor) || fallbackBackground;
        const text = this.parseColor(computed?.getPropertyValue("--pie-text")) || fallbackText;
        const accent = this.explicitTTSColorOverride
            ? this.parseColor(this.explicitTTSColorOverride.color) || fallbackMissing
            : this.parseColor(computed?.getPropertyValue("--pie-missing")) ||
                fallbackMissing;
        const opacityCandidates = this.explicitTTSColorOverride
            ? [Math.max(0.3, Math.min(0.95, this.explicitTTSColorOverride.opacity))]
            : [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];
        let selectedOpacity = opacityCandidates[opacityCandidates.length - 1];
        let bestScore = -Infinity;
        for (const opacity of opacityCandidates) {
            const blended = this.blend(accent, background, opacity);
            const backgroundDelta = this.contrastRatio(blended, background);
            const textContrast = this.contrastRatio(blended, text);
            const score = backgroundDelta * 1.2 + textContrast * 0.8;
            if (backgroundDelta >= 1.25 && textContrast >= 2.4) {
                selectedOpacity = opacity;
                break;
            }
            if (score > bestScore) {
                bestScore = score;
                selectedOpacity = opacity;
            }
        }
        const sentenceOpacity = Math.max(0.24, Math.min(0.85, selectedOpacity * 0.55));
        const underlineOpacity = Math.max(0.55, Math.min(0.95, selectedOpacity + 0.2));
        const shadowOpacity = Math.max(0.22, Math.min(0.6, selectedOpacity * 0.45));
        const underlineColor = text;
        const underlineBlend = this.blend(underlineColor, background, underlineOpacity);
        const underlineDelta = this.contrastRatio(underlineBlend, background);
        const fallbackUnderline = this.relativeLuminance(background) > 0.45 ? [0, 0, 0] : [255, 255, 255];
        const finalUnderline = underlineDelta >= 1.35 ? underlineColor : fallbackUnderline;
        const wordHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${selectedOpacity})`;
        const sentenceHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${sentenceOpacity})`;
        const wordUnderline = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${underlineOpacity})`;
        const wordShadow = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${shadowOpacity})`;
        return {
            wordHighlight,
            sentenceHighlight,
            wordUnderline,
            wordShadow,
        };
    }
    applyAdaptiveTTSStyle(sourceEl) {
        if (typeof document === "undefined")
            return;
        const vars = this.resolveAdaptiveTTSStyle(sourceEl);
        document.documentElement.style.setProperty("--pie-tts-word-highlight", vars.wordHighlight);
        document.documentElement.style.setProperty("--pie-tts-sentence-highlight", vars.sentenceHighlight);
        document.documentElement.style.setProperty("--pie-tts-line-highlight", vars.sentenceHighlight);
        document.documentElement.style.setProperty("--pie-tts-word-underline", vars.wordUnderline);
        document.documentElement.style.setProperty("--pie-tts-word-shadow", vars.wordShadow);
    }
    /**
     * Initialize CSS Custom Highlights
     */
    initializeHighlights() {
        if (!this.supported)
            return;
        // Create highlight registries for TTS
        this.ttsWordHighlight = new Highlight();
        this.ttsSentenceHighlight = new Highlight();
        // Register TTS highlights with CSS
        CSS.highlights.set("tts-word", this.ttsWordHighlight);
        CSS.highlights.set("tts-sentence", this.ttsSentenceHighlight);
        // Create shared highlights for each annotation color
        for (const color of Object.values(HighlightColor)) {
            const highlight = new Highlight();
            this.colorHighlights.set(color, highlight);
            CSS.highlights.set(`annotation-${color}`, highlight);
        }
    }
    /**
     * Register CSS styles for highlights
     */
    registerStyles() {
        if (!this.supported)
            return;
        if (typeof document === "undefined")
            return; // SSR guard
        // Check if styles already exist
        if (document.getElementById("pie-highlight-styles"))
            return;
        const style = document.createElement("style");
        style.id = "pie-highlight-styles";
        style.textContent = `
      /* TTS highlights - temporary */
      ::highlight(tts-word) {
        background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));
        text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));
        text-underline-offset: 2px;
        text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));
        color: inherit;
      }

      /* tts-sentence registry id: coarse read-along band (visual line boxes in layout) */
      ::highlight(tts-sentence) {
        background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));
        color: inherit;
      }

      [data-pie-tts-sentence-element="true"] {
        background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));
        border-radius: 0.12em;
        box-decoration-break: clone;
        -webkit-box-decoration-break: clone;
      }

      [data-pie-tts-word-element="true"] {
        background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));
        text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));
        text-underline-offset: 2px;
        text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));
        border-bottom: 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));
        padding-bottom: 1px;
        border-radius: 0.12em;
        box-decoration-break: clone;
        -webkit-box-decoration-break: clone;
      }

      /* Annotation highlights - persistent */
      ::highlight(annotation-yellow) {
        background-color: var(--pie-annotation-yellow-highlight, rgba(253, 233, 149, 0.5));
        color: inherit;
      }

      ::highlight(annotation-green) {
        background-color: var(--pie-annotation-green-highlight, rgba(166, 225, 197, 0.5));
        color: inherit;
      }

      ::highlight(annotation-blue) {
        background-color: var(--pie-annotation-blue-highlight, rgba(167, 224, 246, 0.5));
        color: inherit;
      }

      ::highlight(annotation-pink) {
        background-color: var(--pie-annotation-pink-highlight, rgba(255, 159, 174, 0.5));
        color: inherit;
      }

      ::highlight(annotation-orange) {
        background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.5));
        color: inherit;
      }

      ::highlight(annotation-underline) {
        background-color: transparent;
        text-decoration: underline 2px solid var(--pie-primary, #0066cc);
        text-underline-offset: 2px;
        color: inherit;
      }
    `;
        document.head.appendChild(style);
    }
    /**
     * Highlight a word for TTS (temporary)
     *
     * @param textNode Text node containing the word
     * @param startOffset Start position in text node
     * @param endOffset End position in text node
     */
    highlightTTSWord(textNode, startOffset, endOffset) {
        if (!this.ttsWordHighlight)
            return;
        this.applyAdaptiveTTSStyle(textNode.parentElement);
        // Clear previous word highlight
        this.clearTTSWord();
        // Create range for word
        const range = document.createRange();
        range.setStart(textNode, startOffset);
        range.setEnd(textNode, endOffset);
        // Add to highlight
        this.ttsWordHighlight.add(range);
    }
    /**
     * Highlight a single element as the active TTS word (temporary).
     *
     * Unlike {@link highlightTTSWord} (which paints a CSS range over a text node)
     * this marks the element itself via `data-pie-tts-word-element`. It is used
     * for atomic targets that have no direct text node to range over — most
     * notably MathJax CHTML tokens (e.g. `<mjx-mi><mjx-c/></mjx-mi>`, whose glyph
     * lives in a font-driven pseudo-element a CSS range cannot paint) and
     * whole-expression fallbacks.
     *
     * The supplied element is painted exactly; we deliberately do NOT walk up to
     * a containing `<math>` / `<mjx-container>`. That escalation is what made
     * MathJax-rendered math highlight as a full block while native MathML tracked
     * per-token: a resolved single token must paint as a token, and only a
     * genuine expression fallback (where the element already is the container)
     * should paint the whole expression.
     */
    highlightTTSWordElement(element) {
        if (!this.ttsWordHighlight)
            return;
        this.applyAdaptiveTTSStyle(element);
        // Clear previous word highlight (CSS range and any element attributes)
        this.clearTTSWord();
        element.setAttribute("data-pie-tts-word-element", "true");
        this.ttsWordElementHighlights.add(element);
    }
    /**
     * Highlight the coarse TTS read-along band (temporary).
     * Usually one Range per visual line; CSS layer `tts-sentence` for compatibility.
     *
     * @param ranges Line (or sentence) ranges to paint with `--pie-tts-line-highlight`
     */
    highlightTTSSentence(ranges) {
        if (!this.ttsSentenceHighlight)
            return;
        const source = ranges[0]?.startContainer;
        const sourceElement = source?.nodeType === Node.ELEMENT_NODE
            ? source
            : source?.parentElement;
        this.applyAdaptiveTTSStyle(sourceElement);
        // Clear previous sentence highlight
        this.clearTTSSentence();
        // Add all ranges
        for (const range of ranges) {
            this.ttsSentenceHighlight.add(range);
        }
        this.highlightTTSElementFallbacks(ranges);
    }
    /**
     * Highlight sentence/block elements for TTS (background layer).
     *
     * @param elements Visible elements to paint with `--pie-tts-line-highlight`
     */
    highlightTTSSentenceElements(elements) {
        if (!this.ttsSentenceHighlight)
            return;
        this.applyAdaptiveTTSStyle(elements[0] || null);
        this.clearTTSSentence();
        for (const element of elements) {
            element.setAttribute("data-pie-tts-sentence-element", "true");
            this.ttsSentenceElementHighlights.add(element);
        }
    }
    // The read-along (sentence) band legitimately covers whole equations and
    // replaced elements, so it escalates to them.
    static SENTENCE_FALLBACK_SELECTOR = "math, mjx-container, svg, img, canvas, [role='img']";
    // The word layer must NOT escalate a multi-node range to the enclosing
    // `<math>` / `<mjx-container>`: that is what made an equation flash as a
    // whole block whenever a spoken word's visible range crossed several math
    // glyph nodes. Math is tracked per token (or held / region) by the highlight
    // pipeline instead. Replaced elements (svg/img/canvas) still need the element
    // fallback because a CSS range cannot paint them.
    static WORD_FALLBACK_SELECTOR = "svg, img, canvas, [role='img']";
    highlightTTSElementFallbacks(ranges) {
        if (typeof Element === "undefined")
            return;
        this.highlightElementFallbacks(ranges, "data-pie-tts-sentence-element", this.ttsSentenceElementHighlights, HighlightCoordinator.SENTENCE_FALLBACK_SELECTOR);
    }
    highlightTTSWordElementFallbacks(ranges) {
        if (typeof Element === "undefined")
            return;
        this.highlightElementFallbacks(ranges, "data-pie-tts-word-element", this.ttsWordElementHighlights, HighlightCoordinator.WORD_FALLBACK_SELECTOR);
    }
    highlightElementFallbacks(ranges, attributeName, trackedElements, selector) {
        for (const range of ranges) {
            const root = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE
                ? range.commonAncestorContainer
                : range.commonAncestorContainer.parentElement;
            if (!root)
                continue;
            const candidates = [];
            const rangeStartElement = range.startContainer.nodeType === Node.ELEMENT_NODE
                ? range.startContainer
                : range.startContainer.parentElement;
            const rangeEndElement = range.endContainer.nodeType === Node.ELEMENT_NODE
                ? range.endContainer
                : range.endContainer.parentElement;
            for (const element of [rangeStartElement, rangeEndElement]) {
                let current = element;
                while (current) {
                    if (current.matches?.(selector))
                        candidates.push(current);
                    current = current.parentElement;
                }
            }
            if (root.matches?.(selector)) {
                candidates.push(root);
            }
            candidates.push(...Array.from(root.querySelectorAll(selector)));
            for (const element of new Set(candidates)) {
                try {
                    if (!range.intersectsNode(element) &&
                        !element.contains(rangeStartElement) &&
                        !element.contains(rangeEndElement)) {
                        continue;
                    }
                }
                catch {
                    continue;
                }
                element.setAttribute(attributeName, "true");
                trackedElements.add(element);
            }
        }
    }
    /**
     * Clear TTS word highlight
     */
    clearTTSWord() {
        if (!this.ttsWordHighlight)
            return;
        this.ttsWordHighlight.clear();
        for (const element of this.ttsWordElementHighlights) {
            element.removeAttribute("data-pie-tts-word-element");
        }
        this.ttsWordElementHighlights.clear();
    }
    /**
     * Clear TTS sentence highlight
     */
    clearTTSSentence() {
        if (!this.ttsSentenceHighlight)
            return;
        this.ttsSentenceHighlight.clear();
        for (const element of this.ttsSentenceElementHighlights) {
            element.removeAttribute("data-pie-tts-sentence-element");
        }
        this.ttsSentenceElementHighlights.clear();
    }
    /**
     * Clear all TTS highlights
     */
    clearTTS() {
        this.clearTTSWord();
        this.clearTTSSentence();
    }
    /**
     * Add an annotation highlight (persistent)
     *
     * @param range Text range to annotate
     * @param color Highlight color
     * @returns Annotation ID for later removal
     */
    addAnnotation(range, color = HighlightColor.YELLOW) {
        const id = `annotation-${this.nextAnnotationId++}`;
        // Clone the range to store
        const clonedRange = range.cloneRange();
        // Store annotation data
        const annotation = {
            id,
            range: clonedRange,
            type: HighlightType.ANNOTATION,
            color,
            timestamp: Date.now(),
        };
        this.annotations.set(id, annotation);
        // Add the SAME range object to the shared color highlight
        // This ensures we can later delete it by reference
        const colorHighlight = this.colorHighlights.get(color);
        if (colorHighlight) {
            colorHighlight.add(clonedRange);
        }
        return id;
    }
    /**
     * Remove an annotation
     *
     * @param id Annotation ID
     */
    removeAnnotation(id) {
        const annotation = this.annotations.get(id);
        if (!annotation) {
            console.warn(`[HighlightCoordinator] Annotation ${id} not found`);
            return;
        }
        console.log(`[HighlightCoordinator] Removing annotation ${id} (color: ${annotation.color})`);
        // Remove range from the shared color highlight
        const colorHighlight = this.colorHighlights.get(annotation.color);
        if (colorHighlight) {
            const deleted = colorHighlight.delete(annotation.range);
            console.log(`[HighlightCoordinator] Highlight.delete() returned:`, deleted);
        }
        // Clean up
        this.annotations.delete(id);
        console.log(`[HighlightCoordinator] Annotation ${id} removed from map. Remaining:`, this.annotations.size);
    }
    /**
     * Remove all annotations
     */
    clearAnnotations() {
        // Clear all color highlights
        for (const colorHighlight of this.colorHighlights.values()) {
            colorHighlight.clear();
        }
        // Clear annotation data
        this.annotations.clear();
    }
    /**
     * Get all annotations
     */
    getAnnotations() {
        return Array.from(this.annotations.values());
    }
    /**
     * Get annotation by ID
     */
    getAnnotation(id) {
        return this.annotations.get(id) ?? null;
    }
    /**
     * Change annotation color
     *
     * @param id Annotation ID
     * @param newColor New color
     */
    changeAnnotationColor(id, newColor) {
        const annotation = this.annotations.get(id);
        if (!annotation)
            return;
        const oldColor = annotation.color;
        if (oldColor === newColor)
            return;
        // Remove from old color highlight
        const oldColorHighlight = this.colorHighlights.get(oldColor);
        if (oldColorHighlight) {
            oldColorHighlight.delete(annotation.range);
        }
        // Update color
        annotation.color = newColor;
        // Add to new color highlight
        const newColorHighlight = this.colorHighlights.get(newColor);
        if (newColorHighlight) {
            newColorHighlight.add(annotation.range);
        }
    }
    /**
     * Export annotations as serializable data for persistence.
     * Uses RangeSerializer for robust DOM range serialization.
     *
     * @param root Root element for serialization (typically document.body or content container)
     * @returns Array of serialized annotations
     */
    exportAnnotations(root = document.body) {
        return this.getAnnotations().map((annotation) => ({
            ...this.rangeSerializer.serialize(annotation.range, root),
            id: annotation.id,
            color: annotation.color,
            timestamp: annotation.timestamp,
        }));
    }
    /**
     * Import annotations from serialized data.
     * Restores annotations after page navigation or refresh.
     *
     * @param data Array of serialized annotations
     * @param root Root element for deserialization (same as used in export)
     * @returns Number of successfully restored annotations
     */
    importAnnotations(data, root = document.body) {
        let restored = 0;
        for (const item of data) {
            const range = this.rangeSerializer.deserialize(item, root);
            if (range) {
                this.addAnnotation(range, item.color);
                restored++;
            }
        }
        return restored;
    }
    // ============================================================================
    // HighlightCoordinatorApi interface implementation
    // ============================================================================
    /**
     * Highlight a text range (interface method)
     *
     * Generic method that adapts to the specific highlight type.
     * For more control, use the specific methods like highlightTTSWord().
     */
    highlightRange(range, type, color = HighlightColor.YELLOW) {
        if (!this.supported)
            return;
        switch (type) {
            case HighlightType.TTS_WORD:
            case HighlightType.TTS_SENTENCE: {
                // For TTS, add to appropriate highlight
                const highlight = type === HighlightType.TTS_WORD
                    ? this.ttsWordHighlight
                    : this.ttsSentenceHighlight;
                if (highlight) {
                    highlight.clear();
                    highlight.add(range);
                    if (type === HighlightType.TTS_WORD) {
                        for (const element of this.ttsWordElementHighlights) {
                            element.removeAttribute("data-pie-tts-word-element");
                        }
                        this.ttsWordElementHighlights.clear();
                        this.highlightTTSWordElementFallbacks([range]);
                    }
                }
                break;
            }
            case HighlightType.ANNOTATION:
                // For annotations, use the existing method
                this.addAnnotation(range, color);
                break;
        }
    }
    /**
     * Clear highlights of a specific type (interface method)
     */
    clearHighlights(type) {
        if (!this.supported)
            return;
        switch (type) {
            case HighlightType.TTS_WORD:
                this.clearTTSWord();
                break;
            case HighlightType.TTS_SENTENCE:
                this.clearTTSSentence();
                break;
            case HighlightType.ANNOTATION:
                this.clearAnnotations();
                break;
        }
    }
    /**
     * Clear all highlights (interface method)
     */
    clearAll() {
        if (!this.supported)
            return;
        this.clearTTS();
        this.clearAnnotations();
    }
    /**
     * Check if CSS Highlight API is supported (interface method)
     */
    isSupported() {
        return this.supported;
    }
    /**
     * Update TTS highlight style dynamically
     *
     * @param color CSS color value (e.g., '#ffeb3b')
     * @param opacity Opacity value (0.0 to 1.0)
     */
    updateTTSHighlightStyle(color, opacity) {
        if (!this.supported)
            return;
        if (typeof document === "undefined")
            return;
        const styleEl = document.getElementById("pie-highlight-styles");
        if (!styleEl)
            return;
        // Convert hex to rgba
        const hexToRgb = (hex) => {
            const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
            return result
                ? {
                    r: parseInt(result[1], 16),
                    g: parseInt(result[2], 16),
                    b: parseInt(result[3], 16),
                }
                : null;
        };
        const rgb = hexToRgb(color);
        if (!rgb)
            return;
        this.explicitTTSColorOverride = {
            color: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
            opacity: Math.max(0.2, Math.min(0.95, opacity)),
        };
        this.applyAdaptiveTTSStyle();
    }
    /**
     * Cleanup - remove all highlights
     */
    destroy() {
        if (!this.supported)
            return;
        this.clearTTS();
        this.clearAnnotations();
        // Remove style element
        if (typeof document !== "undefined") {
            const styleEl = document.getElementById("pie-highlight-styles");
            if (styleEl) {
                styleEl.remove();
            }
        }
        this.themeObserver?.disconnect();
        this.themeObserver = null;
    }
}
//# sourceMappingURL=HighlightCoordinator.js.map