import SwiftUI

private let maxTrackedSwarmGroups = 10000
private let maxTrackedSwarmChildren = 100_000
private let maxRenderedSwarmDotsPerPhase = 256
private let swarmRefreshRetryDelays = [1.0, 2.0, 4.0]

struct OpenClawChatSwarmActivityState: Equatable {
    private var currentPhaseByGroup: [String: String] = [:]
    private var phaseRankByGroupPhase: [String: Int] = [:]
    private var nextPhaseRank = 0
    private var latestLogByGroup: [String: String] = [:]
    private var phaseByChild: [String: String] = [:]

    mutating func clear() {
        self = OpenClawChatSwarmActivityState()
    }

    mutating func observe(_ event: OpenClawChatSessionsChangedEvent) -> Bool {
        guard let groupID = Self.normalized(event.swarmGroupId) else { return false }
        let kind = Self.normalized(event.kind)
        let text = Self.normalized(event.text)
        if let text, kind == "phase" || kind == "log" {
            if kind == "phase" {
                let rankKey = Self.phaseRankKey(groupID: groupID, phase: text)
                if self.phaseRankByGroupPhase[rankKey] == nil {
                    Self.setBounded(
                        &self.phaseRankByGroupPhase,
                        key: rankKey,
                        value: self.nextPhaseRank,
                        limit: maxTrackedSwarmChildren)
                    self.nextPhaseRank += 1
                }
                Self.setBounded(
                    &self.currentPhaseByGroup,
                    key: groupID,
                    value: text,
                    limit: maxTrackedSwarmGroups)
            } else {
                Self.setBounded(
                    &self.latestLogByGroup,
                    key: groupID,
                    value: text,
                    limit: maxTrackedSwarmGroups)
            }
            return true
        }

        guard let childKey = Self.normalized(event.sessionKey) else { return true }
        if let phase = Self.normalized(event.swarmPhase) {
            Self.setBounded(
                &self.phaseByChild,
                key: childKey,
                value: phase,
                limit: maxTrackedSwarmChildren)
            return true
        }
        if event.reason == "create",
           self.phaseByChild[childKey] == nil,
           let currentPhase = currentPhaseByGroup[groupID]
        {
            Self.setBounded(
                &self.phaseByChild,
                key: childKey,
                value: currentPhase,
                limit: maxTrackedSwarmChildren)
        }
        return true
    }

    func decorate(_ sessions: [OpenClawChatSessionEntry]) -> [OpenClawChatSessionEntry] {
        sessions.map { row in
            guard let groupID = Self.normalized(row.swarmGroupId) else { return row }
            let phase = self.phaseByChild[row.key] ?? row.swarmPhase
            let rank = phase.flatMap { self.phaseRankByGroupPhase[Self.phaseRankKey(groupID: groupID, phase: $0)] }
                ?? row.swarmPhaseRank
            let log = self.latestLogByGroup[groupID] ?? row.swarmLog
            var decorated = row
            decorated.swarmPhase = phase
            decorated.swarmPhaseRank = rank
            decorated.swarmLog = log
            return decorated
        }
    }

    private static func normalized(_ value: String?) -> String? {
        let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines)
        return normalized?.isEmpty == false ? normalized : nil
    }

    private static func phaseRankKey(groupID: String, phase: String) -> String {
        "\(groupID)\u{0}\(phase)"
    }

    private static func setBounded<Value>(
        _ values: inout [String: Value],
        key: String,
        value: Value,
        limit: Int)
    {
        if values[key] == nil, values.count >= limit, let evicted = values.keys.first {
            values.removeValue(forKey: evicted)
        }
        values[key] = value
    }
}

enum OpenClawChatSwarmDotStatus: Int, Comparable, Sendable {
    case running
    case queued
    case failed
    case done

    static func < (lhs: Self, rhs: Self) -> Bool {
        lhs.rawValue < rhs.rawValue
    }

    var label: String {
        switch self {
        case .running: String(localized: "Running")
        case .queued: String(localized: "Queued")
        case .failed: String(localized: "Failed")
        case .done: String(localized: "Done")
        }
    }
}

struct OpenClawChatSwarmDot: Identifiable, Sendable {
    let id: String
    let label: String
    let status: OpenClawChatSwarmDotStatus
}

struct OpenClawChatSwarmPhase: Identifiable, Sendable {
    let id: String
    let title: String?
    let dots: [OpenClawChatSwarmDot]
    let hidden: Int
}

struct OpenClawChatSwarmGroup: Identifiable, Sendable {
    let id: String
    let label: String
    let running: Int
    let done: Int
    let failed: Int
    let narrator: String?
    let phases: [OpenClawChatSwarmPhase]
}

func buildOpenClawChatSwarmGroups(
    sessions: [OpenClawChatSessionEntry],
    matchesParent: (String) -> Bool) -> [OpenClawChatSwarmGroup]
{
    struct Entry {
        let phase: String?
        let phaseRank: Int
        let log: String?
        let dot: OpenClawChatSwarmDot
    }

    var byGroup: [String: [Entry]] = [:]
    for row in sessions {
        guard let groupID = row.swarmGroupId?.trimmingCharacters(in: .whitespacesAndNewlines),
              !groupID.isEmpty,
              SelfContainedSwarmHelpers.belongsToParent(row, groupID: groupID, matchesParent: matchesParent),
              let status = SelfContainedSwarmHelpers.status(row)
        else { continue }
        let label = [row.label, row.displayName, row.derivedTitle, row.key]
            .compactMap { value -> String? in
                let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines)
                return normalized?.isEmpty == false ? normalized : nil
            }
            .first ?? row.key
        byGroup[groupID, default: []].append(Entry(
            phase: row.swarmPhase?.trimmingCharacters(in: .whitespacesAndNewlines),
            phaseRank: row.swarmPhaseRank ?? .max,
            log: row.swarmLog?.trimmingCharacters(in: .whitespacesAndNewlines),
            dot: OpenClawChatSwarmDot(id: row.key, label: label, status: status)))
    }

    return byGroup.map { groupID, entries in
        var phaseBuckets: [String: (title: String?, rank: Int, dots: [OpenClawChatSwarmDot])] = [:]
        for entry in entries {
            let phaseKey = entry.phase?.isEmpty == false ? entry.phase! : "\u{0}"
            var bucket = phaseBuckets[phaseKey] ?? (entry.phase, entry.phaseRank, [])
            bucket.rank = min(bucket.rank, entry.phaseRank)
            bucket.dots.append(entry.dot)
            phaseBuckets[phaseKey] = bucket
        }
        let phases = phaseBuckets.map { key, bucket -> OpenClawChatSwarmPhase in
            let ordered = bucket.dots.count > maxRenderedSwarmDotsPerPhase
                ? bucket.dots.sorted { $0.status < $1.status }
                : bucket.dots
            return OpenClawChatSwarmPhase(
                id: key,
                title: bucket.title,
                dots: Array(ordered.prefix(maxRenderedSwarmDotsPerPhase)),
                hidden: max(0, ordered.count - maxRenderedSwarmDotsPerPhase))
        }.sorted { lhs, rhs in
            let leftRank = phaseBuckets[lhs.id]?.rank ?? .max
            let rightRank = phaseBuckets[rhs.id]?.rank ?? .max
            if leftRank != rightRank {
                return leftRank < rightRank
            }
            return lhs.id < rhs.id
        }
        let dots = entries.map(\.dot)
        return OpenClawChatSwarmGroup(
            id: groupID,
            label: groupID.split(separator: ":").last.map(String.init) ?? groupID,
            running: dots.count { $0.status == .running },
            done: dots.count { $0.status == .done },
            failed: dots.count { $0.status == .failed },
            narrator: entries.compactMap(\.log).first { !$0.isEmpty },
            phases: phases)
    }.filter { group in
        group.phases.contains { phase in
            phase.dots.contains { $0.status == .queued || $0.status == .running }
        }
    }.sorted { $0.id < $1.id }
}

enum SelfContainedSwarmHelpers {
    static func status(_ row: OpenClawChatSessionEntry) -> OpenClawChatSwarmDotStatus? {
        if row.status == "running" || row.hasActiveRun == true {
            return .running
        }
        if row.status == "done" {
            return .done
        }
        if row.status == "failed" || row.status == "killed" || row.status == "timeout" {
            return .failed
        }
        return row.subagentRunState == "active" || row.hasActiveSubagentRun == true ? .queued : nil
    }

    static func belongsToParent(
        _ row: OpenClawChatSessionEntry,
        groupID: String,
        matchesParent: (String) -> Bool) -> Bool
    {
        if let parent = row.parentSessionKey, matchesParent(parent) {
            return true
        }
        if let spawnedBy = row.spawnedBy, matchesParent(spawnedBy) {
            return true
        }
        return self.generatedGroupBelongsToParent(groupID, matchesParent: matchesParent)
    }

    static func isActivityNote(_ event: OpenClawChatSessionsChangedEvent) -> Bool {
        let kind = self.normalized(event.kind)
        return kind == "phase" || kind == "log"
    }

    static func eventBelongsToParent(
        _ event: OpenClawChatSessionsChangedEvent,
        matchesParent: (String) -> Bool) -> Bool
    {
        if let parent = normalized(event.parentSessionKey) ?? normalized(event.spawnedBy) {
            return matchesParent(parent)
        }
        let kind = self.normalized(event.kind)
        if kind == "phase" || kind == "log" {
            guard let sessionKey = normalized(event.sessionKey) else { return false }
            return matchesParent(sessionKey)
        }
        guard let groupID = normalized(event.swarmGroupId) else { return false }
        return self.generatedGroupBelongsToParent(groupID, matchesParent: matchesParent)
    }

    private static func generatedGroupBelongsToParent(
        _ groupID: String,
        matchesParent: (String) -> Bool) -> Bool
    {
        let parts = groupID.split(separator: ":", omittingEmptySubsequences: false)
        guard parts.first == "swarm", parts.count > 2 else { return false }
        return matchesParent(parts.dropFirst().dropLast().joined(separator: ":"))
    }

    private static func normalized(_ value: String?) -> String? {
        let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines)
        return normalized?.isEmpty == false ? normalized : nil
    }
}

extension OpenClawChatViewModel {
    private func updateSwarmProjection() {
        self.activeSwarmGroups = buildOpenClawChatSwarmGroups(sessions: self.swarmSessions) { candidate in
            self.matchesCurrentSessionKey(incoming: candidate, current: self.sessionKey)
        }
    }

    func observeSwarmEvent(_ event: OpenClawChatSessionsChangedEvent) -> Bool {
        guard swarmEnabled,
              SelfContainedSwarmHelpers.eventBelongsToParent(event, matchesParent: { candidate in
                  self.matchesCurrentSessionKey(
                      incoming: candidate,
                      agentId: event.agentId,
                      current: self.sessionKey)
              })
        else { return false }
        var nextActivity = swarmActivityState
        guard nextActivity.observe(event) else { return false }
        swarmActivityState = nextActivity
        swarmSessions = nextActivity.decorate(swarmSessions)
        self.updateSwarmProjection()
        if event.kind != "phase", event.kind != "log" {
            self.scheduleSwarmRefresh()
        }
        return true
    }

    func refreshSwarmCapability(
        sessionSnapshot: SessionSnapshot? = nil,
        retryAttempt: Int = 0) async
    {
        let requestedKey = sessionSnapshot?.key ?? sessionKey
        guard matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey) else { return }
        guard let routeLease = await transport.acquireSwarmRouteLease() else {
            self.scheduleSwarmCapabilityRetry(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt)
            return
        }
        do {
            let enabled = try await routeLease.isEnabled(sessionKey: requestedKey)
            guard matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey) else { return }
            swarmEnabled = enabled
            guard enabled else {
                self.resetSwarmProgress()
                return
            }
            await self.refreshSwarmSessions(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                routeLease: routeLease)
        } catch {
            guard matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey) else { return }
            chatUILogger.debug("swarm capability refresh failed: \(error.localizedDescription, privacy: .public)")
            self.scheduleSwarmCapabilityRetry(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt)
        }
    }

    private func scheduleSwarmCapabilityRetry(
        sessionKey requestedKey: String,
        sessionSnapshot: SessionSnapshot?,
        retryAttempt: Int)
    {
        guard matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey),
              swarmRefreshRetryDelays.indices.contains(retryAttempt)
        else { return }
        let delay = swarmRefreshRetryDelays[retryAttempt]
        swarmRefreshTask?.cancel()
        swarmRefreshTask = Task { [weak self] in
            try? await Task.sleep(for: .seconds(delay))
            guard !Task.isCancelled else { return }
            await self?.refreshSwarmCapability(
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt + 1)
        }
    }

    func scheduleSwarmRefresh() {
        guard swarmEnabled else { return }
        let requestedKey = sessionKey
        swarmRefreshTask?.cancel()
        swarmRefreshTask = Task { [weak self] in
            try? await Task.sleep(for: .milliseconds(250))
            guard !Task.isCancelled else { return }
            await self?.refreshSwarmSessions(sessionKey: requestedKey)
        }
    }

    func refreshSwarmSessions(
        sessionKey requestedSessionKey: String? = nil,
        sessionSnapshot: SessionSnapshot? = nil,
        routeLease: OpenClawChatSwarmRouteLease? = nil,
        retryAttempt: Int = 0) async
    {
        guard swarmEnabled else { return }
        let requestedKey = requestedSessionKey ?? sessionSnapshot?.key ?? sessionKey
        guard matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey) else { return }
        let activeRouteLease: OpenClawChatSwarmRouteLease
        if let routeLease {
            activeRouteLease = routeLease
        } else if let capturedRouteLease = await transport.acquireSwarmRouteLease() {
            guard swarmEnabled,
                  matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
            else { return }
            do {
                let enabled = try await capturedRouteLease.isEnabled(sessionKey: requestedKey)
                guard swarmEnabled,
                      matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
                else { return }
                guard enabled else {
                    swarmEnabled = false
                    self.resetSwarmProgress()
                    return
                }
            } catch {
                guard swarmEnabled,
                      matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
                else { return }
                self.scheduleSwarmSessionRetry(
                    sessionKey: requestedKey,
                    sessionSnapshot: sessionSnapshot,
                    retryAttempt: retryAttempt)
                return
            }
            guard swarmEnabled,
                  matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
            else { return }
            activeRouteLease = capturedRouteLease
        } else {
            guard swarmEnabled,
                  matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
            else { return }
            self.scheduleSwarmSessionRetry(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt)
            return
        }
        guard swarmEnabled,
              matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
        else { return }
        if swarmSessionKey != sessionKey {
            swarmSessionKey = sessionKey
            swarmActivityState.clear()
            swarmSessions = []
            self.updateSwarmProjection()
        }
        swarmRefreshGeneration &+= 1
        let generation = swarmRefreshGeneration
        do {
            let rows = try await activeRouteLease.listChildSessions(parentKey: requestedKey)
            guard generation == swarmRefreshGeneration,
                  swarmEnabled,
                  matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
            else { return }
            swarmSessions = swarmActivityState.decorate(rows)
            self.updateSwarmProjection()
        } catch {
            guard generation == swarmRefreshGeneration,
                  swarmEnabled,
                  matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey)
            else { return }
            chatUILogger.debug("swarm child refresh failed: \(error.localizedDescription, privacy: .public)")
            self.scheduleSwarmSessionRetry(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt)
        }
    }

    private func scheduleSwarmSessionRetry(
        sessionKey requestedKey: String,
        sessionSnapshot: SessionSnapshot?,
        retryAttempt: Int)
    {
        guard swarmEnabled,
              matchesCurrentSessionKey(incoming: requestedKey, current: sessionKey),
              swarmRefreshRetryDelays.indices.contains(retryAttempt)
        else { return }
        let delay = swarmRefreshRetryDelays[retryAttempt]
        swarmRefreshTask?.cancel()
        swarmRefreshTask = Task { [weak self] in
            try? await Task.sleep(for: .seconds(delay))
            guard !Task.isCancelled else { return }
            await self?.refreshSwarmSessions(
                sessionKey: requestedKey,
                sessionSnapshot: sessionSnapshot,
                retryAttempt: retryAttempt + 1)
        }
    }

    func resetSwarmProgress() {
        swarmRefreshTask?.cancel()
        swarmRefreshTask = nil
        swarmRefreshGeneration &+= 1
        swarmSessionKey = sessionKey
        swarmActivityState.clear()
        swarmSessions = []
        self.updateSwarmProjection()
    }
}

struct OpenClawChatSwarmProgressView: View {
    let groups: [OpenClawChatSwarmGroup]

    var body: some View {
        if !self.groups.isEmpty {
            ScrollView(.vertical) {
                VStack(spacing: 6) {
                    ForEach(self.groups) { group in
                        OpenClawChatSwarmGroupView(group: group)
                    }
                }
                .padding(.trailing, 2)
            }
            .frame(maxHeight: 260)
            .accessibilityElement(children: .contain)
            .accessibilityLabel(Text("Swarm"))
        }
    }
}

private struct OpenClawChatSwarmGroupView: View {
    let group: OpenClawChatSwarmGroup

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack(alignment: .firstTextBaseline, spacing: 8) {
                Text(verbatim: self.group.label)
                    .font(OpenClawChatTypography.captionSemiBold)
                    .lineLimit(1)
                Text(verbatim: String(
                    format: String(localized: "%1$lld Running · %2$lld Done · %3$lld Failed"),
                    Int64(self.group.running),
                    Int64(self.group.done),
                    Int64(self.group.failed)))
                    .font(OpenClawChatTypography.caption2)
                    .foregroundStyle(.secondary)
                    .lineLimit(1)
                Spacer(minLength: 0)
            }
            if let narrator = self.group.narrator, !narrator.isEmpty {
                Text(verbatim: narrator)
                    .font(OpenClawChatTypography.caption2)
                    .foregroundStyle(.secondary)
                    .lineLimit(2)
            }
            ForEach(self.group.phases) { phase in
                HStack(alignment: .top, spacing: 8) {
                    Text(verbatim: phase.title ?? String(localized: "Unphased"))
                        .font(OpenClawChatTypography.caption2)
                        .foregroundStyle(.secondary)
                        .lineLimit(1)
                        .frame(minWidth: 56, alignment: .leading)
                    LazyVGrid(
                        columns: [GridItem(.adaptive(minimum: 9, maximum: 9), spacing: 6)],
                        alignment: .leading,
                        spacing: 6)
                    {
                        ForEach(phase.dots) { dot in
                            OpenClawChatSwarmDotView(dot: dot)
                        }
                        if phase.hidden > 0 {
                            Text(verbatim: "+\(phase.hidden)")
                                .font(OpenClawChatTypography.caption2)
                                .foregroundStyle(.secondary)
                                .accessibilityLabel(Text(
                                    verbatim: phase.hidden == 1
                                        ? String(localized: "1 more worker")
                                        : String(
                                            format: String(localized: "%1$lld more workers"),
                                            Int64(phase.hidden))))
                        }
                    }
                }
            }
        }
        .padding(.horizontal, 10)
        .padding(.vertical, 8)
        .background(OpenClawChatTheme.assistantBubble.opacity(0.5), in: RoundedRectangle(cornerRadius: 10))
        .overlay {
            RoundedRectangle(cornerRadius: 10)
                .stroke(OpenClawChatTheme.accent.opacity(0.2), lineWidth: 1)
        }
    }
}

private struct OpenClawChatSwarmDotView: View {
    let dot: OpenClawChatSwarmDot

    var body: some View {
        self.shape
            .frame(width: 9, height: 9)
            .accessibilityElement()
            .accessibilityLabel(Text(verbatim: "\(self.dot.label): \(self.dot.status.label)"))
    }

    @ViewBuilder
    private var shape: some View {
        switch self.dot.status {
        case .queued:
            Circle().stroke(OpenClawChatTheme.muted, lineWidth: 1)
        case .running:
            Circle().fill(OpenClawChatTheme.accent)
        case .done:
            Circle().fill(OpenClawChatTheme.success)
        case .failed:
            RoundedRectangle(cornerRadius: 2)
                .fill(OpenClawChatTheme.danger)
                .rotationEffect(.degrees(45))
                .scaleEffect(0.82)
        }
    }
}
