import MapKit
import SwiftUI

/// The nearby-scooter map  the home screen's centrepiece in the design (`img_6`).
///
/// **MapKit, not the Google Maps SDK.** MapKit is first-party, needs no API key and no
/// extra dependency, so unlike Android this renders for every build with nothing to
/// configure. If the two platforms ever need pixel-identical cartography, this is the
/// file to swap  the rest of the screen doesn't care.
///
/// The nearby **list** below it shows the same scooters on purpose: it's the accessible
/// equivalent of this map, the same chart-plus-table rule the design system applies to
/// data views.
struct ScooterMap: View {
    let scooters: [NearbyScooter]
    let parking: [ParkingSpot]
    let center: CLLocationCoordinate2D
    let selectedId: String?
    let onSelect: (NearbyScooter) -> Void

    @State private var region: MKCoordinateRegion

    init(
        scooters: [NearbyScooter],
        parking: [ParkingSpot] = [],
        center: CLLocationCoordinate2D,
        selectedId: String?,
        onSelect: @escaping (NearbyScooter) -> Void
    ) {
        self.scooters = scooters
        self.parking = parking
        self.center = center
        self.selectedId = selectedId
        self.onSelect = onSelect
        _region = State(
            initialValue: MKCoordinateRegion(
                center: center,
                span: MKCoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03)
            )
        )
    }

    /// Move to a new centre, keeping whatever zoom the rider has chosen.
    private func recentre() {
        withAnimation {
            region = MKCoordinateRegion(center: center, span: region.span)
        }
    }

    /// `annotationItems` takes a single collection, so the two kinds of pin share one type.
    /// A parking spot with no polygon (and therefore no centre) is skipped rather than pinned
    /// at (0,0), which is in the Gulf of Guinea.
    private var items: [MapItem] {
        parking.compactMap { spot in
            spot.centre.map { MapItem.parking(spot, CLLocationCoordinate2D(latitude: $0.lat, longitude: $0.lng)) }
        } + scooters.map { .scooter($0) }
    }

    var body: some View {
        // iOS 16 target, so this is the `annotationItems` Map rather than the iOS 17
        // MapContentBuilder API. Worth revisiting when the deployment target moves.
        //
        // NOTE: this API renders annotations only, never overlays, so the parking bay is a pin
        // here while Android draws the actual polygon. Filling the shape needs an MKMapView
        // wrapped in UIViewRepresentable (MKPolygon + MKPolygonRenderer); left for the Mac,
        // where it can be compiled and seen.
        // `showsUserLocation` is the counterpart of Android's my-location layer: the rider needs
        // to see themselves among the pins to judge which scooter is actually close. MapKit draws
        // no recenter button of its own here, so unlike Android there is no duplicate to suppress.
        Map(
            coordinateRegion: $region,
            showsUserLocation: true,
            annotationItems: items
        ) { item in
            MapAnnotation(coordinate: item.coordinate) {
                switch item {
                case let .scooter(scooter):
                    ScooterPin(
                        battery: scooter.batteryPercent,
                        selected: scooter.id == selectedId
                    )
                    .onTapGesture { onSelect(scooter) }
                case let .parking(spot, _):
                    ParkingPin(spot: spot)
                }
            }
        }
        // The region is seeded once in init, so without this the map would stay on the fallback
        // centre after the first real fix arrives and never show the rider where they are.
        .onChange(of: center.latitude) { _ in recentre() }
        .onChange(of: center.longitude) { _ in recentre() }
        .clipShape(RoundedRectangle(cornerRadius: BeebRadius.card))
        .overlay(
            RoundedRectangle(cornerRadius: BeebRadius.card)
                .strokeBorder(Color.line, lineWidth: 1)
        )
    }
}

/// One pin, either kind. Parking is listed before scooters in `items` so a vehicle pin is never
/// hidden behind a parking pin when the two overlap.
private enum MapItem: Identifiable {
    case scooter(NearbyScooter)
    case parking(ParkingSpot, CLLocationCoordinate2D)

    // Prefixed so a scooter and a spot can never collide on id.
    var id: String {
        switch self {
        case let .scooter(scooter): return "scooter-\(scooter.id)"
        case let .parking(spot, _): return "parking-\(spot.id)"
        }
    }

    var coordinate: CLLocationCoordinate2D {
        switch self {
        case let .scooter(scooter):
            return CLLocationCoordinate2D(latitude: scooter.lat, longitude: scooter.lng)
        case let .parking(_, coordinate):
            return coordinate
        }
    }
}

/// A parking spot: a rounded square carrying a "P".
///
/// Square rather than a disc on purpose. The scooter pins are circles, and on a busy map the only
/// thing that separates "a vehicle I can take" from "a place I may leave one" at a glance is
/// silhouette; relying on colour alone would fail for a colour-blind rider and in bright sun.
/// A closed spot is drawn muted rather than hidden, so it does not look like it has vanished.
private struct ParkingPin: View {
    let spot: ParkingSpot

    /// Price first when there is one: whether parking costs money changes the rider's choice far
    /// more than how many slots it has.
    private var detail: String {
        if !spot.openNow { return "parking_closed".localized }
        if !spot.free {
            return String(format: "parking_fee".localized, formatParkingFee(spot.parkingFee))
        }
        return "parking_free".localized + " · "
            + String(format: "parking_capacity".localized, spot.totalCapacity)
    }

    var body: some View {
        Text(verbatim: "P")
            .font(.system(size: 15, weight: .heavy))
            .foregroundStyle(Color.brandInk)
            .frame(width: 28, height: 28)
            .background(
                spot.openNow ? Color.info : Color.muted,
                in: RoundedRectangle(cornerRadius: 8)
            )
            .opacity(spot.openNow ? 1 : 0.6)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .strokeBorder(Color.brandInk.opacity(0.25), lineWidth: 1)
            )
            .accessibilityLabel(
                spot.displayName ?? "parking_unnamed".localized
            )
            .accessibilityValue(detail)
    }
}

/// Brand-yellow when selected, neutral otherwise, so the current choice is obvious.
/// Low battery carries the warning tone  it's the thing that changes the decision.
private struct ScooterPin: View {
    let battery: Int
    let selected: Bool

    private var tone: Color {
        if selected { return .brand }
        return battery < 20 ? .warning : .surfaceOverlay
    }

    var body: some View {
        Image(systemName: "scooter")
            .font(.system(size: 15, weight: .bold))
            .foregroundStyle(selected ? Color.brandInk : Color.appForeground)
            .frame(width: 34, height: 34)
            .background(tone, in: Circle())
            .overlay(Circle().strokeBorder(Color.brandInk.opacity(0.25), lineWidth: 1))
            .scaleEffect(selected ? 1.15 : 1)
            .animation(.easeOut(duration: 0.15), value: selected)
    }
}

/// The tap-a-pin card: what you need to decide before committing to a scooter.
struct SelectedScooterCard: View {
    let scooter: NearbyScooter
    let onScan: () -> Void

    var body: some View {
        VStack(spacing: 12) {
            HStack {
                IconChip(glyph: "⚡︎", tone: .brand)
                VStack(alignment: .leading, spacing: 4) {
                    Text(scooter.qrCode).fontWeight(.semibold)
                    StatusBadge(
                        text: "\(scooter.batteryPercent)%",
                        tone: scooter.batteryPercent < 20 ? .warning : .success
                    )
                }
                Spacer()
                Text(String(format: "%.0f m", scooter.distanceMeters))
                    .font(.beebBody)
                    .foregroundStyle(Color.muted)
                    .numeric()
            }
            // Secondary, not primary: the screen's one brand-yellow action is the scan
            // card above, and two filled-yellow buttons would break the design rule.
            SecondaryButton(title: "scan_with_camera".localized, action: onScan)
        }
        .padding(16)
        .beebCard()
    }
}
