import Foundation
import AppKit
import MapKit

struct MapImageCreator {

    let locations: [CLLocation]

    func createMapSnapshot(
        size layoutSize: CGSize,
        scale: CGFloat = 2.0,
        lineWidth: CGFloat = 5,
        paddingFactor: Double = 1.2,
        lineColor: NSColor = .systemBlue
    ) -> (image: NSImage, imagePoints: [CGPoint])? {
        let semaphore = DispatchSemaphore(value: 0)

        var result: (image: NSImage, imagePoints: [CGPoint])?

        self.createMapSnapshot(
            size: layoutSize,
            scale: scale,
            lineWidth: lineWidth,
            paddingFactor: paddingFactor,
            lineColor: lineColor
        ) { res in
            result = res
            semaphore.signal()
        }

        semaphore.wait()
        return result
    }

    func createMapSnapshot(
        size layoutSize: CGSize,
        scale: CGFloat = 2.0,
        lineWidth: CGFloat = 5,
        paddingFactor: Double = 1.2,
        lineColor: NSColor = .systemBlue
    ) async -> (image: NSImage, imagePoints: [CGPoint])? {
        await withCheckedContinuation { c in
            self.createMapSnapshot(
                size: layoutSize,
                scale: scale,
                lineWidth: lineWidth,
                paddingFactor: paddingFactor,
                lineColor: lineColor
            ) { c.resume(returning: $0) }
        }
    }

    func createMapSnapshot(
        size layoutSize: CGSize,
        scale: CGFloat = 2.0,
        lineWidth: CGFloat = 5,
        paddingFactor: Double = 1.2,
        lineColor: NSColor = .systemBlue,
        completion: @escaping ((image: NSImage, imagePoints: [CGPoint])?) -> Void
    ) {
        guard !locations.isEmpty else {
            completion(nil)
            return
        }
        let coordinates = locations.map { $0.coordinate }

        let pixelSize = CGSize(width: layoutSize.width * scale, height: layoutSize.height * scale)

        let options = MKMapSnapshotter.Options()
        options.size = pixelSize
        options.preferredConfiguration = MKHybridMapConfiguration(elevationStyle: .flat)

        let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
        let boundingMapRect = polyline.boundingMapRect
        let region = MKCoordinateRegion(boundingMapRect)

        let latDelta = region.span.latitudeDelta * paddingFactor
        let lonDelta = region.span.longitudeDelta * paddingFactor
        let paddedRegion = MKCoordinateRegion(
            center: region.center,
            span: MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
        )

        options.region = paddedRegion

        let snapshotter = MKMapSnapshotter(options: options)
        snapshotter.start { snapshotOrNil, error in
            guard let snapshot = snapshotOrNil, error == nil else {
                print("Snapshot error: \(error?.localizedDescription ?? "unknown error")")
                completion(nil)
                return
            }

            let image = NSImage(size: pixelSize)
            image.lockFocus()

            snapshot.image.draw(in: CGRect(origin: .zero, size: pixelSize))

            let path = NSBezierPath()
            path.lineJoinStyle = .round
            let imagePoints = coordinates.map { snapshot.point(for: $0) }

            if let first = imagePoints.first {
                path.move(to: first)
                for point in imagePoints.dropFirst() {
                    path.line(to: point)
                }

                lineColor.setStroke()
                path.lineWidth = lineWidth * scale
                path.stroke()
            }

            image.unlockFocus()

            // Recalculate imagePoints since they were inside the drawing block
            let widthFactor = 1 / pixelSize.width
            let heightFactor = 1 / pixelSize.height
            let finalImagePoints = coordinates.map { coordinate in
                let point = snapshot.point(for: coordinate)
                return CGPoint(x: point.x * widthFactor,
                               y: point.y * heightFactor)
            }

            completion((image, finalImagePoints))
        }
    }
}
