import Foundation

/// The host-state projection behind the phone's Home / Projects / To-Dos surfaces — the
/// mobile mirror of the Mac dashboard (UX_MACOS §2). Pushed on connect and whenever it
/// changes (`HostMsg.dashboard`). Read-friendly value types; the phone acts on them via the
/// control `ClientMsg`s.

public struct DashboardCounts: Sendable, Codable, Equatable {
    public let projects: Int
    public let chats: Int
    public let activeChats: Int
    public let messages: Int
    public let activeDays: Int
    /// Total tokens used across every session (mirrors `DashboardStats.tokens`).
    public let tokens: Int

    public init(
        projects: Int, chats: Int, activeChats: Int, messages: Int, activeDays: Int, tokens: Int = 0
    ) {
        self.projects = projects
        self.chats = chats
        self.activeChats = activeChats
        self.messages = messages
        self.activeDays = activeDays
        self.tokens = tokens
    }
    public static let empty = DashboardCounts(
        projects: 0, chats: 0, activeChats: 0, messages: 0, activeDays: 0, tokens: 0)

    private enum CodingKeys: String, CodingKey {
        case projects, chats, activeChats, messages, activeDays, tokens
    }

    public init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        self.projects = try c.decode(Int.self, forKey: .projects)
        self.chats = try c.decode(Int.self, forKey: .chats)
        self.activeChats = try c.decode(Int.self, forKey: .activeChats)
        self.messages = try c.decode(Int.self, forKey: .messages)
        self.activeDays = try c.decode(Int.self, forKey: .activeDays)
        // Tolerate a host that predates token tracking — its dashboards report no token total.
        self.tokens = try c.decodeIfPresent(Int.self, forKey: .tokens) ?? 0
    }
}

/// One day's activity for the grid: its message count and tokens used. (An array of these, not a
/// `[Date:Int]` map, since CBOR map keys are strings — keeps the wire shape clean and ordered.)
public struct ActivityDay: Sendable, Codable, Equatable {
    public let day: Date     // start-of-day
    public let count: Int    // user messages
    public let tokens: Int   // tokens used that day (0 when none recorded — grid falls back to count)
    public init(day: Date, count: Int, tokens: Int = 0) {
        self.day = day
        self.count = count
        self.tokens = tokens
    }

    private enum CodingKeys: String, CodingKey { case day, count, tokens }

    public init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        self.day = try c.decode(Date.self, forKey: .day)
        self.count = try c.decode(Int.self, forKey: .count)
        // Tolerate days from a host that predates token tracking — they shade by message count.
        self.tokens = try c.decodeIfPresent(Int.self, forKey: .tokens) ?? 0
    }
}

public struct WireProject: Sendable, Codable, Equatable, Identifiable {
    public let id: ProjectID
    public let name: String
    public let defaultBranch: String
    public let sessionCount: Int
    public let activeCount: Int
    /// Whether the project is under Nucleic Control. Gates control-only capabilities on the
    /// remote — notably Orchestra, which a non-Control project's sessions may not use.
    public let isNucleicControlled: Bool

    public init(
        id: ProjectID, name: String, defaultBranch: String, sessionCount: Int, activeCount: Int,
        isNucleicControlled: Bool = false
    ) {
        self.id = id
        self.name = name
        self.defaultBranch = defaultBranch
        self.sessionCount = sessionCount
        self.activeCount = activeCount
        self.isNucleicControlled = isNucleicControlled
    }

    private enum CodingKeys: String, CodingKey {
        case id, name, defaultBranch, sessionCount, activeCount, isNucleicControlled
    }

    public init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try c.decode(ProjectID.self, forKey: .id)
        self.name = try c.decode(String.self, forKey: .name)
        self.defaultBranch = try c.decode(String.self, forKey: .defaultBranch)
        self.sessionCount = try c.decode(Int.self, forKey: .sessionCount)
        self.activeCount = try c.decode(Int.self, forKey: .activeCount)
        // Tolerate dashboards from a host that predates the field — they default to "not controlled".
        self.isNucleicControlled =
            try c.decodeIfPresent(Bool.self, forKey: .isNucleicControlled) ?? false
    }
}

public struct WireTodo: Sendable, Codable, Equatable, Identifiable {
    public let id: TodoID
    public let text: String
    public let summary: String?
    public let projectID: ProjectID?
    public let projectName: String?
    public let status: TodoStatus
    public let dispatchedSessionID: SessionID?
    public let updatedAt: Date

    public init(
        id: TodoID, text: String, summary: String?, projectID: ProjectID?, projectName: String?,
        status: TodoStatus, dispatchedSessionID: SessionID?, updatedAt: Date
    ) {
        self.id = id
        self.text = text
        self.summary = summary
        self.projectID = projectID
        self.projectName = projectName
        self.status = status
        self.dispatchedSessionID = dispatchedSessionID
        self.updatedAt = updatedAt
    }
}

public struct DashboardSnapshot: Sendable, Codable, Equatable {
    public let counts: DashboardCounts
    public let activity: [ActivityDay]
    public let projects: [WireProject]
    public let todos: [WireTodo]

    public init(counts: DashboardCounts, activity: [ActivityDay], projects: [WireProject], todos: [WireTodo]) {
        self.counts = counts
        self.activity = activity
        self.projects = projects
        self.todos = todos
    }
    public static let empty = DashboardSnapshot(counts: .empty, activity: [], projects: [], todos: [])
}

// MARK: - Control request payloads (control scope)

/// Start a new chat in a project (the Mac's home composer, mirrored — UX_MACOS §2).
public struct StartChatRequest: Sendable, Codable, Equatable {
    public let projectID: ProjectID
    public let message: String
    public let model: String?
    public let effort: String?
    public let baseBranch: String?
    public let useWorktree: Bool
    public let auto: Bool?

    public init(
        projectID: ProjectID, message: String, model: String? = nil, effort: String? = nil,
        baseBranch: String? = nil, useWorktree: Bool = true, auto: Bool? = nil
    ) {
        self.projectID = projectID
        self.message = message
        self.model = model
        self.effort = effort
        self.baseBranch = baseBranch
        self.useWorktree = useWorktree
        self.auto = auto
    }
}

/// Capture a new idea into the to-do inbox.
public struct CaptureTodoRequest: Sendable, Codable, Equatable {
    public let text: String
    public let projectID: ProjectID?
    public init(text: String, projectID: ProjectID? = nil) {
        self.text = text
        self.projectID = projectID
    }
}
