import Foundation

enum GeoJSONAreaUtil {

    private static let sqMetersToSqFeet = 10.7639

    /// Compute square footage from GeoJSON-style coordinates ([Any]).
    /// Handles Polygon `[[[lng,lat],...]]` and MultiPolygon `[[[[lng,lat],...]]]`.
    static func squareFeet(from geojsonCoordinates: [Any]) -> Double? {
        if let rings = extractPolygonRings(geojsonCoordinates) {
            let m2 = polygonArea(rings: rings)
            return m2 > 0 ? m2 * sqMetersToSqFeet : nil
        }
        if let polygons = extractMultiPolygonRings(geojsonCoordinates) {
            let m2 = polygons.reduce(0.0) { $0 + polygonArea(rings: $1) }
            return m2 > 0 ? m2 * sqMetersToSqFeet : nil
        }
        return nil
    }

    // MARK: - Coordinate extraction

    /// Polygon: `[[[lng, lat], ...], ...]` (array of linear rings)
    private static func extractPolygonRings(_ coords: [Any]) -> [[[Double]]]? {
        guard let rings = coords as? [[[NSNumber]]] else { return nil }
        return rings.map { ring in ring.map { pt in pt.map { $0.doubleValue } } }
    }

    /// MultiPolygon: `[[[[lng, lat], ...], ...], ...]`
    private static func extractMultiPolygonRings(_ coords: [Any]) -> [[[[Double]]]]? {
        guard let polys = coords as? [[[[NSNumber]]]] else { return nil }
        return polys.map { rings in
            rings.map { ring in ring.map { pt in pt.map { $0.doubleValue } } }
        }
    }

    // MARK: - Area calculation

    /// Area in square meters for a polygon defined by one or more linear rings.
    /// First ring is the outer boundary; subsequent rings are holes (subtracted).
    private static func polygonArea(rings: [[[Double]]]) -> Double {
        guard let outer = rings.first, outer.count >= 3 else { return 0 }
        var area = ringArea(outer)
        for hole in rings.dropFirst() {
            area -= ringArea(hole)
        }
        return abs(area)
    }

    /// Signed area of a single ring using equirectangular projection + Shoelace.
    private static func ringArea(_ ring: [[Double]]) -> Double {
        guard ring.count >= 3 else { return 0 }
        let avgLat = ring.reduce(0.0) { $0 + $1[1] } / Double(ring.count)
        let latRad = avgLat * .pi / 180.0
        let mPerDegLat = 111_320.0
        let mPerDegLng = 111_320.0 * cos(latRad)

        var sum = 0.0
        for i in 0..<ring.count {
            let j = (i + 1) % ring.count
            let xi = ring[i][0] * mPerDegLng
            let yi = ring[i][1] * mPerDegLat
            let xj = ring[j][0] * mPerDegLng
            let yj = ring[j][1] * mPerDegLat
            sum += xi * yj - xj * yi
        }
        return abs(sum) / 2.0
    }
}
