import Foundation
import UIKit
import DGis

/// Per-map bridge: one instance per map view. Holds map state, camera, and markers for that map.
/// Supports multiple independent maps in the app.
@objcMembers
public final class DGisMapViewBridge: NSObject {

    /// The UIKit view that hosts the map. Add this to your view hierarchy.
    public private(set) var view: UIView!

    private let sdk: DGis.Container
    private let factory: DGis.IMapFactory
    private let map: DGis.Map
    private let mapObjectManager: DGis.MapObjectManager
    private var markerData: [String: MarkerEntry] = [:]
    private var markerIdCounter = 0
    private let lock = NSLock()

    private struct MarkerEntry {
        let marker: DGis.Marker
    }

    @MainActor
    init(sdk: DGis.Container, factory: DGis.IMapFactory, map: DGis.Map, mapObjectManager: DGis.MapObjectManager) {
        self.sdk = sdk
        self.factory = factory
        self.map = map
        self.mapObjectManager = mapObjectManager
        super.init()
        self.view = factory.mapUIView
    }

    // MARK: - Markers

    @MainActor
    public func addMarker(lat: Double, lng: Double, icon: UIImage?, anchorX: Float, anchorY: Float, opacity: Float, width: Float) -> DGisMarkerViewBridge? {
        let position = DGis.GeoPointWithElevation(
            latitude: DGis.Latitude(value: lat),
            longitude: DGis.Longitude(value: lng)
        )
        var options = DGis.MarkerOptions(
            position: position,
            icon: nil,
            anchor: DGis.Anchor(x: anchorX, y: anchorY),
            iconOpacity: DGis.Opacity(value: opacity),
            iconWidth: DGis.LogicalPixel(value: width)
        )
        if let icon = icon, let dgisImage = try? sdk.imageFactory.make(image: icon) {
            options.icon = dgisImage
        }
        do {
            let marker = try DGis.Marker(options: options)
            mapObjectManager.addObject(item: marker)
            lock.lock()
            markerIdCounter += 1
            let markerId = "m\(markerIdCounter)"
            markerData[markerId] = MarkerEntry(marker: marker)
            lock.unlock()
            let bridge = DGisMarkerViewBridge(
                markerId: markerId,
                marker: marker,
                sdk: sdk,
                onRemove: { [weak self] in
                    self?.removeMarker(markerId: markerId)
                }
            )
            return bridge
        } catch {
            NSLog("DGisMapViewBridge: create marker failed: \(error)")
            return nil
        }
    }

    public func removeMarker(markerId: String) {
        lock.lock()
        guard let entry = markerData.removeValue(forKey: markerId) else {
            lock.unlock()
            return
        }
        let marker = entry.marker
        lock.unlock()
        DispatchQueue.main.async {
            self.mapObjectManager.removeObject(item: marker)
        }
    }

    private func markerId(for mapObject: DGis.MapObject) -> String? {
        guard let marker = mapObject as? DGis.Marker else { return nil }
        lock.lock()
        defer { lock.unlock() }

        for (id, entry) in markerData {
          if (entry.marker.position == marker.position) {
            return id
          }
        }
        return nil
    }

    // MARK: - Camera

    @MainActor
    public func setCenter(lat: Double, lng: Double, zoom: Double, animated: Bool) {
        let position = DGis.CameraPosition(
            point: DGis.GeoPoint(latitude: DGis.Latitude(value: lat), longitude: DGis.Longitude(value: lng)),
            zoom: DGis.Zoom(value: Float(zoom))
        )
        let duration = animated ? 0.3 : 0
        _ = map.camera.move(position: position, time: duration, animationType: .linear)
    }

    @MainActor
    public func setZoom(zoom: Double, durationMs: Double) {
        let current = map.camera.position
        let newPosition = DGis.CameraPosition(
            point: current.point,
            zoom: DGis.Zoom(value: Float(zoom)),
            tilt: current.tilt,
            bearing: current.bearing
        )
        _ = map.camera.move(position: newPosition, time: durationMs / 1000.0, animationType: .linear)
    }

    @MainActor
    public func zoomIn() {
        let current = map.camera.position
        let newZoom = min(current.zoom.value + 1, Float(20))
        let newPosition = DGis.CameraPosition(
            point: current.point,
            zoom: DGis.Zoom(value: newZoom),
            tilt: current.tilt,
            bearing: current.bearing
        )
        _ = map.camera.move(position: newPosition, time: 0.3, animationType: .linear)
    }

    @MainActor
    public func zoomOut() {
        let current = map.camera.position
        let newZoom = max(current.zoom.value - 1, Float(1))
        let newPosition = DGis.CameraPosition(
            point: current.point,
            zoom: DGis.Zoom(value: newZoom),
            tilt: current.tilt,
            bearing: current.bearing
        )
        _ = map.camera.move(position: newPosition, time: 0.3, animationType: .linear)
    }

    @MainActor
    public func fitAllMarkers(positions: [[NSNumber]], durationMs: Double) {
        guard !positions.isEmpty else { return }
        let geometries: [DGis.Geometry] = positions.compactMap { arr in
            guard arr.count >= 2 else { return nil }
            let point = DGis.GeoPoint(
                latitude: DGis.Latitude(value: arr[0].doubleValue),
                longitude: DGis.Longitude(value: arr[1].doubleValue)
            )
            return DGis.PointGeometry(point: point)
        }
        guard !geometries.isEmpty else { return }
        let complex = DGis.ComplexGeometry(geometries: geometries)
        let padding = DGis.Padding(left: 50, top: 100, right: 50, bottom: 100)
        map.camera.padding = padding
        let newPosition = DGis.calcPosition(camera: map.camera, geometry: complex, screenArea: padding)
        _ = map.camera.move(position: newPosition, time: durationMs / 1000.0, animationType: .linear)
    }

    // MARK: - Callbacks

    @MainActor
    public func addObjectTappedCallback(callback: @escaping (String?, Double, Double) -> Void) {
        let uiView = factory.mapUIView
        let wrapped = DGis.MapObjectTappedCallback { [weak self] info in
            let lat = info.closestMapPoint.latitude.value
            let lng = info.closestMapPoint.longitude.value
            let markerId = self?.markerId(for: info.item.item)
            callback(markerId, lat, lng)
        }
        uiView.addObjectTappedCallback(callback: wrapped)
    }

    @MainActor
    public func addObjectLongPressCallback(callback: @escaping (Double, Double) -> Void) {
        let uiView = factory.mapUIView
        let wrapped = DGis.MapObjectTappedCallback { info in
            let lat = info.closestMapPoint.latitude.value
            let lng = info.closestMapPoint.longitude.value
            callback(lat, lng)
        }
        uiView.addObjectLongPressCallback(callback: wrapped)
    }
}
