import AppKit
import Combine
import CoreLocation
import SwiftUI
import WebKit

// MARK: - Public SwiftUI view

/// Desktop widget embedding Google Maps in a `WKWebView`, with search, traffic, and navigation shortcuts.
struct MapsInteractiveWidgetView: View {
    let app: LauncherApp
    let widgetSize: WidgetSize
    var isPreview: Bool = false

    @State private var searchText = ""
    @State private var mapURL: URL = MapsInteractiveWidgetView.defaultMapURL
    @State private var trafficLayerOn = false
    @StateObject private var locationHelper = MapsWidgetLocationHelper()
    @FocusState private var searchFieldFocused: Bool

    private static var defaultMapURL: URL {
        MapsInteractiveWidgetView.makeMapURL(center: nil, zoom: nil, layer: nil)
    }

    /// Builds `https://www.google.com/maps/@` with official Maps URL query parameters.
    private static func makeMapURL(center: String?, zoom: Int?, layer: String?) -> URL {
        var c = URLComponents(string: "https://www.google.com/maps/@")!
        var items: [URLQueryItem] = [
            URLQueryItem(name: "api", value: "1"),
            URLQueryItem(name: "map_action", value: "map"),
        ]
        if let center { items.append(URLQueryItem(name: "center", value: center)) }
        if let zoom { items.append(URLQueryItem(name: "zoom", value: "\(zoom)")) }
        if let layer { items.append(URLQueryItem(name: "layer", value: layer)) }
        c.queryItems = items
        return c.url ?? URL(string: "https://www.google.com/maps")!
    }

    var body: some View {
        Group {
            if isPreview {
                previewChrome
            } else {
                interactiveChrome
            }
        }
        .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
    }

    private var previewChrome: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 22, style: .continuous)
                .fill(
                    LinearGradient(
                        colors: [Color(red: 0.12, green: 0.18, blue: 0.22), Color(red: 0.1, green: 0.35, blue: 0.32)],
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
            VStack(spacing: 12) {
                AppIconView(app: app, size: 48, showAppBackground: false, iconPaddingFactor: 0.08)
                Text("Live Maps")
                    .font(.system(size: 15, weight: .bold))
                    .foregroundStyle(.white.opacity(0.92))
                Text("Interactive map loads on your desktop.")
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(.white.opacity(0.65))
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 8)
            }
            .padding(16)
        }
    }

    private var interactiveChrome: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 22, style: .continuous)
                .fill(Color.black.opacity(0.35))

            VStack(spacing: 0) {
                VStack(spacing: 8) {
                    headerBar
                    searchBar
                    primaryToolbar
                    quickPlacesBar
                }
                .padding(.horizontal, 4)
                .padding(.vertical, 6)
                .background(
                    RoundedRectangle(cornerRadius: 16, style: .continuous)
                        .fill(Color(red: 0.07, green: 0.08, blue: 0.1))
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 16, style: .continuous)
                        .stroke(Color.white.opacity(0.1), lineWidth: 1)
                )
                .zIndex(20)
                .layoutPriority(1)
                .allowsHitTesting(true)

                MapsWebWidgetView(url: $mapURL)
                    .frame(height: mapContentHeight)
                    .frame(maxWidth: .infinity)
                    .clipped()
                    .compositingGroup()
                    .zIndex(0)
                    .layoutPriority(0)
                    .allowsHitTesting(true)
            }
            .padding(10)
        }
    }

    private var headerBar: some View {
        HStack(spacing: 8) {
            AppIconView(app: app, size: 28, showAppBackground: false, iconPaddingFactor: 0.08)
            Text("Google Maps")
                .font(.system(size: 14, weight: .bold))
                .foregroundStyle(.white.opacity(0.95))
                .lineLimit(1)
            Spacer(minLength: 0)
        }
        .padding(.bottom, 6)
    }

    private var searchBar: some View {
        HStack(spacing: 8) {
            Image(systemName: "magnifyingglass")
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(.white.opacity(0.75))
            TextField("Search places", text: $searchText)
                .textFieldStyle(.plain)
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(.white.opacity(0.96))
                .focused($searchFieldFocused)
                .onSubmit { submitSearch() }
                .onChange(of: searchFieldFocused) { focused in
                    if focused {
                        NSApp.activate(ignoringOtherApps: true)
                    }
                }
            MapSearchGoButton(action: {
                NSApp.activate(ignoringOtherApps: true)
                submitSearch()
            })
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 10)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(Color.white.opacity(0.1))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .stroke(Color.white.opacity(0.12), lineWidth: 1)
        )
        .padding(.bottom, 8)
    }

    private var primaryToolbar: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack(spacing: 8) {
                MapToolbarButton(
                    title: trafficLayerOn ? "Traffic on" : "Traffic",
                    systemImage: "car.side.fill",
                    isSelected: trafficLayerOn,
                    helpText: "Show or hide live traffic on the map"
                ) {
                    NSApp.activate(ignoringOtherApps: true)
                    trafficLayerOn.toggle()
                    applyTrafficLayer()
                }
                MapToolbarButton(
                    title: "Directions",
                    systemImage: "arrow.triangle.turn.up.right.diamond.fill",
                    helpText: "Open driving directions (set start and destination in Maps)"
                ) {
                    NSApp.activate(ignoringOtherApps: true)
                    loadURLString("https://www.google.com/maps/dir/?api=1&travelmode=driving")
                }
                MapToolbarButton(
                    title: "My location",
                    systemImage: "location.fill",
                    helpText: "Center the map on your current location"
                ) {
                    NSApp.activate(ignoringOtherApps: true)
                    locationHelper.requestLocation { coord in
                        let center = "\(coord.latitude),\(coord.longitude)"
                        let layer: String? = trafficLayerOn ? "traffic" : nil
                        let url = MapsInteractiveWidgetView.makeMapURL(center: center, zoom: 15, layer: layer)
                        Task { @MainActor in
                            mapURL = url
                        }
                    }
                }
            }
            HStack(spacing: 8) {
                MapToolbarButton(
                    title: "Satellite",
                    systemImage: "globe.americas.fill",
                    helpText: "Switch to satellite imagery"
                ) {
                    NSApp.activate(ignoringOtherApps: true)
                    loadURLString("https://www.google.com/maps/@?api=1&map_action=map&basemap=satellite")
                }
                MapToolbarButton(
                    title: "Open",
                    systemImage: "arrow.up.right.square",
                    isEmphasized: true,
                    helpText: "Open this map in the full browser window"
                ) {
                    NSApp.activate(ignoringOtherApps: true)
                    InAppBrowserWindowManager.shared.open(url: mapURL, title: app.name)
                }
                Spacer(minLength: 0)
            }
            .animation(.easeInOut(duration: 0.18), value: trafficLayerOn)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(.bottom, 8)
    }

    private var quickPlacesBar: some View {
        HStack(alignment: .center, spacing: 8) {
            MapQuickPlaceChip(title: "Food", icon: "fork.knife", helpText: "Search for restaurants nearby") {
                activateAndSearch(.restaurants)
            }
            MapQuickPlaceChip(title: "Gas", icon: "fuelpump.fill", helpText: "Find gas stations") {
                activateAndSearch(.gasStations)
            }
            MapQuickPlaceChip(title: "Hotels", icon: "bed.double.fill", helpText: "Search for hotels") {
                activateAndSearch(.hotels)
            }
            MapQuickPlaceChip(title: "Coffee", icon: "cup.and.saucer.fill", helpText: "Find coffee shops") {
                activateAndSearch(.coffee)
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(.bottom, 2)
    }

    private var mapContentHeight: CGFloat {
        switch widgetSize {
        case .small: return 220
        case .medium: return 200
        case .large: return 340
        }
    }

    private func submitSearch() {
        let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !q.isEmpty else { return }
        openSearchQueryString(q)
    }

    /// Search terms for the four quick chips (Maps “near you” search).
    private enum QuickPlaceSearch: String {
        case restaurants
        case gasStations = "gas stations"
        case hotels
        case coffee
    }

    private func activateAndSearch(_ place: QuickPlaceSearch) {
        NSApp.activate(ignoringOtherApps: true)
        openSearchQueryString(place.rawValue)
    }

    private func openSearchQueryString(_ query: String) {
        guard let url = Self.mapsSearchURL(for: query) else { return }
        DispatchQueue.main.async {
            mapURL = url
        }
    }

    /// Official search URL: `https://www.google.com/maps/search/?api=1&query=…`
    private static func mapsSearchURL(for query: String) -> URL? {
        let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return nil }
        var c = URLComponents(string: "https://www.google.com/maps/search/")!
        c.queryItems = [
            URLQueryItem(name: "api", value: "1"),
            URLQueryItem(name: "query", value: trimmed),
        ]
        return c.url
    }

    private func loadURLString(_ string: String) {
        if let u = URL(string: string) { mapURL = u }
    }

    private func applyTrafficLayer() {
        let layer: String? = trafficLayerOn ? "traffic" : nil
        mapURL = MapsInteractiveWidgetView.makeMapURL(center: nil, zoom: nil, layer: layer)
    }
}

// MARK: - Interactive controls (hover, press, tooltips)

private struct MapPressableButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.94 : 1)
            .opacity(configuration.isPressed ? 0.88 : 1)
            .animation(.easeOut(duration: 0.12), value: configuration.isPressed)
    }
}

private struct MapSearchGoButton: View {
    let action: () -> Void
    @State private var isHovering = false

    var body: some View {
        Button(action: action) {
            Text("Search")
                .font(.system(size: 12, weight: .bold))
                .foregroundStyle(Color(red: 0.06, green: 0.08, blue: 0.1))
                .padding(.horizontal, 14)
                .padding(.vertical, 8)
                .background(
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .fill(isHovering ? Color.green.opacity(0.92) : Color.white.opacity(0.9))
                )
                .overlay(
                    RoundedRectangle(cornerRadius: 8, style: .continuous)
                        .stroke(Color.white.opacity(0.4), lineWidth: 1)
                )
                .contentShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
        }
        .buttonStyle(MapPressableButtonStyle())
        .onHover { isHovering = $0 }
        .help("Search for this place (or press Return)")
    }
}

private struct MapToolbarButton: View {
    let title: String
    let systemImage: String
    var isSelected: Bool = false
    var isEmphasized: Bool = false
    let helpText: String
    let action: () -> Void

    @State private var isHovering = false

    var body: some View {
        Button(action: action) {
            HStack(spacing: 6) {
                Image(systemName: systemImage)
                    .font(.system(size: 12, weight: .semibold))
                    .symbolRenderingMode(.hierarchical)
                Text(title)
                    .font(.system(size: 11, weight: .semibold))
                    .lineLimit(1)
            }
            .foregroundStyle(foregroundColor)
            .padding(.horizontal, 12)
            .padding(.vertical, 8)
            .frame(minHeight: 32)
            .background(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(backgroundColor)
            )
            .overlay(
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .stroke(borderColor, lineWidth: isSelected ? 1.5 : 1)
            )
            .shadow(color: shadowColor, radius: isHovering ? 5 : 2, x: 0, y: isHovering ? 3 : 1)
            .contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
        }
        .buttonStyle(MapPressableButtonStyle())
        .onHover { isHovering = $0 }
        .help(helpText)
    }

    private var foregroundColor: Color {
        if isSelected || isEmphasized { return .white }
        return Color.white.opacity(0.94)
    }

    private var backgroundColor: Color {
        if isSelected { return Color.green.opacity(0.48) }
        if isEmphasized { return Color.blue.opacity(0.45) }
        if isHovering { return Color.white.opacity(0.24) }
        return Color.white.opacity(0.12)
    }

    private var borderColor: Color {
        if isSelected { return Color.green.opacity(0.7) }
        if isEmphasized { return Color.blue.opacity(0.6) }
        return Color.white.opacity(isHovering ? 0.32 : 0.16)
    }

    private var shadowColor: Color {
        Color.black.opacity(isHovering ? 0.3 : 0.14)
    }
}

private struct MapQuickPlaceChip: View {
    let title: String
    let icon: String
    let helpText: String
    let action: () -> Void

    @State private var isHovering = false

    var body: some View {
        Button(action: action) {
            HStack(spacing: 5) {
                Image(systemName: icon)
                    .font(.system(size: 11, weight: .semibold))
                Text(title)
                    .font(.system(size: 11, weight: .semibold))
            }
            .foregroundStyle(.white.opacity(0.95))
            .padding(.horizontal, 12)
            .padding(.vertical, 9)
            .frame(minWidth: 72, minHeight: 34)
            .background(
                Capsule(style: .continuous)
                    .fill(isHovering ? Color.white.opacity(0.2) : Color.black.opacity(0.45))
            )
            .overlay(
                Capsule(style: .continuous)
                    .stroke(Color.white.opacity(isHovering ? 0.38 : 0.2), lineWidth: 1)
            )
            .shadow(color: Color.black.opacity(isHovering ? 0.25 : 0.1), radius: isHovering ? 4 : 2, x: 0, y: 1)
            .contentShape(Capsule())
        }
        // Plain avoids the default NSButton bezel on macOS, which can shrink the hit target.
        .buttonStyle(.plain)
        .onHover { isHovering = $0 }
        .help(helpText)
    }
}

// MARK: - Location (recenter map)

private final class MapsWidgetLocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    private var onFix: ((CLLocationCoordinate2D) -> Void)?

    func requestLocation(_ handler: @escaping (CLLocationCoordinate2D) -> Void) {
        onFix = handler
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        switch manager.authorizationStatus {
        case .notDetermined:
            manager.requestWhenInUseAuthorization()
        case .authorized, .authorizedAlways, .authorizedWhenInUse:
            manager.requestLocation()
        default:
            break
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let c = locations.last?.coordinate else { return }
        onFix?(c)
        onFix = nil
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {}

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .authorized, .authorizedAlways, .authorizedWhenInUse:
            manager.requestLocation()
        default:
            break
        }
    }
}

// MARK: - WKWebView (clipped container so the map cannot overlap or steal hits from controls above)

private final class MapsWebViewClippingContainer: NSView {
    override var isFlipped: Bool { true }
}

private struct MapsWebWidgetView: NSViewRepresentable {
    @Binding var url: URL

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeNSView(context: Context) -> NSView {
        let container = MapsWebViewClippingContainer()
        container.wantsLayer = true
        container.layer?.masksToBounds = true
        container.layer?.cornerRadius = 12
        container.layer?.borderWidth = 0

        let config = WKWebViewConfiguration()
        config.defaultWebpagePreferences.allowsContentJavaScript = true
        let wv = WKWebView(frame: .zero, configuration: config)
        wv.translatesAutoresizingMaskIntoConstraints = false
        wv.customUserAgent =
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
        wv.navigationDelegate = context.coordinator
        wv.uiDelegate = context.coordinator

        container.addSubview(wv)
        NSLayoutConstraint.activate([
            wv.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            wv.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            wv.topAnchor.constraint(equalTo: container.topAnchor),
            wv.bottomAnchor.constraint(equalTo: container.bottomAnchor),
        ])

        context.coordinator.webView = wv
        context.coordinator.container = container
        context.coordinator.lastLoadedURLString = url.absoluteString
        wv.load(URLRequest(url: url))
        return container
    }

    func updateNSView(_ container: NSView, context: Context) {
        guard let wv = context.coordinator.webView else { return }
        let next = url.absoluteString
        guard context.coordinator.lastLoadedURLString != next else { return }
        context.coordinator.lastLoadedURLString = next
        wv.load(URLRequest(url: url))
    }

    final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
        var parent: MapsWebWidgetView
        weak var webView: WKWebView?
        weak var container: NSView?
        /// Compared as strings so equivalent URLs (e.g. query reorder) still trigger reload when the binding changes.
        var lastLoadedURLString: String?

        init(_ parent: MapsWebWidgetView) {
            self.parent = parent
        }

        func webView(
            _ webView: WKWebView,
            requestGeolocationPermissionFor origin: WKSecurityOrigin,
            initiatedByFrame frame: WKFrameInfo,
            decisionHandler: @escaping (WKPermissionDecision) -> Void
        ) {
            // Keep geolocation scoped to the explicit "My location" control flow.
            // Avoid silently granting all webpage geolocation requests in the embedded web view.
            decisionHandler(.deny)
        }
    }
}
