import Combine
import SwiftUI
import UIKit
import Photos

// MARK: - Data Types

struct ViewableImageInfo: Equatable, Identifiable {
    let attachmentId: String
    let senderName: String
    let timestamp: Date
    let caption: String
    var id: String { attachmentId }
}

struct ImageViewerState: Equatable {
    let images: [ViewableImageInfo]
    let initialIndex: Int
    let sourceFrame: CGRect
}

// MARK: - GalleryDismissPanCoordinator

/// Manages the vertical pan gesture for gallery dismiss.
/// Attached to the hosting controller's view (NOT as a SwiftUI overlay) so it
/// doesn't block SwiftUI gestures (pinch zoom, taps) on the content below.
final class GalleryDismissPanCoordinator: NSObject, ObservableObject, UIGestureRecognizerDelegate {
    @Published var dragOffset: CGSize = .zero
    @Published private(set) var panEndSignal: Bool = false
    private(set) var endVelocityY: CGFloat = 0
    var isEnabled: Bool = true

    @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
        guard isEnabled else {
            if gesture.state == .began { gesture.state = .cancelled }
            return
        }
        let t = gesture.translation(in: gesture.view)
        switch gesture.state {
        case .began, .changed:
            dragOffset = CGSize(width: 0, height: t.y)
        case .ended, .cancelled:
            endVelocityY = gesture.velocity(in: gesture.view).y
            panEndSignal.toggle()
        default: break
        }
    }

    func gestureRecognizerShouldBegin(_ g: UIGestureRecognizer) -> Bool {
        guard isEnabled, let pan = g as? UIPanGestureRecognizer else { return false }
        let v = pan.velocity(in: pan.view)
        return v.y > abs(v.x)
    }

    func gestureRecognizer(_ g: UIGestureRecognizer,
                           shouldRecognizeSimultaneouslyWith o: UIGestureRecognizer) -> Bool {
        !(o is UIPanGestureRecognizer)
    }
}

// MARK: - ImageViewerPresenter

private final class StatusBarHiddenHostingController: UIHostingController<AnyView> {
    override var prefersStatusBarHidden: Bool { true }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { .fade }
}

@MainActor
final class ImageViewerPresenter {
    static let shared = ImageViewerPresenter()
    private weak var presentedController: UIViewController?
    private var panCoordinator: GalleryDismissPanCoordinator?

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

        let coordinator = GalleryDismissPanCoordinator()
        panCoordinator = coordinator

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

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

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

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

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

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

// MARK: - Window Safe Area Helper

private var windowSafeArea: UIEdgeInsets {
    UIApplication.shared.connectedScenes
        .compactMap { $0 as? UIWindowScene }
        .first?.keyWindow?.safeAreaInsets ?? .zero
}

// MARK: - ImageGalleryViewer

struct ImageGalleryViewer: View {

    let state: ImageViewerState
    @ObservedObject var panCoordinator: GalleryDismissPanCoordinator
    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

    private let screenSize = UIScreen.main.bounds.size

    private static let dateFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .none
        f.timeStyle = .short
        return f
    }()

    private static let relativeDateFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .medium
        f.timeStyle = .none
        f.doesRelativeDateFormatting = true
        return f
    }()

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

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

    private var backgroundOpacity: CGFloat {
        let progress = min(abs(panCoordinator.dragOffset.height) / 80, 1)
        return isExpanded ? max(1 - progress, 0) : 0
    }

    private var overlayDragOpacity: CGFloat {
        1 - min(abs(panCoordinator.dragOffset.height) / 50, 1)
    }

    private func formattedDate(_ date: Date) -> String {
        let day = Self.relativeDateFormatter.string(from: date)
        let time = Self.dateFormatter.string(from: date)
        return "\(day) at \(time)"
    }

    // MARK: - Body

    var body: some View {
        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: { dir in navigateEdgeTap(direction: dir) }
                )
                .offset(panCoordinator.dragOffset)
                .tag(index)
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
        .ignoresSafeArea()
        .scrollDisabled(currentZoomScale > 1.05 || isDismissing)
        .contentShape(Rectangle())
        .overlay { galleryOverlay }
        .background {
            Color.black.opacity(backgroundOpacity).ignoresSafeArea()
        }
        .allowsHitTesting(isExpanded)
        .statusBarHidden(true)
        .task {
            prefetchAdjacentImages(around: state.initialIndex)
            guard !isExpanded else { return }
            withAnimation(.easeOut(duration: 0.2)) { isExpanded = true }
        }
        .onChange(of: currentPage) { _, p in prefetchAdjacentImages(around: p) }
        .onChange(of: currentZoomScale) { _, s in panCoordinator.isEnabled = s <= 1.05 }
        .onChange(of: panCoordinator.panEndSignal) { _, _ in handlePanEnd() }
    }

    // MARK: - Pan End

    private func handlePanEnd() {
        let y = panCoordinator.dragOffset.height
        let v = panCoordinator.endVelocityY
        if y > 50 || v > 1000 {
            dismissAction()
        } else {
            withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) {
                panCoordinator.dragOffset = .zero
            }
        }
    }

    // MARK: - Overlay (Telegram parity)

    @ViewBuilder
    private var galleryOverlay: some View {
        let sa = windowSafeArea

        if !isDismissing && isExpanded {
            ZStack {
                // Top panel
                VStack(spacing: 4) {
                    topPanel
                    if state.images.count > 1 {
                        counterBadge
                    }
                    Spacer()
                }
                .frame(maxWidth: .infinity)
                .padding(.top, sa.top > 0 ? sa.top : 20)
                .offset(y: showControls ? 0 : -(sa.top + 120))
                .allowsHitTesting(showControls)

                // Bottom panel
                VStack {
                    Spacer()
                    bottomPanel
                        .padding(.bottom, sa.bottom > 0 ? sa.bottom : 16)
                }
                .offset(y: showControls ? 0 : (sa.bottom + 120))
                .allowsHitTesting(showControls)
            }
            .compositingGroup()
            .opacity(overlayDragOpacity)
            .animation(.spring(response: 0.3, dampingFraction: 0.85), value: showControls)
            .environment(\.colorScheme, .dark)
        }
    }

    // MARK: - Top Panel (Telegram parity)

    private var topPanel: some View {
        HStack(alignment: .top) {
            glassCircleButton(systemName: "chevron.left") { dismissAction() }
            Spacer(minLength: 0)
            glassCircleButton(systemName: "ellipsis") { }
        }
        .overlay(alignment: .top) {
            if let info = currentInfo {
                VStack(spacing: 2) {
                    Text(info.senderName)
                        .font(.system(size: 17, weight: .semibold))
                        .foregroundStyle(.white)
                        .lineLimit(1)
                    Text(formattedDate(info.timestamp))
                        .font(.system(size: 12))
                        .foregroundStyle(.white.opacity(0.6))
                }
                .padding(.horizontal, 14)
                .padding(.vertical, 8)
                .background { TelegramGlassCapsule() }
                .contentTransition(.numericText())
                .animation(.easeInOut, value: currentPage)
            }
        }
        .padding(.horizontal, 16)
    }

    // MARK: - Counter

    private var counterBadge: some View {
        Text("\(currentPage + 1) of \(state.images.count)")
            .font(.system(size: 12, weight: .semibold))
            .foregroundStyle(.white)
            .padding(.horizontal, 12)
            .padding(.vertical, 4)
            .background { TelegramGlassCapsule() }
            .padding(.top, 6)
            .contentTransition(.numericText())
            .animation(.easeInOut, value: currentPage)
    }

    // MARK: - Bottom Panel (Telegram parity)
    // Telegram: Forward (left) — [draw | caption center] — Delete (right)
    // Rosetta: Forward — Share — Save — Delete

    private var bottomPanel: some View {
        HStack(spacing: 0) {
            // Forward
            glassCircleButton(systemName: "arrowshape.turn.up.right") { }

            Spacer()

            // Share
            glassCircleButton(systemName: "square.and.arrow.up") { shareCurrentImage() }

            Spacer()

            // Save to Photos
            glassCircleButton(systemName: "square.and.arrow.down") { saveCurrentImage() }

            Spacer()

            // Delete
            glassCircleButton(systemName: "trash") { }
        }
        .padding(.horizontal, 16)
    }

    // MARK: - Glass Button

    private func glassCircleButton(systemName: String, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Image(systemName: systemName)
                .font(.system(size: 22))
                .foregroundStyle(.white)
                .frame(width: 44, height: 44)
        }
        .background { TelegramGlassCircle() }
    }

    // MARK: - Navigation

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

    // MARK: - Dismiss

    private func dismissAction() {
        fadeDismiss()
    }

    private func fadeDismiss() {
        guard !isDismissing else { return }
        isDismissing = true
        panCoordinator.isEnabled = false
        withAnimation(.easeOut(duration: 0.25)) {
            panCoordinator.dragOffset = CGSize(width: 0, height: screenSize.height * 0.4)
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.27) { onDismiss() }
    }

    // MARK: - Actions

    private func shareCurrentImage() {
        guard let info = currentInfo,
              let image = AttachmentCache.shared.loadImage(forAttachmentId: info.attachmentId)
        else { return }
        let vc = UIActivityViewController(activityItems: [image], applicationActivities: nil)
        if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let root = scene.keyWindow?.rootViewController {
            var p = root; while let pp = p.presentedViewController { p = pp }
            vc.popoverPresentationController?.sourceView = p.view
            vc.popoverPresentationController?.sourceRect = CGRect(
                x: p.view.bounds.midX, y: p.view.bounds.maxY - 50, width: 0, height: 0)
            p.present(vc, 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 aid = state.images[i].attachmentId
            guard AttachmentCache.shared.cachedImage(forAttachmentId: aid) == nil else { continue }
            Task.detached(priority: .utility) {
                await ImageLoadLimiter.shared.acquire()
                _ = AttachmentCache.shared.loadImage(forAttachmentId: aid)
                await ImageLoadLimiter.shared.release()
            }
        }
    }
}

