//
//  OperatingAreaModels.swift
//  suixinkan
//
//  Created by Codex on 2026/6/25.
//

import Foundation

/// 运营区域入口模式，区分店铺管理员与景区管理员两条接口链路。
enum OperatingAreaEntryMode: Equatable {
    case storeAdmin(storeId: Int)
    case scenicAdmin(scenicId: Int)

    var title: String {
        switch self {
        case .storeAdmin:
            "店铺运营区域"
        case .scenicAdmin:
            "景区运营区域"
        }
    }
}

/// 运营区域接口单项，表示一个门店或区域围栏。
struct OperatingAreaItem: Decodable, Equatable, Identifiable {
    let id: Int
    let name: String
    let businessMapArea: OperatingMapArea?
    let statusText: String
    let typeText: String
    let auditStatusText: String

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case businessMapArea = "business_map_area"
        case statusText = "status_text"
        case typeText = "type_text"
        case auditStatusText = "audit_status_text"
    }

    init(
        id: Int = 0,
        name: String = "",
        businessMapArea: OperatingMapArea? = nil,
        statusText: String = "",
        typeText: String = "",
        auditStatusText: String = ""
    ) {
        self.id = id
        self.name = name
        self.businessMapArea = businessMapArea
        self.statusText = statusText
        self.typeText = typeText
        self.auditStatusText = auditStatusText
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.operatingDecodeLossyInt(forKey: .id) ?? 0
        name = try container.operatingDecodeLossyString(forKey: .name)
        businessMapArea = try container.decodeIfPresent(OperatingMapArea.self, forKey: .businessMapArea)
        statusText = try container.operatingDecodeLossyString(forKey: .statusText)
        typeText = try container.operatingDecodeLossyString(forKey: .typeText)
        auditStatusText = try container.operatingDecodeLossyString(forKey: .auditStatusText)
    }
}

/// 运营区域原始 JSON 值，用于保留并解析后端不稳定的 `business_map_area`。
enum OperatingMapArea: Decodable, Equatable {
    case null
    case bool(Bool)
    case number(Double)
    case string(String)
    case array([OperatingMapArea])
    case object([String: OperatingMapArea])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if container.decodeNil() {
            self = .null
        } else if let value = try? container.decode(Bool.self) {
            self = .bool(value)
        } else if let value = try? container.decode(Double.self) {
            self = .number(value)
        } else if let value = try? container.decode(String.self) {
            self = .string(value)
        } else if let value = try? container.decode([OperatingMapArea].self) {
            self = .array(value)
        } else {
            let keyed = try decoder.container(keyedBy: OperatingDynamicCodingKey.self)
            var object: [String: OperatingMapArea] = [:]
            for key in keyed.allKeys {
                object[key.stringValue] = try keyed.decode(OperatingMapArea.self, forKey: key)
            }
            self = .object(object)
        }
    }

    /// 将 JSON 字符串解析为运营区域 JSON 值。
    static func parseString(_ text: String) -> OperatingMapArea? {
        guard let data = text.data(using: .utf8),
              let raw = try? JSONSerialization.jsonObject(with: data)
        else { return nil }
        return fromJSONObject(raw)
    }

    private static func fromJSONObject(_ value: Any) -> OperatingMapArea {
        switch value {
        case is NSNull:
            return .null
        case let value as Bool:
            return .bool(value)
        case let value as NSNumber:
            return .number(value.doubleValue)
        case let value as String:
            return .string(value)
        case let value as [Any]:
            return .array(value.map(fromJSONObject))
        case let value as [String: Any]:
            return .object(value.mapValues(fromJSONObject))
        default:
            return .null
        }
    }
}

/// 运营区域地理坐标点。
struct OperatingGeoPoint: Equatable, Hashable {
    let latitude: Double
    let longitude: Double
}

/// 运营区域围栏环，用于地图绘制和列表展示。
struct OperatingFenceRing: Identifiable, Equatable {
    let id: String
    let itemId: Int
    let regionName: String
    let points: [OperatingGeoPoint]
    let isCurrentStore: Bool

    init(itemId: Int, regionName: String, points: [OperatingGeoPoint], isCurrentStore: Bool, ringIndex: Int) {
        self.id = "\(itemId)-\(ringIndex)"
        self.itemId = itemId
        self.regionName = regionName
        self.points = points
        self.isCurrentStore = isCurrentStore
    }
}

/// 运营区域阻断原因，用于缺少上下文或数据不可展示时给出稳定提示。
enum OperatingAreaBlockReason: Equatable {
    case missingStore
    case missingScenic
    case unsupportedRole
    case emptyAreaList
    case noParsableFenceData
    case backendMessage(String)

    var message: String {
        switch self {
        case .missingStore:
            "请先选择店铺"
        case .missingScenic:
            "请先选择景区"
        case .unsupportedRole:
            "当前账号暂不支持运营区域"
        case .emptyAreaList:
            "当前景区无区域数据"
        case .noParsableFenceData:
            "有区域记录，但未解析出有效围栏，请检查坐标数据"
        case .backendMessage(let message):
            message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "运营区域加载失败" : message
        }
    }
}

/// 运营区域围栏解析器，兼容 GeoJSON、数组、字符串 JSON 和常见路径字段。
enum OperatingAreaParser {
    /// 将后端 `business_map_area` 解析为若干闭合多边形环。
    static func parseToRings(_ area: OperatingMapArea?) -> [[OperatingGeoPoint]] {
        guard let area else { return [] }
        return parseElement(area)
    }

    private static func parseElement(_ value: OperatingMapArea) -> [[OperatingGeoPoint]] {
        switch value {
        case .null, .bool, .number:
            return []
        case .string(let text):
            let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
            guard !trimmed.isEmpty, let parsed = OperatingMapArea.parseString(trimmed) else { return [] }
            return parseElement(parsed)
        case .array(let array):
            return parseArrayRoot(array)
        case .object(let object):
            return parseObject(object)
        }
    }

    private static func parseObject(_ object: [String: OperatingMapArea]) -> [[OperatingGeoPoint]] {
        let type: String?
        if case let .string(rawType)? = object["type"] {
            type = rawType.lowercased()
        } else {
            type = nil
        }

        switch type {
        case "polygon":
            guard case let .array(coordinates)? = object["coordinates"],
                  case let .array(outer)? = coordinates.first
            else { return [] }
            return parseRing(outer).map { [$0] } ?? []
        case "multipolygon":
            guard case let .array(polygons)? = object["coordinates"] else { return [] }
            return polygons.compactMap { polygon -> [OperatingGeoPoint]? in
                guard case let .array(rings) = polygon,
                      case let .array(firstRing)? = rings.first
                else { return nil }
                return parseRing(firstRing)
            }
        default:
            for key in ["path", "paths", "points", "coordinates"] {
                if let nested = object[key] {
                    return parseElement(nested)
                }
            }
            return []
        }
    }

    private static func parseArrayRoot(_ array: [OperatingMapArea]) -> [[OperatingGeoPoint]] {
        guard let first = array.first else { return [] }
        switch first {
        case .object:
            return parseObjectRing(array).map { [$0] } ?? []
        case .array(let nested):
            guard let sample = nested.first else { return [] }
            switch sample {
            case .array(let pointCandidate):
                if pointCandidate.count >= 2, pointCandidate[0].numberValue != nil, pointCandidate[1].numberValue != nil {
                    return parseRing(nested).map { [$0] } ?? []
                }
                return array.compactMap { element -> [OperatingGeoPoint]? in
                    guard case let .array(segment) = element else { return nil }
                    return parseRing(segment)
                }
            case .object:
                return parseObjectRing(nested).map { [$0] } ?? []
            default:
                return parseRing(nested).map { [$0] } ?? []
            }
        default:
            return []
        }
    }

    private static func parseObjectRing(_ array: [OperatingMapArea]) -> [OperatingGeoPoint]? {
        let points = array.compactMap { element -> OperatingGeoPoint? in
            guard case let .object(object) = element else { return nil }
            return pointFromObject(object)
        }
        return points.count >= 3 ? points : nil
    }

    private static func parseRing(_ array: [OperatingMapArea]) -> [OperatingGeoPoint]? {
        let points = array.compactMap { element -> OperatingGeoPoint? in
            switch element {
            case .array(let pair):
                guard pair.count >= 2,
                      let a = pair[0].numberValue,
                      let b = pair[1].numberValue
                else { return nil }
                return normalizePair(a, b)
            case .object(let object):
                return pointFromObject(object)
            default:
                return nil
            }
        }
        return points.count >= 3 ? points : nil
    }

    private static func pointFromObject(_ object: [String: OperatingMapArea]) -> OperatingGeoPoint? {
        let lat = object["lat"]?.numberValue ?? object["latitude"]?.numberValue
        let lng = object["lng"]?.numberValue ?? object["lon"]?.numberValue ?? object["longitude"]?.numberValue
        guard let lat, let lng else { return nil }
        return OperatingGeoPoint(latitude: lat, longitude: lng)
    }

    private static func normalizePair(_ a: Double, _ b: Double) -> OperatingGeoPoint {
        if looksLikeLngLatPair(lng: a, lat: b) {
            return OperatingGeoPoint(latitude: b, longitude: a)
        }
        if looksLikeLatLngPair(lat: a, lng: b) {
            return OperatingGeoPoint(latitude: a, longitude: b)
        }
        if abs(a) > 90 {
            return OperatingGeoPoint(latitude: b, longitude: a)
        }
        return OperatingGeoPoint(latitude: a, longitude: b)
    }

    private static func looksLikeLngLatPair(lng: Double, lat: Double) -> Bool {
        (-90.0...90.0).contains(lat) && abs(lng) <= 180.0 && abs(lng) > abs(lat)
    }

    private static func looksLikeLatLngPair(lat: Double, lng: Double) -> Bool {
        (-90.0...90.0).contains(lat) && abs(lng) <= 180.0 && abs(lat) <= abs(lng)
    }
}

private struct OperatingDynamicCodingKey: CodingKey {
    let stringValue: String
    let intValue: Int?

    init?(stringValue: String) {
        self.stringValue = stringValue
        intValue = nil
    }

    init?(intValue: Int) {
        stringValue = String(intValue)
        self.intValue = intValue
    }
}

private extension OperatingMapArea {
    var numberValue: Double? {
        switch self {
        case .number(let value):
            return value
        case .string(let text):
            return Double(text.trimmingCharacters(in: .whitespacesAndNewlines))
        default:
            return nil
        }
    }
}

private extension KeyedDecodingContainer {
    func operatingDecodeLossyString(forKey key: Key) throws -> String {
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Bool.self, forKey: key) {
            return value ? "true" : "false"
        }
        return ""
    }

    func operatingDecodeLossyInt(forKey key: Key) throws -> Int? {
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
            if let intValue = Int(text) { return intValue }
            if let doubleValue = Double(text) { return Int(doubleValue) }
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return Int(value)
        }
        return nil
    }
}
