import Foundation
import ARKit
import React

@objc(ARModule)
class ARModule: NSObject {
    
    // MARK: - Properties
    
    private var session: ARSession?
    private var configuration: ARWorldTrackingConfiguration?
    private var objects: [String: ObjectEntry] = [:]
    
    private struct ObjectEntry {
        let id: String
        var position: SIMD3<Float>
        var rotation: SIMD3<Float>
        var scale: Float
        var anchor: ARAnchor?
    }
    
    // MARK: - Module Setup
    
    @objc static func requiresMainQueueSetup() -> Bool {
        return true
    }
    
    // MARK: - Public Methods
    
    @objc(startSession:withResolver:withRejecter:)
    func startSession(options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            do {
                if !ARWorldTrackingConfiguration.isSupported {
                    throw NSError(domain: "com.reactnativear", code: 1, userInfo: [NSLocalizedDescriptionKey: "ARKit is not supported on this device"])
                }
                
                // Create session if needed
                if self.session == nil {
                    self.session = ARSession()
                }
                
                // Configure AR session
                let configuration = ARWorldTrackingConfiguration()
                configuration.planeDetection = [.horizontal, .vertical]
                configuration.environmentTexturing = .automatic
                
                if #available(iOS 12.0, *) {
                    configuration.maximumNumberOfTrackedImages = 4
                }
                
                // Start AR session
                self.session?.run(configuration)
                self.configuration = configuration
                
                // Send event
                self.sendEvent(name: "onSessionStarted", body: [:])
                
                resolve(nil)
            } catch {
                reject("E_AR_ERROR", "Failed to start AR session: \(error.localizedDescription)", error)
            }
        }
    }
    
    @objc(pauseSession:withRejecter:)
    func pauseSession(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            self.session?.pause()
            self.sendEvent(name: "onSessionPaused", body: [:])
            resolve(nil)
        }
    }
    
    @objc(resetSession:withRejecter:)
    func resetSession(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            // Remove all anchors
            self.objects.values.forEach { entry in
                if let anchor = entry.anchor {
                    self.session?.remove(anchor: anchor)
                }
            }
            self.objects.removeAll()
            
            // Reset session with current configuration
            if let configuration = self.configuration {
                self.session?.run(configuration, options: [.resetTracking, .removeExistingAnchors])
            }
            
            resolve(nil)
        }
    }
    
    @objc(isSupported:withRejecter:)
    func isSupported(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        resolve(ARWorldTrackingConfiguration.isSupported)
    }
    
    @objc(addObject:withResolver:withRejecter:)
    func addObject(model: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        let objectId = UUID().uuidString
        
        // Extract position, rotation, scale from the model
        var position = SIMD3<Float>(0, 0, 0)
        if let positionDict = model["position"] as? NSDictionary {
            position = SIMD3<Float>(
                Float(positionDict["x"] as? Double ?? 0),
                Float(positionDict["y"] as? Double ?? 0),
                Float(positionDict["z"] as? Double ?? 0)
            )
        }
        
        var rotation = SIMD3<Float>(0, 0, 0)
        if let rotationDict = model["rotation"] as? NSDictionary {
            rotation = SIMD3<Float>(
                Float(rotationDict["x"] as? Double ?? 0),
                Float(rotationDict["y"] as? Double ?? 0),
                Float(rotationDict["z"] as? Double ?? 0)
            )
        }
        
        let scale = Float(model["scale"] as? Double ?? 1.0)
        
        // Create object entry
        let objectEntry = ObjectEntry(id: objectId, position: position, rotation: rotation, scale: scale, anchor: nil)
        objects[objectId] = objectEntry
        
        resolve(objectId)
    }
    
    @objc(removeObject:withResolver:withRejecter:)
    func removeObject(id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            if let entry = self.objects[id], let anchor = entry.anchor {
                self.session?.remove(anchor: anchor)
                self.objects.removeValue(forKey: id)
                resolve(nil)
            } else {
                reject("E_OBJECT_NOT_FOUND", "Object with ID \(id) not found", nil)
            }
        }
    }
    
    @objc(moveObject:position:withResolver:withRejecter:)
    func moveObject(id: String, position: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            if var entry = self.objects[id] {
                // Update position
                entry.position = SIMD3<Float>(
                    Float(position["x"] as? Double ?? 0),
                    Float(position["y"] as? Double ?? 0),
                    Float(position["z"] as? Double ?? 0)
                )
                
                // Update anchor if needed
                self.updateObjectAnchor(objectEntry: &entry)
                self.objects[id] = entry
                resolve(nil)
            } else {
                reject("E_OBJECT_NOT_FOUND", "Object with ID \(id) not found", nil)
            }
        }
    }
    
    @objc(rotateObject:rotation:withResolver:withRejecter:)
    func rotateObject(id: String, rotation: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            if var entry = self.objects[id] {
                // Update rotation
                entry.rotation = SIMD3<Float>(
                    Float(rotation["x"] as? Double ?? 0),
                    Float(rotation["y"] as? Double ?? 0),
                    Float(rotation["z"] as? Double ?? 0)
                )
                
                self.objects[id] = entry
                resolve(nil)
            } else {
                reject("E_OBJECT_NOT_FOUND", "Object with ID \(id) not found", nil)
            }
        }
    }
    
    @objc(scaleObject:scale:withResolver:withRejecter:)
    func scaleObject(id: String, scale: Double, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            if var entry = self.objects[id] {
                // Update scale
                entry.scale = Float(scale)
                self.objects[id] = entry
                resolve(nil)
            } else {
                reject("E_OBJECT_NOT_FOUND", "Object with ID \(id) not found", nil)
            }
        }
    }
    
    // MARK: - Private Methods
    
    private func updateObjectAnchor(objectEntry: inout ObjectEntry) {
        // Remove existing anchor if present
        if let anchor = objectEntry.anchor {
            session?.remove(anchor: anchor)
        }
        
        // Create transform from position and rotation
        var transform = matrix_identity_float4x4
        transform.columns.3 = SIMD4<Float>(objectEntry.position.x, objectEntry.position.y, objectEntry.position.z, 1)
        
        // Add rotation if needed
        // (In a full implementation, you would apply a proper rotation matrix)
        
        // Create new anchor
        let anchor = ARAnchor(transform: transform)
        session?.add(anchor: anchor)
        objectEntry.anchor = anchor
    }
    
    private func sendEvent(name: String, body: [String: Any]) {
        RCTSharedApplication().eventDispatcher?.sendAppEvent(withName: name, body: body)
    }
}
