//
//  ProfileSpaceSettingsViewController.swift
//  suixinkan
//

import Kingfisher
import PhotosUI
import SnapKit
import UIKit

/// 个人空间配置页面，对齐 Android `ProfileInfoScreen`。
final class ProfileSpaceSettingsViewController: BaseViewController {

    private let viewModel: ProfileSpaceSettingsViewModel
    private let profileAPI: ProfileAPI

    private let scrollView = UIScrollView()
    private let contentStack = UIStackView()
    private let bottomBar = UIView()
    private let saveButton = AppButton(title: "保存")

    private let avatarImageView = UIImageView()
    private let avatarChangeOverlay = UIView()
    private let avatarChangeLabel = UILabel()
    private let realNameLabel = UILabel()
    private let nicknameField = UITextField()
    private let editProfileButton = UIButton(type: .system)
    private let introductionTextView = UITextView()
    private let introductionPlaceholderLabel = UILabel()
    private let certificationWrap = ProfileTagWrapView()
    private let certificationExpandButton = UIButton(type: .system)

    private let attrWrap = ProfileTagWrapView()
    private let shootWrap = ProfileTagWrapView()
    private let attrField = UITextField()
    private let shootField = UITextField()
    private let attrAddButton = UIButton(type: .system)
    private let shootAddButton = UIButton(type: .system)

    private let businessStartButton = ProfileTimeButton()
    private let businessEndButton = ProfileTimeButton()
    private let holidayStartButton = ProfileTimeButton()
    private let holidayEndButton = ProfileTimeButton()

    private let deviceWrap = ProfileTagWrapView()
    private let addDeviceButton = ProfileDashedBorderButton(type: .system)

    private var statusButtons: [UIButton] = []
    private let dateStack = UIStackView()
    private let selectedDateLabel = UILabel()
    private let scheduleStack = UIStackView()
    private let addScheduleButton = UIButton(type: .system)

    init(
        viewModel: ProfileSpaceSettingsViewModel = ProfileSpaceSettingsViewModel(),
        profileAPI: ProfileAPI? = nil
    ) {
        self.viewModel = viewModel
        self.profileAPI = profileAPI ?? NetworkServices.shared.profileAPI
        super.init(nibName: nil, bundle: nil)
    }

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

    override func setupNavigationBar() {
        title = "个人空间配置"
        navigationItem.backButtonDisplayMode = .minimal
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        ProfileNavigationStyle.apply(to: navigationController, showsDivider: false)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        ProfileNavigationStyle.restore(navigationController)
    }

    override func setupUI() {
        view.backgroundColor = AppColor.pageBackground
        scrollView.alwaysBounceVertical = true
        view.addSubview(scrollView)
        view.addSubview(bottomBar)
        scrollView.addSubview(contentStack)
        bottomBar.addSubview(saveButton)

        contentStack.axis = .vertical
        contentStack.spacing = 16
        contentStack.layoutMargins = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
        contentStack.isLayoutMarginsRelativeArrangement = true

        bottomBar.backgroundColor = .white
        saveButton.isEnabled = false
        saveButton.snp.updateConstraints { make in make.height.equalTo(48) }

        configureProfileCard()
        configureLabelCard()
        configureBusinessHoursCard()
        configureDeviceCard()
        configureOrderStatusCard()
        configureScheduleCard()
    }

    override func setupConstraints() {
        bottomBar.snp.makeConstraints { make in
            make.leading.trailing.bottom.equalToSuperview()
        }
        saveButton.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(16)
            make.leading.trailing.equalToSuperview().inset(15)
            make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
        }
        scrollView.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.bottom.equalTo(bottomBar.snp.top)
        }
        contentStack.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.width.equalTo(scrollView.snp.width)
        }
    }

    override func bindActions() {
        viewModel.onStateChange = { [weak self] in
            Task { @MainActor in self?.applyViewModel() }
        }
        viewModel.onShowMessage = { [weak self] message in
            Task { @MainActor in self?.showToast(message) }
        }
        saveButton.addTarget(self, action: #selector(saveTapped), for: .touchUpInside)
        editProfileButton.addTarget(self, action: #selector(editProfileTapped), for: .touchUpInside)
        certificationExpandButton.addTarget(self, action: #selector(certificationExpandTapped), for: .touchUpInside)
        addDeviceButton.addTarget(self, action: #selector(addDeviceTapped), for: .touchUpInside)
        addScheduleButton.addTarget(self, action: #selector(addScheduleTapped), for: .touchUpInside)
        attrField.addTarget(self, action: #selector(labelInputChanged), for: .editingChanged)
        shootField.addTarget(self, action: #selector(labelInputChanged), for: .editingChanged)
        [businessStartButton, businessEndButton, holidayStartButton, holidayEndButton].forEach {
            $0.addTarget(self, action: #selector(timeButtonTapped(_:)), for: .touchUpInside)
        }
        let avatarTap = UITapGestureRecognizer(target: self, action: #selector(avatarTapped))
        avatarImageView.addGestureRecognizer(avatarTap)
        avatarImageView.isUserInteractionEnabled = true
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        Task { await viewModel.load(api: profileAPI) }
    }

    @MainActor
    private func applyViewModel() {
        if viewModel.isLoading {
            showLoading()
        } else {
            hideLoading()
        }
        saveButton.isEnabled = viewModel.hasChanges
        updateAvatar(urlString: viewModel.avatarURL)
        realNameLabel.text = "姓名: \(viewModel.realName)"
        nicknameField.text = viewModel.isEditingProfile ? viewModel.editingNickname : "昵称: \(viewModel.nickname)"
        nicknameField.isEnabled = viewModel.isEditingProfile
        introductionTextView.isEditable = viewModel.isEditingProfile
        introductionTextView.text = viewModel.introduction.isEmpty && !viewModel.isEditingProfile ? "暂无简介" : viewModel.introduction
        introductionTextView.textColor = viewModel.introduction.isEmpty && !viewModel.isEditingProfile ? UIColor(hex: 0xB6BECA) : UIColor(hex: 0x4B5563)
        updateIntroductionPlaceholder()
        editProfileButton.setImage(ProfileSpaceIcon.edit, for: .normal)
        avatarChangeOverlay.isHidden = !viewModel.isEditingProfile
        let visibleCertifications = viewModel.isCertificationExpanded
            ? viewModel.scenicCertification
            : Array(viewModel.scenicCertification.prefix(3))
        certificationWrap.setItems(visibleCertifications, style: .certification, removable: false)
        certificationExpandButton.isHidden = viewModel.scenicCertification.count <= 3
        certificationExpandButton.setTitle(viewModel.isCertificationExpanded ? "收起" : "展开", for: .normal)
        attrWrap.setItems(viewModel.attrLabels, style: .label, removable: true) { [weak self] label in
            self?.confirmDelete(title: "删除标签", message: "您确定要删除\(label)标签吗?") {
                self?.viewModel.removeAttrLabel(label)
            }
        }
        shootWrap.setItems(viewModel.shootLabels, style: .label, removable: true) { [weak self] label in
            self?.confirmDelete(title: "删除拍摄说明", message: "您确定要删除\(label)拍摄说明吗?") {
                self?.viewModel.removeShootLabel(label)
            }
        }
        businessStartButton.setTitle(viewModel.businessStartTime ?? "00:00", for: .normal)
        businessEndButton.setTitle(viewModel.businessEndTime ?? "00:00", for: .normal)
        holidayStartButton.setTitle(viewModel.holidayStartTime ?? "00:00", for: .normal)
        holidayEndButton.setTitle(viewModel.holidayEndTime ?? "00:00", for: .normal)
        deviceWrap.setItems(viewModel.cameraDevices, style: .device, removable: true) { [weak self] device in
            self?.confirmDelete(title: "删除设备", message: "您确定要删除\(device)设备吗?") {
                self?.viewModel.removeDevice(device)
            }
        }
        addDeviceButton.isHidden = viewModel.cameraDevices.count > 10
        deviceWrap.invalidateIntrinsicContentSize()
        deviceWrap.setNeedsLayout()
        updateStatusButtons()
        updateLabelAddButtons()
        rebuildDateButtons()
        rebuildSchedules()
    }

    private func configureProfileCard() {
        let card = makeCard()
        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .top
        row.spacing = 16

        avatarImageView.layer.cornerRadius = 58
        avatarImageView.clipsToBounds = true
        avatarImageView.backgroundColor = AppColor.primaryLight
        avatarImageView.contentMode = .scaleAspectFill
        avatarImageView.image = UIImage(named: "profile_avatar_default")
        avatarImageView.tintColor = UIColor(hex: 0xB6BECA)
        avatarImageView.accessibilityLabel = "头像"
        avatarImageView.snp.makeConstraints { make in
            make.width.height.equalTo(116)
        }
        avatarImageView.addSubview(avatarChangeOverlay)
        avatarChangeOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.35)
        avatarChangeOverlay.isHidden = true
        avatarChangeOverlay.addSubview(avatarChangeLabel)
        avatarChangeLabel.text = "点击更换"
        avatarChangeLabel.textColor = .white
        avatarChangeLabel.font = .systemFont(ofSize: 12, weight: .medium)
        avatarChangeOverlay.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        avatarChangeLabel.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }

        let textStack = UIStackView()
        textStack.axis = .vertical
        textStack.spacing = 0

        realNameLabel.font = .systemFont(ofSize: 14)
        realNameLabel.textColor = UIColor(hex: 0x4B5563)

        let nicknameRow = UIStackView()
        nicknameRow.axis = .horizontal
        nicknameRow.alignment = .center
        nicknameRow.spacing = 8
        nicknameField.font = .systemFont(ofSize: 16)
        nicknameField.textColor = .black
        nicknameField.borderStyle = .none
        nicknameField.placeholder = "请输入昵称"
        nicknameField.returnKeyType = .done
        nicknameField.delegate = self
        nicknameField.addTarget(self, action: #selector(nicknameChanged), for: .editingChanged)
        editProfileButton.tintColor = AppColor.primary
        editProfileButton.accessibilityLabel = "编辑个人信息"
        editProfileButton.snp.makeConstraints { make in
            make.width.height.equalTo(44)
        }
        nicknameRow.addArrangedSubview(nicknameField)
        nicknameRow.addArrangedSubview(editProfileButton)

        introductionTextView.font = .systemFont(ofSize: 14)
        introductionTextView.backgroundColor = .clear
        introductionTextView.textContainerInset = .zero
        introductionTextView.textContainer.lineFragmentPadding = 0
        introductionTextView.delegate = self
        introductionTextView.isScrollEnabled = false
        introductionTextView.snp.makeConstraints { make in
            make.height.greaterThanOrEqualTo(24)
        }
        introductionPlaceholderLabel.text = "请输入个人简介"
        introductionPlaceholderLabel.font = introductionTextView.font
        introductionPlaceholderLabel.textColor = UIColor(hex: 0xB6BECA)
        introductionTextView.addSubview(introductionPlaceholderLabel)
        introductionPlaceholderLabel.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
        }

        textStack.addArrangedSubview(realNameLabel)
        textStack.addArrangedSubview(nicknameRow)
        textStack.setCustomSpacing(8, after: nicknameRow)
        textStack.addArrangedSubview(introductionTextView)
        textStack.setCustomSpacing(4, after: introductionTextView)
        textStack.addArrangedSubview(certificationWrap)
        certificationExpandButton.setTitleColor(AppColor.primary, for: .normal)
        certificationExpandButton.titleLabel?.font = .systemFont(ofSize: 14)
        certificationExpandButton.contentHorizontalAlignment = .left
        certificationExpandButton.isHidden = true
        certificationExpandButton.snp.makeConstraints { make in
            make.height.greaterThanOrEqualTo(20)
        }
        textStack.addArrangedSubview(certificationExpandButton)

        row.addArrangedSubview(avatarImageView)
        row.addArrangedSubview(textStack)
        card.addArrangedSubview(row)
        contentStack.addArrangedSubview(card)
    }

    private func configureLabelCard() {
        let card = makeCard()
        card.spacing = 0
        let title = makeSectionTitle("标签")
        card.addArrangedSubview(title)
        card.setCustomSpacing(12, after: title)
        card.addArrangedSubview(attrWrap)
        card.setCustomSpacing(8, after: attrWrap)
        let attrRow = makeAddRow(
            field: attrField,
            button: attrAddButton,
            placeholder: "添加标签",
            action: #selector(addAttrTapped)
        )
        card.addArrangedSubview(attrRow)
        card.setCustomSpacing(20, after: attrRow)
        card.addArrangedSubview(shootWrap)
        card.setCustomSpacing(12, after: shootWrap)
        let shootRow = makeAddRow(
            field: shootField,
            button: shootAddButton,
            placeholder: "添加拍摄说明",
            action: #selector(addShootTapped)
        )
        card.addArrangedSubview(shootRow)
        card.setCustomSpacing(8, after: shootRow)
        let hint = UILabel()
        hint.text = "每个标签最多20个字符,最多8个标签"
        hint.font = .systemFont(ofSize: 12)
        hint.textColor = UIColor(hex: 0xB6BECA)
        card.addArrangedSubview(hint)
        contentStack.addArrangedSubview(card)
    }

    private func configureBusinessHoursCard() {
        let card = makeCard()
        let title = makeSectionTitle("营业时间")
        let workdayRow = makeTimeRow(title: "工作日营业时间", start: businessStartButton, end: businessEndButton)
        card.addArrangedSubview(title)
        card.addArrangedSubview(workdayRow)
        card.setCustomSpacing(20, after: workdayRow)
        card.addArrangedSubview(makeTimeRow(title: "法定节假日营业时间", start: holidayStartButton, end: holidayEndButton))
        contentStack.addArrangedSubview(card)
    }

    private func configureDeviceCard() {
        let card = makeCard()
        card.addArrangedSubview(makeSectionTitle("相机设备"))
        card.addArrangedSubview(deviceWrap)
        addDeviceButton.setTitle("＋  添加设备", for: .normal)
        addDeviceButton.setTitleColor(AppColor.primary, for: .normal)
        addDeviceButton.titleLabel?.font = .systemFont(ofSize: 14)
        addDeviceButton.layer.cornerRadius = 4
        addDeviceButton.setConfigurationContentInsets(
            NSDirectionalEdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)
        )
        addDeviceButton.contentHorizontalAlignment = .left
        addDeviceButton.snp.makeConstraints { make in make.height.equalTo(28) }
        deviceWrap.setTrailingView(addDeviceButton)
        contentStack.addArrangedSubview(card)
    }

    private func configureOrderStatusCard() {
        let card = makeCard()
        card.addArrangedSubview(makeSectionTitle("接单状态"))
        let labels = ["可接单", "暂停接单", "已约满", "下线"]
        for rowIndex in 0..<2 {
            let row = UIStackView()
            row.axis = .horizontal
            row.spacing = 15
            row.distribution = .fillEqually
            for index in 0..<2 {
                let status = rowIndex * 2 + index + 1
                let button = UIButton(type: .system)
                button.setTitle(labels[status - 1], for: .normal)
                button.titleLabel?.font = .systemFont(ofSize: 14)
                button.tag = status
                button.layer.cornerRadius = 8
                button.layer.borderWidth = 1
                button.addTarget(self, action: #selector(statusTapped(_:)), for: .touchUpInside)
                button.snp.makeConstraints { make in
                    make.height.equalTo(52)
                }
                statusButtons.append(button)
                row.addArrangedSubview(button)
            }
            card.addArrangedSubview(row)
        }
        contentStack.addArrangedSubview(card)
    }

    private func configureScheduleCard() {
        let card = makeCard()
        card.addArrangedSubview(makeSectionTitle("日程安排"))
        dateStack.axis = .horizontal
        dateStack.spacing = 4
        dateStack.distribution = .fillEqually
        card.addArrangedSubview(dateStack)

        let header = UIStackView()
        header.axis = .horizontal
        header.alignment = .center
        header.spacing = 12
        selectedDateLabel.font = .systemFont(ofSize: 14)
        selectedDateLabel.textColor = UIColor(hex: 0x252525)
        addScheduleButton.setTitle("添加日程", for: .normal)
        addScheduleButton.setTitleColor(AppColor.primary, for: .normal)
        addScheduleButton.titleLabel?.font = .systemFont(ofSize: 14)
        header.addArrangedSubview(selectedDateLabel)
        header.addArrangedSubview(UIView())
        header.addArrangedSubview(addScheduleButton)
        card.addArrangedSubview(header)
        card.setCustomSpacing(8, after: header)

        scheduleStack.axis = .vertical
        scheduleStack.spacing = 12
        card.addArrangedSubview(scheduleStack)
        contentStack.addArrangedSubview(card)
    }

    private func makeCard() -> UIStackView {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 16
        stack.backgroundColor = .white
        stack.layer.cornerRadius = 12
        stack.clipsToBounds = true
        stack.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        stack.isLayoutMarginsRelativeArrangement = true
        return stack
    }

    private func makeSectionTitle(_ title: String) -> UILabel {
        let label = UILabel()
        label.text = title
        label.font = .systemFont(ofSize: 14, weight: .medium)
        label.textColor = ["相机设备", "接单状态", "日程安排"].contains(title)
            ? UIColor(hex: 0x4B5563)
            : UIColor(hex: 0x252525)
        return label
    }

    private func makeAddRow(
        field: UITextField,
        button: UIButton,
        placeholder: String,
        action: Selector
    ) -> UIView {
        let row = UIStackView()
        row.axis = .horizontal
        row.spacing = 8
        row.alignment = .fill
        field.placeholder = placeholder
        field.font = .systemFont(ofSize: 14)
        field.returnKeyType = .done
        field.delegate = self
        field.layer.cornerRadius = 8
        field.layer.borderColor = AppColor.border.cgColor
        field.layer.borderWidth = 1
        field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
        field.leftViewMode = .always
        field.snp.makeConstraints { make in
            make.height.equalTo(36)
        }
        button.setTitle("添加标签", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.setTitleColor(.white, for: .disabled)
        button.backgroundColor = AppColor.primary
        button.titleLabel?.font = .systemFont(ofSize: 14)
        button.layer.cornerRadius = 4
        button.addTarget(self, action: action, for: .touchUpInside)
        button.isEnabled = false
        button.snp.makeConstraints { make in
            make.width.equalTo(80)
            make.height.equalTo(36)
        }
        row.addArrangedSubview(field)
        row.addArrangedSubview(button)
        return row
    }

    private func makeTimeRow(title: String, start: ProfileTimeButton, end: ProfileTimeButton) -> UIView {
        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .center
        row.spacing = 10
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 14)
        titleLabel.textColor = UIColor(hex: 0x4B5563)
        titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
        let toLabel = UILabel()
        toLabel.text = "至"
        toLabel.font = .systemFont(ofSize: 14)
        toLabel.textColor = UIColor(hex: 0x4B5563)
        let spacer = UIView()
        row.addArrangedSubview(titleLabel)
        row.addArrangedSubview(spacer)
        row.addArrangedSubview(start)
        row.addArrangedSubview(toLabel)
        row.addArrangedSubview(end)
        [start, end].forEach { button in
            button.snp.makeConstraints { make in
                make.width.equalTo(60)
                make.height.equalTo(36)
            }
        }
        return row
    }

    private func updateAvatar(urlString: String?) {
        let trimmed = urlString?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        guard !trimmed.isEmpty else {
            avatarImageView.kf.cancelDownloadTask()
            avatarImageView.backgroundColor = AppColor.primaryLight
            avatarImageView.image = UIImage(named: "profile_avatar_default")
            avatarImageView.tintColor = UIColor(hex: 0xB6BECA)
            avatarImageView.contentMode = .scaleAspectFit
            return
        }
        avatarImageView.contentMode = .scaleAspectFill
        avatarImageView.loadRemoteImage(urlString: trimmed)
    }

    private func updateIntroductionPlaceholder() {
        introductionPlaceholderLabel.isHidden = !viewModel.isEditingProfile || !introductionTextView.text.isEmpty
        introductionTextView.layer.borderWidth = viewModel.isEditingProfile ? 1 : 0
        introductionTextView.layer.borderColor = UIColor(hex: 0xEEEEEE).cgColor
        introductionTextView.layer.cornerRadius = 4
        introductionTextView.textContainerInset = viewModel.isEditingProfile
            ? UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
            : .zero
        introductionPlaceholderLabel.snp.remakeConstraints { make in
            make.top.equalToSuperview().offset(viewModel.isEditingProfile ? 8 : 0)
            make.leading.equalToSuperview().offset(viewModel.isEditingProfile ? 8 : 0)
            make.trailing.equalToSuperview().inset(viewModel.isEditingProfile ? 8 : 0)
        }
    }

    private func updateLabelAddButtons() {
        let pairs = [(attrField, attrAddButton), (shootField, shootAddButton)]
        for (field, button) in pairs {
            let enabled = !(field.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            button.isEnabled = enabled
            button.backgroundColor = enabled ? AppColor.primary : AppColor.buttonDisabled
        }
    }

    private func updateStatusButtons() {
        for button in statusButtons {
            let selected = button.tag == viewModel.acceptOrderStatus
            button.backgroundColor = selected ? AppColor.primary : .white
            button.layer.borderColor = (selected ? AppColor.primary : UIColor(hex: 0xB6BECA)).cgColor
            button.setTitleColor(selected ? .white : UIColor(hex: 0x4B5563), for: .normal)
            button.accessibilityTraits = selected ? [.button, .selected] : .button
        }
    }

    private func rebuildDateButtons() {
        dateStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
        for date in viewModel.weekDates {
            let button = ProfileDateButton()
            button.configure(
                weekday: ProfileSpaceSettingsViewModel.displayWeekday(date),
                day: ProfileSpaceSettingsViewModel.displayDay(date),
                selected: Calendar.current.isDate(date, inSameDayAs: viewModel.selectedDate)
            )
            button.date = date
            button.addTarget(self, action: #selector(dateTapped(_:)), for: .touchUpInside)
            dateStack.addArrangedSubview(button)
        }
        selectedDateLabel.text = ProfileSpaceSettingsViewModel.displayDate(viewModel.selectedDate)
    }

    private func rebuildSchedules() {
        scheduleStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
        let schedules = viewModel.selectedDateSchedules
        guard !schedules.isEmpty else {
            let empty = UILabel()
            empty.text = "该日无日程"
            empty.textColor = AppColor.textTertiary
            empty.font = .systemFont(ofSize: 14)
            empty.textAlignment = .center
            empty.snp.makeConstraints { make in
                make.height.equalTo(76)
            }
            scheduleStack.addArrangedSubview(empty)
            return
        }
        for schedule in schedules {
            let item = ProfileScheduleItemView(schedule: schedule)
            item.onDelete = { [weak self] in
                self?.confirmDelete(title: "删除日程", message: "您确定要删除该日程吗?") {
                    guard let self else { return }
                    Task { await self.viewModel.deleteSchedule(id: schedule.id, api: self.profileAPI) }
                }
            }
            item.onCall = { [weak self] phone in
                self?.callPhone(phone)
            }
            scheduleStack.addArrangedSubview(item)
        }
    }

    private func confirmDelete(title: String, message: String, onConfirm: @escaping () -> Void) {
        let dialog = ProfileDeleteDialogViewController(title: title, message: message, onConfirm: onConfirm)
        present(dialog, animated: false)
    }

    private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
        let dialog = ProfileTimePickerDialogViewController(
            initialDate: date(fromTime: initialTime ?? "00:00"),
            onConfirm: { [weak self] date in
                guard let self else { return }
                onConfirm(timeString(from: date))
            }
        )
        present(dialog, animated: false)
    }

    private func date(fromTime value: String) -> Date {
        var components = DateComponents()
        let pieces = value.split(separator: ":").compactMap { Int($0) }
        components.hour = pieces.first ?? 0
        components.minute = pieces.dropFirst().first ?? 0
        return Calendar.current.date(from: components) ?? Date()
    }

    private func timeString(from date: Date) -> String {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "HH:mm"
        return formatter.string(from: date)
    }

    private func callPhone(_ phone: String) {
        let digits = phone.trimmingCharacters(in: .whitespacesAndNewlines)
        guard let url = URL(string: "tel://\(digits)"), UIApplication.shared.canOpenURL(url) else { return }
        UIApplication.shared.open(url)
    }

    @objc private func saveTapped() {
        view.endEditing(true)
        Task { await viewModel.save(api: profileAPI) }
    }

    @objc private func editProfileTapped() {
        view.endEditing(true)
        Task { await viewModel.toggleProfileEditing(api: profileAPI) }
    }

    @objc private func nicknameChanged() {
        viewModel.updateEditingNickname(nicknameField.text ?? "")
    }

    @objc private func addAttrTapped() {
        viewModel.addAttrLabel(attrField.text ?? "")
        attrField.text = ""
        updateLabelAddButtons()
    }

    @objc private func addShootTapped() {
        viewModel.addShootLabel(shootField.text ?? "")
        shootField.text = ""
        updateLabelAddButtons()
    }

    @objc private func labelInputChanged() {
        updateLabelAddButtons()
    }

    @objc private func certificationExpandTapped() {
        viewModel.toggleCertificationExpanded()
    }

    @objc private func timeButtonTapped(_ sender: ProfileTimeButton) {
        if sender === businessStartButton {
            showTimePicker(initialTime: viewModel.businessStartTime) { [weak self] in self?.viewModel.updateBusinessStartTime($0) }
        } else if sender === businessEndButton {
            showTimePicker(initialTime: viewModel.businessEndTime) { [weak self] in self?.viewModel.updateBusinessEndTime($0) }
        } else if sender === holidayStartButton {
            showTimePicker(initialTime: viewModel.holidayStartTime) { [weak self] in self?.viewModel.updateHolidayStartTime($0) }
        } else {
            showTimePicker(initialTime: viewModel.holidayEndTime) { [weak self] in self?.viewModel.updateHolidayEndTime($0) }
        }
    }

    @objc private func addDeviceTapped() {
        let dialog = ProfileDeviceDialogViewController { [weak self] value in
            self?.viewModel.addDevice(value)
        }
        present(dialog, animated: false)
    }

    @objc private func statusTapped(_ sender: UIButton) {
        viewModel.updateAcceptOrderStatus(sender.tag)
    }

    @objc private func dateTapped(_ sender: ProfileDateButton) {
        guard let date = sender.date else { return }
        viewModel.selectDate(date)
    }

    @objc private func addScheduleTapped() {
        let controller = ProfileAddScheduleViewController(
            selectedDate: viewModel.selectedDate,
            viewModel: viewModel,
            profileAPI: profileAPI
        )
        navigationController?.pushViewController(controller, animated: true)
    }

    @objc private func avatarTapped() {
        guard viewModel.isEditingProfile else { return }
        var configuration = PHPickerConfiguration(photoLibrary: .shared())
        configuration.filter = .images
        configuration.selectionLimit = 1
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        present(picker, animated: true)
    }
}

extension ProfileSpaceSettingsViewController: UITextFieldDelegate, UITextViewDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    func textField(
        _ textField: UITextField,
        shouldChangeCharactersIn range: NSRange,
        replacementString string: String
    ) -> Bool {
        guard textField === attrField || textField === shootField else { return true }
        let current = textField.text ?? ""
        guard let swiftRange = Range(range, in: current) else { return false }
        return current.replacingCharacters(in: swiftRange, with: string).count <= 20
    }

    func textViewDidChange(_ textView: UITextView) {
        viewModel.updateIntroduction(textView.text)
        updateIntroductionPlaceholder()
    }

    func textView(
        _ textView: UITextView,
        shouldChangeTextIn range: NSRange,
        replacementText text: String
    ) -> Bool {
        guard text == "\n" else { return true }
        textView.resignFirstResponder()
        return false
    }
}

extension ProfileSpaceSettingsViewController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)
        guard let provider = results.first?.itemProvider else { return }
        Task {
            do {
                let image = try await ProfileAvatarPickerLoader.loadImage(from: provider)
                await MainActor.run {
                    let crop = ProfileAvatarCropViewController(image: image) { [weak self] data in
                        guard let self else { return }
                        Task {
                            await self.viewModel.uploadAvatar(
                                data: data,
                                fileName: "avatar_\(UUID().uuidString).jpg",
                                api: self.profileAPI
                            )
                        }
                    }
                    self.present(crop, animated: true)
                }
            } catch {
                await MainActor.run { showToast(error.localizedDescription) }
            }
        }
    }
}

/// 添加日程页面，对齐 Android `AddScheduleScreen`。
final class ProfileAddScheduleViewController: BaseViewController {

    private let selectedDate: Date
    private let viewModel: ProfileSpaceSettingsViewModel
    private let profileAPI: ProfileAPI

    private let contentStack = UIStackView()
    private let bottomBar = UIView()
    private let saveButton = AppButton(title: "保存")
    private let nameField = UITextField()
    private let startButton = ProfileTimeButton()
    private let endButton = ProfileTimeButton()
    private let orderButton = UIButton(type: .system)
    private let remarkTextView = UITextView()
    private let remarkPlaceholderLabel = UILabel()
    private let countLabel = UILabel()
    private var selectedOrderNumber: String?

    init(selectedDate: Date, viewModel: ProfileSpaceSettingsViewModel, profileAPI: ProfileAPI) {
        self.selectedDate = selectedDate
        self.viewModel = viewModel
        self.profileAPI = profileAPI
        super.init(nibName: nil, bundle: nil)
    }

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

    override func setupNavigationBar() {
        title = "添加日程"
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        ProfileNavigationStyle.apply(to: navigationController, showsDivider: true)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        ProfileNavigationStyle.restore(navigationController)
    }

    override func setupUI() {
        view.addSubview(contentStack)
        view.addSubview(bottomBar)
        bottomBar.addSubview(saveButton)
        bottomBar.backgroundColor = .white
        view.backgroundColor = AppColor.pageBackground
        saveButton.isEnabled = false
        saveButton.snp.updateConstraints { make in make.height.equalTo(48) }

        contentStack.axis = .vertical
        contentStack.spacing = 20
        contentStack.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        contentStack.isLayoutMarginsRelativeArrangement = true
        contentStack.backgroundColor = .white
        contentStack.layer.cornerRadius = 12

        contentStack.addArrangedSubview(makeTitle("日程安排"))
        configureNameField()
        contentStack.addArrangedSubview(nameField)
        contentStack.addArrangedSubview(makeTimeRow())
        configureOrderButton()
        contentStack.addArrangedSubview(orderButton)
        configureRemarkView()
        contentStack.addArrangedSubview(remarkTextView)
        contentStack.addArrangedSubview(countLabel)
    }

    override func setupConstraints() {
        bottomBar.snp.makeConstraints { make in
            make.leading.trailing.bottom.equalToSuperview()
        }
        saveButton.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(16)
            make.leading.trailing.equalToSuperview().inset(15)
            make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
        }
        contentStack.snp.makeConstraints { make in
            make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
            make.leading.trailing.equalToSuperview().inset(16)
        }
        nameField.snp.makeConstraints { make in
            make.height.equalTo(44)
        }
        orderButton.snp.makeConstraints { make in
            make.height.equalTo(44)
        }
        remarkTextView.snp.makeConstraints { make in
            make.height.equalTo(100)
        }
    }

    override func bindActions() {
        saveButton.addTarget(self, action: #selector(saveTapped), for: .touchUpInside)
        startButton.addTarget(self, action: #selector(startTapped), for: .touchUpInside)
        endButton.addTarget(self, action: #selector(endTapped), for: .touchUpInside)
        orderButton.addTarget(self, action: #selector(orderTapped), for: .touchUpInside)
        remarkTextView.delegate = self
        nameField.addTarget(self, action: #selector(formValueChanged), for: .editingChanged)
    }

    private func makeTitle(_ title: String) -> UILabel {
        let label = UILabel()
        label.text = title
        label.font = .systemFont(ofSize: 14, weight: .medium)
        label.textColor = .black
        return label
    }

    private func configureNameField() {
        nameField.placeholder = "请输入日程名称"
        nameField.font = .systemFont(ofSize: 14)
        nameField.returnKeyType = .done
        nameField.delegate = self
        nameField.layer.borderColor = AppColor.border.cgColor
        nameField.layer.borderWidth = 1
        nameField.layer.cornerRadius = 8
        nameField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
        nameField.leftViewMode = .always
    }

    private func makeTimeRow() -> UIView {
        let row = UIStackView()
        row.axis = .horizontal
        row.alignment = .center
        row.spacing = 12
        let title = UILabel()
        title.text = "时间"
        title.font = .systemFont(ofSize: 14)
        title.textColor = UIColor(hex: 0x4B5563)
        let toLabel = UILabel()
        toLabel.text = "至"
        toLabel.font = .systemFont(ofSize: 14)
        toLabel.textColor = UIColor(hex: 0x4B5563)
        [startButton, endButton].forEach { button in
            button.setTitle("请选择", for: .normal)
            button.snp.makeConstraints { make in
                make.width.equalTo(60)
                make.height.equalTo(36)
            }
        }
        row.addArrangedSubview(title)
        row.addArrangedSubview(UIView())
        row.addArrangedSubview(startButton)
        row.addArrangedSubview(toLabel)
        row.addArrangedSubview(endButton)
        return row
    }

    private func configureOrderButton() {
        orderButton.setTitle("关联订单 (可选)", for: .normal)
        orderButton.setTitleColor(.black, for: .normal)
        orderButton.contentHorizontalAlignment = .left
        orderButton.titleLabel?.font = .systemFont(ofSize: 14)
        orderButton.layer.borderColor = AppColor.border.cgColor
        orderButton.layer.borderWidth = 1
        orderButton.layer.cornerRadius = 8
        var configuration = UIButton.Configuration.plain()
        configuration.title = "关联订单 (可选)"
        configuration.image = UIImage(systemName: "chevron.down")
        configuration.imagePlacement = .trailing
        configuration.imagePadding = 8
        configuration.baseForegroundColor = .black
        configuration.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
        orderButton.configuration = configuration
    }

    private func configureRemarkView() {
        remarkTextView.font = .systemFont(ofSize: 14)
        remarkTextView.textColor = UIColor(hex: 0x4B5563)
        remarkTextView.layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
        remarkTextView.layer.borderWidth = 1
        remarkTextView.layer.cornerRadius = 8
        remarkTextView.textContainerInset = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12)
        remarkTextView.textContainer.lineFragmentPadding = 0
        remarkPlaceholderLabel.text = "请输入日程内容"
        remarkPlaceholderLabel.font = remarkTextView.font
        remarkPlaceholderLabel.textColor = UIColor(hex: 0xB6BECA)
        remarkTextView.addSubview(remarkPlaceholderLabel)
        remarkPlaceholderLabel.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(10)
            make.leading.equalToSuperview().offset(12)
            make.trailing.equalToSuperview().inset(12)
        }
        countLabel.text = "0/200"
        countLabel.textAlignment = .right
        countLabel.font = .systemFont(ofSize: 12)
        countLabel.textColor = UIColor(hex: 0xB6BECA)
    }

    private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
        let date = Self.date(from: initialTime)
        let dialog = ProfileTimePickerDialogViewController(initialDate: date) { selectedDate in
            let formatter = DateFormatter()
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.dateFormat = "HH:mm"
            onConfirm(formatter.string(from: selectedDate))
        }
        present(dialog, animated: false)
    }

    @objc private func startTapped() {
        showTimePicker(initialTime: startButton.title(for: .normal)) { [weak self] value in
            if let end = self?.endButton.title(for: .normal), end != "请选择", !ProfileSpaceSettingsViewModel.isStart(value, before: end) {
                self?.showToast("结束时间必须晚于开始时间，已清除结束时间")
                self?.endButton.setTitle("请选择", for: .normal)
            }
            self?.startButton.setTitle(value, for: .normal)
            self?.updateSaveEnabled()
        }
    }

    @objc private func endTapped() {
        showTimePicker(initialTime: endButton.title(for: .normal)) { [weak self] value in
            if let start = self?.startButton.title(for: .normal), start != "请选择", !ProfileSpaceSettingsViewModel.isStart(start, before: value) {
                self?.showToast("结束时间必须晚于开始时间")
                return
            }
            self?.endButton.setTitle(value, for: .normal)
            self?.updateSaveEnabled()
        }
    }

    @objc private func orderTapped() {
        let controller = TaskOrderSelectViewController(initialOrderNumber: selectedOrderNumber)
        controller.onConfirmed = { [weak self] orderNumber in
            self?.selectedOrderNumber = orderNumber
            self?.applySelectedOrder()
        }
        navigationController?.pushViewController(controller, animated: true)
    }

    @objc private func saveTapped() {
        view.endEditing(true)
        let start = startButton.title(for: .normal) == "请选择" ? nil : startButton.title(for: .normal)
        let end = endButton.title(for: .normal) == "请选择" ? nil : endButton.title(for: .normal)
        Task {
            let success = await viewModel.addSchedule(
                name: nameField.text ?? "",
                remark: remarkTextView.text ?? "",
                startTime: start,
                endTime: end,
                scheduleDate: selectedDate,
            orderNumber: selectedOrderNumber,
                api: profileAPI
            )
            if success {
                _ = await MainActor.run { navigationController?.popViewController(animated: true) }
            }
        }
    }

    @objc private func formValueChanged() {
        updateSaveEnabled()
    }

    private func updateSaveEnabled() {
        let name = (nameField.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
        let start = startButton.title(for: .normal)
        let end = endButton.title(for: .normal)
        saveButton.isEnabled = !name.isEmpty
            && start != nil && start != "请选择"
            && end != nil && end != "请选择"
            && ProfileSpaceSettingsViewModel.isStart(start ?? "", before: end ?? "")
    }

    private func applySelectedOrder() {
        var configuration = orderButton.configuration ?? .plain()
        configuration.title = selectedOrderNumber ?? "关联订单 (可选)"
        configuration.baseForegroundColor = .black
        orderButton.configuration = configuration
    }

    private static func date(from time: String?) -> Date {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "HH:mm"
        return formatter.date(from: time ?? "")
            ?? formatter.date(from: "00:00")
            ?? Date(timeIntervalSince1970: 0)
    }
}

extension ProfileAddScheduleViewController: UITextFieldDelegate, UITextViewDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        if textView.text.count > 200 {
            textView.text = String(textView.text.prefix(200))
        }
        remarkPlaceholderLabel.isHidden = !textView.text.isEmpty
        countLabel.text = "\(textView.text.count)/200"
    }

    func textView(
        _ textView: UITextView,
        shouldChangeTextIn range: NSRange,
        replacementText text: String
    ) -> Bool {
        guard text == "\n" else { return true }
        textView.resignFirstResponder()
        return false
    }
}

/// 个人空间配置时间按钮。
private final class ProfileTimeButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        titleLabel?.font = .systemFont(ofSize: 14)
        titleLabel?.lineBreakMode = .byClipping
        setTitleColor(UIColor(hex: 0x4B5563), for: .normal)
        backgroundColor = AppColor.inputBackground
        layer.cornerRadius = 4
        setConfigurationContentInsets(
            NSDirectionalEdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)
        )
        contentHorizontalAlignment = .center
    }

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

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        bounds.insetBy(dx: -4, dy: -4).contains(point)
    }
}

/// 个人空间配置日期按钮。
private final class ProfileDateButton: UIButton {
    var date: Date?

    private let weekdayLabel = UILabel()
    private let dayContainer = UIView()
    private let dayLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        weekdayLabel.font = .systemFont(ofSize: 14)
        weekdayLabel.textAlignment = .center
        weekdayLabel.textColor = UIColor(hex: 0x4B5563)
        dayContainer.layer.cornerRadius = 4
        dayContainer.isUserInteractionEnabled = false
        dayLabel.font = .systemFont(ofSize: 14)
        dayLabel.textAlignment = .center
        dayContainer.addSubview(dayLabel)
        addSubview(weekdayLabel)
        addSubview(dayContainer)
        weekdayLabel.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.height.equalTo(18)
        }
        dayContainer.snp.makeConstraints { make in
            make.top.equalTo(weekdayLabel.snp.bottom).offset(12)
            make.centerX.equalToSuperview()
            make.width.height.equalTo(36)
            make.bottom.equalToSuperview()
        }
        dayLabel.snp.makeConstraints { make in make.center.equalToSuperview() }
    }

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

    func configure(weekday: String, day: String, selected: Bool) {
        weekdayLabel.text = weekday
        dayLabel.text = day
        dayLabel.textColor = selected ? .white : UIColor(hex: 0x4B5563)
        dayContainer.backgroundColor = selected ? AppColor.primary : AppColor.inputBackground
        accessibilityLabel = "星期\(weekday)，\(day)日"
        accessibilityTraits = selected ? [.button, .selected] : .button
    }
}

/// 个人空间配置标签换行容器。
private final class ProfileTagWrapView: UIView {
    enum Style {
        case label
        case certification
        case device
    }

    private var itemViews: [UIView] = []
    private var removeHandler: ((String) -> Void)?
    private weak var trailingView: UIView?

    func setTrailingView(_ view: UIView) {
        trailingView = view
        if view.superview !== self {
            addSubview(view)
        }
        itemViews.append(view)
        invalidateIntrinsicContentSize()
        setNeedsLayout()
    }

    func setItems(_ items: [String], style: Style, removable: Bool, onRemove: ((String) -> Void)? = nil) {
        subviews.forEach { $0.removeFromSuperview() }
        itemViews = items.map { item in
            let view = makeItem(title: item, style: style, removable: removable)
            addSubview(view)
            return view
        }
        if let trailingView {
            addSubview(trailingView)
            itemViews.append(trailingView)
        }
        removeHandler = onRemove
        invalidateIntrinsicContentSize()
        setNeedsLayout()
    }

    override var intrinsicContentSize: CGSize {
        CGSize(width: UIView.noIntrinsicMetric, height: measuredHeight(for: bounds.width > 0 ? bounds.width : UIScreen.main.bounds.width - 62))
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        layoutItems(width: bounds.width)
    }

    private func layoutItems(width: CGFloat) {
        let spacing: CGFloat = 8
        var x: CGFloat = 0
        var y: CGFloat = 0
        var rowHeight: CGFloat = 0
        for view in itemViews {
            guard !view.isHidden else { continue }
            let size = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            if x > 0, x + size.width > width {
                x = 0
                y += rowHeight + spacing
                rowHeight = 0
            }
            view.frame = CGRect(x: x, y: y, width: size.width, height: size.height)
            x += size.width + spacing
            rowHeight = max(rowHeight, size.height)
        }
    }

    private func measuredHeight(for width: CGFloat) -> CGFloat {
        guard !itemViews.isEmpty else { return 1 }
        let spacing: CGFloat = 8
        var x: CGFloat = 0
        var height: CGFloat = 0
        var rowHeight: CGFloat = 0
        for view in itemViews {
            guard !view.isHidden else { continue }
            let size = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            if x > 0, x + size.width > width {
                x = 0
                height += rowHeight + spacing
                rowHeight = 0
            }
            x += size.width + spacing
            rowHeight = max(rowHeight, size.height)
        }
        return height + rowHeight
    }

    private func makeItem(title: String, style: Style, removable: Bool) -> UIView {
        let button = UIButton(type: .system)
        button.setTitle(removable ? "\(title)  ×" : title, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: style == .device ? 14 : 12)
        let horizontalInset: CGFloat = style == .device ? 8 : 4
        button.setConfigurationContentInsets(
            NSDirectionalEdgeInsets(top: 2, leading: horizontalInset, bottom: 2, trailing: horizontalInset)
        )
        button.layer.cornerRadius = 4
        switch style {
        case .label:
            button.backgroundColor = AppColor.primaryLight
            button.setTitleColor(AppColor.primary, for: .normal)
        case .certification:
            button.backgroundColor = AppColor.warningBackground
            button.setTitleColor(AppColor.warning, for: .normal)
        case .device:
            button.backgroundColor = AppColor.inputBackground
            button.setTitleColor(UIColor(hex: 0x4B5563), for: .normal)
            button.snp.makeConstraints { make in
                make.height.equalTo(28)
            }
        }
        if removable {
            button.addAction(UIAction { [weak self] _ in
                self?.removeHandler?(title)
            }, for: .touchUpInside)
        }
        return button
    }
}

/// 个人空间配置日程列表项。
private final class ProfileScheduleItemView: UIStackView {
    var onDelete: (() -> Void)?
    var onCall: ((String) -> Void)?

    private let schedule: PhotographerSchedule

    init(schedule: PhotographerSchedule) {
        self.schedule = schedule
        super.init(frame: .zero)
        setupUI()
    }

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

    private func setupUI() {
        axis = .vertical
        spacing = 4
        backgroundColor = UIColor(hex: 0xF9FAFB)
        layer.cornerRadius = 8
        layoutMargins = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
        isLayoutMarginsRelativeArrangement = true

        let top = UIStackView()
        top.axis = .horizontal
        top.spacing = 8
        let title = UILabel()
        title.text = "\(schedule.startTime) - \(schedule.endTime) \(schedule.name)"
        title.font = .systemFont(ofSize: 14)
        title.textColor = UIColor(hex: 0x4B5563)
        title.numberOfLines = 1
        let delete = UIButton(type: .system)
        delete.setTitle("删除", for: .normal)
        delete.setTitleColor(UIColor(hex: 0xFF5656), for: .normal)
        delete.titleLabel?.font = .systemFont(ofSize: 14)
        delete.addAction(UIAction { [weak self] _ in self?.onDelete?() }, for: .touchUpInside)
        top.addArrangedSubview(title)
        top.addArrangedSubview(delete)
        addArrangedSubview(top)

        if let order = schedule.orderNumber, !order.isEmpty {
            addArrangedSubview(makeLine("订单号: \(order)"))
        }
        if let phone = schedule.userPhone, !phone.isEmpty {
            let row = UIStackView()
            row.axis = .horizontal
            row.spacing = 16
            row.addArrangedSubview(makeLine("客户手机号: \(Self.maskPhone(phone))"))
            let call = UIButton(type: .system)
            call.setTitle("拨打", for: .normal)
            call.setTitleColor(AppColor.primary, for: .normal)
            call.titleLabel?.font = .systemFont(ofSize: 14)
            call.addAction(UIAction { [weak self] _ in self?.onCall?(phone) }, for: .touchUpInside)
            row.addArrangedSubview(call)
            addArrangedSubview(row)
        }
        if !schedule.remark.isEmpty {
            addArrangedSubview(makeLine("备注信息: \(schedule.remark)"))
        }
    }

    private func makeLine(_ text: String) -> UILabel {
        let label = UILabel()
        label.text = text
        label.font = .systemFont(ofSize: 14)
        label.textColor = UIColor(hex: 0x4B5563)
        label.numberOfLines = 1
        return label
    }

    private static func maskPhone(_ phone: String) -> String {
        guard phone.count >= 11 else { return phone }
        let prefix = phone.prefix(3)
        let suffix = phone.suffix(phone.count - 7)
        return "\(prefix)****\(suffix)"
    }
}

/// 头像选择器数据加载器。
private enum ProfileAvatarPickerLoader {
    static func loadImage(from provider: NSItemProvider) async throws -> UIImage {
        try await withCheckedThrowingContinuation { continuation in
            guard provider.canLoadObject(ofClass: UIImage.self) else {
                continuation.resume(throwing: OSSUploadError.unsupportedFileType)
                return
            }
            provider.loadObject(ofClass: UIImage.self) { object, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let image = object as? UIImage else {
                    continuation.resume(throwing: OSSUploadError.emptyFile)
                    return
                }
                continuation.resume(returning: image)
            }
        }
    }
}
