//
//  PunchPointViewModels.swift
//  suixinkan
//
//  Created by Codex on 2026/6/24.
//

import Foundation
import Combine

/// 打卡点列表 ViewModel，负责筛选、分页、详情加载和删除刷新。
@MainActor
final class PunchPointListViewModel: ObservableObject {
    @Published var selectedFilter: PunchPointFilter = .all
    @Published var items: [PunchPointItem] = []
    @Published var selectedDetail: PunchPointItem?
    @Published var errorMessage: String?
    private var isLoading = false
    @Published var isLoadingMore = false
    @Published var total = 0

    @Published private var page = 1
    private let pageSize = 20

    /// 是否还存在下一页数据。
    var hasMore: Bool {
        items.count < total
    }

    /// 重新加载当前筛选下的打卡点列表。
    func reload(scenicId: Int?, api: any PunchPointServing) async {
        guard let scenicId else {
            reset()
            return
        }
        page = 1
        isLoading = true
        errorMessage = nil
        defer { isLoading = false }

        do {
            let payload = try await api.punchPointList(
                scenicId: scenicId,
                status: selectedFilter.rawValue,
                page: page,
                pageSize: pageSize
            )
            items = payload.list
            total = payload.total
        } catch {
            items = []
            total = 0
            errorMessage = error.localizedDescription
        }
    }

    /// 加载下一页打卡点列表。
    func loadMore(scenicId: Int?, api: any PunchPointServing) async {
        guard let scenicId, hasMore, !isLoadingMore, !isLoading else { return }
        isLoadingMore = true
        let nextPage = page + 1
        defer { isLoadingMore = false }

        do {
            let payload = try await api.punchPointList(
                scenicId: scenicId,
                status: selectedFilter.rawValue,
                page: nextPage,
                pageSize: pageSize
            )
            page = nextPage
            items.append(contentsOf: payload.list)
            total = payload.total
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    /// 切换筛选并重载列表。
    func selectFilter(_ filter: PunchPointFilter, scenicId: Int?, api: any PunchPointServing) async {
        guard selectedFilter != filter else { return }
        selectedFilter = filter
        await reload(scenicId: scenicId, api: api)
    }

    /// 加载单个打卡点详情。
    func loadDetail(id: Int, api: any PunchPointServing) async {
        errorMessage = nil
        do {
            selectedDetail = try await api.punchPointInfo(id: id)
        } catch {
            selectedDetail = items.first { $0.id == id }
            errorMessage = error.localizedDescription
        }
    }

    /// 删除打卡点并刷新当前列表。
    func delete(_ item: PunchPointItem, scenicId: Int?, api: any PunchPointServing) async -> Bool {
        do {
            try await api.deletePunchPoint(id: item.id)
            await reload(scenicId: scenicId, api: api)
            return true
        } catch {
            errorMessage = error.localizedDescription
            return false
        }
    }

    /// 清空列表状态，通常用于缺少当前景区时。
    func reset() {
        items = []
        selectedDetail = nil
        errorMessage = nil
        total = 0
        page = 1
        isLoading = false
        isLoadingMore = false
    }
}

/// 打卡点编辑 ViewModel，负责新增、编辑、表单校验和 OSS 图片上传。
@MainActor
final class PunchPointEditorViewModel: ObservableObject {
    @Published var name = ""
    @Published var description = ""
    @Published var address = ""
    @Published var latitudeText = ""
    @Published var longitudeText = ""
    @Published var scenicSpotText = ""
    @Published var remoteImages: [String] = []
    @Published var localImages: [PunchPointLocalImage] = []
    @Published var errorMessage: String?
    @Published var isSubmitting = false

    private(set) var editingItem: PunchPointItem?

    /// 初始化编辑 ViewModel，可传入已有打卡点作为编辑表单初值。
    init(item: PunchPointItem? = nil) {
        editingItem = item
        if let item {
            apply(item)
        }
    }

    /// 回填编辑详情，保留同一个 ObservableObject 实例。
    func apply(_ item: PunchPointItem) {
        editingItem = item
        name = item.name
        description = item.description
        address = item.region?.address ?? ""
        latitudeText = item.region.map { String($0.lat) } ?? ""
        longitudeText = item.region.map { String($0.lot) } ?? ""
        scenicSpotText = item.scenicSpotStr
        remoteImages = item.guideImages
        localImages = []
    }

    /// 设置经纬度和地址，通常由定位或地图选点回填。
    func applyLocation(latitude: Double, longitude: Double, address: String) {
        latitudeText = String(format: "%.6f", latitude)
        longitudeText = String(format: "%.6f", longitude)
        self.address = address
        if scenicSpotText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            scenicSpotText = address
        }
    }

    /// 添加本地待上传图片。
    func addLocalImages(_ images: [PunchPointLocalImage]) {
        localImages.append(contentsOf: images)
    }

    /// 删除远程图片。
    func removeRemoteImage(_ url: String) {
        remoteImages.removeAll { $0 == url }
    }

    /// 删除本地待上传图片。
    func removeLocalImage(id: UUID) {
        localImages.removeAll { $0.id == id }
    }

    /// 提交新增或编辑表单，图片会先上传 OSS，再提交业务接口。
    func submit(
        scenicId: Int?,
        api: any PunchPointServing,
        uploadService: any OSSUploadServing
    ) async -> Bool {
        guard !isSubmitting else { return false }
        guard let scenicId else {
            errorMessage = "缺少当前景区"
            return false
        }
        guard let region = makeRegion() else { return false }

        let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedName.isEmpty else {
            errorMessage = "请输入打卡点名称"
            return false
        }
        guard !region.address.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            errorMessage = "请选择或填写打卡点地址"
            return false
        }
        guard !remoteImages.isEmpty || !localImages.isEmpty else {
            errorMessage = "请至少上传一张打卡点图片"
            return false
        }

        isSubmitting = true
        errorMessage = nil
        defer { isSubmitting = false }

        do {
            let uploadedImages = try await uploadImages(scenicId: scenicId, uploadService: uploadService)
            let allImages = remoteImages + uploadedImages
            if let editingItem {
                try await api.editPunchPoint(
                    EditPunchPointRequest(
                        id: editingItem.id,
                        scenicAreaId: "\(scenicId)",
                        name: trimmedName,
                        description: description.trimmingCharacters(in: .whitespacesAndNewlines),
                        region: region,
                        scenicSpotStr: scenicSpotText.trimmingCharacters(in: .whitespacesAndNewlines),
                        guideImages: allImages
                    )
                )
            } else {
                try await api.addPunchPoint(
                    AddPunchPointRequest(
                        scenicAreaId: "\(scenicId)",
                        name: trimmedName,
                        description: description.trimmingCharacters(in: .whitespacesAndNewlines),
                        region: region,
                        scenicSpotStr: scenicSpotText.trimmingCharacters(in: .whitespacesAndNewlines),
                        guideImages: allImages
                    )
                )
            }
            remoteImages = allImages
            localImages = []
            return true
        } catch {
            errorMessage = error.localizedDescription
            return false
        }
    }

    /// 构建提交区域，校验经纬度是否可用。
    private func makeRegion() -> PunchPointRegion? {
        let trimmedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines)
        guard let lat = Double(latitudeText.trimmingCharacters(in: .whitespacesAndNewlines)),
              let lot = Double(longitudeText.trimmingCharacters(in: .whitespacesAndNewlines)) else {
            errorMessage = "请选择打卡点坐标"
            return nil
        }
        return PunchPointRegion(
            lat: lat,
            lot: lot,
            address: trimmedAddress,
            scenicSpotStr: scenicSpotText.trimmingCharacters(in: .whitespacesAndNewlines)
        )
    }

    /// 上传本地图片，并返回 OSS URL 列表。
    private func uploadImages(scenicId: Int, uploadService: any OSSUploadServing) async throws -> [String] {
        var uploaded: [String] = []
        for index in localImages.indices {
            let local = localImages[index]
            let url = try await uploadService.uploadPunchPointImage(
                data: local.data,
                fileName: local.fileName,
                scenicId: scenicId
            ) { [weak self] progress in
                Task { @MainActor in
                    self?.localImages[index].progress = progress
                }
            }
            localImages[index].remoteURL = url
            uploaded.append(url)
        }
        return uploaded
    }
}
