//
//  ChevronAuraView.swift
//  Adamantium
//
//  Created by Sunny Uppal on 11/19/24.
//
import Metal
import MetalKit
import SwiftUI
import UIKit

public protocol ChevronAuraViewCoordinatorDelegate: AnyObject {
    func didUpdateAuraFrame(_ deltaTime: TimeInterval)
}

public struct ChevronAuraView: UIViewRepresentable {
    @ObservedObject var coordinator: Coordinator

    let colorSetA: SunoTriColorSet
    let colorSetB: SunoTriColorSet

    let morphSpeed: ADFloat
    let scale: ADFloat
    let seed: ADFloat
    let chevronScale: ADFloat
    let chevronOffsetX: ADFloat
    let chevronOffsetY: ADFloat
    let chevronRotation: ADFloat
    let chevronVerticalOffsetVelocity: ADFloat
    let chevronAuraBFractalNoiseZOffset: ADFloat
    let repeatUVMixRatio: ADFloat
    let repeatUVScalar: ADFloat
    let frameRate: Int

    public init(_ coordinator: Coordinator,
                colorSetA: SunoTriColorSet,
                colorSetB: SunoTriColorSet,
                morphSpeed: ADFloat = 0.1,
                scale: ADFloat = 1.0,
                seed: ADFloat = .zero,
                chevronScale: ADFloat = 1.0,
                chevronOffsetX: ADFloat = 0.0,
                chevronOffsetY: ADFloat = 0.0,
                chevronRotation: ADFloat = 0.785398163397448,
                chevronVerticalOffsetVelocity: ADFloat = 1.0,
                chevronAuraBFractalNoiseZOffset: ADFloat = .zero,
                repeatUVMixRatio: ADFloat = .zero,
                repeatUVScalar: ADFloat = .zero,
                frameRate: Int = 60)
    {
        self.coordinator = coordinator
        coordinator.setScale(scale)
        coordinator.setGradientSeed(seed)
        coordinator.setGradientSpeed(morphSpeed)

        coordinator.setColorSetA(colorSetA, deltaTime: 1.0)
        coordinator.setColorSetB(colorSetB, deltaTime: 1.0)

        coordinator.setChevronScale(chevronScale)
        coordinator.setChevronOffsetX(chevronOffsetX)
        coordinator.setChevronOffsetY(chevronOffsetY)
        coordinator.setChevronRotation(chevronRotation)
        coordinator.setChevronVerticalOffsetVelocity(chevronVerticalOffsetVelocity)
        coordinator.setChevronAuraBFractalNoiseZOffset(chevronAuraBFractalNoiseZOffset)
        coordinator.setRepeatUVMixRatio(repeatUVMixRatio)
        coordinator.setRepeatUVScalar(repeatUVScalar)

        self.colorSetA = colorSetA
        self.colorSetB = colorSetB

        self.morphSpeed = morphSpeed
        self.scale = scale
        self.seed = seed
        self.chevronScale = chevronScale
        self.chevronOffsetX = chevronOffsetX
        self.chevronOffsetY = chevronOffsetY
        self.chevronRotation = chevronRotation
        self.chevronVerticalOffsetVelocity = chevronVerticalOffsetVelocity
        self.chevronAuraBFractalNoiseZOffset = chevronAuraBFractalNoiseZOffset
        self.repeatUVMixRatio = repeatUVMixRatio
        self.repeatUVScalar = repeatUVScalar
        self.frameRate = frameRate
    }

    public func makeCoordinator() -> Coordinator {
        return coordinator
    }

    public func makeUIView(context: Context) -> some UIView {
        let mtkView = MTKView()
        mtkView.delegate = context.coordinator
        mtkView.preferredFramesPerSecond = frameRate
        mtkView.device = Adamantium.sharedDevice

        mtkView.framebufferOnly = false
        mtkView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
        mtkView.drawableSize = mtkView.frame.size
        mtkView.enableSetNeedsDisplay = true
        mtkView.isPaused = false
        return mtkView
    }

    public func updateUIView(_: UIViewType, context _: Context) {}

    public class Coordinator: NSObject, ObservableObject, MTKViewDelegate {
        var metalDevice: MTLDevice?
        var metalCommandQueue: MTLCommandQueue?
        public weak var delegate: ChevronAuraViewCoordinatorDelegate?

        private var lastAColor: SIMD3<Float> = .zero
        private var lastBColor: SIMD3<Float> = .zero
        private var lastCColor: SIMD3<Float> = .zero

        private var lastDColor: SIMD3<Float> = .zero
        private var lastEColor: SIMD3<Float> = .zero
        private var lastFColor: SIMD3<Float> = .zero

        private var gradientSpeed: ADFloat = 0.1
        private var gradientScale: ADFloat = 1.0
        private var gradientSeed: ADFloat = .zero

        private var chevronScale: ADFloat = 10.0
        private var chevronOffsetX: ADFloat = .zero
        private var chevronOffsetY: ADFloat = .zero
        private var chevronRotation: ADFloat = 0.785398163397448
        private var chevronVerticalOffsetVelocity: ADFloat = 1.0
        private var chevronAuraBFractalNoiseZOffset: ADFloat = .zero
        private var chevronAuraRepeatUVMixRatio: ADFloat = .zero
        private var chevronAuraRepeatUVScalar: ADFloat = .zero

        private let startingTime: Date = .now
        private var lastTime: Date?
        private var speakingIntensity: Float = .zero
        private var mixCircleShape: Float = .zero
        private var isLerpDirectionForward: Bool = true

        override public init() {
            self.metalDevice = Adamantium.sharedDevice
            self.metalCommandQueue = Adamantium.sharedCommandQueue
            super.init()
        }

        public func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {}

        func setGradientSpeed(_ speedScalar: ADFloat) {
            gradientSpeed = speedScalar
        }

        func setScale(_ scale: ADFloat) {
            gradientScale = scale
        }

        func setGradientSeed(_ seed: ADFloat = .zero) {
            gradientSeed = seed
        }

        func setChevronScale(_ scalar: ADFloat) {
            chevronScale = scalar
        }

        func setChevronOffsetX(_ offset: ADFloat) {
            chevronOffsetX = offset
        }

        func setChevronOffsetY(_ offset: ADFloat) {
            chevronOffsetY = offset
        }

        func setChevronRotation(_ rotation: ADFloat) {
            chevronRotation = rotation
        }

        func setChevronVerticalOffsetVelocity(_ velocity: ADFloat) {
            chevronVerticalOffsetVelocity = velocity
        }

        func setChevronAuraBFractalNoiseZOffset(_ offset: ADFloat) {
            chevronAuraBFractalNoiseZOffset = offset
        }

        func setRepeatUVMixRatio(_ ratio: ADFloat) {
            chevronAuraRepeatUVMixRatio = ratio
        }

        func setRepeatUVScalar(_ scalar: ADFloat) {
            chevronAuraRepeatUVScalar = scalar
        }

        func setColorSetA(_ triColorSet: SunoTriColorSet, deltaTime: TimeInterval) {
            let floatDeltaTime = Float(deltaTime)
            let targetColorA = triColorSet.floatColorA
            let targetColorB = triColorSet.floatColorB
            let targetColorC = triColorSet.floatColorC

            let aR = simd_mix(lastAColor.x, targetColorA.x, floatDeltaTime)
            let aG = simd_mix(lastAColor.y, targetColorA.y, floatDeltaTime)
            let aB = simd_mix(lastAColor.z, targetColorA.z, floatDeltaTime)
            lastAColor = simd_float3(x: aR, y: aG, z: aB)

            let bR = simd_mix(lastBColor.x, targetColorB.x, floatDeltaTime)
            let bG = simd_mix(lastBColor.y, targetColorB.y, floatDeltaTime)
            let bB = simd_mix(lastBColor.z, targetColorB.z, floatDeltaTime)
            lastBColor = simd_float3(x: bR, y: bG, z: bB)

            let cR = simd_mix(lastCColor.x, targetColorC.x, floatDeltaTime)
            let cG = simd_mix(lastCColor.y, targetColorC.y, floatDeltaTime)
            let cB = simd_mix(lastCColor.z, targetColorC.z, floatDeltaTime)
            lastCColor = simd_float3(x: cR, y: cG, z: cB)
        }

        func setColorSetB(_ triColorSet: SunoTriColorSet, deltaTime: TimeInterval) {
            let floatDeltaTime = Float(deltaTime)
            let targetColorA = triColorSet.floatColorA
            let targetColorB = triColorSet.floatColorB
            let targetColorC = triColorSet.floatColorC

            let aR = simd_mix(lastDColor.x, targetColorA.x, floatDeltaTime)
            let aG = simd_mix(lastDColor.y, targetColorA.y, floatDeltaTime)
            let aB = simd_mix(lastDColor.z, targetColorA.z, floatDeltaTime)
            lastDColor = simd_float3(x: aR, y: aG, z: aB)

            let bR = simd_mix(lastEColor.x, targetColorB.x, floatDeltaTime)
            let bG = simd_mix(lastEColor.y, targetColorB.y, floatDeltaTime)
            let bB = simd_mix(lastEColor.z, targetColorB.z, floatDeltaTime)
            lastEColor = simd_float3(x: bR, y: bG, z: bB)

            let cR = simd_mix(lastFColor.x, targetColorC.x, floatDeltaTime)
            let cG = simd_mix(lastFColor.y, targetColorC.y, floatDeltaTime)
            let cB = simd_mix(lastFColor.z, targetColorC.z, floatDeltaTime)
            lastFColor = simd_float3(x: cR, y: cG, z: cB)
        }

        public func draw(in view: MTKView) {
            var size: ADVector2 {
                return ADVector2(
                    x: Float(view.drawableSize.width),
                    y: Float(view.drawableSize.height)
                )
            }

            let deltaTime: TimeInterval
            if let lastTime = lastTime {
                deltaTime = Date.now.timeIntervalSince(lastTime)
            } else {
                deltaTime = .zero
            }

            lastTime = .now

            guard
                let commandBuffer = Adamantium.sharedCommandQueue?.makeCommandBuffer(),
                let currentDrawable = view.currentDrawable,
                let renderDescriptor = view.currentRenderPassDescriptor,
                let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderDescriptor)
            else { return }

            delegate?.didUpdateAuraFrame(deltaTime)
            renderAura(size, renderEncoder: renderEncoder)

            renderEncoder.endEncoding()
            commandBuffer.present(currentDrawable)
            commandBuffer.commit()
        }
    }
}

private extension ChevronAuraView.Coordinator {
    func renderAura(_ size: ADVector2, renderEncoder: MTLRenderCommandEncoder) {
        let timeElapsed = ADFloat(Date.now.timeIntervalSince(startingTime))
        let fractalElapsedTime = timeElapsed * gradientSpeed
        let grainElapsedTime = timeElapsed * 0.01

        let chevronAnimationOffset: ADFloat = timeElapsed * chevronVerticalOffsetVelocity

        let mixPointStart: ADFloat = -0.2
        let mixPointBackground: ADFloat = -0.1
        let mixPointBackgroundA: ADFloat = -0.05
        let mixPointAB: ADFloat = 0.2
        let mixPointBC: ADFloat = 0.4
        let mixPointEnd: ADFloat = 2.0

        let noiseRenderable = ChevronSimple3DAuraRenderable()
        noiseRenderable.config = .init(
            displaySize: size,
            fractalNoiseX: .zero,
            fractalNoiseY: .zero,
            fractalNoiseZ: fractalElapsedTime + gradientSeed,
            fractalUniformScale: 0.5 * gradientScale,
            grainNoiseZ: grainElapsedTime,
            grainNoiseScale: 5.0 * gradientScale,
            circleMaskRadius: 100.0,
            backgroundColor: SunoColorPalette.backgroundDotCom.floatThree,
            colorA: lastAColor,
            colorB: lastBColor,
            colorC: lastCColor,
            colorD: lastDColor,
            colorE: lastEColor,
            colorF: lastFColor,
            mixPointStart: mixPointStart,
            mixPointBackground: mixPointBackground,
            mixPointBackgroundA: mixPointBackgroundA,
            mixPointAB: mixPointAB,
            mixPointBC: mixPointBC,
            mixPointEnd: mixPointEnd,
            chevronScale: chevronScale,
            chevronOffsetX: chevronOffsetX,
            chevronOffsetY: chevronOffsetY + chevronAnimationOffset,
            chevronRotation: chevronRotation,
            chevronAuraBFractalNoiseZOffset: fractalElapsedTime + chevronAuraBFractalNoiseZOffset,
            repeatUvMixRatio: chevronAuraRepeatUVMixRatio,
            repeatUvScalar: chevronAuraRepeatUVScalar
        )

        noiseRenderable.render(encoder: renderEncoder)
    }
}
