//
//  ImmersiveView.swift
//  HelloVisionOS - Niall George Horn - June 2026
//
//  ImmersiveSpace content — mixed immersion, world-tracked.
//
//  Skeleton: world-space, no scale. Hips at (0, 0.95, -1.5) so feet land on
//  the floor and the body is 1.5m in front of the user.
//
//  ControlPanel: head-anchored RealityKit attachment. Tracks the simulator
//  camera — appears as a persistent overlay that follows as you orbit.
//
//  Solver: static pose held between slider drags. Only runs on needsBurst.
//  Fingers + face joints permanently hidden for a clean limb skeleton.
//

import SwiftUI
import RealityKit

// Face (24-34) + left fingers (39-62) + right fingers (67-90)
private let hiddenJoints: Set<Int> = Set(24...34)
    .union(Set(39...62))
    .union(Set(67...90))

struct ImmersiveView: View {

    @Environment(AppModel.self) var appModel

    @State private var worldRenderer = SkeletonRenderer()
    @State private var isSetup = false
    @State private var updateTask: Task<Void, Never>? = nil
    private var numIter = 512 

    var body: some View {
        RealityView { content, attachments in
            // World-space: hips at 0.95m height, 1.5m in front.
            // Hip-normalised joints, feet land at floor level.
            worldRenderer.rootEntity.position = SIMD3(0, 0.95, -1.5)
            content.add(worldRenderer.rootEntity)

            // Head-anchored ControlPanel — follows camera in simulator.
            // Offset: 0.35m left, 0.10m below eye level, 0.60m in front.
            let headAnchor = AnchorEntity(.head)
            content.add(headAnchor)
            if let panel = attachments.entity(for: "controls") {
                panel.position = SIMD3(-0.35, -0.10, -0.60)
                headAnchor.addChild(panel)
            }
        } update: { _, _ in } attachments: {
            Attachment(id: "controls") {
                ControlPanel()
                    .environment(appModel)
            }
        }
        .onAppear {
            setupBridge()
            startUpdateLoop()
        }
        .onDisappear {
            updateTask?.cancel()
        }
    }

    // ============ Setup ============

    private func setupBridge() {
        let bridge = appModel.bridge
        let count = bridge.jointCount > 0 ? bridge.jointCount : 91
        worldRenderer.buildSkeleton(jointCount: count)
        worldRenderer.hideJoints(hiddenJoints)

        if bridge.isLoaded {
            bridge.setHeadPose(x: appModel.headX, y: appModel.headY, z: appModel.headZ)
            bridge.setWristPose(isLeft: true,  x: appModel.leftX,  y: appModel.leftY,  z: appModel.leftZ)
            bridge.setWristPose(isLeft: false, x: appModel.rightX, y: appModel.rightY, z: appModel.rightZ)
            for _ in 0..<numIter { _ = bridge.solve() }
            let initJoints = bridge.solve()
            if !initJoints.isEmpty {
                worldRenderer.updateSkeleton(joints: initJoints)
            }
        }

        appModel.solverStatus = bridge.isLoaded
            ? "Solver: active | Joints: \(count)"
            : bridge.statusMessage
        appModel.totalJoints = count
        isSetup = true
    }

    // ============ Update loop ============

    private func startUpdateLoop() {
        updateTask = Task {
            while !Task.isCancelled {
                try? await Task.sleep(nanoseconds: 16_666_667)
                guard isSetup, appModel.needsBurst else { continue }
                await MainActor.run { updateFrame() }
            }
        }
    }

    @MainActor
    private func updateFrame() {
        let bridge = appModel.bridge
        appModel.needsBurst = false

        bridge.setHeadPose(x: appModel.headX, y: appModel.headY, z: appModel.headZ)
        bridge.setWristPose(isLeft: true,  x: appModel.leftX,  y: appModel.leftY,  z: appModel.leftZ)
        bridge.setWristPose(isLeft: false, x: appModel.rightX, y: appModel.rightY, z: appModel.rightZ)

        var joints: [JointTransform] = []
        for _ in 0..<numIter { joints = bridge.solve() }
        guard !joints.isEmpty else { return }

        worldRenderer.updateSkeleton(joints: joints)

        let nonZero = joints.filter { $0.localPosition != .zero }.count
        appModel.solverStatus = "Solver: active | Joints: \(nonZero)/\(joints.count)"
    }
}
