//
//  Requests.swift
//  Navvo Maps SDK for iOS
//
//  Typed request objects and wire enums for the services client.
//

import Foundation

// MARK: - Enums

/// Costing model for a route / matrix / isochrone request.
public enum RouteCosting: String, CaseIterable, Sendable {
    /// Car / driving.
    case auto
    /// Walking.
    case pedestrian
    /// Bicycle.
    case bicycle
    /// Motor scooter / moped.
    case motorScooter = "motor_scooter"
    /// Heavy goods vehicle.
    case truck
    /// Wheelchair-accessible pedestrian.
    case wheelchair
}

/// Route optimisation preference.
public enum RoutePreference: String, CaseIterable, Sendable {
    /// Minimise travel time.
    case fastest
    /// Minimise distance.
    case shortest
    /// Balance time and distance.
    case balanced
}

/// A road/route feature to avoid when planning.
public enum RouteAvoid: String, CaseIterable, Sendable {
    /// Toll roads.
    case tolls
    /// Motorways / highways.
    case highways
    /// Ferries.
    case ferries
    /// Unpaved roads.
    case unpaved
    /// Border crossings.
    case borders
}

/// Units for a simple route.
public enum RouteUnits: String, CaseIterable, Sendable {
    /// Kilometres.
    case kilometers
    /// Miles.
    case miles
}

/// Sort order for a `GET /places` listing.
public enum PlaceSort: String, CaseIterable, Sendable {
    /// Most relevant.
    case relevance
    /// Highest rated.
    case rating
    /// Nearest first (requires `near`).
    case distance
    /// Most popular.
    case popularity
}

/// Shape-match mode for `POST /routing/map-match`.
public enum ShapeMatch: String, CaseIterable, Sendable {
    /// Snap the trace onto the road network.
    case mapSnap = "map_snap"
    /// Walk/edge match.
    case walkOrSnap = "walk_or_snap"
    /// Edge walk only.
    case edgeWalk = "edge_walk"
}

// MARK: - Routing requests

/// One stop in a ``RoutePlanRequest``.
public struct PlanStop: Sendable {
    /// The stop location.
    public var point: LatLng

    /// An optional label carried through to the plan.
    public var name: String?

    /// Create a stop.
    public init(_ point: LatLng, name: String? = nil) {
        self.point = point
        self.name = name
    }

    /// The stop as a (possibly named) point.
    var located: LatLng {
        guard let name else { return point }
        return point.named(name)
    }
}

/// Truck/vehicle dimensions for `POST /routing/plan`.
public struct TruckOptions: Sendable {
    /// Height in metres.
    public var heightM: Double?
    /// Width in metres.
    public var widthM: Double?
    /// Length in metres.
    public var lengthM: Double?
    /// Weight in tonnes.
    public var weightT: Double?
    /// Whether the load is hazardous material.
    public var hazmat: Bool?

    /// Create truck options.
    public init(
        heightM: Double? = nil,
        widthM: Double? = nil,
        lengthM: Double? = nil,
        weightT: Double? = nil,
        hazmat: Bool? = nil
    ) {
        self.heightM = heightM
        self.widthM = widthM
        self.lengthM = lengthM
        self.weightT = weightT
        self.hazmat = hazmat
    }

    /// Serialise to the engine `vehicle` block.
    func json() -> NavvoJSON {
        var object: [String: NavvoJSON] = [:]
        if let heightM { object["height"] = .number(heightM) }
        if let widthM { object["width"] = .number(widthM) }
        if let lengthM { object["length"] = .number(lengthM) }
        if let weightT { object["weight"] = .number(weightT) }
        if let hazmat { object["hazmat"] = .bool(hazmat) }
        return .object(object)
    }
}

/// A `POST /routing/plan` request (the corridor-aware planner — auto-avoids
/// no-entry signs, active incidents and admin road closures/restrictions).
public struct RoutePlanRequest: Sendable {
    /// Ordered stops (first = origin, last = destination); at least two.
    public var locations: [PlanStop]

    /// The costing model.
    public var costing: RouteCosting?

    /// The optimisation preference.
    public var preference: RoutePreference?

    /// Features to avoid.
    public var avoid: Set<RouteAvoid>?

    /// Reorder intermediate stops for the shortest tour.
    public var optimize: Bool?

    /// Include AI labels (slower; keep off for interactive use).
    public var includeLabels: Bool?

    /// Route around active community incidents.
    public var avoidIncidents: Bool?

    /// Narrative language (e.g. `en-US`, `ar-JO`).
    public var language: String?

    /// Truck/vehicle dimensions.
    public var truck: TruckOptions?

    /// Free-text constraints for the AI planner.
    public var constraintsText: String?

    /// Departure time (ISO-8601), for time-dependent routing.
    public var departureTime: String?

    /// Create a plan request.
    public init(
        locations: [PlanStop],
        costing: RouteCosting? = nil,
        preference: RoutePreference? = nil,
        avoid: Set<RouteAvoid>? = nil,
        optimize: Bool? = nil,
        includeLabels: Bool? = nil,
        avoidIncidents: Bool? = nil,
        language: String? = nil,
        truck: TruckOptions? = nil,
        constraintsText: String? = nil,
        departureTime: String? = nil
    ) {
        self.locations = locations
        self.costing = costing
        self.preference = preference
        self.avoid = avoid
        self.optimize = optimize
        self.includeLabels = includeLabels
        self.avoidIncidents = avoidIncidents
        self.language = language
        self.truck = truck
        self.constraintsText = constraintsText
        self.departureTime = departureTime
    }
}

/// A `POST /routing/route` (or `POST /traffic/route`) request.
public struct RouteRequest: Sendable {
    /// Ordered locations; at least two.
    public var locations: [LatLng]

    /// The costing model.
    public var costing: RouteCosting?

    /// Output units.
    public var units: RouteUnits?

    /// Narrative language.
    public var language: String?

    /// Number of alternate routes to request.
    public var alternates: Int?

    /// Departure time (ISO-8601).
    public var departureTime: String?

    /// Create a route request.
    public init(
        locations: [LatLng],
        costing: RouteCosting? = nil,
        units: RouteUnits? = nil,
        language: String? = nil,
        alternates: Int? = nil,
        departureTime: String? = nil
    ) {
        self.locations = locations
        self.costing = costing
        self.units = units
        self.language = language
        self.alternates = alternates
        self.departureTime = departureTime
    }
}

/// A `POST /routing/matrix` request.
public struct MatrixRequest: Sendable {
    /// Source points.
    public var sources: [LatLng]

    /// Target points.
    public var targets: [LatLng]

    /// The costing model.
    public var costing: RouteCosting?

    /// Create a matrix request.
    public init(sources: [LatLng], targets: [LatLng], costing: RouteCosting? = nil) {
        self.sources = sources
        self.targets = targets
        self.costing = costing
    }
}

/// A single isochrone contour (time in minutes or distance in km).
public struct IsochroneContour: Sendable {
    /// Contour in minutes (time), or `nil` if a distance contour.
    public var minutes: Int?

    /// Contour in kilometres (distance), or `nil` if a time contour.
    public var km: Double?

    /// Optional colour (hex, no `#`) for the contour.
    public var color: String?

    private init(minutes: Int?, km: Double?, color: String?) {
        self.minutes = minutes
        self.km = km
        self.color = color
    }

    /// A time contour.
    public static func time(_ minutes: Int, color: String? = nil) -> IsochroneContour {
        IsochroneContour(minutes: minutes, km: nil, color: color)
    }

    /// A distance contour.
    public static func distance(_ km: Double, color: String? = nil) -> IsochroneContour {
        IsochroneContour(minutes: nil, km: km, color: color)
    }

    /// Serialise to the engine contour object.
    func json() -> NavvoJSON {
        var object: [String: NavvoJSON] = [:]
        if let minutes { object["time"] = .number(Double(minutes)) }
        if let km { object["distance"] = .number(km) }
        if let color { object["color"] = .string(color) }
        return .object(object)
    }
}

/// A `POST /routing/isochrone` request.
public struct IsochroneRequest: Sendable {
    /// The origin point.
    public var origin: LatLng

    /// The costing model.
    public var costing: RouteCosting?

    /// The contours to compute.
    public var contours: [IsochroneContour]

    /// Return filled polygons (vs lines).
    public var polygons: Bool?

    /// Create an isochrone request.
    public init(
        origin: LatLng,
        contours: [IsochroneContour],
        costing: RouteCosting? = nil,
        polygons: Bool? = nil
    ) {
        self.origin = origin
        self.contours = contours
        self.costing = costing
        self.polygons = polygons
    }

    /// Convenience: a set of time contours in minutes.
    public static func times(
        _ origin: LatLng,
        minutes: [Int],
        costing: RouteCosting? = nil,
        polygons: Bool = true
    ) -> IsochroneRequest {
        IsochroneRequest(
            origin: origin,
            contours: minutes.map { IsochroneContour.time($0) },
            costing: costing,
            polygons: polygons
        )
    }
}

/// One point of a GPS trace for map-matching.
public struct TracePoint: Sendable {
    /// The location.
    public var point: LatLng

    /// Optional epoch time (seconds).
    public var time: Int?

    /// Create a trace point.
    public init(_ point: LatLng, time: Int? = nil) {
        self.point = point
        self.time = time
    }
}

/// A `POST /routing/map-match` request.
public struct MapMatchRequest: Sendable {
    /// The raw GPS trace.
    public var trace: [TracePoint]

    /// The costing model.
    public var costing: RouteCosting?

    /// The shape-match mode.
    public var shapeMatch: ShapeMatch?

    /// Create a map-match request.
    public init(trace: [TracePoint], costing: RouteCosting? = nil, shapeMatch: ShapeMatch? = nil) {
        self.trace = trace
        self.costing = costing
        self.shapeMatch = shapeMatch
    }
}

/// A `POST /routing/optimize` (TSP) request.
public struct OptimizeRequest: Sendable {
    /// Ordered locations; first and last are the fixed start/end.
    public var locations: [LatLng]

    /// The costing model.
    public var costing: RouteCosting?

    /// Create an optimize request.
    public init(locations: [LatLng], costing: RouteCosting? = nil) {
        self.locations = locations
        self.costing = costing
    }
}

/// A `POST /places/reachable` request.
public struct ReachableRequest: Sendable {
    /// The origin.
    public var origin: LatLng

    /// Time budget in minutes.
    public var minutes: Int?

    /// The costing model (wire value, e.g. `pedestrian`).
    public var costing: String?

    /// Category filters.
    public var categories: [String]?

    /// Free-text query filter.
    public var query: String?

    /// Max results.
    public var limit: Int?

    /// Create a reachable request.
    public init(
        origin: LatLng,
        minutes: Int? = nil,
        costing: String? = nil,
        categories: [String]? = nil,
        query: String? = nil,
        limit: Int? = nil
    ) {
        self.origin = origin
        self.minutes = minutes
        self.costing = costing
        self.categories = categories
        self.query = query
        self.limit = limit
    }
}
