//
//  CloudSkyView.swift
//  Adamantium
//
//  Created by Sunny Uppal on 4/29/25.
//

import SwiftUI
import Metal
import MetalKit
import simd

/// A SwiftUI wrapper around a full-screen quad running your `clouds_fragment` shader.
public struct CloudySkyView: UIViewRepresentable, Identifiable {
    public let id: String
    
    // Exposed uniforms:
    public var timeScale:  ADFloat
    public var cloudscale: ADFloat
    public var speed:      ADFloat
    public var clouddark:  ADFloat
    public var cloudlight: ADFloat
    public var cloudcover: ADFloat
    public var cloudalpha: ADFloat
    public var skytint:    ADFloat
    public var skycolour1: ADVector3
    public var skycolour2: ADVector3
    
    // Frame control:
    public var frameRate: Int = 60
    public var isPaused:  Bool = false
    
    @ObservedObject private var coordinator: Coordinator
    
    public init(
        id: String,
        timeScale: ADFloat   = 1.0,
        cloudscale: ADFloat  = 1.1,
        speed:      ADFloat  = 0.03,
        clouddark:  ADFloat  = 0.5,
        cloudlight: ADFloat  = 0.3,
        cloudcover: ADFloat  = 0.2,
        cloudalpha: ADFloat  = 8.0,
        skytint:    ADFloat  = 0.5,
        skycolour1: ADVector3 = .init(x: 0.2, y: 0.4, z: 0.6),
        skycolour2: ADVector3 = .init(x: 0.4, y: 0.7, z: 1.0),
        frameRate:  Int    = 60,
        isPaused:   Bool   = false
    ) {
        self.id          = id
        self.timeScale   = timeScale
        self.cloudscale  = cloudscale
        self.speed       = speed
        self.clouddark   = clouddark
        self.cloudlight  = cloudlight
        self.cloudcover  = cloudcover
        self.cloudalpha  = cloudalpha
        self.skytint     = skytint
        self.skycolour1  = skycolour1
        self.skycolour2  = skycolour2
        self.frameRate   = frameRate
        self.isPaused    = isPaused
        
        let c = Coordinator()
        self.coordinator = c
    }
    
    public func makeCoordinator() -> Coordinator { coordinator }
    
    public func makeUIView(context: Context) -> MTKView {
        let mtk = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
        mtk.delegate = context.coordinator
        mtk.preferredFramesPerSecond = frameRate
        mtk.framebufferOnly = false
        mtk.clearColor = MTLClearColorMake(0,0,0,0)
        mtk.enableSetNeedsDisplay = true
        mtk.isPaused = isPaused
        return mtk
    }
    
    public func updateUIView(_ uiView: MTKView, context: Context) {
        uiView.isPaused = isPaused
        // push updated uniforms down each update
        coordinator.updateTimeScale(timeScale)
        coordinator.updateUniforms(
            cloudscale: cloudscale,
            speed:      speed,
            clouddark:  clouddark,
            cloudlight: cloudlight,
            cloudcover: cloudcover,
            cloudalpha: cloudalpha,
            skytint:    skytint,
            skycolour1: skycolour1,
            skycolour2: skycolour2
        )
    }
    
    public class Coordinator: NSObject, ObservableObject, MTKViewDelegate {
        private let device: MTLDevice?
        private let queue:  MTLCommandQueue?
        private let renderable: CloudySkyRenderable
        
        // current uniform values
        private var uniforms = CloudySkyRenderable.Config.defaultConfiguration
        
        // seed time
        private let start = CFAbsoluteTimeGetCurrent()
        private var lastTime: CFAbsoluteTime?
        private var timeScale: ADFloat = 1.0
        
        public override init() {
            device = Adamantium.sharedDevice
            queue = Adamantium.sharedCommandQueue
            renderable = CloudySkyRenderable()
            super.init()
        }
        
        func updateTimeScale(_ newTimeScale: ADFloat) {
            timeScale = newTimeScale
        }
        
        // push new uniform values
        func updateUniforms(
            cloudscale: ADFloat,
            speed:      ADFloat,
            clouddark:  ADFloat,
            cloudlight: ADFloat,
            cloudcover: ADFloat,
            cloudalpha: ADFloat,
            skytint:    ADFloat,
            skycolour1: ADVector3,
            skycolour2: ADVector3
        ) {
            uniforms.cloudscale = cloudscale
            uniforms.speed      = speed
            uniforms.clouddark  = clouddark
            uniforms.cloudlight = cloudlight
            uniforms.cloudcover = cloudcover
            uniforms.cloudalpha = cloudalpha
            uniforms.skytint    = skytint
            uniforms.skycolour1 = skycolour1
            uniforms.skycolour2 = skycolour2
        }
        
        public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
            // nothing special
        }
        
        public func draw(in view: MTKView) {
            guard
                let buf = queue?.makeCommandBuffer(),
                let desc = view.currentRenderPassDescriptor,
                let drw  = view.currentDrawable
            else { return }
            
            // compute delta time and absolute time
            let now = CFAbsoluteTimeGetCurrent()
            let delta = lastTime.map { now - $0 } ?? 0
            lastTime = now
            
            let elapsed = Float(now - start)
            // update per-frame uniforms:
            uniforms.resolution = .init(
                x: Float(view.drawableSize.width),
                y: Float(view.drawableSize.height)
            )
            
            uniforms.time = elapsed * timeScale
            
            // apply config and render
            renderable.config = uniforms
            
            let enc = buf.makeRenderCommandEncoder(descriptor: desc)!
            renderable.render(encoder: enc)
            enc.endEncoding()
            
            buf.present(drw)
            buf.commit()
        }
    }
}
