import APIClient
import Foundation

/// T-iOS-26 · Projects 列表的纯分组逻辑 —— 逐条镜像 web v0.6 的
/// public/projects.ts（filterProjects / sortProjects / groupProjects /
/// displayLabel），使手机与网页看到同一套分区。
///
/// 关键契约：**组 key 与 web 逐字节一致**（namespace 首见大小写、哨兵
/// `" active"` / `" other"`），因为折叠状态以组 key 存进跨端共享的
/// `/prefs.collapsed` —— key 漂移 = 两端互相丢折叠状态。
/// label 则是本端 UI 文案（哨兵组中文；namespace 组 = key 本身）。
///
/// Active 置顶（任务要求 assert reality）：`ProjectSessionRef.exited` 字段
/// 实测存在（src/types.ts:262-269），running = 任一会话 `!exited` —— 与 web
/// `hasRunningSession` 同一判据；running 项目**复制**进置顶组，原组保留。
enum ProjectGrouping {
    /// 哨兵组 key（带空格前缀，不可能与真实 `First.Second` namespace 撞车，
    /// 镜像 public/projects.ts:83-84）。
    static let activeGroupKey = " active"
    static let otherGroupKey = " other"
    /// namespace 至少要这么多成员才配得上独立分区（MIN_GROUP_SIZE）。
    static let minGroupSize = 2

    // MARK: - filter（镜像 filterProjects：name/path 子串，大小写不敏感）

    static func filter(_ projects: [ProjectInfo], query: String) -> [ProjectInfo] {
        let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return projects }
        let lower = trimmed.lowercased()
        return projects.filter {
            $0.name.lowercased().contains(lower) || $0.path.lowercased().contains(lower)
        }
    }

    // MARK: - sort（镜像 sortProjects：收藏优先 → lastActiveMs 降序；显式稳定）

    /// JS 的 `Array.sort` 是稳定的，web 靠它在平局时保持服务器顺序；Swift 的
    /// `sorted` 未承诺稳定 → 用输入下标做最终决胜键，行为逐字节对齐。
    static func sort(_ projects: [ProjectInfo], favourites: Set<String>) -> [ProjectInfo] {
        projects.enumerated().sorted { a, b in
            let aFav = favourites.contains(a.element.path)
            let bFav = favourites.contains(b.element.path)
            if aFav != bFav { return aFav }
            let aActive = a.element.lastActiveMs ?? 0
            let bActive = b.element.lastActiveMs ?? 0
            if aActive != bActive { return aActive > bActive }
            return a.offset < b.offset
        }.map(\.element)
    }

    // MARK: - group（镜像 groupProjects）

    static func group(_ projects: [ProjectInfo], favourites: Set<String>) -> [ProjectGroup] {
        let (namespaceGroups, other) = bucketByNamespace(projects, favourites: favourites)

        // 分组一无所获 → 单一 flat 组（无 chrome 的平铺网格回退）。
        guard !namespaceGroups.isEmpty else {
            return [makeGroup(
                key: otherGroupKey, label: ProjectsCopy.allGroupLabel,
                kind: .flat, items: projects, favourites: favourites
            )]
        }

        var groups: [ProjectGroup] = []
        let active = projects.filter(hasRunningSession)
        if !active.isEmpty {
            groups.append(makeGroup(
                key: activeGroupKey, label: ProjectsCopy.activeGroupLabel,
                kind: .active, items: active, favourites: favourites
            ))
        }
        groups.append(contentsOf: orderedByRecency(namespaceGroups))
        if !other.isEmpty {
            groups.append(makeGroup(
                key: otherGroupKey, label: ProjectsCopy.otherGroupLabel,
                kind: .other, items: other, favourites: favourites
            ))
        }
        return groups
    }

    /// 组内卡片名：namespace 组剥掉 `<key>.` 前缀（大小写不敏感），免得每张
    /// 卡都在喊 `Billo.Platform.`；哨兵组保留全名（镜像 displayLabel）。
    static func displayLabel(name: String, groupKey: String) -> String {
        if groupKey == activeGroupKey || groupKey == otherGroupKey { return name }
        let prefix = "\(groupKey)."
        guard name.lowercased().hasPrefix(prefix.lowercased()) else { return name }
        return String(name.dropFirst(prefix.count))
    }

    static func hasRunningSession(_ project: ProjectInfo) -> Bool {
        project.sessions.contains { !$0.exited }
    }

    // MARK: - Internals

    /// namespace = 名字的前两个点分段（`'a.b.c'` → `'a.b'`；不足两段 → nil），
    /// 空段保留（镜像 JS `split('.')` 语义）。
    private static func namespaceKey(_ name: String) -> String? {
        let segments = name.split(separator: ".", omittingEmptySubsequences: false)
        guard segments.count >= 2 else { return nil }
        return segments.prefix(2).joined(separator: ".")
    }

    /// 桶 key 小写去重、显示名取首见大小写、首见顺序稳定（镜像 JS Map 的
    /// 插入序遍历）。返回 (成组的 namespace, 塌进 Other 的项目)。
    private static func bucketByNamespace(
        _ projects: [ProjectInfo], favourites: Set<String>
    ) -> (groups: [ProjectGroup], other: [ProjectInfo]) {
        var bucketOrder: [String] = []
        var buckets: [String: (display: String, items: [ProjectInfo])] = [:]
        var other: [ProjectInfo] = []
        for project in projects {
            guard let namespace = namespaceKey(project.name) else {
                other.append(project)
                continue
            }
            let lowerKey = namespace.lowercased()
            if var bucket = buckets[lowerKey] {
                bucket.items.append(project)
                buckets[lowerKey] = bucket
            } else {
                buckets[lowerKey] = (namespace, [project])
                bucketOrder.append(lowerKey)
            }
        }

        var groups: [ProjectGroup] = []
        for lowerKey in bucketOrder {
            guard let bucket = buckets[lowerKey] else { continue }
            if bucket.items.count < minGroupSize {
                other.append(contentsOf: bucket.items)
            } else {
                groups.append(makeGroup(
                    key: bucket.display, label: bucket.display,
                    kind: .namespace, items: bucket.items, favourites: favourites
                ))
            }
        }
        return (groups, other)
    }

    /// namespace 分区按组内最新活跃时间降序，平局按 label 升序（镜像
    /// groupProjects 的 sort）。
    private static func orderedByRecency(_ groups: [ProjectGroup]) -> [ProjectGroup] {
        groups.sorted { a, b in
            let aMax = maxLastActive(a.projects)
            let bMax = maxLastActive(b.projects)
            if aMax != bMax { return aMax > bMax }
            return a.label.localizedCompare(b.label) == .orderedAscending
        }
    }

    private static func maxLastActive(_ items: [ProjectInfo]) -> Int {
        items.reduce(0) { max($0, $1.lastActiveMs ?? 0) }
    }

    private static func makeGroup(
        key: String, label: String, kind: ProjectGroupKind,
        items: [ProjectInfo], favourites: Set<String>
    ) -> ProjectGroup {
        ProjectGroup(
            key: key, label: label, kind: kind,
            projects: sort(items, favourites: favourites),
            activeCount: items.filter(hasRunningSession).count
        )
    }
}

/// 分区种类（镜像 web `ProjectGroupKind`）。
enum ProjectGroupKind: Equatable, Sendable {
    case active
    case namespace
    case other
    case flat
}

/// 一个可折叠分区的不可变快照（镜像 web `ProjectGroup`）。
struct ProjectGroup: Equatable, Identifiable, Sendable {
    /// 折叠状态的持久化 key —— 必须与 web 逐字节一致（/prefs 跨端共享）。
    let key: String
    let label: String
    let kind: ProjectGroupKind
    /// 已排序（收藏优先 → 活跃降序）。
    let projects: [ProjectInfo]
    /// 有运行中会话的项目数（折叠的分区绝不悄悄埋掉活跃会话）。
    let activeCount: Int

    var id: String { key }
    /// Active now 永远展开（它就是重点）；flat 无 chrome。
    var isCollapsible: Bool { kind == .namespace || kind == .other }
}
