import SwiftUI

public struct ShimmerModifier: ViewModifier {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var shimmerPosition: CGFloat = -1

    private let blur: CGFloat
    private let opacity: CGFloat
    private let duration: TimeInterval
    private let cornerRadius: CGFloat
    private let color: Color
    public init(
        blur: CGFloat = 5,
        opacity: CGFloat = 0.8,
        duration: TimeInterval = 1.25,
        cornerRadius: CGFloat = 4,
        color: Color = .SemanticV1.backgroundSecondary
    ) {
        self.blur = blur
        self.opacity = opacity
        self.duration = duration
        self.cornerRadius = cornerRadius
        self.color = color
    }

    private var animation: Animation? {
        reduceMotion ? nil : .linear(duration: duration).delay(0.25).repeatForever(autoreverses: false)
    }

    private var linearGradient: some ShapeStyle {
        .linearGradient(
            colors: [.clear, color.opacity(opacity), .clear],
            startPoint: .leading,
            endPoint: .trailing
        )
    }

    private var shimmerEffect: some View {
        GeometryReader {
            Rectangle()
                .fill(linearGradient)
                .rotationEffect(.degrees(0))
                .scaleEffect(y: 1.25)
                .blur(radius: blur)
                .offset(x: horizontalOffset(for: $0.size))
                .clipShape(.rect(cornerRadius: cornerRadius))
        }
    }

    public func body(content: Content) -> some View {
        content
            .overlay(shimmerEffect)
            .onAppear {
                DispatchQueue.main.async {
                    withAnimation(animation) {
                        shimmerPosition = 1
                    }
                }
            }
            .onDisappear {
                shimmerPosition = -1
            }
            .clipped()
    }

    private func horizontalOffset(for size: CGSize) -> CGFloat {
        // Adjust the position beyond the horizontal bounds of the view, so the shimmer appears and disappears
        let position = size.width * shimmerPosition
        // Offset proportional to how tall the view is, taller views will show the gradient on the top corner due to the rotation effect
        let offset = size.height / 2.5

        return position + (shimmerPosition > 0 ? offset : -offset)
    }
}

public extension View {
    @ViewBuilder func placeholderShimmering(
        isVisible: Bool,
        cornerRadius: CGFloat = 12,
        opacity: CGFloat = 0.6,
        color: Color = .SemanticV1.shimmerShade,
        duration: TimeInterval = 1.25
    ) -> some View {
        if isVisible {
            modifier(
                ShimmerModifier(
                    opacity: opacity,
                    duration: duration,
                    cornerRadius: cornerRadius,
                    color: color
                )
            )
        } else {
            self
        }
    }
}
