import Localization
import SwiftUI
import Utilities

public struct ExpandableText: View {
    public struct Shadow {
        public var color: Color
        public var radius: CGFloat
        public var x: CGFloat
        public var y: CGFloat
        
        public init(
            color: Color,
            radius: CGFloat = 0,
            x: CGFloat = 0,
            y: CGFloat = 0
        ) {
            self.color = color
            self.radius = radius
            self.x = x
            self.y = y
        }
    }
    
    var inputString: String
    var inputAttributedString: AttributedString?
    var font: TypographyV1
    var condensedLineLimit: Int
    var foregroundColor: Color
    var shadow: Shadow
    @Binding var isExpanded: Bool

    @Environment(\.layoutDirection) private var layoutDirection

    @State private var canBeExpanded: Bool = false
    @State private var moreTextContainerSize: CGSize = .zero
    
    private let showMoreText: String = {
        " ..." + L10n.ComponentLibrary.expandToSeeMore
    }()
    private let showLessText: String = {
        " " + L10n.ComponentLibrary.collpaseToSeeLess
    }()
}

// MARK: - Initializers
extension ExpandableText {
    public init(
        _ inputString: String,
        font: TypographyV1,
        condensedLineLimit: Int = 2,
        foregroundColor: Color = Color.SemanticV1.textPrimary,
        shadow: Shadow = .init(color: .clear),
        isExpanded: Binding<Bool>
    ) {
        self.inputString = inputString
        self.font = font
        self.condensedLineLimit = condensedLineLimit
        self.foregroundColor = foregroundColor
        self.shadow = shadow
        self._isExpanded = isExpanded
    }
    
    public init(
        _ inputAttributedString: AttributedString,
        font: TypographyV1,
        condensedLineLimit: Int = 2,
        foregroundColor: Color = Color.SemanticV1.textPrimary,
        shadow: Shadow = .init(color: .clear),
        isExpanded: Binding<Bool>
    ) {
        self.inputString = String(inputAttributedString.characters)
        self.inputAttributedString = inputAttributedString
        self.font = font
        self.condensedLineLimit = condensedLineLimit
        self.foregroundColor = foregroundColor
        self.shadow = shadow
        self._isExpanded = isExpanded
    }
}

// MARK: - Computed
extension ExpandableText {
    private var moreAttributedString: AttributedString {
        var more = AttributedString(showMoreText)
        more.font = font.ppNeueMontrealBold().font
        more.foregroundColor = foregroundColor
        return more
    }
    private var lessAttributedString: AttributedString {
        var less = AttributedString(showLessText)
        less.font = font.ppNeueMontrealBold().font
        less.foregroundColor = foregroundColor
        return less
    }
    private var attributedString: AttributedString {
        var result = AttributedString(inputString)
        result.font = font.font
        result.foregroundColor = foregroundColor
        
        if let inputAttributedString {
            result = inputAttributedString
        }
        if isExpanded {
            result.append(lessAttributedString)
        }
        
        return result
    }
    private var lineLimit: Int? {
        isExpanded ? nil : condensedLineLimit
    }
    private var maskEnabled: Bool {
        !isExpanded && canBeExpanded
    }
}

// MARK: - UI
extension ExpandableText {
    public var body: some View {
        styledText
            .frame(maxWidth: .infinity, alignment: .leading)
            .lineLimit(lineLimit)
            .background(content: background)
            .applyingTruncationMask(
                size: moreTextContainerSize,
                enabled: maskEnabled
            )
            .overlay(
                alignment: .trailingLastTextBaseline,
                content: moreText
            )
            .contentShape(.rect)
            .onTapGesture(perform: onTapGesture)
    }
    
    private var styledText: some View {
        Text(attributedString)
            .shadow(
                color: shadow.color,
                radius: shadow.radius,
                x: shadow.x,
                y: shadow.y
            )
            .dynamicTypeSize(.large)
    }
    
    private func background() -> some View {
        ViewThatFits(in: .vertical) {
            styledText
                .hidden()
                .onAppear {
                    canBeExpanded = false
                }
            Color.clear
                .onAppear {
                    canBeExpanded = true
                }
        }
        .allowsHitTesting(false)
    }
    
    private func moreText() -> some View {
        let hide = (isExpanded || !canBeExpanded)

        return Text(moreAttributedString)
            .dynamicTypeSize(.large)
            .onGeometryChange(for: CGSize.self) { proxy in
                proxy.size
            } action: {
                moreTextContainerSize = $0
            }
            .opacity(hide ? 0 : 1)
            .allowsHitTesting(false)
            .animation(.none, value: hide)
    }
}

/// `https://edoardo.fyi/blog/2022/07/swiftui-expandable-text/`
private struct TruncationTextMask: ViewModifier {
    let size: CGSize
    let enabled: Bool
    
    @Environment(\.layoutDirection) private var layoutDirection
    
    func body(content: Content) -> some View {
        content
            .mask {
                ZStack {
                    if enabled {
                        VStack(spacing: 0) {
                            Rectangle()
                            HStack(spacing: 0) {
                                Rectangle()
                                HStack(spacing: 0) {
                                    LinearGradient(
                                        colors: [.black, .black, .black, .clear],
                                        startPoint: layoutDirection == .rightToLeft ? .trailing : .leading,
                                        endPoint: layoutDirection == .rightToLeft ? .leading : .trailing
                                    )
                                    .frame(width: size.width, height: size.height)
                                    
                                    Rectangle()
                                        .foregroundColor(.clear)
                                        .frame(width: size.width)
                                }
                            }
                            .frame(height: size.height)
                        }
                    } else {
                        Rectangle()
                    }
                }
                .animation(.none, value: enabled)
            }
    }
}

private extension View {
    func applyingTruncationMask(size: CGSize, enabled: Bool) -> some View {
        modifier(
            TruncationTextMask(size: size, enabled: enabled)
        )
    }
}

// MARK: - Methods
extension ExpandableText {
    private func onTapGesture() {
        let canToggleExpansion = isExpanded || canBeExpanded
        guard canToggleExpansion else { return }
        withAnimation(.interactiveSpring) {
            isExpanded.toggle()
        }
    }
}

// MARK: - Previews
fileprivate
struct PreviewView: View {
    @State private var isExpandedMap: [String: Bool] = [:]
    
    private let captions: [String] = [
        "This one fits without expansion",
        "✨ Dive into the mystery of the purple eye 🔮 'Magical Vibes' is a song about magic, secrets, and ancient legends woven into a hypnotic melody. What does the purple eye see, and what stories does it hold? Let your imagination run wild as you listen! 👁️💜 #MagicalVibes #OriginalSong #PurpleMagic",
        "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS",
        "Line 1\nLine 2\nLine 3"
    ]
    
    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            ForEach(captions, id: \.self) { string in
                ExpandableText(
                    string,
                    font: .playerCaption,
                    isExpanded: isExpandedBinding(for: string)
                )
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        .padding()
    }
    
    private func isExpandedBinding(
        for caption: String
    ) -> Binding<Bool> {
        .init {
            isExpandedMap[caption, default: false]
        } set: {
            isExpandedMap[caption] = $0
        }
    }
}

#Preview {
    @Previewable @State var isExpanded: Bool = false
    
    VStack {
        Spacer()
        ExpandableText(
            "✨ Dive into the mystery of the purple eye 🔮 'Magical Vibes' is a song about magic, secrets, and ancient legends woven into a hypnotic melody. What does the purple eye see, and what stories does it hold? Let your imagination run wild as you listen! 👁️💜 #MagicalVibes #OriginalSong #PurpleMagic",
            font: .playerCaption,
            isExpanded: $isExpanded
        )
    }
    .padding()
}

#Preview("Many") {
    ScrollView {
        PreviewView()
        Divider()
        PreviewView()
            .environment(\.sizeCategory, .extraExtraLarge)
        Divider()
        PreviewView()
            .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
    }
}
