import SwiftUI
import AVFoundation
import UIKit
import Combine

/// 指标速记 · 整幅单拍相机。
/// 全屏实时预览 + 一个快门 → 返回**整幅** upright UIImage(不裁剪)。
/// 拍完后由 `RegionAdjustView` 在静态图上手动框选识别区域。
/// 只在真机可用(模拟器无相机,`QuickRegionCaptureFlow` 退化到 PhotoPicker)。
struct SingleShotCameraView: View {
    let onCapture: (UIImage) -> Void
    let onCancel: () -> Void

    @StateObject private var controller = RegionCameraController()
    @State private var authState: AuthState = .checking
    @State private var isCapturing = false
    @State private var flash = false

    enum AuthState { case checking, authorized, denied }

    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()

            switch authState {
            case .checking:
                ProgressView().tint(.white)
            case .denied:
                deniedView
            case .authorized:
                RegionCameraPreview(controller: controller, cropsToBox: false)
                    .ignoresSafeArea()
                controlsOverlay
            }

            if flash {
                Color.white.ignoresSafeArea().transition(.opacity)
            }
        }
        .task { await resolveAuth() }
    }

    private var controlsOverlay: some View {
        VStack {
            HStack {
                Button {
                    onCancel()
                } label: {
                    Text("取消")
                        .font(.tjScaled( 16, weight: .medium))
                        .foregroundStyle(.white)
                        .padding(.horizontal, 18)
                        .frame(minHeight: 44)   // 苹果 HIG 最小命中区
                        .background(Capsule().fill(.black.opacity(0.35)))
                        .contentShape(Capsule())
                }
                .buttonStyle(.plain)
                Spacer()
            }
            .padding(.horizontal, 16)
            .padding(.top, 8)

            Spacer()

            Text("轻点画面对焦 · 拍完再框选")
                .font(.tjScaled( 13, weight: .medium))
                .foregroundStyle(.white)
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .background(Capsule().fill(.black.opacity(0.4)))
                .padding(.bottom, 14)

            shutterButton
                .padding(.bottom, 36)
        }
    }

    private var shutterButton: some View {
        Button {
            capture()
        } label: {
            ZStack {
                Circle().fill(.white).frame(width: 72, height: 72)
                Circle().strokeBorder(.white.opacity(0.6), lineWidth: 3).frame(width: 84, height: 84)
                if isCapturing {
                    ProgressView().tint(.black)
                }
            }
        }
        .disabled(isCapturing)
        .accessibilityLabel("拍摄照片")
    }

    private var deniedView: some View {
        VStack(spacing: 16) {
            Image(systemName: "camera.fill")
                .font(.tjScaled( 40))
                .foregroundStyle(.white.opacity(0.8))
            Text("相机权限未开启")
                .font(.tjH2())
                .foregroundStyle(.white)
            Text("指标速记需要相机。去「设置 → 康康 → 相机」打开后再回来。")
                .font(.tjScaled( 13))
                .foregroundStyle(.white.opacity(0.7))
                .multilineTextAlignment(.center)
                .padding(.horizontal, 36)
            HStack(spacing: 12) {
                Button("取消") { onCancel() }
                    .font(.tjScaled( 15))
                    .foregroundStyle(.white)
                    .padding(.horizontal, 18).padding(.vertical, 10)
                    .background(Capsule().strokeBorder(.white.opacity(0.5), lineWidth: 1))
                Button("去设置") {
                    if let url = URL(string: UIApplication.openSettingsURLString) {
                        UIApplication.shared.open(url)
                    }
                }
                .font(.tjScaled( 15, weight: .semibold))
                .foregroundStyle(.black)
                .padding(.horizontal, 18).padding(.vertical, 10)
                .background(Capsule().fill(.white))
            }
        }
    }

    private func capture() {
        guard !isCapturing else { return }
        isCapturing = true
        withAnimation(.easeOut(duration: 0.08)) { flash = true }
        controller.capture { image in
            withAnimation(.easeIn(duration: 0.15)) { flash = false }
            isCapturing = false
            guard let image else { return }
            onCapture(image)
        }
    }

    private func resolveAuth() async {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            authState = .authorized
        case .notDetermined:
            let granted = await AVCaptureDevice.requestAccess(for: .video)
            authState = granted ? .authorized : .denied
        default:
            authState = .denied
        }
    }
}

// MARK: - AVFoundation 桥接

/// SwiftUI 持有,作为快门触发的句柄(weak 指向真正的 UIView)。
final class RegionCameraController: ObservableObject {
    weak var view: RegionPreviewUIView?
    func capture(_ completion: @escaping (UIImage?) -> Void) {
        guard let view else { completion(nil); return }
        view.capture(completion: completion)
    }
}

struct RegionCameraPreview: UIViewRepresentable {
    let controller: RegionCameraController
    /// 是否在拍摄后裁到居中小框。整幅单拍传 false(返回整图)。
    var cropsToBox: Bool = false

    func makeUIView(context: Context) -> RegionPreviewUIView {
        let v = RegionPreviewUIView()
        v.cropsToBox = cropsToBox
        controller.view = v
        return v
    }

    func updateUIView(_ uiView: RegionPreviewUIView, context: Context) {}

    static func dismantleUIView(_ uiView: RegionPreviewUIView, coordinator: ()) {
        uiView.stop()
    }
}

/// 实时预览 + 单张拍摄。`cropsToBox` 为真时按居中小框裁剪,否则返回整幅 upright 图。
final class RegionPreviewUIView: UIView, AVCapturePhotoCaptureDelegate {
    var cropsToBox = false

    private let session = AVCaptureSession()
    private let output = AVCapturePhotoOutput()
    private var previewLayer: AVCaptureVideoPreviewLayer?
    private var setupDone = false
    private var captureCompletion: ((UIImage?) -> Void)?
    /// 持有当前输入设备,供点击对焦时重新 lockForConfiguration。
    private var device: AVCaptureDevice?
    /// 点击对焦时短暂显示的黄框;弱引用,移除后自动置空。
    private weak var focusIndicator: UIView?

    override func didMoveToWindow() {
        super.didMoveToWindow()
        guard !setupDone, window != nil else { return }
        setupDone = true
        configure()
    }

    private func configure() {
        session.beginConfiguration()
        session.sessionPreset = .photo
        guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: device),
              session.canAddInput(input) else {
            session.commitConfiguration()
            return
        }
        session.addInput(input)
        self.device = device

        // 近距对焦:贴着拍化验单/小标签时持续自动对焦,并把搜索范围偏向近处,
        // 避免反复拉风箱。所有机型生效;不支持的项静默跳过。
        if (try? device.lockForConfiguration()) != nil {
            if device.isFocusModeSupported(.continuousAutoFocus) {
                device.focusMode = .continuousAutoFocus
            }
            if device.isAutoFocusRangeRestrictionSupported {
                device.autoFocusRangeRestriction = .near
            }
            device.unlockForConfiguration()
        }

        if session.canAddOutput(output) { session.addOutput(output) }
        session.commitConfiguration()

        let preview = AVCaptureVideoPreviewLayer(session: session)
        preview.videoGravity = .resizeAspectFill
        preview.frame = bounds
        layer.addSublayer(preview)
        self.previewLayer = preview
        applyPortrait(preview.connection)

        // 点击对焦:贴着拍小标签时连续自动对焦偶尔锁不准,允许用户点屏指定对焦点。
        let tap = UITapGestureRecognizer(target: self, action: #selector(handleFocusTap(_:)))
        addGestureRecognizer(tap)

        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            self?.session.startRunning()
        }
    }

    /// 锁竖屏(iOS 17+ 用 videoRotationAngle,避免 videoOrientation 弃用告警)。
    private func applyPortrait(_ connection: AVCaptureConnection?) {
        guard let connection else { return }
        if connection.isVideoRotationAngleSupported(90) {
            connection.videoRotationAngle = 90
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        previewLayer?.frame = bounds
    }

    // MARK: - 点击对焦

    @objc private func handleFocusTap(_ gr: UITapGestureRecognizer) {
        guard let previewLayer, device != nil else { return }
        let point = gr.location(in: self)
        // 屏幕点 → 设备归一化对焦坐标(随 videoGravity/旋转正确换算)。
        let devicePoint = previewLayer.captureDevicePointConverted(fromLayerPoint: point)
        focus(at: devicePoint)
        showFocusIndicator(at: point)
    }

    /// 把对焦/测光点锁到指定设备坐标,单次自动对焦;不支持的项静默跳过。
    private func focus(at devicePoint: CGPoint) {
        guard let device, (try? device.lockForConfiguration()) != nil else { return }
        if device.isFocusPointOfInterestSupported {
            device.focusPointOfInterest = devicePoint
        }
        if device.isFocusModeSupported(.autoFocus) {
            device.focusMode = .autoFocus          // 单次对焦,锁到该点
        }
        if device.isExposurePointOfInterestSupported {
            device.exposurePointOfInterest = devicePoint
        }
        if device.isExposureModeSupported(.autoExpose) {
            device.exposureMode = .autoExpose
        }
        device.unlockForConfiguration()
    }

    /// 在点击位置短暂显示一个黄色对焦框作为反馈。
    private func showFocusIndicator(at point: CGPoint) {
        focusIndicator?.removeFromSuperview()
        let box = UIView(frame: CGRect(x: 0, y: 0, width: 76, height: 76))
        box.center = point
        box.backgroundColor = .clear
        box.layer.borderColor = UIColor.systemYellow.cgColor
        box.layer.borderWidth = 1.5
        box.layer.cornerRadius = 6
        box.isUserInteractionEnabled = false
        box.alpha = 0
        box.transform = CGAffineTransform(scaleX: 1.35, y: 1.35)
        addSubview(box)
        focusIndicator = box
        UIView.animate(withDuration: 0.2, animations: {
            box.alpha = 1
            box.transform = .identity
        }, completion: { _ in
            UIView.animate(withDuration: 0.3, delay: 0.7, options: []) {
                box.alpha = 0
            } completion: { _ in
                box.removeFromSuperview()
            }
        })
    }

    func capture(completion: @escaping (UIImage?) -> Void) {
        guard session.isRunning else { completion(nil); return }
        captureCompletion = completion
        applyPortrait(output.connection(with: .video))
        output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
    }

    func stop() {
        guard session.isRunning else { return }
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            self?.session.stopRunning()
        }
    }

    func photoOutput(_ output: AVCapturePhotoOutput,
                     didFinishProcessingPhoto photo: AVCapturePhoto,
                     error: Error?) {
        let completion = captureCompletion
        captureCompletion = nil
        // 代理回调在 AVFoundation 私有队列,SwiftUI 状态更新必须切回主线程。
        let deliver: (UIImage?) -> Void = { result in
            DispatchQueue.main.async { completion?(result) }
        }
        guard error == nil,
              let data = photo.fileDataRepresentation(),
              let image = UIImage(data: data) else {
            deliver(nil)
            return
        }
        let upright = image.normalizedUp()
        // 整幅单拍:直接返回整图,框选在静态图阶段做。
        guard cropsToBox, previewLayer != nil else {
            deliver(upright)
            return
        }
        DispatchQueue.main.async {
            let viewSize = self.bounds.size
            let box = RegionFraming.box(in: viewSize)
            let cropped = RegionImageCropper.crop(upright, box: box, viewSize: viewSize)
            completion?(cropped)
        }
    }
}

// MARK: - 小框几何(旧 fill 裁剪路径保留,供 cropsToBox 用)

enum RegionFraming {
    /// 居中、略高于中心的小框。宽 84% 屏宽,高取 160 与 28% 屏高的较小值。
    static func box(in size: CGSize) -> CGRect {
        guard size.width > 0, size.height > 0 else { return .zero }
        let w = size.width * 0.84
        let h = min(160, size.height * 0.28)
        let x = (size.width - w) / 2
        let y = (size.height - h) / 2 - size.height * 0.06
        return CGRect(x: x, y: y, width: w, height: h)
    }
}

// MARK: - 裁剪纯函数

enum RegionImageCropper {
    /// 把屏上小框(view 点坐标)按 `.resizeAspectFill` 反算到 `.up` 照片的像素裁剪 rect。
    /// 前提:预览以 aspect-fill 铺满 viewSize,照片与预览同源、同为竖屏方向。
    /// 单边越界自动夹紧。仅 cropsToBox 实时相机路径用。
    static func cropRect(photoPixelSize p: CGSize, box: CGRect, in viewSize: CGSize) -> CGRect {
        guard p.width > 0, p.height > 0, viewSize.width > 0, viewSize.height > 0 else { return .zero }
        let scale = max(viewSize.width / p.width, viewSize.height / p.height)
        let scaledW = p.width * scale
        let scaledH = p.height * scale
        let ox = (viewSize.width - scaledW) / 2
        let oy = (viewSize.height - scaledH) / 2
        var x = (box.minX - ox) / scale
        var y = (box.minY - oy) / scale
        var w = box.width / scale
        var h = box.height / scale
        x = max(0, min(p.width, x))
        y = max(0, min(p.height, y))
        w = max(0, min(p.width - x, w))
        h = max(0, min(p.height - y, h))
        return CGRect(x: x, y: y, width: w, height: h).integral
    }

    /// 按屏上小框裁 `.up` 照片(aspect-fill 路径);失败回退原图。
    static func crop(_ image: UIImage, box: CGRect, viewSize: CGSize) -> UIImage {
        guard let cg = image.cgImage else { return image }
        let rect = cropRect(photoPixelSize: CGSize(width: cg.width, height: cg.height),
                            box: box, in: viewSize)
        guard rect.width >= 1, rect.height >= 1, let cropped = cg.cropping(to: rect) else { return image }
        return UIImage(cgImage: cropped, scale: image.scale, orientation: .up)
    }

    /// aspect-FIT 版:静态图以 `.scaledToFit` 显示在 `imageFrame`(view 点坐标,通常用
    /// `AVMakeRect(aspectRatio:insideRect:)` 算得)内,把屏上选框反算到照片像素 rect。
    /// `RegionAdjustView` 框选识别用。越界自动夹紧。
    static func cropRectAspectFit(photoPixelSize p: CGSize, box: CGRect, imageFrame f: CGRect) -> CGRect {
        guard p.width > 0, p.height > 0, f.width > 0, f.height > 0 else { return .zero }
        // aspect-fit:照片完整显示在 imageFrame 内,缩放系数两轴一致。
        let scale = f.width / p.width
        guard scale > 0 else { return .zero }
        var x = (box.minX - f.minX) / scale
        var y = (box.minY - f.minY) / scale
        var w = box.width / scale
        var h = box.height / scale
        x = max(0, min(p.width, x))
        y = max(0, min(p.height, y))
        w = max(0, min(p.width - x, w))
        h = max(0, min(p.height - y, h))
        return CGRect(x: x, y: y, width: w, height: h).integral
    }

    /// 按静态图上的选框(aspect-fit)裁子图;失败回退原图。
    static func cropAspectFit(_ image: UIImage, box: CGRect, imageFrame: CGRect) -> UIImage {
        let up = image.normalizedUp()
        guard let cg = up.cgImage else { return image }
        let rect = cropRectAspectFit(
            photoPixelSize: CGSize(width: cg.width, height: cg.height),
            box: box, imageFrame: imageFrame
        )
        guard rect.width >= 1, rect.height >= 1, let cropped = cg.cropping(to: rect) else { return up }
        return UIImage(cgImage: cropped, scale: up.scale, orientation: .up)
    }
}

extension UIImage {
    /// 把 EXIF 方向 bake 进像素,返回 `.up` 方向图,便于按归一化 rect 直接裁 CGImage。
    func normalizedUp() -> UIImage {
        if imageOrientation == .up { return self }
        let format = UIGraphicsImageRendererFormat.default()
        format.scale = scale
        let renderer = UIGraphicsImageRenderer(size: size, format: format)
        return renderer.image { _ in draw(in: CGRect(origin: .zero, size: size)) }
    }
}
