// generated by bun run generate from the SwiftUI SDK and codegen/catalog.ts.
// edit the generator or catalog, then regenerate.
import SwiftUI
import UIKit
import MapKit

private final class MapModel: ObservableObject {
  @Published var latitude: Double = 37.7749
  @Published var longitude: Double = -122.4194
  @Published var distance: Double = 5000
  @Published var markers: [OneNativeMapMarker] = []
  @Published var accessibility = OneNativeAccessibility()
  @Published var swiftStyle = OneNativeStyle()
  var active = false
  var onSDKEvent: ((String, String) -> Void)?
  func emitSDKEvent(_ name: String, _ value: String) { if active { onSDKEvent?(name, value) } }

  var onRegionChange: ((Double, Double, Double, Int) -> Void)?
  private var regionChangeCount = 0
  func regionChange(_ latitude: Double, _ longitude: Double, _ distance: Double) {
    guard active else { return }
    regionChangeCount += 1
    onRegionChange?(latitude, longitude, distance, regionChangeCount)
  }
}
@objcMembers public final class OneNativeMapView: UIView, OneNativeComposable, OneNativeCompositionParent {
  public var onSDKEvent: ((String, String) -> Void)?

  public var onRegionChange: ((Double, Double, Double, Int) -> Void)?
  private var model = MapModel()
  private let mapControls = OneNativeChildren()
  private var childViews: [UIView] = []
  public var compositionActive: Bool { model.active }
  private var controller: OneNativeHostingController<OneNativeStandalone<MapContent>>?
  public override init(frame: CGRect) { super.init(frame: frame) }
  required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") }
  public func configureAccessibility(_ label: String, hint: String, value: String, identifier: String) {
    let next = OneNativeAccessibility(label: label, hint: hint, value: value, identifier: identifier)
    if model.accessibility != next { model.accessibility = next }
  }
  public func configureStyle(_ style: [String: Any]) {
    let next = OneNativeStyle(dictionary: style)
    if model.swiftStyle != next {
      let previousRows = model.swiftStyle.sdkModifiers.filter { $0.first?.hasPrefix("listRow") == true }
      let nextRows = next.sdkModifiers.filter { $0.first?.hasPrefix("listRow") == true }
      model.swiftStyle = next
      if previousRows != nextRows { compositionParent?.refreshRow(for: self) }
    }
  }
  public func configure(_ latitude: Double, longitude: Double, distance: Double) {
    if model.latitude != latitude { model.latitude = latitude }
    if model.longitude != longitude { model.longitude = longitude }
    if model.distance != distance { model.distance = distance }
  }
  public func setMarkers(_ items: [[String: Any]]) { model.markers = items.map { OneNativeMapMarker(id: $0["id"] as! String, label: $0["label"] as! String, latitude: $0["latitude"] as! Double, longitude: $0["longitude"] as! Double) } }

  public func insertChild(_ child: UIView, at index: Int) {
    precondition(child is OneNativeMapCompassView || child is OneNativeMapPitchToggleView || child is OneNativeMapUserLocationButtonView, "One.iOS.Map accepts MapCompass, MapPitchToggle, MapUserLocationButton children")
    guard let composable = child as? OneNativeComposable else {
      preconditionFailure("One Native child must support composition")
    }
    let at = min(index, childViews.count)
    childViews.insert(child, at: at)
    composable.composeInto(self)
    mapControls.items.insert(
      OneNativeComposedChild(id: String(describing: ObjectIdentifier(child)),
        sourceID: ObjectIdentifier(child), content: composable.compositionContent()), at: at)
  }
  public func removeChild(_ child: UIView) {
    guard let index = childViews.firstIndex(where: { $0 === child }) else { return }
    childViews.remove(at: index)
    (child as? OneNativeComposable)?.decompose()
    mapControls.items.removeAll { $0.sourceID == ObjectIdentifier(child) }
  }
  private weak var compositionParent: OneNativeCompositionParent?
  public func compositionContent() -> AnyView { AnyView(MapContent(model: model, mapControls: mapControls)) }
  // composed, activation follows the parent: a subtree mounted before root attachment
  // stays silent until the root attaches.
  public func composeInto(_ parent: OneNativeCompositionParent) {
    controller?.detach(); controller = nil
    compositionParent = parent
    bindCallbacks()
    propagateActive(parent.compositionActive)
  }
  public func decompose() { compositionParent = nil; propagateActive(false) }
  public func propagateActive(_ active: Bool) {
    model.active = active
    for child in childViews { (child as? OneNativeComposable)?.propagateActive(active) }
  }
  public override func didMoveToWindow() { super.didMoveToWindow(); updateHost() }
  public override func layoutSubviews() { super.layoutSubviews(); updateHost() }
  private func bindCallbacks() {
    model.onSDKEvent = { [weak self] name, value in self?.onSDKEvent?(name, value) }

    model.onRegionChange = { [weak self] latitude, longitude, distance, regionChangeCount in self?.onRegionChange?(latitude, longitude, distance, regionChangeCount) }
  }
  private func updateHost() {
    guard compositionParent == nil else { return }
    propagateActive(false)
    guard window != nil else { controller?.detach(); return }
    if controller == nil {
      bindCallbacks()
      controller = OneNativeHostingController(rootView: OneNativeStandalone(content: MapContent(model: model, mapControls: mapControls)))
    }
    controller?.attach(to: self)
    propagateActive(controller?.isAttached == true)
  }
  public func reset() {
    compositionParent = nil
    for child in childViews { (child as? OneNativeComposable)?.decompose() }
    childViews.removeAll()
    mapControls.items = []
    model.active = false; model.onSDKEvent = nil; model.onRegionChange = nil
    controller?.detach(); controller = nil; model = MapModel()
  }
}
private struct MapContent: View {
  @ObservedObject var model: MapModel
  @ObservedObject var mapControls: OneNativeChildren
  var body: some View {
    MapSurface(
        latitude: model.latitude,
        longitude: model.longitude,
        distance: model.distance,
        markers: model.markers,
        mapControls: mapControls,
        onRegionChange: { latitude, longitude, distance in
          model.regionChange(latitude, longitude, distance)
        }
      )
      .oneNativeAccessibility(model.accessibility)
      .oneNativeStyle(model.swiftStyle, emit: model.emitSDKEvent)
  }
}
// the camera is SwiftUI state so a pan is not fought by the next render, and it is
// re-seeded only when the props describing it actually move. driving it straight from the
// props would snap the map back to the prop value every time anything else changed.
private struct MapSurface: View {
  let latitude: Double
  let longitude: Double
  let distance: Double
  let markers: [OneNativeMapMarker]
  // the map's own controls, composed from the Fabric children so they operate on the
  // owning map. an empty slot renders no controls, which is the map without them.
  @ObservedObject var mapControls: OneNativeChildren
  let onRegionChange: (Double, Double, Double) -> Void
  @State private var position: MapCameraPosition = .automatic
  @State private var seeded: String?
  var body: some View {
    Map(position: $position) {
      ForEach(markers, id: \.id) { marker in
        Marker(
          coordinate: CLLocationCoordinate2D(
            latitude: marker.latitude,
            longitude: marker.longitude
          )
        ) {
          Text(marker.label)
        }
      }
    }
    .mapControls {
      ForEach(mapControls.items) { child in child.content }
    }
    .onMapCameraChange(frequency: .onEnd) { context in
      onRegionChange(
        context.camera.centerCoordinate.latitude,
        context.camera.centerCoordinate.longitude,
        context.camera.distance
      )
    }
    .onAppear { seed() }
    .onChange(of: camera) { seed() }
  }
  private var camera: String { "\(latitude),\(longitude),\(distance)" }
  private func seed() {
    guard seeded != camera else { return }
    seeded = camera
    position = .camera(
      MapCamera(
        centerCoordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude),
        distance: distance
      )
    )
  }
}
