//
//  TaskAddViews.swift
//  suixinkan
//

import AVFoundation
import Kingfisher
import SnapKit
import UIKit

/// 添加任务素材网格区。
final class TaskAddMediaSectionView: UIView {

    var onCloudImport: (() -> Void)?
    var onLocalImport: (() -> Void)?
    var onFileTap: ((TaskFileItem) -> Void)?
    var onDeleteFile: ((TaskFileItem) -> Void)?

    private var files: [TaskFileItem] = []
    private var collectionHeightConstraint: Constraint?
    private let gridColumns = 3
    private let gridSpacing: CGFloat = 8

    private enum Section { case main }
    private enum Item: Hashable { case add; case file(String) }

    private lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewCompositionalLayout { [weak self] _, environment in
            let columns = self?.gridColumns ?? 3
            let spacing = self?.gridSpacing ?? 8
            let availableWidth = environment.container.effectiveContentSize.width
            let totalSpacing = spacing * CGFloat(columns - 1)
            let itemWidth = max(0, (availableWidth - totalSpacing) / CGFloat(columns))
            let itemHeight = itemWidth / 1.77
            let itemSize = NSCollectionLayoutSize(
                widthDimension: .absolute(itemWidth),
                heightDimension: .absolute(itemHeight)
            )
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let groupSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(1),
                heightDimension: .absolute(itemHeight)
            )
            let group = NSCollectionLayoutGroup.horizontal(
                layoutSize: groupSize,
                repeatingSubitem: item,
                count: columns
            )
            group.interItemSpacing = .fixed(spacing)
            let section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = spacing
            return section
        }
        let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
        view.backgroundColor = .clear
        view.isScrollEnabled = false
        return view
    }()

    private lazy var dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
        [weak self] collectionView, indexPath, item in
        switch item {
        case .add:
            let cell = collectionView.dequeueReusableCell(
                withReuseIdentifier: TaskAddAddMediaCell.reuseIdentifier,
                for: indexPath
            ) as! TaskAddAddMediaCell
            return cell
        case .file(let id):
            let cell = collectionView.dequeueReusableCell(
                withReuseIdentifier: TaskAddMediaCell.reuseIdentifier,
                for: indexPath
            ) as! TaskAddMediaCell
            if let file = self?.files.first(where: { $0.id == id }) {
                cell.apply(file: file)
                cell.onDelete = { self?.onDeleteFile?(file) }
            }
            return cell
        }
    }

    private let titleLabel = UILabel()
    private let tipsLabel = UILabel()
    private let cloudButton = UIButton(type: .system)

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear

        titleLabel.text = "添加图片"
        titleLabel.font = .app(.bodyMedium)
        titleLabel.textColor = AppColor.textPrimary

        tipsLabel.text = "(点击图片或视频可添加备注)"
        tipsLabel.font = .app(.caption)
        tipsLabel.textColor = AppColor.textPrimary
        tipsLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

        cloudButton.setTitle("云盘导入", for: .normal)
        cloudButton.setTitleColor(AppColor.primary, for: .normal)
        cloudButton.titleLabel?.font = .app(.caption)
        cloudButton.addTarget(self, action: #selector(cloudTapped), for: .touchUpInside)
        cloudButton.setContentCompressionResistancePriority(.required, for: .horizontal)

        collectionView.register(TaskAddMediaCell.self, forCellWithReuseIdentifier: TaskAddMediaCell.reuseIdentifier)
        collectionView.register(TaskAddAddMediaCell.self, forCellWithReuseIdentifier: TaskAddAddMediaCell.reuseIdentifier)
        collectionView.delegate = self

        addSubview(titleLabel)
        addSubview(tipsLabel)
        addSubview(cloudButton)
        addSubview(collectionView)

        titleLabel.snp.makeConstraints { make in
            make.top.leading.equalToSuperview()
        }
        tipsLabel.snp.makeConstraints { make in
            make.leading.equalTo(titleLabel.snp.trailing).offset(AppSpacing.xxs)
            make.centerY.equalTo(titleLabel)
            make.trailing.lessThanOrEqualTo(cloudButton.snp.leading).offset(-AppSpacing.xs)
        }
        cloudButton.snp.makeConstraints { make in
            make.trailing.equalToSuperview()
            make.centerY.equalTo(titleLabel)
        }
        collectionView.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
            make.leading.trailing.bottom.equalToSuperview()
            collectionHeightConstraint = make.height.equalTo(88).constraint
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func apply(files: [TaskFileItem]) {
        self.files = files
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(files.map { Item.file($0.id) } + [.add])
        let existingItems = Set(dataSource.snapshot().itemIdentifiers)
        let itemsToReconfigure = snapshot.itemIdentifiers.filter(existingItems.contains)
        snapshot.reconfigureItems(itemsToReconfigure)
        dataSource.apply(snapshot, animatingDifferences: false)
        updateCollectionHeight(itemCount: files.count + 1)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateCollectionHeight(itemCount: files.count + 1)
    }

    private func updateCollectionHeight(itemCount: Int) {
        let availableWidth = collectionView.bounds.width
        guard availableWidth > 0 else { return }
        let itemWidth = max(0, (availableWidth - gridSpacing * CGFloat(gridColumns - 1)) / CGFloat(gridColumns))
        let itemHeight = itemWidth / 1.77
        let rows = max(1, Int(ceil(Double(itemCount) / Double(gridColumns))))
        let height = itemHeight * CGFloat(rows) + gridSpacing * CGFloat(rows - 1)
        collectionHeightConstraint?.update(offset: max(88, height))
    }

    @objc private func cloudTapped() {
        onCloudImport?()
    }
}

extension TaskAddMediaSectionView: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
        switch item {
        case .add:
            onLocalImport?()
        case .file(let id):
            guard let file = files.first(where: { $0.id == id }) else { return }
            onFileTap?(file)
        }
    }
}

/// 圆形上传进度视图，对齐 Android 上传素材 cell 的进度表现。
final class TaskCircularProgressView: UIView {

    private let trackLayer = CAShapeLayer()
    private let progressLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        isHidden = true
        layer.addSublayer(trackLayer)
        layer.addSublayer(progressLayer)
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.strokeColor = UIColor.white.withAlphaComponent(0.35).cgColor
        trackLayer.lineWidth = 3
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeColor = UIColor.white.cgColor
        progressLayer.lineCap = .round
        progressLayer.lineWidth = 3
        progressLayer.strokeEnd = 0
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        let radius = min(bounds.width, bounds.height) / 2 - progressLayer.lineWidth / 2
        let path = UIBezierPath(
            arcCenter: CGPoint(x: bounds.midX, y: bounds.midY),
            radius: radius,
            startAngle: -.pi / 2,
            endAngle: .pi * 1.5,
            clockwise: true
        )
        trackLayer.frame = bounds
        progressLayer.frame = bounds
        trackLayer.path = path.cgPath
        progressLayer.path = path.cgPath
    }

    /// 设置 0...1 的上传进度。
    func setProgress(_ progress: CGFloat) {
        progressLayer.strokeEnd = min(1, max(0, progress))
    }
}

/// 素材缩略图 cell。
final class TaskAddMediaCell: UICollectionViewCell {

    static let reuseIdentifier = "TaskAddMediaCell"

    var onDelete: (() -> Void)?

    private let imageView = UIImageView()
    private let overlayView = UIView()
    private let progressRingView = TaskCircularProgressView()
    private let progressLabel = UILabel()
    private let deleteButton = UIButton(type: .system)
    private let playIconView = UIImageView()
    private let sourceBadgeLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = AppColor.inputBackground
        contentView.layer.cornerRadius = AppRadius.sm
        contentView.clipsToBounds = true

        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true

        overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.2)
        overlayView.isHidden = true

        progressLabel.font = .app(.captionMedium)
        progressLabel.textColor = .white
        progressLabel.textAlignment = .center

        deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
        deleteButton.tintColor = .white
        deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)

        playIconView.image = UIImage(systemName: "play.circle.fill")
        playIconView.tintColor = .white
        playIconView.isHidden = true

        sourceBadgeLabel.font = .app(.caption)
        sourceBadgeLabel.textColor = .white
        sourceBadgeLabel.textAlignment = .center
        sourceBadgeLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        sourceBadgeLabel.layer.cornerRadius = AppRadius.xs
        sourceBadgeLabel.clipsToBounds = true
        sourceBadgeLabel.isHidden = true

        contentView.addSubview(imageView)
        contentView.addSubview(overlayView)
        contentView.addSubview(sourceBadgeLabel)
        contentView.addSubview(playIconView)
        contentView.addSubview(progressRingView)
        contentView.addSubview(progressLabel)
        contentView.addSubview(deleteButton)

        imageView.snp.makeConstraints { make in make.edges.equalToSuperview() }
        overlayView.snp.makeConstraints { make in make.edges.equalToSuperview() }
        sourceBadgeLabel.snp.makeConstraints { make in
            make.leading.bottom.equalToSuperview().inset(AppSpacing.xxs)
            make.height.equalTo(18)
            make.width.greaterThanOrEqualTo(34)
        }
        playIconView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(24)
        }
        progressRingView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalToSuperview().offset(-10)
            make.width.height.equalTo(28)
        }
        progressLabel.snp.makeConstraints { make in
            make.top.equalTo(progressRingView.snp.bottom).offset(2)
            make.centerX.equalToSuperview()
        }
        deleteButton.snp.makeConstraints { make in
            make.top.trailing.equalToSuperview().inset(AppSpacing.xxs)
            make.width.height.equalTo(20)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        onDelete = nil
        imageView.kf.cancelDownloadTask()
        imageView.image = nil
        progressRingView.setProgress(0)
    }

    func apply(file: TaskFileItem) {
        let urlString = file.previewURLString
        if let localPreviewData = file.localPreviewData,
           let localImage = UIImage(data: localPreviewData) {
            imageView.image = localImage
        } else if let url = URL(string: urlString), !urlString.isEmpty {
            imageView.kf.setImage(with: url)
        } else {
            imageView.image = UIImage(systemName: "photo")
            imageView.tintColor = AppColor.textTertiary
        }
        playIconView.isHidden = file.fileType != 1 || file.isUploading
        deleteButton.isHidden = file.isUploading
        sourceBadgeLabel.isHidden = file.isUploading
        sourceBadgeLabel.text = file.sourceBadgeText
        if file.isUploading {
            overlayView.isHidden = false
            progressRingView.isHidden = false
            progressRingView.setProgress(CGFloat(file.uploadProgress) / 100)
            progressLabel.text = "\(file.uploadProgress)%"
        } else {
            overlayView.isHidden = true
            progressRingView.isHidden = true
            progressLabel.text = nil
        }
    }

    @objc private func deleteTapped() {
        onDelete?()
    }
}

/// 本地导入占位 cell。
final class TaskAddAddMediaCell: UICollectionViewCell {

    static let reuseIdentifier = "TaskAddAddMediaCell"

    private let iconView = UIImageView()
    private let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = AppColor.inputBackground
        contentView.layer.cornerRadius = AppRadius.sm

        iconView.image = UIImage(systemName: "plus")
        iconView.tintColor = AppColor.textTabInactive

        titleLabel.text = "本地导入"
        titleLabel.font = .app(.caption)
        titleLabel.textColor = AppColor.textTabInactive

        contentView.addSubview(iconView)
        contentView.addSubview(titleLabel)
        iconView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalToSuperview().offset(-8)
            make.width.height.equalTo(24)
        }
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(iconView.snp.bottom).offset(2)
            make.centerX.equalToSuperview()
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 任务优先级选择区。
final class TaskPrioritySectionView: UIView {

    var onUrgentHourChange: ((Int) -> Void)?
    var onCustomHourTextChange: ((String) -> Void)?
    var onShowMessage: ((String) -> Void)?

    private let titleLabel = UILabel()
    private let noneButton = UIButton(type: .system)
    private let twoHourButton = UIButton(type: .system)
    private let customButton = UIButton(type: .system)
    private let customField = UITextField()
    private let customConfirmButton = UIButton(type: .system)
    private let optionRow = UIStackView()
    private let customRow = UIStackView()
    private let stackView = UIStackView()
    private var customHourText = ""
    private var lastUrgentHour = 0

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear

        titleLabel.text = "优先级"
        titleLabel.font = .app(.bodyMedium)
        titleLabel.textColor = AppColor.textPrimary

        configureOptionButton(noneButton, title: "不加急")
        configureOptionButton(twoHourButton, title: "加急(2小时)")
        configureOptionButton(customButton, title: "自定义")

        noneButton.addTarget(self, action: #selector(noneTapped), for: .touchUpInside)
        twoHourButton.addTarget(self, action: #selector(twoHourTapped), for: .touchUpInside)
        customButton.addTarget(self, action: #selector(customTapped), for: .touchUpInside)

        customField.placeholder = "请输入小时数"
        customField.font = .app(.body)
        customField.textColor = AppColor.textPrimary
        customField.keyboardType = .numberPad
        customField.backgroundColor = .white
        customField.layer.cornerRadius = AppRadius.sm
        customField.layer.borderWidth = 1
        customField.layer.borderColor = AppColor.border.cgColor
        customField.delegate = self
        customField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: AppSpacing.sm, height: 1))
        customField.leftViewMode = .always
        customField.addTarget(self, action: #selector(customFieldChanged), for: .editingChanged)

        customConfirmButton.setTitle("确定", for: .normal)
        customConfirmButton.setTitleColor(.white, for: .normal)
        customConfirmButton.titleLabel?.font = .app(.bodyMedium)
        customConfirmButton.backgroundColor = AppColor.primary
        customConfirmButton.layer.cornerRadius = AppRadius.xs
        customConfirmButton.addTarget(self, action: #selector(customConfirmTapped), for: .touchUpInside)

        optionRow.addArrangedSubview(noneButton)
        optionRow.addArrangedSubview(twoHourButton)
        optionRow.addArrangedSubview(customButton)
        optionRow.axis = .horizontal
        optionRow.spacing = AppSpacing.xs
        optionRow.distribution = .fillEqually

        customRow.addArrangedSubview(customField)
        customRow.addArrangedSubview(customConfirmButton)
        customRow.axis = .horizontal
        customRow.spacing = AppSpacing.xs
        customRow.alignment = .fill
        customRow.isHidden = true

        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(optionRow)
        stackView.addArrangedSubview(customRow)
        stackView.axis = .vertical
        stackView.spacing = AppSpacing.xs

        addSubview(stackView)

        stackView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        optionRow.snp.makeConstraints { make in
            make.height.equalTo(42)
        }
        customField.snp.makeConstraints { make in
            make.height.equalTo(46)
        }
        customConfirmButton.snp.makeConstraints { make in
            make.width.equalTo(72)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func apply(urgentHour: Int) {
        let isCustom = urgentHour > 0 && urgentHour != 2
        let wasCustom = lastUrgentHour > 0 && lastUrgentHour != 2
        styleOption(noneButton, selected: urgentHour == 0)
        styleOption(twoHourButton, selected: urgentHour == 2)
        styleOption(customButton, selected: isCustom)
        customRow.isHidden = !isCustom
        if isCustom {
            if !wasCustom || urgentHour != lastUrgentHour {
                customHourText = "\(urgentHour)"
            }
            customField.text = customHourText
        } else {
            customHourText = ""
            customField.text = ""
        }
        lastUrgentHour = urgentHour
    }

    private func configureOptionButton(_ button: UIButton, title: String) {
        button.setTitle(title, for: .normal)
        button.titleLabel?.font = .app(.caption)
        button.layer.cornerRadius = AppRadius.xs
        button.clipsToBounds = true
    }

    private func styleOption(_ button: UIButton, selected: Bool) {
        button.backgroundColor = selected ? AppColor.primary : AppColor.inputBackground
        button.setTitleColor(selected ? .white : AppColor.textSecondary, for: .normal)
    }

    @objc private func noneTapped() { onUrgentHourChange?(0) }
    @objc private func twoHourTapped() { onUrgentHourChange?(2) }
    @objc private func customTapped() {
        guard lastUrgentHour <= 0 || lastUrgentHour == 2 else { return }
        onUrgentHourChange?(1)
        onCustomHourTextChange?("1")
    }
    @objc private func customFieldChanged() {
        customHourText = customField.text ?? ""
        onCustomHourTextChange?(customHourText)
    }
    @objc private func customConfirmTapped() {
        guard let hour = Int(customHourText), hour > 0 else {
            onShowMessage?("请输入有效的正整数")
            return
        }
        onCustomHourTextChange?(customHourText)
        onShowMessage?("加急\(hour)小时")
    }
}

extension TaskPrioritySectionView: UITextFieldDelegate {
    func textField(
        _ textField: UITextField,
        shouldChangeCharactersIn range: NSRange,
        replacementString string: String
    ) -> Bool {
        string.isEmpty || string.allSatisfy(\.isNumber)
    }
}

/// 关联订单选择按钮。
final class TaskOrderSelectButton: UIControl {

    private let titleLabel = UILabel()
    private let arrowView = UIImageView(image: UIImage(systemName: "chevron.down"))

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .white
        layer.cornerRadius = AppRadius.sm
        layer.borderWidth = 1
        layer.borderColor = AppColor.border.cgColor

        titleLabel.font = .app(.bodyMedium)
        titleLabel.textColor = AppColor.textTabInactive
        titleLabel.text = "关联订单（可选）"

        arrowView.tintColor = AppColor.textTabInactive

        addSubview(titleLabel)
        addSubview(arrowView)
        titleLabel.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(AppSpacing.sm)
            make.centerY.equalToSuperview()
        }
        arrowView.snp.makeConstraints { make in
            make.trailing.equalToSuperview().inset(AppSpacing.sm)
            make.centerY.equalToSuperview()
        }
        snp.makeConstraints { make in make.height.equalTo(44) }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func apply(orderNumber: String?) {
        if let orderNumber, !orderNumber.isEmpty {
            titleLabel.text = orderNumber
            titleLabel.textColor = AppColor.textPrimary
        } else {
            titleLabel.text = "关联订单（可选）"
            titleLabel.textColor = AppColor.textTabInactive
        }
    }
}

/// 任务素材备注弹窗。
final class TaskFileRemarkDialogView: KeyboardAvoidingDialogView {

    var onCancel: (() -> Void)?
    var onConfirm: (() -> Void)?

    private let containerView = UIView()
    private let titleLabel = UILabel()
    private let previewContainerView = UIView()
    private let previewView = UIImageView()
    private let videoView = TaskRemarkVideoView()
    private let remarkTitleLabel = UILabel()
    private let textView = UITextView()
    private let placeholderLabel = UILabel()
    private let countLabel = UILabel()
    private let cancelButton = UIButton(type: .system)
    private let confirmButton = UIButton(type: .system)
    private let buttonRow = UIStackView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = AppColor.overlayScrim
        keyboardAvoidingContentView = containerView

        containerView.backgroundColor = .white
        containerView.layer.cornerRadius = AppRadius.lg

        titleLabel.text = "请填写剪辑备注"
        titleLabel.font = .app(.title)
        titleLabel.textColor = AppColor.textPrimary

        previewView.contentMode = .scaleAspectFill
        previewView.clipsToBounds = true
        previewView.layer.cornerRadius = AppRadius.lg
        previewView.backgroundColor = AppColor.inputBackground

        previewContainerView.backgroundColor = AppColor.inputBackground
        previewContainerView.layer.cornerRadius = AppRadius.lg
        previewContainerView.clipsToBounds = true

        remarkTitleLabel.text = "备注信息"
        remarkTitleLabel.font = .app(.caption)
        remarkTitleLabel.textColor = AppColor.textPrimary

        textView.font = .app(.body)
        textView.textColor = AppColor.textPrimary
        textView.layer.borderWidth = 1
        textView.layer.borderColor = AppColor.border.cgColor
        textView.layer.cornerRadius = AppRadius.sm
        textView.delegate = self
        textView.textContainerInset = UIEdgeInsets(top: AppSpacing.sm, left: AppSpacing.xs, bottom: AppSpacing.sm, right: AppSpacing.xs)

        placeholderLabel.text = "请输入备注（最多50字）"
        placeholderLabel.font = .app(.body)
        placeholderLabel.textColor = AppColor.textTertiary

        countLabel.font = .app(.caption)
        countLabel.textColor = AppColor.textTertiary
        countLabel.text = "0/50"

        cancelButton.setTitle("取消", for: .normal)
        cancelButton.setTitleColor(AppColor.textSecondary, for: .normal)
        cancelButton.backgroundColor = AppColor.inputBackground
        cancelButton.layer.cornerRadius = AppRadius.xs
        cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)

        confirmButton.setTitle("确定", for: .normal)
        confirmButton.setTitleColor(.white, for: .normal)
        confirmButton.backgroundColor = AppColor.primary
        confirmButton.layer.cornerRadius = AppRadius.xs
        confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)

        buttonRow.axis = .horizontal
        buttonRow.spacing = AppSpacing.xs
        buttonRow.alignment = .center
        buttonRow.distribution = .fill
        buttonRow.addArrangedSubview(cancelButton)
        buttonRow.addArrangedSubview(confirmButton)

        addSubview(containerView)
        containerView.addSubview(titleLabel)
        containerView.addSubview(previewContainerView)
        previewContainerView.addSubview(previewView)
        previewContainerView.addSubview(videoView)
        containerView.addSubview(remarkTitleLabel)
        containerView.addSubview(countLabel)
        containerView.addSubview(textView)
        textView.addSubview(placeholderLabel)
        containerView.addSubview(buttonRow)

        containerView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            keyboardAvoidingCenterYConstraint = make.centerY.equalToSuperview().constraint
            make.leading.trailing.equalToSuperview().inset(AppSpacing.lg)
        }
        titleLabel.snp.makeConstraints { make in
            make.top.leading.equalToSuperview().offset(AppSpacing.md)
        }
        previewContainerView.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm)
            make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
            make.height.equalTo(previewContainerView.snp.width)
        }
        previewView.snp.makeConstraints { make in make.edges.equalToSuperview() }
        videoView.snp.makeConstraints { make in make.edges.equalToSuperview() }
        remarkTitleLabel.snp.makeConstraints { make in
            make.top.equalTo(previewContainerView.snp.bottom).offset(AppSpacing.md)
            make.leading.equalToSuperview().offset(AppSpacing.md)
        }
        countLabel.snp.makeConstraints { make in
            make.centerY.equalTo(remarkTitleLabel)
            make.trailing.equalToSuperview().inset(AppSpacing.md)
        }
        textView.snp.makeConstraints { make in
            make.top.equalTo(remarkTitleLabel.snp.bottom).offset(AppSpacing.xs)
            make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
            make.height.equalTo(80)
        }
        placeholderLabel.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(AppSpacing.sm)
            make.leading.equalToSuperview().offset(AppSpacing.sm + 3)
            make.trailing.lessThanOrEqualToSuperview().inset(AppSpacing.sm)
        }
        buttonRow.snp.makeConstraints { make in
            make.top.equalTo(textView.snp.bottom).offset(AppSpacing.md)
            make.trailing.bottom.equalToSuperview().inset(AppSpacing.md)
        }
        cancelButton.snp.makeConstraints { make in
            make.height.equalTo(36)
            make.width.equalTo(72)
        }
        confirmButton.snp.makeConstraints { make in
            make.height.equalTo(36)
            make.width.equalTo(72)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func apply(file: TaskFileItem, text: String) {
        textView.text = text
        countLabel.text = "\(text.count)/50"
        placeholderLabel.isHidden = !text.isEmpty
        if file.fileType == 1,
           let url = URL(string: file.uploadFileUrl ?? file.cloudFile?.fileUrl ?? ""),
           !url.absoluteString.isEmpty {
            previewView.isHidden = true
            videoView.isHidden = false
            videoView.configure(url: url)
            return
        }

        videoView.stop()
        videoView.isHidden = true
        previewView.isHidden = false
        let urlString = file.previewURLString
        if let localPreviewData = file.localPreviewData,
           let localImage = UIImage(data: localPreviewData) {
            previewView.image = localImage
        } else if let url = URL(string: urlString), !urlString.isEmpty {
            previewView.kf.setImage(with: url)
        } else {
            previewView.image = UIImage(systemName: "photo")
            previewView.tintColor = AppColor.textTertiary
        }
    }

    @objc private func cancelTapped() {
        videoView.stop()
        onCancel?()
    }

    @objc private func confirmTapped() {
        videoView.stop()
        onConfirm?()
    }

    /// 当前输入的备注文本。
    var inputText: String { textView.text ?? "" }
}

/// 任务备注弹窗中的视频播放视图。
final class TaskRemarkVideoView: UIView {

    private let playerLayer = AVPlayerLayer()
    private let playButton = UIButton(type: .system)
    private var player: AVPlayer?

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .black
        layer.addSublayer(playerLayer)

        playButton.setImage(UIImage(systemName: "play.circle.fill"), for: .normal)
        playButton.tintColor = .white
        playButton.addTarget(self, action: #selector(togglePlayback), for: .touchUpInside)
        addSubview(playButton)
        playButton.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(52)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

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

    /// 切换到指定视频并准备播放。
    func configure(url: URL) {
        stop()
        let player = AVPlayer(url: url)
        self.player = player
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspect
        playButton.isHidden = false
    }

    /// 停止播放并释放播放器。
    func stop() {
        player?.pause()
        player = nil
        playerLayer.player = nil
        playButton.isHidden = false
    }

    @objc private func togglePlayback() {
        guard let player else { return }
        if player.timeControlStatus == .playing {
            player.pause()
            playButton.isHidden = false
        } else {
            player.play()
            playButton.isHidden = true
        }
    }
}

extension TaskFileRemarkDialogView: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        if textView.text.count > 50 {
            textView.text = String(textView.text.prefix(50))
        }
        placeholderLabel.isHidden = !textView.text.isEmpty
        countLabel.text = "\(textView.text.count)/50"
    }
}

/// 带边框的单行输入框。
final class TaskBorderTextField: UIView {

    let textField = UITextField()

    init(placeholder: String) {
        super.init(frame: .zero)
        backgroundColor = .white
        layer.cornerRadius = AppRadius.sm
        layer.borderWidth = 1
        layer.borderColor = AppColor.border.cgColor

        textField.placeholder = placeholder
        textField.font = .app(.body)
        textField.textColor = AppColor.textPrimary
        addSubview(textField)
        textField.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12))
        }
        snp.makeConstraints { make in make.height.equalTo(44) }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 带边框的多行输入框。
final class TaskBorderTextView: UIView {

    let textView = UITextView()

    init(placeholder: String) {
        super.init(frame: .zero)
        backgroundColor = .white
        layer.cornerRadius = AppRadius.sm
        layer.borderWidth = 1
        layer.borderColor = AppColor.border.cgColor

        textView.font = .app(.body)
        textView.textColor = AppColor.textPrimary
        textView.text = placeholder
        textView.textColor = AppColor.textTertiary
        addSubview(textView)
        textView.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(AppSpacing.xs)
        }
        snp.makeConstraints { make in make.height.equalTo(80) }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

/// 任务上传类型底部弹层，对齐 Android `AddFileBottomModalForTask`。
final class TaskUploadTypeSheetView: UIView {

    var onChooseImage: (() -> Void)?
    var onChooseVideo: (() -> Void)?
    var onCancel: (() -> Void)?

    private let dimmingView = UIView()
    private let panelView = UIView()
    private let titleLabel = UILabel()
    private let imageItem = TaskUploadTypeItemView(
        title: "上传图片",
        image: UIImage(named: "icon_cloud_storage_add_photo") ?? UIImage(systemName: "photo")
    )
    private let videoItem = TaskUploadTypeItemView(
        title: "上传视频",
        image: UIImage(named: "icon_cloud_storage_add_video") ?? UIImage(systemName: "video")
    )
    private let dividerView = UIView()
    private let cancelButton = UIButton(type: .system)
    private var panelBottomConstraint: Constraint?

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear

        dimmingView.backgroundColor = AppColor.overlayScrim
        dimmingView.alpha = 0
        let tap = UITapGestureRecognizer(target: self, action: #selector(cancelTapped))
        dimmingView.addGestureRecognizer(tap)

        panelView.backgroundColor = .white
        panelView.layer.cornerRadius = AppRadius.sheetTop
        panelView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        panelView.clipsToBounds = true

        titleLabel.text = "选择上传类型"
        titleLabel.font = .app(.bodyMedium)
        titleLabel.textColor = AppColor.textPrimary
        titleLabel.textAlignment = .center

        imageItem.addTarget(self, action: #selector(imageTapped), for: .touchUpInside)
        videoItem.addTarget(self, action: #selector(videoTapped), for: .touchUpInside)

        dividerView.backgroundColor = AppColor.border

        cancelButton.setTitle("取消", for: .normal)
        cancelButton.titleLabel?.font = .app(.bodyMedium)
        cancelButton.setTitleColor(UIColor(hex: 0x4B5563), for: .normal)
        cancelButton.backgroundColor = AppColor.inputBackground
        cancelButton.layer.cornerRadius = AppRadius.lg
        cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)

        addSubview(dimmingView)
        addSubview(panelView)
        panelView.addSubview(titleLabel)
        panelView.addSubview(imageItem)
        panelView.addSubview(videoItem)
        panelView.addSubview(dividerView)
        panelView.addSubview(cancelButton)

        dimmingView.snp.makeConstraints { make in make.edges.equalToSuperview() }
        panelView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            panelBottomConstraint = make.bottom.equalToSuperview().offset(260).constraint
        }
        titleLabel.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(AppSpacing.md)
            make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
        }
        imageItem.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.md)
            make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
            make.height.equalTo(58)
        }
        videoItem.snp.makeConstraints { make in
            make.top.equalTo(imageItem.snp.bottom).offset(AppSpacing.sm)
            make.leading.trailing.equalTo(imageItem)
            make.height.equalTo(imageItem)
        }
        dividerView.snp.makeConstraints { make in
            make.top.equalTo(videoItem.snp.bottom).offset(AppSpacing.md)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(0.5)
        }
        cancelButton.snp.makeConstraints { make in
            make.top.equalTo(dividerView.snp.bottom).offset(AppSpacing.md)
            make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
            make.height.equalTo(48)
            make.bottom.equalTo(safeAreaLayoutGuide).inset(AppSpacing.md)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 展示底部弹层。
    func show() {
        layoutIfNeeded()
        panelBottomConstraint?.update(offset: 0)
        UIView.animate(withDuration: 0.22, delay: 0, options: [.curveEaseOut]) {
            self.dimmingView.alpha = 1
            self.layoutIfNeeded()
        }
    }

    /// 关闭底部弹层。
    func dismiss(completion: (() -> Void)? = nil) {
        panelBottomConstraint?.update(offset: panelView.bounds.height)
        UIView.animate(withDuration: 0.18, delay: 0, options: [.curveEaseIn]) {
            self.dimmingView.alpha = 0
            self.layoutIfNeeded()
        } completion: { _ in
            completion?()
        }
    }

    @objc private func imageTapped() {
        onChooseImage?()
    }

    @objc private func videoTapped() {
        onChooseVideo?()
    }

    @objc private func cancelTapped() {
        onCancel?()
    }
}

/// 上传类型弹层内的单个选项。
final class TaskUploadTypeItemView: UIControl {

    private let iconView = UIImageView()
    private let titleLabel = UILabel()

    init(title: String, image: UIImage?) {
        super.init(frame: .zero)
        backgroundColor = .white
        layer.cornerRadius = AppRadius.sm
        layer.borderWidth = 1
        layer.borderColor = AppColor.inputBackground.cgColor

        iconView.image = image
        iconView.contentMode = .scaleAspectFit
        iconView.tintColor = AppColor.primary

        titleLabel.text = title
        titleLabel.font = .app(.bodyMedium)
        titleLabel.textColor = .black

        addSubview(iconView)
        addSubview(titleLabel)

        iconView.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(AppSpacing.md)
            make.centerY.equalToSuperview()
            make.width.height.equalTo(32)
        }
        titleLabel.snp.makeConstraints { make in
            make.leading.equalTo(iconView.snp.trailing).offset(AppSpacing.sm)
            make.centerY.equalToSuperview()
            make.trailing.lessThanOrEqualToSuperview().inset(AppSpacing.md)
        }
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
