import SwiftUI
import Charts

// MARK: - Time-series chart zoom
//
// Shared zoom behavior for every chart with a Date X axis. Default state is
// "fit all" (no behavior change); zooming in narrows the visible X domain and
// enables horizontal scrolling so the user can pan through history. Zoom is
// driven by pinch (magnification) and by explicit +/- buttons for
// discoverability and accessibility (HIG: never make gestures the only way).

/// View-side zoom state for a time-series chart. `visibleSpan == nil` means fit-all.
struct ChartZoomModel {
    /// Currently visible X-domain length in seconds; nil = show everything.
    var visibleSpan: TimeInterval?
    /// Span captured when a pinch gesture begins.
    var pinchAnchor: TimeInterval?

    init() {
        visibleSpan = nil
        pinchAnchor = nil
    }

    static let minSpan: TimeInterval = 60 * 60 * 24 * 45      // ~1.5 months
    private static let zoomStep = 0.6                         // per button tap

    func clamped(_ span: TimeInterval, fullSpan: TimeInterval) -> TimeInterval? {
        let upper = max(fullSpan, Self.minSpan)
        let clamped = min(max(span, Self.minSpan), upper)
        // Snap back to fit-all when zoomed (almost) fully out.
        return clamped >= upper * 0.98 ? nil : clamped
    }

    mutating func zoomIn(fullSpan: TimeInterval) {
        let current = visibleSpan ?? fullSpan
        visibleSpan = clamped(current * Self.zoomStep, fullSpan: fullSpan)
    }

    mutating func zoomOut(fullSpan: TimeInterval) {
        guard let current = visibleSpan else { return }
        visibleSpan = clamped(current / Self.zoomStep, fullSpan: fullSpan)
    }

    mutating func reset() {
        visibleSpan = nil
    }
}

extension View {
    /// Applies zoomable behavior to a Chart with a Date X axis.
    /// - Parameters:
    ///   - dates: the full set of X values (used to compute the total span)
    ///   - zoom: binding to the chart's zoom state
    @ViewBuilder
    func zoomableTimeSeries(dates: [Date], zoom: Binding<ChartZoomModel>) -> some View {
        let fullSpan = ChartZoomHelper.fullSpan(of: dates)
        self
            .modifier(ZoomDomainModifier(zoom: zoom, fullSpan: fullSpan))
            .simultaneousGesture(
                MagnifyGesture()
                    .onChanged { value in
                        var model = zoom.wrappedValue
                        if model.pinchAnchor == nil {
                            model.pinchAnchor = model.visibleSpan ?? fullSpan
                        }
                        if let anchor = model.pinchAnchor, value.magnification > 0 {
                            model.visibleSpan = model.clamped(anchor / value.magnification, fullSpan: fullSpan)
                        }
                        zoom.wrappedValue = model
                    }
                    .onEnded { _ in
                        zoom.wrappedValue.pinchAnchor = nil
                    }
            )
            .overlay(alignment: .topTrailing) {
                ChartZoomControls(zoom: zoom, fullSpan: fullSpan)
            }
    }
}

enum ChartZoomHelper {
    static func fullSpan(of dates: [Date]) -> TimeInterval {
        guard let min = dates.min(), let max = dates.max(), max > min else {
            return ChartZoomModel.minSpan
        }
        return max.timeIntervalSince(min)
    }
}

private struct ZoomDomainModifier: ViewModifier {
    @Binding var zoom: ChartZoomModel
    let fullSpan: TimeInterval

    func body(content: Content) -> some View {
        if let span = zoom.visibleSpan {
            content
                .chartScrollableAxes(.horizontal)
                .chartXVisibleDomain(length: span)
        } else {
            content
        }
    }
}

/// Compact +/- (and reset when zoomed) control cluster shown on top of the chart.
struct ChartZoomControls: View {
    @Binding var zoom: ChartZoomModel
    let fullSpan: TimeInterval

    var body: some View {
        HStack(spacing: 0) {
            controlButton(systemName: "minus.magnifyingglass", enabled: zoom.visibleSpan != nil) {
                withAnimation(.easeOut(duration: 0.15)) { zoom.zoomOut(fullSpan: fullSpan) }
            }
            .accessibilityLabel(String(localized: "chart_zoom_out"))

            Divider().frame(height: 16)

            controlButton(systemName: "plus.magnifyingglass",
                          enabled: (zoom.visibleSpan ?? fullSpan) > ChartZoomModel.minSpan) {
                withAnimation(.easeOut(duration: 0.15)) { zoom.zoomIn(fullSpan: fullSpan) }
            }
            .accessibilityLabel(String(localized: "chart_zoom_in"))

            if zoom.visibleSpan != nil {
                Divider().frame(height: 16)
                controlButton(systemName: "arrow.counterclockwise", enabled: true) {
                    withAnimation(.easeOut(duration: 0.15)) { zoom.reset() }
                }
                .accessibilityLabel(String(localized: "chart_zoom_reset"))
            }
        }
        .background(.ultraThinMaterial, in: Capsule())
        .overlay(Capsule().stroke(Color.gray.opacity(0.2), lineWidth: 0.5))
        .padding(6)
    }

    private func controlButton(systemName: String, enabled: Bool, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Image(systemName: systemName)
                .font(.system(size: 13, weight: .medium))
                .foregroundColor(enabled ? .appPrimary : .secondary.opacity(0.4))
                .frame(width: 32, height: 28)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
        .disabled(!enabled)
    }
}
