//
//  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 ossUploadService: OSSUploadService

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

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

    private let attrWrap = ProfileTagWrapView()
    private let shootWrap = ProfileTagWrapView()
    private let attrField = UITextField()
    private let shootField = UITextField()

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

    private let deviceWrap = ProfileTagWrapView()
    private let addDeviceButton = UIButton(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 = NetworkServices.shared.profileAPI,
        ossUploadService: OSSUploadService = NetworkServices.shared.ossUploadService
    ) {
        self.viewModel = viewModel
        self.profileAPI = profileAPI
        self.ossUploadService = ossUploadService
        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 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

        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.edges.equalToSuperview().inset(UIEdgeInsets(top: 16, left: 15, bottom: 16, right: 15))
        }
        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)
        addDeviceButton.addTarget(self, action: #selector(addDeviceTapped), for: .touchUpInside)
        addScheduleButton.addTarget(self, action: #selector(addScheduleTapped), for: .touchUpInside)
        [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
        avatarImageView.loadRemoteImage(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: 0x565656)
        editProfileButton.setImage(UIImage(systemName: viewModel.isEditingProfile ? "checkmark" : "square.and.pencil"), for: .normal)
        certificationWrap.setItems(viewModel.scenicCertification, style: .certification, removable: false)
        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
        updateStatusButtons()
        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.inputBackground
        avatarImageView.contentMode = .scaleAspectFill
        avatarImageView.image = UIImage(systemName: "person.crop.circle.fill")
        avatarImageView.tintColor = UIColor(hex: 0xB6BECA)
        avatarImageView.snp.makeConstraints { make in
            make.width.height.equalTo(116)
        }

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

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

        let nicknameRow = UIStackView()
        nicknameRow.axis = .horizontal
        nicknameRow.alignment = .center
        nicknameRow.spacing = 8
        nicknameField.font = .systemFont(ofSize: 16)
        nicknameField.textColor = .black
        nicknameField.borderStyle = .none
        nicknameField.addTarget(self, action: #selector(nicknameChanged), for: .editingChanged)
        editProfileButton.tintColor = AppColor.primary
        editProfileButton.snp.makeConstraints { make in
            make.width.height.equalTo(28)
        }
        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)
        }

        textStack.addArrangedSubview(realNameLabel)
        textStack.addArrangedSubview(nicknameRow)
        textStack.addArrangedSubview(introductionTextView)
        textStack.addArrangedSubview(certificationWrap)

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

    private func configureLabelCard() {
        let card = makeCard()
        card.addArrangedSubview(makeSectionTitle("标签"))
        card.addArrangedSubview(attrWrap)
        card.addArrangedSubview(makeAddRow(field: attrField, placeholder: "添加标签", action: #selector(addAttrTapped)))
        card.addArrangedSubview(shootWrap)
        card.addArrangedSubview(makeAddRow(field: shootField, placeholder: "添加拍摄说明", action: #selector(addShootTapped)))
        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()
        card.addArrangedSubview(makeSectionTitle("营业时间"))
        card.addArrangedSubview(makeTimeRow(title: "工作日营业时间", start: businessStartButton, end: businessEndButton))
        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.borderColor = AppColor.primary.cgColor
        addDeviceButton.layer.borderWidth = 1
        addDeviceButton.layer.cornerRadius = 4
        addDeviceButton.snp.makeConstraints { make in
            make.height.equalTo(32)
        }
        card.addArrangedSubview(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)

        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 = UIColor(hex: 0x252525)
        return label
    }

    private func makeAddRow(field: UITextField, 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.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)
        }
        let button = UIButton(type: .system)
        button.setTitle("添加标签", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.backgroundColor = AppColor.primary
        button.titleLabel?.font = .systemFont(ofSize: 14)
        button.layer.cornerRadius = 4
        button.addTarget(self, action: action, for: .touchUpInside)
        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: 0x565656)
        titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        let toLabel = UILabel()
        toLabel.text = "至"
        toLabel.font = .systemFont(ofSize: 14)
        toLabel.textColor = UIColor(hex: 0x565656)
        row.addArrangedSubview(titleLabel)
        row.addArrangedSubview(start)
        row.addArrangedSubview(toLabel)
        row.addArrangedSubview(end)
        [start, end].forEach { button in
            button.snp.makeConstraints { make in
                make.width.equalTo(70)
                make.height.equalTo(36)
            }
        }
        return row
    }

    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: 0x565656), for: .normal)
        }
    }

    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 alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "确定", style: .destructive) { _ in onConfirm() })
        present(alert, animated: true)
    }

    private func showTimePicker(initialTime: String?, onConfirm: @escaping (String) -> Void) {
        let alert = UIAlertController(title: "选择时间", message: "\n\n\n\n\n\n\n", preferredStyle: .actionSheet)
        let picker = UIDatePicker()
        picker.datePickerMode = .time
        picker.preferredDatePickerStyle = .wheels
        picker.locale = Locale(identifier: "zh_CN")
        if let initialTime {
            picker.date = date(fromTime: initialTime)
        }
        alert.view.addSubview(picker)
        picker.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(16)
            make.top.equalToSuperview().offset(42)
            make.height.equalTo(160)
        }
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
            onConfirm(self.timeString(from: picker.date))
        })
        present(alert, animated: true)
    }

    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 = ""
    }

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

    @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 alert = UIAlertController(title: "添加设备", message: nil, preferredStyle: .alert)
        alert.addTextField { field in
            field.placeholder = "请输入设备名称"
        }
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in
            self?.viewModel.addDevice(alert.textFields?.first?.text ?? "")
        })
        present(alert, animated: true)
    }

    @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: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        viewModel.updateIntroduction(textView.text)
    }
}

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 data = try await ProfileAvatarPickerLoader.loadImageData(from: provider)
                let scenicId = AppStore.shared.currentScenicId
                let fileURL = try await ossUploadService.uploadUserAvatar(
                    data: data,
                    fileName: "avatar_\(UUID().uuidString).jpg",
                    scenicId: scenicId
                ) { _ in }
                try await profileAPI.updateUserInfo(nickname: nil, password: nil, avatar: fileURL)
                await MainActor.run {
                    viewModel.updateAvatarURL(fileURL)
                    showToast("修改成功")
                }
            } 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 countLabel = UILabel()

    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 setupUI() {
        view.addSubview(contentStack)
        view.addSubview(bottomBar)
        bottomBar.addSubview(saveButton)
        bottomBar.backgroundColor = .white

        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.edges.equalToSuperview().inset(UIEdgeInsets(top: 16, left: 15, bottom: 16, right: 15))
        }
        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
    }

    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.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: 0x565656)
        let toLabel = UILabel()
        toLabel.text = "至"
        toLabel.font = .systemFont(ofSize: 14)
        toLabel.textColor = UIColor(hex: 0x565656)
        [startButton, endButton].forEach { button in
            button.setTitle("请选择", for: .normal)
            button.snp.makeConstraints { make in
                make.width.equalTo(78)
                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
        orderButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
    }

    private func configureRemarkView() {
        remarkTextView.font = .systemFont(ofSize: 14)
        remarkTextView.textColor = UIColor(hex: 0x565656)
        remarkTextView.layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
        remarkTextView.layer.borderWidth = 1
        remarkTextView.layer.cornerRadius = 8
        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 alert = UIAlertController(title: "选择时间", message: "\n\n\n\n\n\n\n", preferredStyle: .actionSheet)
        let picker = UIDatePicker()
        picker.datePickerMode = .time
        picker.preferredDatePickerStyle = .wheels
        picker.locale = Locale(identifier: "zh_CN")
        alert.view.addSubview(picker)
        picker.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(16)
            make.top.equalToSuperview().offset(42)
            make.height.equalTo(160)
        }
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
            let formatter = DateFormatter()
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.dateFormat = "HH:mm"
            onConfirm(formatter.string(from: picker.date))
        })
        present(alert, animated: true)
        _ = initialTime
    }

    @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)
        }
    }

    @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)
        }
    }

    @objc private func orderTapped() {
        showToast("关联订单 (可选)")
    }

    @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: nil,
                api: profileAPI
            )
            if success {
                await MainActor.run { navigationController?.popViewController(animated: true) }
            }
        }
    }
}

extension ProfileAddScheduleViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        if textView.text.count > 200 {
            textView.text = String(textView.text.prefix(200))
        }
        countLabel.text = "\(textView.text.count)/200"
    }
}

/// 个人空间配置时间按钮。
private final class ProfileTimeButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        titleLabel?.font = .systemFont(ofSize: 14)
        setTitleColor(AppColor.textPrimary, for: .normal)
        backgroundColor = .white
        layer.cornerRadius = 8
        layer.borderColor = AppColor.border.cgColor
        layer.borderWidth = 1
    }

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

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

    func configure(weekday: String, day: String, selected: Bool) {
        let title = "\(weekday)\n\(day)"
        setTitle(title, for: .normal)
        titleLabel?.numberOfLines = 2
        titleLabel?.textAlignment = .center
        titleLabel?.font = .systemFont(ofSize: 14)
        setTitleColor(selected ? .white : UIColor(hex: 0x565656), for: .normal)
        backgroundColor = selected ? AppColor.primary : AppColor.inputBackground
        layer.cornerRadius = 4
        snp.makeConstraints { make in
            make.height.equalTo(58)
        }
    }
}

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

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

    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
        }
        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 {
            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 {
            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 == .certification ? 12 : 14)
        button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
        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: 0x565656), for: .normal)
        }
        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: 0x565656)
        title.numberOfLines = 1
        let delete = UIButton(type: .system)
        delete.setTitle("删除", for: .normal)
        delete.setTitleColor(AppColor.danger, 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: 0x565656)
        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 loadImageData(from provider: NSItemProvider) async throws -> Data {
        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, let data = image.jpegData(compressionQuality: 0.9) else {
                    continuation.resume(throwing: OSSUploadError.emptyFile)
                    return
                }
                continuation.resume(returning: data)
            }
        }
    }
}
