//
//  ResolvedText.swift
//  OpenSwiftUICore
//
//  Audited for 6.5.4
//  Status: Complete
//  ID: 7AFAB46D18FA6D189589CFA78D8B2B2E (SwiftUICore)

package import Foundation
package import UIFoundation_Private
#if canImport(CoreText)
import CoreText
#endif

// MARK: - ResolvedTextContainer

package protocol ResolvedTextContainer {
    var style: Text.Style { get set }

    var idiom: AnyInterfaceIdiom? { get }

    mutating func append<S>(
        _ string: S,
        in env: EnvironmentValues,
        with options: Text.ResolveOptions,
        isUniqueSizeVariant: Bool
    ) where S: StringProtocol

    mutating func append(
        _ attributedString: NSAttributedString,
        in env: EnvironmentValues,
        with options: Text.ResolveOptions,
        isUniqueSizeVariant: Bool
    )

    mutating func append(
        _ image: Image.Resolved,
        in environment: EnvironmentValues,
        with options: Text.ResolveOptions
    )

    mutating func append(
        _ namedImage: Image.NamedResolved,
        in environment: EnvironmentValues,
        with options: Text.ResolveOptions
    )

    mutating func append<R>(
        resolvable: R,
        in environment: EnvironmentValues,
        with options: Text.ResolveOptions,
        transition: ContentTransition?
    ) where R: ResolvableStringAttribute
}

extension ResolvedTextContainer {
    mutating func append<S>(
        _ string: S,
        in env: EnvironmentValues,
        with options: Text.ResolveOptions,
    ) where S: StringProtocol {
        append(
            string,
            in: env,
            with: options,
            isUniqueSizeVariant: env.textSizeVariant != .regular
        )
    }

    mutating func append(
        _ attributedString: NSAttributedString,
        in env: EnvironmentValues,
        with options: Text.ResolveOptions,
    ) {
        append(
            attributedString,
            in: env,
            with: options,
            isUniqueSizeVariant: env.textSizeVariant != .regular
        )
    }
}

extension Text {

    // MARK: - Text.Resolved

    package struct Resolved: ResolvedTextContainer {
        package var style: Text.Style = .init()
        package var attributedString: NSMutableAttributedString?
        package var includeDefaultAttributes: Bool = true
        package var idiom: AnyInterfaceIdiom?
        package var properties: Text.ResolvedProperties = .init()
        
        package init() {
            _openSwiftUIEmptyStub()
        }
        
        package mutating func append<S>(
            _ string: S,
            in env: EnvironmentValues,
            with options: Text.ResolveOptions,
            isUniqueSizeVariant: Bool
        ) where S: StringProtocol {
            let string = String(string).caseConvertedIfNeeded(env)
            let attributes = style.nsAttributes(
                content: { string },
                environment: env,
                includeDefaultAttributes: includeDefaultAttributes,
                with: options,
                properties: &properties
            )
            append(string, with: attributes, in: env)
            if attributedString!.isEmptyOrTerminatedByParagraphSeparator {
                properties.paragraph.cachedStyle = nil
            }
            if isUniqueSizeVariant {
                properties.features.formUnion(.isUniqueSizeVariant)
            }
        }
        
        package mutating func append(
            _ attributedString: NSAttributedString,
            in env: EnvironmentValues,
            with options: Text.ResolveOptions,
            isUniqueSizeVariant: Bool
        ) {
            attributedString.enumerateAttributes(
                in: NSRange(location: 0, length: attributedString.length)
            ) { sourceAttributes, range, _ in
                var attributes = sourceAttributes
                var style = self.style
                attributes.transferAttributedStringStyles(to: &style)
                if sourceAttributes[.kitLink] != nil {
                    let foregroundColorKey = NSAttributedString.Key(AttributeScopes.OpenSwiftUIAttributes.ForegroundColorAttribute.name)
                    if !options.contains(.disableLinkColor),
                       sourceAttributes[foregroundColorKey] == nil {
                        let linkColor = env.tintColor
                        ?? env.resolvedTextProvider?.defaultLinkColor(for: env)
                        ?? .accentColor
                        style.color = .explicit(AnyShapeStyle(linkColor))
                    }
                }
                let string = attributedString
                    .attributedSubstring(from: range)
                    .string
                    .caseConvertedIfNeeded(env)
                let platformAttributes = style.nsAttributes(
                    content: { string },
                    environment: env,
                    includeDefaultAttributes: includeDefaultAttributes,
                    with: options,
                    properties: &properties
                )
                attributes.merge(platformAttributes) { old, _ in old }
                append(string, with: attributes, in: env)
                if self.attributedString!.isEmptyOrTerminatedByParagraphSeparator {
                    properties.paragraph.cachedStyle = nil
                }
            }
            if self.attributedString == nil {
                self.attributedString = NSMutableAttributedString(
                    attributedString: attributedString
                )
            }
            if isUniqueSizeVariant {
                properties.features.formUnion(.isUniqueSizeVariant)
            }
        }
        
        package mutating func append(
            _ image: Image.Resolved,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions
        ) {
            var attributes = nsAttributes(
                content: nil,
                in: environment,
                with: options,
                properties: &properties
            )
            if !environment.shouldRedactContent {
                let resolvedImage = image.foregroundColor {
                    let color = style.color.resolve(
                        in: environment,
                        with: options,
                        properties: &properties,
                        includeDefaultAttributes: true
                    )!
                    properties.addColor(color)
                    return color
                }
                let attachment = OpenSwiftUITextAttachment(image: resolvedImage)
                if options.contains(.includeAccessibility),
                   !resolvedImage.decorative,
                   let label = resolvedImage.label {
                    attachment.accessibilityLabel = label.text.resolveString(
                        in: environment,
                        with: options
                    )
                }
                environment.resolvedTextProvider?.updateImageTextAttachment(
                    in: attachment,
                    image: resolvedImage
                )
                attributes[.kitAttachment] = attachment
            }
            append(
                .nsAttachment,
                with: attributes,
                in: environment
            )
            properties.addAttachment()
        }
        
        package mutating func append(
            _ namedImage: Image.NamedResolved,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions
        ) {
            guard options.contains(.writeAuxiliaryMetadata) else {
                return
            }
            var attributes = nsAttributes(
                content: nil,
                in: environment,
                with: options,
                properties: &properties
            )
            if !environment.shouldRedactContent {
                let attachment = NSTextAttachment()
                environment.resolvedTextProvider?.updateWidgetTextAttachment(
                    attachment,
                    namedImage: namedImage
                )
                attributes[.kitAttachment] = attachment
            }
            append(
                .nsAttachment,
                with: attributes,
                in: environment
            )
            properties.addAttachment()
        }
        
        package mutating func append<R>(
            resolvable: R,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions,
            transition: ContentTransition?
        ) where R: ResolvableStringAttribute {
            var style = style
            if style.transition == nil, var transition {
                transition.isReplaceable = true
                style.transition = TextTransitionModifier(transition)
            }
            let attributedString: NSMutableAttributedString?
            if environment.redactionReasons.contains(.placeholder) {
                attributedString = ResolvableTextSegmentAttribute.buildStaticTextSegment(
                    for: resolvable,
                    style: style,
                    environment: environment,
                    includeDefaultAttributes: includeDefaultAttributes,
                    options: options,
                    properties: &properties
                )
            } else {
                attributedString = ResolvableTextSegmentAttribute.buildDynamicTextSegment(
                    for: resolvable,
                    style: style,
                    environment: environment,
                    includeDefaultAttributes: includeDefaultAttributes,
                    options: options,
                    properties: &properties
                )
            }
            guard let attributedString else {
                Log.internalWarning("Unable to resolve custom attribute \(resolvable)")
                return
            }
            if let current = self.attributedString {
                current.append(attributedString)
            } else {
                self.attributedString = NSMutableAttributedString(
                    attributedString: attributedString
                )
            }
        }
        
        package func nsAttributes(
            content: (() -> String)?,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions,
            properties: inout Text.ResolvedProperties
        ) -> [NSAttributedString.Key: Any] {
            style.nsAttributes(
                content: content,
                environment: environment,
                includeDefaultAttributes: includeDefaultAttributes,
                with: options,
                properties: &properties
            )
        }
        
        private mutating func append(
            _ string: String,
            with attributes: [NSAttributedString.Key: Any],
            in environment: EnvironmentValues
        ) {
            var string = string.caseConvertedIfNeeded(environment)
            if environment.shouldRedactContent {
                string = String.init(repeating: "􀮷", count: string.count)
            }
            if environment.sensitiveContent {
                properties.addSensitive()
            }
            if let attributedString {
                attributedString.append(
                    NSAttributedString(
                        string: string,
                        attributes: attributes
                    )
                )
            } else {
                attributedString = NSMutableAttributedString(
                    string: string,
                    attributes: attributes
                )
            }
        }
    }
    
    // MARK: - Text.Style
    
    package struct Style {
        var baseFont: TextStyleFont = .implicit
        var fontModifiers: [AnyFontModifier] = []
        var color: TextStyleColor = .implicit
        var backgroundColor: Color?
        var baselineOffset: CGFloat?
        var kerning: CGFloat?
        var tracking: CGFloat?
        var strikethrough: LineStyle = .implicit
        var underline: LineStyle = .implicit
        var encapsulation: Text.Encapsulation?
        var speech: AccessibilitySpeechAttributes?
        package var accessibility: AccessibilityTextAttributes?
        #if canImport(CoreText)
        var glyphInfo: CTGlyphInfo?
        #endif
        var shadow: TextShadowModifier?
        var transition: TextTransitionModifier?
        var scale: Text.Scale?
        var superscript: Text.Superscript?
        var typesettingConfiguration: TypesettingConfiguration = .init()
        var customAttributes: [TextAttributeModifierBase] = []
        #if canImport(Darwin)
        var adaptiveImageGlyph: AttributedString.AdaptiveImageGlyph?
        #endif
        package var clearedFontModifiers: Set<ObjectIdentifier> = []
        
        init() {
            _openSwiftUIEmptyStub()
        }
        
        // MARK: - Text.Style.LineStyle
        
        package enum LineStyle {
            case implicit
            case explicit(Text.LineStyle)
            case `default`
            
            package func resolve(
                in environment: EnvironmentValues,
                fallbackStyle: @autoclosure () -> Text.LineStyle?
            ) -> Text.LineStyle.Resolved? {
                let style: Text.LineStyle
                switch self {
                case .implicit:
                    guard let fallbackStyle = fallbackStyle() else {
                        return nil
                    }
                    style = fallbackStyle
                case let .explicit(lineStyle):
                    style = lineStyle
                case .default:
                    return nil
                }
                return Text.LineStyle.Resolved(
                    nsUnderlineStyle: style.nsUnderlineStyle,
                    color: style.color?.resolve(in: environment)
                )
            }
        }
        
        // MARK: - Text.Style.TextStyleColor
        
        package enum TextStyleColor {
            case implicit
            case explicit(AnyShapeStyle)
            case `default`
            case foregroundKeyColor(base: AnyShapeStyle)
            
            package func resolve(
                in environment: EnvironmentValues,
                with options: Text.ResolveOptions,
                properties: inout Text.ResolvedProperties,
                includeDefaultAttributes: Bool = true
            ) -> Color.Resolved? {
                let style: AnyShapeStyle
                switch self {
                case .implicit:
                    guard includeDefaultAttributes else {
                        return nil
                    }
                    guard !options.contains(.foregroundKeyColor) else {
                        return .init(linearWhite: -1, opacity: 1)
                    }
                    let s = environment.foregroundStyle
                    style = .init(s?.fallbackColor(in: environment, level: 0) ?? .primary)
                case .explicit(let anyShapeStyle):
                    style = anyShapeStyle
                case .default:
                    guard includeDefaultAttributes else {
                        return nil
                    }
                    guard !options.contains(.foregroundKeyColor) else {
                        return .init(linearWhite: -1, opacity: 1)
                    }
                    let s = environment.defaultForegroundStyle
                    style = .init(s?.fallbackColor(in: environment, level: 0) ?? .primary)
                case .foregroundKeyColor(let base):
                    guard !options.contains(.allowsKeyColors) else {
                        return .init(linearWhite: -1, opacity: 1)
                    }
                    style = base
                }
                if options.contains(.allowsKeyColors) {
                    var shape = _ShapeStyle_Shape(
                        operation: .resolveStyle(name: .foreground, levels: 0..<1),
                        environment: environment,
                        role: .stroke
                    )
                    style._apply(to: &shape)
                    let shapeStyle = shape.stylePack[.foreground, 0]
                    return properties.addCustomStyle(shapeStyle)
                } else {
                    let color = style.fallbackColor(in: environment, level: 0) ?? .foreground
                    return color.resolve(in: environment)
                }
            }
        }
        
        // MARK: - Text.Style.TextStyleFont
        
        package enum TextStyleFont {
            case implicit
            case explicit(Font)
            case `default`
            
            package func resolve(
                in environment: EnvironmentValues,
                includeDefaultAttributes: Bool = true
            ) -> Font? {
                guard case let .explicit(font) = self else {
                    guard includeDefaultAttributes else {
                        return nil
                    }
                    if case .implicit = self {
                        return environment.effectiveFont
                    } else { // default
                        return environment.defaultFont ?? environment.fallbackFont
                    }
                }
                return font
            }
        }
        
        package func fontTraits(in environment: EnvironmentValues) -> Font.ResolvedTraits {
            let font = baseFont.resolve(in: environment, includeDefaultAttributes: true)
            let environmentModifiers = environment.fontModifiers
            if fontModifiers.isEmpty && environmentModifiers.isEmpty {
                return font!.resolveTraits(in: environment.fontResolutionContext)
            }
            let context = environment.fontResolutionContext
            var descriptor = font!.resolve(in: context)
            for modifier in environmentModifiers {
                modifier.modify(descriptor: &descriptor, in: context)
            }
            for modifier in fontModifiers {
                modifier.modify(descriptor: &descriptor, in: context)
            }
            return .init(descriptor)
        }
        
        package mutating func addFontModifier<M>(_ modifier: M) where M: FontModifier {
            fontModifiers.append(.dynamic(modifier))
        }
        
        package mutating func addFontModifier<M>(type: M.Type) where M: StaticFontModifier {
            fontModifiers.append(.static(M.self))
        }
        
        package mutating func removeFontModifier<M>(ofType _: M.Type) where M: FontModifier {
            let typeID = ObjectIdentifier(M.self)
            fontModifiers.removeAll { $0 is AnyDynamicFontModifier<M> }
            clearedFontModifiers.insert(typeID)
        }
        
        package mutating func removeFontModifier<M>(ofType _: M.Type) where M: StaticFontModifier {
            let typeID = ObjectIdentifier(M.self)
            fontModifiers.removeAll {
                $0 is AnyStaticFontModifier<M> ||
                    (M.self == Font.BoldModifier.self && $0.isboldFontWeightModifier)
            }
            clearedFontModifiers.insert(typeID)
        }
    }

    // MARK: - Text.ResolvedProperties

    package struct ResolvedProperties {
        package var insets: EdgeInsets = .zero
        
        package var features: Text.ResolvedProperties.Features = []
        
        package var styles: [_ShapeStyle_Pack.Style] = []
        
        package var transitions: [Text.ResolvedProperties.Transition] = []
        
        package var suffix: ResolvedTextSuffix = .none

        // MARK: - Text.ResolvedProperties.CustomAttachments

        package struct CustomAttachments {
            package var characterIndices: [Int]
            
            package init(characterIndices: [Int] = []) {
                self.characterIndices = characterIndices
            }
            
            package var isEmpty: Bool {
                characterIndices.isEmpty
            }
        }
        
        package var customAttachments: Text.ResolvedProperties.CustomAttachments = .init()
        
        package init() {
            _openSwiftUIEmptyStub()
        }
        
        package mutating func registerCustomAttachment(at offset: Int) {
            customAttachments.characterIndices.append(offset)
        }

        // MARK: - Text.ResolvedProperties.Features

        package struct Features: OptionSet {
            package let rawValue: UInt16
            
            package init(rawValue: UInt16) {
                self.rawValue = rawValue
            }
            
            package static let keyColor: Text.ResolvedProperties.Features = .init(rawValue: 1 << 0)
            
            package static let attachments: Text.ResolvedProperties.Features = .init(rawValue: 1 << 1)
            
            package static let sensitive: Text.ResolvedProperties.Features = .init(rawValue: 1 << 2)
            
            package static let customRenderer: Text.ResolvedProperties.Features = .init(rawValue: 1 << 3)
            
            package static let useTextLayoutManager: Text.ResolvedProperties.Features = .init(rawValue: 1 << 4)
            
            package static let useTextSuffix: Text.ResolvedProperties.Features = .init(rawValue: 1 << 5)

            package static let produceTextLayout: Text.ResolvedProperties.Features = .init(rawValue: 1 << 6)

            package static let checkInterpolationStrategy: Text.ResolvedProperties.Features = .init(rawValue: 1 << 7)

            package static let isUniqueSizeVariant: Text.ResolvedProperties.Features = .init(rawValue: 1 << 8)
        }

        // MARK: - Text.ResolvedProperties.Transition

        package struct Transition: Equatable {
            package var transition: ContentTransition
            
            package init(transition: ContentTransition) {
                self.transition = transition
            }
        }

        // MARK: - Text.ResolvedProperties.Paragraph

        package struct Paragraph {
            package var compositionLanguage: NSCompositionLanguage = .unset

            var cachedStyle: NSParagraphStyle?
        }
        
        package var paragraph: Text.ResolvedProperties.Paragraph = .init()

        package mutating func addColor(_ c: Color.Resolved) {
            if c.linearRed == -1, c.linearGreen == -1 {
                features.insert(.keyColor)
            }
        }
        
        package mutating func addAttachment() {
            features.insert(.attachments)
        }
        
        package mutating func addSensitive() {
            features.insert(.sensitive)
        }
        
        package mutating func addCustomStyle(
            _ style: ShapeStyle.Pack.Style
        ) -> Color.Resolved {
            if let color = style.color {
                return color.multiplyingOpacity(by: style.opacity)
            }
            var index = 0
            while index < styles.count {
                if styles[index] == style {
                    break
                }
                index += 1
            }
            if index == styles.count {
                styles.append(style)
                features.insert(.keyColor)
            }
            return Color.Resolved(
                linearRed: -1,
                linearGreen: -1,
                linearBlue: Float(index) / 1024,
                opacity: 1
            )
        }
    }
}

// MARK: - Text.ResolvedProperties.Paragraph + Extension

extension Text.ResolvedProperties.Paragraph {
    package mutating func style(environment: EnvironmentValues) -> NSParagraphStyle {
        if let cachedStyle {
            return cachedStyle
        }
        let style = makeParagraphStyle(environment: environment)
        style.compositionLanguage = compositionLanguage
        if environment.shouldRedactContent {
            style.fullyJustified = true
            style.baseWritingDirection = switch environment.layoutDirection {
            case .leftToRight: .leftToRight
            case .rightToLeft: .rightToLeft
            }
            style.lineBreakMode = .byCharWrapping
        }
        cachedStyle = style
        return style
    }

    package mutating func markParagraphBoundary(_ paragraphBoundary: Bool) {
        if paragraphBoundary {
            cachedStyle = nil
        }
    }
}

// MARK: - OpenSwiftUITextAttachment

class OpenSwiftUITextAttachment: NSTextAttachment {
    let graphicsImage: GraphicsImage
    let alignmentOrigin: CGPoint

    init(image: Image.Resolved) {
        graphicsImage = image.image
        if case .vectorGlyph = graphicsImage.contents {
            alignmentOrigin = image.alignmentOrigin
        } else {
            alignmentOrigin = .zero
        }
        super.init(data: nil, ofType: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        _openSwiftUIUnreachableCode()
    }

    func draw(in context: inout GraphicsContext, at point: CGPoint) {
        let origin = CGPoint(
            x: alignmentOrigin.x + point.x,
            y: alignmentOrigin.y + point.y
        )
        context.draw(
            graphicsImage,
            in: CGRect(origin: origin, size: graphicsImage.size),
            style: FillStyle(),
            shading: nil
        )
    }
}

// MARK: - EnvironmentValues + disableLinkColor

extension EnvironmentValues {
    private struct DisableLinkColorKey: EnvironmentKey {
        static var defaultValue: Bool { false }
    }

    package var disableLinkColor: Bool {
        get { self[DisableLinkColorKey.self] }
        set { self[DisableLinkColorKey.self] = newValue }
    }
}

// MARK: - EnvironmentValues + resolvedTextProvider

package protocol ResolvedTextProvider {
    static func defaultLinkColor(
        for environment: EnvironmentValues
    ) -> Color

    static func updateImageTextAttachment(
        in attachment: NSTextAttachment,
        image: Image.Resolved
    )

    static func updateWidgetTextAttachment(
        _ attachment: NSTextAttachment,
        namedImage: Image.NamedResolved
    )
}

extension EnvironmentValues {
    private struct ResolvedTextProviderKey: EnvironmentKey {
        static var defaultValue: (any ResolvedTextProvider.Type)? { nil }
    }

    package var resolvedTextProvider: (any ResolvedTextProvider.Type)? {
        get { self[ResolvedTextProviderKey.self] }
        set { self[ResolvedTextProviderKey.self] = newValue }
    }
}

// MARK: - Text.ResolvedString

extension Text {
    struct ResolvedString: ResolvedTextContainer {
        var style: Text.Style = .init()
        var idiom: AnyInterfaceIdiom?
        var string: String = ""
        var hasResolvableAttributes: Bool = false

        mutating func append<S>(
            _ string: S,
            in env: EnvironmentValues,
            with options: Text.ResolveOptions,
            isUniqueSizeVariant: Bool
        ) where S: StringProtocol {
            var s = String(string).caseConvertedIfNeeded(env)
            if env.shouldRedactContent {
                s = String(repeating: "􀮷", count: s.count)
            }
            self.string.append(s)
        }

        mutating func append(
            _ attributedString: NSAttributedString,
            in env: EnvironmentValues,
            with options: Text.ResolveOptions,
            isUniqueSizeVariant: Bool
        ) {
            append(
                attributedString.string,
                in: env,
                with: options
            )
        }

        mutating func append(
            _ image: Image.Resolved,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions
        ) {
            string.append(String.nsAttachment)
        }

        mutating func append(
            _ namedImage: Image.NamedResolved,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions
        ) {
            string.append(String.nsAttachment)
        }

        mutating func append<R>(
            resolvable: R,
            in environment: EnvironmentValues,
            with options: Text.ResolveOptions,
            transition: ContentTransition?
        ) where R: ResolvableStringAttribute {
            let context = ResolvableStringResolutionContext(
                referenceDate: nil,
                environment: environment,
                maximumWidth: nil
            )
            guard let attributedString = resolvable.resolve(in: context) else {
                Log.internalWarning("Unable to resolve custom attribute \(resolvable)")
                return
            }
            hasResolvableAttributes = true
            append(
                String(attributedString.characters),
                in: environment,
                with: options
            )
        }
    }
}
