import { UIColor } from "./UIColor"
import { UILocalizedTextObject } from "./UIInterfaces"
import { EXTEND, FIRST, IS_LIKE_NULL, nil, NO, UIObject, ValueOf, YES } from "./UIObject"
import { UIRectangle } from "./UIRectangle"
import { TextMeasurementStyle, UITextMeasurement } from "./UITextMeasurement"
import { UIView, UIViewBroadcastEvent } from "./UIView"


export class UITextView extends UIView {
    
    //#region Static Properties
    
    static defaultTextColor = UIColor.blackColor
    static notificationTextColor = UIColor.redColor
    static attentionRequiredColor = new UIColor("#f59e0b")
    
    /**
     * Override this to customise the attention indicator HTML that is appended
     * to a label's text when `attentionRequired` is `YES`.
     *
     * The default renders an amber `●` dot:
     * ```
     * UITextView.renderAttentionIndicator = () =>
     *     `<span style="color: #f59e0b; margin-left: 4px;">●</span>`
     * ```
     * Call sites never need to change — only this one function needs to be
     * replaced at app startup to restyle every attention indicator globally.
     */
    static attentionIndicatorHTMLString: () => string = () =>
        "<span style=\"color: " + UITextView.attentionRequiredColor.stringValue + "; margin-left: 4px;\">●</span>"
    
    attentionIndicatorHTMLString: () => string = UITextView.attentionIndicatorHTMLString
    
    // Global caches for all UILabels
    static _intrinsicHeightCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
    static _intrinsicWidthCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
    
    static _ptToPx: number
    static _pxToPt: number
    
    static type = {
        "paragraph": "p",
        "header1": "h1",
        "header2": "h2",
        "header3": "h3",
        "header4": "h4",
        "header5": "h5",
        "header6": "h6",
        "textArea": "textarea",
        "textField": "input",
        "span": "span",
        "label": "label"
    } as const
    
    static textAlignment = {
        "left": "left",
        "center": "center",
        "right": "right",
        "justify": "justify"
    } as const
    
    //#endregion
    
    //#region Constructor
    
    
    constructor(
        elementID?: string,
        textViewType: string | ValueOf<typeof UITextView.type> = UITextView.type.paragraph,
        viewHTMLElement = null
    ) {
        
        // Create inner text element as a UIView
        const innerElementID = elementID ? `${elementID}_textElement` : undefined
        const _textElementView = new UIView(innerElementID, null, textViewType)
        
        // Create outer container (wrapper) - this is the main viewHTMLElement
        super(elementID, viewHTMLElement, "span", { _textElementView })
        
        // Configure outer container for vertical centering using direct property access
        
        this.configureWithObject({
            // @ts-ignore
            viewHTMLElement: {
                style: {
                    display: "flex",
                    alignItems: "center", // Vertical centering
                    overflow: "hidden"
                }
            }
        })
        
        this.text = ""
        
        this._textElementView = _textElementView
        
        // Configure inner text element for ellipsis and positioning
        this._textElementView.configureWithObject({
            style: {
                position: "relative",
                overflow: "hidden",
                textOverflow: "ellipsis",
                width: "100%",
                margin: "0",
                padding: "0"
            },
            // Forward control events from text element to the container
            sendControlEventForKey: EXTEND(this.sendControlEventForKey.bind(this))
        })
        
        // Add text element as a subview
        this.addSubview(this._textElementView)
        
        
        this.isSingleLine = YES
        
        this.textColor = this.textColor
        
        this.userInteractionEnabled = YES
        
        if (textViewType == UITextView.type.textArea) {
            this.pausesPointerEvents = YES
            this.addTargetForControlEvent(
                UIView.controlEvent.PointerUpInside,
                (sender, event) => sender.focus()
            )
        }
    }
    
    //#endregion
    
    //#region Text Element View Property
    
    private _textElementView: UIView
    
    /**
     * The inner text element that holds the actual text content
     */
    get textElementView(): UIView {
        return this._textElementView
    }
    
    /**
     * Override style to apply to the text element instead of the container
     */
    // override get style() {
    //     return this._textElementView.style
    // }
    //
    // /**
    //  * Override computedStyle to get computed styles from the text element
    //  */
    // override get computedStyle() {
    //     return this._textElementView.computedStyle
    // }
    
    /**
     * Access the outer container's style (for positioning, layout, etc.)
     */
    get containerStyle() {
        return this.viewHTMLElement.style
    }
    
    /**
     * Override styleClasses to apply to the text element
     */
    override get styleClasses() {
        return this._textElementView.styleClasses
    }
    
    override set styleClasses(styleClasses: string[]) {
        this._textElementView.styleClasses = styleClasses
    }
    
    //#endregion
    
    //#region Lifecycle Methods
    
    override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {
        super.didReceiveBroadcastEvent(event)
    }
    
    override willMoveToSuperview(superview: UIView) {
        super.willMoveToSuperview(superview)
    }
    
    override documentFontsDidLoad() {
        super.documentFontsDidLoad()
        this._invalidateFontCache()
        this.invalidateMeasurementStrategy()
        this._intrinsicHeightCache = new UIObject() as any
        this._intrinsicWidthCache = new UIObject() as any
        UITextView._intrinsicHeightCache = new UIObject() as any
        UITextView._intrinsicWidthCache = new UIObject() as any
    }
    
    
    override layoutSubviews() {
        super.layoutSubviews()
        
        if (this._automaticFontSizeSelection) {
            this.fontSize = UITextView.automaticallyCalculatedFontSize(
                new UIRectangle(
                    0,
                    0,
                    this.textElementView.viewHTMLElement.offsetHeight,
                    this.textElementView.viewHTMLElement.offsetWidth
                ),
                this.intrinsicContentSize(),
                this.fontSize,
                this._minFontSize,
                this._maxFontSize
            )
        }
    }
    
    //#endregion
    
    //#region Measurement & Sizing - Private Methods
    
    private _invalidateMeasurementStyles(): void {
        this._cachedMeasurementStyles = undefined
        UITextMeasurement.invalidateElement(this.textElementView.viewHTMLElement)
        this._intrinsicSizesCache = {}
    }
    
    private _getMeasurementStyles(): TextMeasurementStyle | null {
        if (this._cachedMeasurementStyles) {
            return this._cachedMeasurementStyles
        }
        
        // Ensure element is in document
        if (!this.textElementView.viewHTMLElement.isConnected) {
            return null
        }
        
        // Force a layout flush ONCE to ensure computed styles are available
        // This is only paid once per style change, then we use cached values
        this.textElementView.viewHTMLElement.offsetHeight
        
        const computed = window.getComputedStyle(this.textElementView.viewHTMLElement)
        const fontSizeStr = computed.fontSize
        const fontSize = parseFloat(fontSizeStr)
        
        if (!fontSize || isNaN(fontSize)) {
            return null
        }
        
        const lineHeight = this._parseLineHeight(computed.lineHeight, fontSize)
        
        if (isNaN(lineHeight)) {
            return null
        }
        
        const font = [
            computed.fontStyle || "normal",
            computed.fontVariant || "normal",
            computed.fontWeight || "normal",
            fontSize + "px",
            computed.fontFamily || "sans-serif"
        ].join(" ")
        
        this._cachedMeasurementStyles = {
            font: font,
            fontSize: fontSize,
            lineHeight: lineHeight,
            whiteSpace: computed.whiteSpace || "normal",
            paddingLeft: parseFloat(computed.paddingLeft) || 0,
            paddingRight: parseFloat(computed.paddingRight) || 0,
            paddingTop: parseFloat(computed.paddingTop) || 0,
            paddingBottom: parseFloat(computed.paddingBottom) || 0,
            letterSpacing: parseFloat(computed.letterSpacing) || 0,
            textTransform: computed.textTransform || "none"
        }
        
        return this._cachedMeasurementStyles
    }
    
    private _parseLineHeight(lineHeight: string, fontSize: number): number {
        if (lineHeight === "normal") {
            return fontSize * 1.2
        }
        if (lineHeight.endsWith("px")) {
            return parseFloat(lineHeight)
        }
        const numericLineHeight = parseFloat(lineHeight)
        if (!isNaN(numericLineHeight)) {
            return fontSize * numericLineHeight
        }
        return fontSize * 1.2
    }
    
    private _shouldUseFastMeasurement(): boolean {
        try {
            const content = this.text || this.textElementView.innerHTML
            
            if (this._innerHTMLKey || this._localizedTextObject) {
                return NO
            }
            
            if (this.notificationAmount > 0) {
                return NO
            }
            
            if (this._attentionRequired) {
                return NO
            }
            
            const hasComplexHTML = /<(?!\/?(b|i|em|strong|span)\b)[^>]+>/i.test(content)
            
            if (hasComplexHTML) {
                return NO
            }
            
            // Canvas measureText silently falls back to the system font when the
            // custom font hasn't been loaded into the canvas font system yet, even
            // if getComputedStyle already reports the correct font family. Guard
            // against this by checking the font is confirmed available before
            // trusting canvas-based measurement.
            const styles = this._getMeasurementStyles()
            if (!styles?.font) {
                return YES
            }
            const documentHasFont = document.fonts.check(styles.font)
            // noinspection RedundantIfStatementJS
            if (!documentHasFont) {
                return NO
            }
            
            return YES
        }
        catch (exception) {
            
            console.error(exception)
            return NO
            
        }
    }
    
    //#endregion
    
    //#region Measurement & Sizing - Public Methods
    
    setUseFastMeasurement(useFast: boolean): void {
        this._useFastMeasurement = useFast
        this._intrinsicSizesCache = {}
    }
    
    invalidateMeasurementStrategy(): void {
        this._useFastMeasurement = undefined
        this._invalidateMeasurementStyles()
    }
    
    //#endregion
    
    //#region Getters & Setters - Text Alignment
    
    get textAlignment() {
        return this._textElementView.style.textAlign as ValueOf<typeof UITextView.textAlignment>
    }
    
    set textAlignment(textAlignment: ValueOf<typeof UITextView.textAlignment>) {
        this._textAlignment = textAlignment
        this._textElementView.style.textAlign = textAlignment
    }
    
    //#endregion
    
    //#region Getters & Setters - Text Color
    
    get textColor() {
        return this._textColor
    }
    
    set textColor(color: UIColor) {
        this._textColor = color || UITextView.defaultTextColor
        this._textElementView.style.color = this._textColor.stringValue
    }
    
    //#endregion
    
    //#region Getters & Setters - Single Line
    
    get isSingleLine() {
        return this._isSingleLine
    }
    
    set isSingleLine(isSingleLine: boolean) {
        this._isSingleLine = isSingleLine
        
        this._intrinsicHeightCache = new UIObject() as any
        this._intrinsicWidthCache = new UIObject() as any
        
        if (isSingleLine) {
            // Single line: use nowrap with ellipsis
            this._textElementView.style.whiteSpace = "nowrap"
            this._textElementView.style.textOverflow = "ellipsis"
            this._textElementView.style.display = ""
            this._textElementView.style.webkitLineClamp = ""
            this._textElementView.style.webkitBoxOrient = ""
            return
        }
        
        // Multiline: allow wrapping, but still show ellipsis if content overflows the container
        // This uses the -webkit-line-clamp approach which works for multiline ellipsis
        this._textElementView.style.whiteSpace = "normal"
        this._textElementView.style.textOverflow = "ellipsis"
        this._textElementView.style.display = "-webkit-box"
        this._textElementView.style.webkitBoxOrient = "vertical"
        // Don't set line-clamp to a specific number - let it fill available space
        // The overflow: hidden from the constructor will clip content that exceeds the height
        this.invalidateMeasurementStrategy()
    }
    
    //#endregion
    
    //#region Getters & Setters - Notification Amount
    
    get notificationAmount() {
        return this._notificationAmount
    }
    
    set notificationAmount(notificationAmount: number) {
        if (this._notificationAmount == notificationAmount) {
            return
        }
        
        this._notificationAmount = notificationAmount
        this.text = this.text
        this.setNeedsLayoutUpToRootView()
        this.notificationAmountDidChange(notificationAmount)
    }
    
    notificationAmountDidChange(notificationAmount: number) {
    }
    
    //#endregion
    
    //#region Getters & Setters - Attention Required
    
    get attentionRequired() {
        return this._attentionRequired
    }
    
    set attentionRequired(attentionRequired: boolean) {
        if (this._attentionRequired == attentionRequired) {
            return
        }
        
        this._attentionRequired = attentionRequired
        this.text = this.text
        this.setNeedsLayoutUpToRootView()
    }
    
    //#endregion
    
    //#region Getters & Setters - Text Content
    
    get text() {
        return (this._text || this.textElementView.viewHTMLElement.innerHTML)
    }
    
    set text(text) {
        this._text = text
        var notificationText = ""
        if (this.notificationAmount) {
            notificationText = "<span style=\"color: " + UITextView.notificationTextColor.stringValue + ";\">" +
                (" (" + this.notificationAmount + ")").bold() + "</span>"
        }
        
        var attentionDot = ""
        if (this._attentionRequired) {
            attentionDot = this.attentionIndicatorHTMLString()
        }
        
        const displayText = this.thousandsSeparator !== null
                            ? UITextView.applyThousandsSeparatorToNumericalString(text, this.thousandsSeparator)
                            : text
        
        const newInnerHTML = this.textPrefix + FIRST(displayText, "") + this.textSuffix + notificationText + attentionDot
        
        if (this.textElementView.viewHTMLElement.innerHTML !== newInnerHTML) {
            this.textElementView.viewHTMLElement.innerHTML = newInnerHTML
            
            if (this.changesOften) {
                this._intrinsicHeightCache = new UIObject() as any
                this._intrinsicWidthCache = new UIObject() as any
            }
            
            this._useFastMeasurement = undefined
            this._intrinsicSizesCache = {}
            this.invalidateMeasurementStrategy()
            this._invalidateMeasurementStyles()
            this.clearIntrinsicSizeCache()
            this.setNeedsLayout()
        }
    }
    
    
    /**
     * Formats a raw number string by inserting `separator` every three digits
     * in the integer part. Handles negative numbers and decimals (machine locale
     * "." as decimal point). Non-numeric strings are returned unchanged.
     */
    static applyThousandsSeparatorToNumericalString(value: string, separator: string): string {
        const trimmed = (value || "").trim()
        if (trimmed === "") {
            return value
        }
        
        // Split on the decimal point (machine locale uses ".")
        const parts = trimmed.split(".")
        const integerPart = parts[0]
        const decimalPart = parts.length > 1 ? parts[1] : null
        
        // Only format if the integer part consists solely of digits (optionally
        // prefixed with a minus sign). Non-numeric strings pass through as-is.
        if (!/^-?\d+$/.test(integerPart)) {
            return value
        }
        
        const isNegative = integerPart.startsWith("-")
        const digits = isNegative ? integerPart.slice(1) : integerPart
        
        let formatted = ""
        const offset = digits.length % 3
        for (let index = 0; index < digits.length; index++) {
            if (index > 0 && (index - offset) % 3 === 0) {
                formatted += separator
            }
            formatted += digits[index]
        }
        
        const result = (isNegative ? "-" : "") + formatted
        return decimalPart !== null ? result + "." + decimalPart : result
    }
    
    override set innerHTML(innerHTML: string) {
        this.text = innerHTML
        this.invalidateMeasurementStrategy()
    }
    
    override get innerHTML() {
        return this.viewHTMLElement.innerHTML
    }
    
    setText(key: string, defaultString: string, parameters?: { [x: string]: string | UILocalizedTextObject }) {
        this.textElementView.setInnerHTML(key, defaultString, parameters)
        this.invalidateMeasurementStrategy()
    }
    
    //#endregion
    
    //#region Getters & Setters - Font Size
    
    get fontSize() {
        const style = this._textElementView.style.fontSize || window.getComputedStyle(this._textElementView.viewHTMLElement, null).fontSize
        const result = (parseFloat(style) * UITextView._pxToPt)
        return result
    }
    
    set fontSize(fontSize: number) {
        if (fontSize != this.fontSize) {
            this._textElementView.style.fontSize = "" + fontSize + "pt"
            
            this._intrinsicHeightCache = new UIObject() as any
            this._intrinsicWidthCache = new UIObject() as any
            
            this._invalidateFontCache()
            this._invalidateMeasurementStyles()
            this.clearIntrinsicSizeCache()
        }
    }
    
    useAutomaticFontSize(minFontSize: number = nil, maxFontSize: number = nil) {
        this._automaticFontSizeSelection = YES
        this._minFontSize = minFontSize
        this._maxFontSize = maxFontSize
        this.setNeedsLayout()
    }
    
    //#endregion
    
    //#region Font Caching - Private Methods
    
    /**
     * Get a stable cache key for the font without triggering reflow.
     * Only computes font on first access or when font properties change.
     */
    private _getFontCacheKey(): string {
        // Check if font-related properties have changed
        const currentTriggers = {
            fontSize: this._textElementView.style.fontSize || "",
            fontFamily: this._textElementView.style.fontFamily || "",
            fontWeight: this._textElementView.style.fontWeight || "",
            fontStyle: this._textElementView.style.fontStyle || "",
            styleClasses: this.styleClasses.join(",")
        }
        
        const hasChanged =
            currentTriggers.fontSize !== this._fontInvalidationTriggers.fontSize ||
            currentTriggers.fontFamily !== this._fontInvalidationTriggers.fontFamily ||
            currentTriggers.fontWeight !== this._fontInvalidationTriggers.fontWeight ||
            currentTriggers.fontStyle !== this._fontInvalidationTriggers.fontStyle ||
            currentTriggers.styleClasses !== this._fontInvalidationTriggers.styleClasses
        
        if (!this._cachedFontKey || hasChanged) {
            // Only access computedStyle when we know something changed
            const computed = this._textElementView.computedStyle
            this._cachedFontKey = [
                computed.fontStyle,
                computed.fontVariant,
                computed.fontWeight,
                computed.fontSize,
                computed.fontFamily
            ].join("_").replace(/[.\s]/g, "_")
            
            this._fontInvalidationTriggers = currentTriggers
        }
        
        return this._cachedFontKey
    }
    
    /**
     * Invalidate font cache when font properties change
     */
    private _invalidateFontCache(): void {
        this._cachedFontKey = undefined
    }
    
    //#endregion
    
    //#region Static Methods
    
    static _determinePXAndPTRatios() {
        if (UITextView._ptToPx) {
            return
        }
        
        const o = document.createElement("div")
        o.style.width = "1000pt"
        document.body.appendChild(o)
        UITextView._ptToPx = o.clientWidth / 1000
        document.body.removeChild(o)
        UITextView._pxToPt = 1 / UITextView._ptToPx
    }
    
    static automaticallyCalculatedFontSize(
        bounds: UIRectangle,
        currentSize: UIRectangle,
        currentFontSize: number,
        minFontSize?: number,
        maxFontSize?: number
    ) {
        minFontSize = FIRST(minFontSize, 1)
        maxFontSize = FIRST(maxFontSize, 100000000000)
        
        const heightMultiplier = bounds.height / (currentSize.height + 1)
        const widthMultiplier = bounds.width / (currentSize.width + 1)
        
        var multiplier = heightMultiplier
        if (heightMultiplier > widthMultiplier) {
            multiplier = widthMultiplier
        }
        
        const maxFittingFontSize = currentFontSize * multiplier
        
        if (maxFittingFontSize > maxFontSize) {
            return maxFontSize
        }
        
        if (minFontSize > maxFittingFontSize) {
            return minFontSize
        }
        
        return maxFittingFontSize
    }
    
    //#endregion
    
    //#region Instance Properties - Text Content
    
    _text?: string
    textPrefix = ""
    textSuffix = ""
    _notificationAmount = 0
    _attentionRequired = NO
    
    _thousandsSeparator: string | null = null
    
    get thousandsSeparator(): string | null {
        return this._thousandsSeparator
    }
    
    set thousandsSeparator(value: string | null) {
        this._thousandsSeparator = value
    }
    
    //#endregion
    
    //#region Instance Properties - Styling
    
    _textColor: UIColor = UITextView.defaultTextColor
    _textAlignment?: ValueOf<typeof UITextView.textAlignment>
    _isSingleLine = YES
    
    //#endregion
    
    //#region Instance Properties - Font & Sizing
    
    _minFontSize?: number
    _maxFontSize?: number
    _automaticFontSizeSelection = NO
    
    // Cache for the computed font string
    private _cachedFontKey?: string
    private _fontInvalidationTriggers = {
        fontSize: "",
        fontFamily: "",
        fontWeight: "",
        fontStyle: "",
        styleClasses: ""
    }
    
    //#endregion
    
    //#region Instance Properties - Caching & Performance
    
    changesOften = NO
    
    // Local cache for this instance if the label changes often
    _intrinsicHeightCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
    _intrinsicWidthCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
    
    private _useFastMeasurement: boolean | undefined
    private _cachedMeasurementStyles: TextMeasurementStyle | undefined | null
    
    override usesVirtualLayoutingForIntrinsicSizing = NO
    
    //#endregion
    
    // Override addStyleClass to invalidate font cache
    override addStyleClass(styleClass: string) {
        super.addStyleClass(styleClass)
        this._invalidateFontCache()
    }
    
    // Override removeStyleClass to invalidate font cache
    override removeStyleClass(styleClass: string) {
        super.removeStyleClass(styleClass)
        this._invalidateFontCache()
    }
    
    // Override focus to focus the text element
    override focus() {
        this._textElementView.focus()
    }
    
    // Override blur to blur the text element
    override blur() {
        this._textElementView.blur()
    }
    
    override intrinsicContentHeight(constrainingWidth = 0) {
        
        constrainingWidth = Math.max(constrainingWidth.integerValue, 0)
        
        const keyPath = ((this.textElementView.viewHTMLElement.innerHTML || this.text) + "_csf_" + this._getFontCacheKey()) + "." +
            ("" + constrainingWidth).replace(new RegExp("\\.", "g"), "_")
        
        let cacheObject = UITextView._intrinsicHeightCache
        
        if (this.changesOften) {
            cacheObject = this._intrinsicHeightCache
        }
        
        var result = cacheObject.valueForKeyPath(keyPath)
        
        if (IS_LIKE_NULL(result)) {
            // Determine if we should use fast measurement
            const shouldUseFastPath = this._useFastMeasurement ?? this._shouldUseFastMeasurement()
            
            if (shouldUseFastPath) {
                // Fast path: use UITextMeasurement with pre-extracted styles
                const styles = this._getMeasurementStyles()
                
                // If styles are invalid (element not properly initialized), fall back to DOM
                if (styles) {
                    const size = UITextMeasurement.calculateTextSize(
                        this.textElementView.viewHTMLElement,
                        ((this.text || this.textElementView.innerHTML || "") + ""),
                        constrainingWidth || undefined,
                        undefined,
                        styles
                    )
                    result = size.height
                }
                else {
                    // Styles not ready, use DOM measurement
                    result = super.intrinsicContentHeight(constrainingWidth)
                }
            }
            else {
                // Fallback: DOM-based measurement for complex content
                result = super.intrinsicContentHeight(constrainingWidth)
            }
            
            cacheObject.setValueForKeyPath(keyPath, result)
        }
        
        if (isNaN(result) || (!result && !this.text)) {
            result = super.intrinsicContentHeight(constrainingWidth)
            cacheObject.setValueForKeyPath(keyPath, result)
        }
        
        return result
    }
    
    override intrinsicContentWidth(constrainingHeight = 0) {
        
        constrainingHeight = Math.max(constrainingHeight.integerValue, 0)
        
        const keyPath = ((this.textElementView.viewHTMLElement.innerHTML || this.text) + "_csf_" + this._getFontCacheKey()) + "." +
            ("" + constrainingHeight).replace(new RegExp("\\.", "g"), "_")
        
        let cacheObject = UITextView._intrinsicWidthCache
        
        if (this.changesOften) {
            cacheObject = this._intrinsicWidthCache
        }
        
        var result = cacheObject.valueForKeyPath(keyPath)
        
        if (IS_LIKE_NULL(result)) {
            // Determine if we should use fast measurement
            const shouldUseFastPath = this._useFastMeasurement ?? this._shouldUseFastMeasurement()
            
            if (shouldUseFastPath) {
                // Fast path: use UITextMeasurement with pre-extracted styles
                const styles = this._getMeasurementStyles()
                
                // If styles are invalid (element not properly initialized), fall back to DOM
                if (styles) {
                    const size = UITextMeasurement.calculateTextSize(
                        this.textElementView.viewHTMLElement,
                        ((this.text || this.textElementView.innerHTML || "") + "").replace(/<br\s*\/?>/gi, "\n"),
                        undefined,
                        constrainingHeight || undefined,
                        styles
                    )
                    result = size.width
                }
                else {
                    // Styles not ready, use DOM measurement
                    result = super.intrinsicContentWidth(constrainingHeight)
                }
            }
            else {
                // Fallback: DOM-based measurement for complex content
                result = super.intrinsicContentWidth(constrainingHeight)
            }
            
            cacheObject.setValueForKeyPath(keyPath, result)
        }
        
        return result
    }
    
    
    override intrinsicContentSizeWithConstraints(constrainingHeight: number = 0, constrainingWidth: number = 0) {
        
        const cacheKey = this._getIntrinsicSizeCacheKey(constrainingHeight, constrainingWidth)
        const cachedResult = this._getCachedIntrinsicSize(cacheKey)
        if (cachedResult) {
            return cachedResult
        }
        
        // UITextView needs to measure the text element, not the outer container
        const result = new UIRectangle(0, 0, 0, 0)
        if (this.rootView.forceIntrinsicSizeZero) {
            return result
        }
        
        let temporarilyInViewTree = NO
        let nodeAboveThisView: Node | null = null
        if (!this.isMemberOfViewTree) {
            document.body.appendChild(this.viewHTMLElement)
            temporarilyInViewTree = YES
            nodeAboveThisView = this.viewHTMLElement.nextSibling
        }
        
        // Save and clear styles on the TEXT ELEMENT (not the container)
        const height = this._textElementView.style.height
        const width = this._textElementView.style.width
        
        this._textElementView.style.height = "" + constrainingHeight + "px"
        this._textElementView.style.width = "" + constrainingWidth + "px"
        
        const left = this._textElementView.style.left
        const right = this._textElementView.style.right
        const bottom = this._textElementView.style.bottom
        const top = this._textElementView.style.top
        
        this._textElementView.style.left = ""
        this._textElementView.style.right = ""
        this._textElementView.style.bottom = ""
        this._textElementView.style.top = ""
        
        // Measure height with the text element
        const resultHeight = this._textElementView.viewHTMLElement.scrollHeight
        
        // Measure width by temporarily setting nowrap
        const whiteSpace = this._textElementView.style.whiteSpace
        this._textElementView.style.whiteSpace = "nowrap"
        
        const resultWidth = this._textElementView.viewHTMLElement.scrollWidth
        
        this._textElementView.style.whiteSpace = whiteSpace
        
        // Restore styles on the TEXT ELEMENT
        this._textElementView.style.height = height
        this._textElementView.style.width = width
        
        this._textElementView.style.left = left
        this._textElementView.style.right = right
        this._textElementView.style.bottom = bottom
        this._textElementView.style.top = top
        
        if (temporarilyInViewTree) {
            document.body.removeChild(this.viewHTMLElement)
            if (this.superview) {
                if (nodeAboveThisView) {
                    this.superview.viewHTMLElement.insertBefore(this.viewHTMLElement, nodeAboveThisView)
                }
                else {
                    this.superview.viewHTMLElement.appendChild(this.viewHTMLElement)
                }
            }
        }
        
        result.height = resultHeight
        result.width = resultWidth
        
        this._setCachedIntrinsicSize(cacheKey, result)
        
        return result
    }
    
    
}
