import SwiftUI
import UIKit
import Photos

// MARK: - Data Types

/// Per-image metadata for the gallery viewer.
/// Android parity: `ViewableImage` in `ImageViewerScreen.kt`.
struct ViewableImageInfo: Equatable, Identifiable {
    let attachmentId: String
    let senderName: String
    let timestamp: Date
    let caption: String

    var id: String { attachmentId }
}

/// State for the image gallery viewer.
struct ImageViewerState: Equatable {
    let images: [ViewableImageInfo]
    let initialIndex: Int
    let sourceFrame: CGRect
}

// MARK: - ImageViewerPresenter

/// UIHostingController subclass that hides the status bar.
/// Uses `AnyView` instead of generic `Content` to avoid a Swift compiler crash
/// in the SIL inliner (SR-XXXXX / rdar://XXXXX).
private final class StatusBarHiddenHostingController: UIHostingController<AnyView> {
    override var prefersStatusBarHidden: Bool { true }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { .fade }
}

/// Presents the image gallery viewer using UIKit `overFullScreen` presentation
/// — no bottom-sheet slide-up. Appears instantly; the viewer itself fades in.
/// Telegram parity: the viewer appears as a fade overlay covering nav bar and tab bar.
@MainActor
final class ImageViewerPresenter {

    static let shared = ImageViewerPresenter()
    private weak var presentedController: UIViewController?

    func present(state: ImageViewerState) {
        guard presentedController == nil else { return }

        let viewer = ImageGalleryViewer(state: state, onDismiss: { [weak self] in
            self?.dismiss()
        })

        let hostingController = StatusBarHiddenHostingController(rootView: AnyView(viewer))
        hostingController.modalPresentationStyle = .overFullScreen
        hostingController.view.backgroundColor = .clear

        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let root = windowScene.keyWindow?.rootViewController
        else { return }

        var presenter = root
        while let presented = presenter.presentedViewController {
            presenter = presented
        }
        presenter.present(hostingController, animated: false)
        presentedController = hostingController
    }

    func dismiss() {
        presentedController?.dismiss(animated: false)
        presentedController = nil
    }
}

// MARK: - ImageGalleryViewer

/// Multi-photo gallery viewer with hero transition animation.
/// Adapted 1:1 from PhotosTransition/Helpers/PhotoGridView.swift — DetailPhotosView.
/// Hero positioning is per-page INSIDE ForEach (not on TabView).
struct ImageGalleryViewer: View {

    let state: ImageViewerState
    let onDismiss: () -> Void

    @State private var currentPage: Int
    @State private var showControls = true
    @State private var currentZoomScale: CGFloat = 1.0
    @State private var isDismissing = false
    @State private var isExpanded: Bool = false
    @State private var dragOffset: CGSize = .zero
    @State private var viewSize: CGSize = UIScreen.main.bounds.size

    private static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "d MMMM, HH:mm"
        return formatter
    }()

    init(state: ImageViewerState, onDismiss: @escaping () -> Void) {
        self.state = state
        self.onDismiss = onDismiss
        self._currentPage = State(initialValue: state.initialIndex)
    }

    private var currentInfo: ViewableImageInfo? {
        state.images.indices.contains(currentPage) ? state.images[currentPage] : nil
    }

    private var heroAnimation: Animation {
        .interpolatingSpring(duration: 0.3, bounce: 0, initialVelocity: 0)
    }

    private var interactiveOpacity: CGFloat {
        let opacityY = abs(dragOffset.height) / (viewSize.height * 0.3)
        return isExpanded ? max(1 - opacityY, 0) : 0
    }

    var body: some View {
        let sourceFrame = state.sourceFrame

        // Hero positioning per-page inside ForEach — matches reference exactly
        TabView(selection: $currentPage) {
            ForEach(Array(state.images.enumerated()), id: \.element.attachmentId) { index, info in
                ZoomableImagePage(
                    attachmentId: info.attachmentId,
                    onDismiss: { dismissAction() },
                    showControls: $showControls,
                    currentScale: $currentZoomScale,
                    onEdgeTap: { direction in navigateEdgeTap(direction: direction) }
                )
                .frame(
                    width: isExpanded ? viewSize.width : sourceFrame.width,
                    height: isExpanded ? viewSize.height : sourceFrame.height
                )
                .clipped()
                .offset(
                    x: isExpanded ? 0 : sourceFrame.minX,
                    y: isExpanded ? 0 : sourceFrame.minY
                )
                .offset(dragOffset)
                .frame(
                    maxWidth: .infinity,
                    maxHeight: .infinity,
                    alignment: isExpanded ? .center : .topLeading
                )
                .tag(index)
                .ignoresSafeArea()
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
        .ignoresSafeArea()
        .scrollDisabled(currentZoomScale > 1.05 || isDismissing)
        .contentShape(Rectangle())
        .overlay {
            // Pan gesture overlay — UIKit gesture for iOS 17+ compat
            HeroPanGestureOverlay { gesture in
                handlePanGesture(gesture)
            }
            .allowsHitTesting(isExpanded && currentZoomScale <= 1.05)
        }
        .overlay {
            overlayActions
        }
        .background {
            Color.black
                .opacity(interactiveOpacity)
                .opacity(isExpanded ? 1 : 0)
                .ignoresSafeArea()
        }
        .allowsHitTesting(isExpanded)
        .statusBarHidden(true)
        .task {
            prefetchAdjacentImages(around: state.initialIndex)
            guard !isExpanded else { return }
            withAnimation(heroAnimation) {
                isExpanded = true
            }
        }
        .onChange(of: currentPage) { _, newPage in
            prefetchAdjacentImages(around: newPage)
        }
    }

    // MARK: - Pan Gesture

    private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
        let panState = gesture.state
        let translation = gesture.translation(in: gesture.view)

        if panState == .began || panState == .changed {
            dragOffset = .init(width: translation.x, height: translation.y)
        } else {
            if dragOffset.height > 50 {
                heroDismiss()
            } else {
                withAnimation(heroAnimation.speed(1.2)) {
                    dragOffset = .zero
                }
            }
        }
    }

    // MARK: - Overlay Actions (matches PhotosTransition/ContentView.swift OverlayActionView)

    @ViewBuilder
    private var overlayActions: some View {
        let overlayOpacity: CGFloat = 1 - min(abs(dragOffset.height / 30), 1)

        if showControls && !isDismissing && isExpanded {
            VStack {
                // Top actions
                HStack {
                    glassButton(systemName: "chevron.left") { dismissAction() }

                    Spacer(minLength: 0)

                    if state.images.count > 1 {
                        glassLabel("\(currentPage + 1) / \(state.images.count)")
                    }
                }
                .overlay {
                    if let info = currentInfo {
                        glassLabel(info.senderName)
                            .contentTransition(.numericText())
                            .animation(.easeInOut, value: currentPage)
                            .frame(maxWidth: .infinity)
                    }
                }

                Spacer(minLength: 0)

                // Bottom actions
                HStack {
                    glassButton(systemName: "square.and.arrow.up.fill") { shareCurrentImage() }

                    Spacer(minLength: 0)

                    glassButton(systemName: "square.and.arrow.down") { saveCurrentImage() }
                }
            }
            .padding(.horizontal, 15)
            .compositingGroup()
            .opacity(overlayOpacity)
            .environment(\.colorScheme, .dark)
            .transition(.opacity)
            .animation(.easeOut(duration: 0.2), value: showControls)
        }
    }

    // MARK: - Glass Button / Label helpers

    private func glassButton(systemName: String, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Image(systemName: systemName)
                .font(.title3)
                .foregroundStyle(.white)
                .frame(width: 36, height: 36)
        }
        .background { TelegramGlassCircle() }
    }

    private func glassLabel(_ text: String) -> some View {
        Text(text)
            .font(.callout)
            .foregroundStyle(.white)
            .lineLimit(1)
            .padding(.horizontal, 15)
            .padding(.vertical, 10)
            .background { TelegramGlassCapsule() }
    }

    // MARK: - Navigation

    private func navigateEdgeTap(direction: Int) {
        let target = currentPage + direction
        guard target >= 0, target < state.images.count else { return }
        currentPage = target
    }

    // MARK: - Dismiss

    private func dismissAction() {
        if currentZoomScale > 1.05 {
            fadeDismiss()
        } else {
            heroDismiss()
        }
    }

    private func heroDismiss() {
        guard !isDismissing else { return }
        isDismissing = true

        Task {
            withAnimation(heroAnimation.speed(1.2)) {
                dragOffset = .zero
                isExpanded = false
            }
            try? await Task.sleep(for: .seconds(0.35))
            onDismiss()
        }
    }

    private func fadeDismiss() {
        guard !isDismissing else { return }
        isDismissing = true

        withAnimation(.easeOut(duration: 0.2)) {
            isExpanded = false
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.22) {
            onDismiss()
        }
    }

    // MARK: - Actions

    private func shareCurrentImage() {
        guard let info = currentInfo,
              let image = AttachmentCache.shared.loadImage(forAttachmentId: info.attachmentId)
        else { return }

        let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let root = windowScene.keyWindow?.rootViewController {
            var presenter = root
            while let presented = presenter.presentedViewController {
                presenter = presented
            }
            activityVC.popoverPresentationController?.sourceView = presenter.view
            activityVC.popoverPresentationController?.sourceRect = CGRect(
                x: presenter.view.bounds.midX, y: presenter.view.bounds.maxY - 50,
                width: 0, height: 0
            )
            presenter.present(activityVC, animated: true)
        }
    }

    private func saveCurrentImage() {
        guard let info = currentInfo,
              let image = AttachmentCache.shared.loadImage(forAttachmentId: info.attachmentId)
        else { return }

        PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
            guard status == .authorized || status == .limited else { return }
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
    }

    // MARK: - Prefetch

    private func prefetchAdjacentImages(around index: Int) {
        for offset in [-2, -1, 1, 2] {
            let i = index + offset
            guard i >= 0, i < state.images.count else { continue }
            let attachmentId = state.images[i].attachmentId
            guard AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) == nil else { continue }
            Task.detached(priority: .utility) {
                await ImageLoadLimiter.shared.acquire()
                _ = AttachmentCache.shared.loadImage(forAttachmentId: attachmentId)
                await ImageLoadLimiter.shared.release()
            }
        }
    }
}

// MARK: - HeroPanGestureOverlay

/// Transparent UIView overlay with UIPanGestureRecognizer for vertical hero dismiss.
/// Uses UIKit gesture (not UIGestureRecognizerRepresentable) for iOS 17+ compat.
/// Matches PanGesture from PhotosTransition reference — vertical only, single touch.
private struct HeroPanGestureOverlay: UIViewRepresentable {
    var onPan: (UIPanGestureRecognizer) -> Void

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear

        let pan = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePan(_:)))
        pan.minimumNumberOfTouches = 1
        pan.maximumNumberOfTouches = 1
        pan.delegate = context.coordinator
        view.addGestureRecognizer(pan)

        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        context.coordinator.onPan = onPan
    }

    func makeCoordinator() -> Coordinator { Coordinator(onPan: onPan) }

    final class Coordinator: NSObject, UIGestureRecognizerDelegate {
        var onPan: (UIPanGestureRecognizer) -> Void

        init(onPan: @escaping (UIPanGestureRecognizer) -> Void) {
            self.onPan = onPan
        }

        @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
            onPan(gesture)
        }

        // Only begin for downward vertical drags
        func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
            guard let pan = gestureRecognizer as? UIPanGestureRecognizer else { return false }
            let velocity = pan.velocity(in: pan.view)
            return velocity.y > abs(velocity.x)
        }

        // Let TabView scroll pass through when scrolled down
        func gestureRecognizer(
            _ gestureRecognizer: UIGestureRecognizer,
            shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
        ) -> Bool {
            if let scrollView = otherGestureRecognizer.view as? UIScrollView {
                return scrollView.contentOffset.y <= 0
            }
            return false
        }

        // Allow simultaneous recognition with other gestures (taps, pinch, etc.)
        func gestureRecognizer(
            _ gestureRecognizer: UIGestureRecognizer,
            shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
        ) -> Bool {
            return !(otherGestureRecognizer is UIPanGestureRecognizer)
        }
    }
}

