/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.data.api.dto

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

/**
 * DTOs for the public shard widgets (PLAN.md §6.2). Shapes mirror the website's
 * `public/shard.controller.js` responses and the live SSE frames emitted by
 * `utils/shardBroadcast.js`. The champ/guild/governor board reads return the
 * stored event payload verbatim (a permissive object), so only the fields the app
 * renders are modeled; unknown keys are ignored by the JSON parser, and the live
 * `*.update` frames on `/public/shard/stream` decode into these same DTOs.
 */

/**
 * Which shard surfaces this caller may reach (`GET /public/shard/features`), plus
 * the audience rung they resolved to.
 *
 * Every shard-derived feature is admin-configurable — it can be switched off or
 * raised to a higher rung — so the menu cannot be a static list (PLAN.md §5, M11).
 * [level] is the SERVER's answer on the `anonymous → logged_in → player → staff →
 * admin` ladder and is authoritative: don't re-derive a rung from the session role,
 * since `player` means *a linked game account* and staff always satisfy it.
 *
 * The response reports only what the caller can see, so the list itself never
 * discloses a feature they're gated out of.
 */
@Serializable
data class ShardFeaturesDto(
    val level: String? = null,
    val features: List<String> = emptyList(),
)

/**
 * A game actor (player/leader/governor) as embedded in board payloads. Per the wire
 * spec (`docs/link/INTEGRATION.md` §1), in-game [serial]s are opaque hex-string keys
 * (e.g. `"0x1A2B"`), never numbers.
 *
 * [acct] and [webId] are **locked to the admin rung** by the visibility framework
 * (`docs/link/v3.md` §3.4 rule 1) — a game account name and a linked site-user id are
 * not in-game-visible the way a character name is, so they are stripped from every
 * response below `admin` and no admin setting can loosen that. The fields stay
 * declared because an admin session does receive them; nothing below one should
 * expect a value.
 */
@Serializable
data class ActorDto(
    val serial: String? = null,
    val name: String? = null,
    val acct: String? = null,
    val webId: String? = null,
) {
    /** Best display label for this actor. */
    val label: String get() = name ?: acct ?: "Someone"
}

/** A gold-supply sample (`economy.supply`), oldest → newest in the series. */
@Serializable
data class EconomySampleDto(
    val accounts: Int? = null,
    val gold: Double? = null,
    val t: Long? = null,
)

/** `GET /public/shard/status` — connection state + online count + latest economy. */
@Serializable
data class ShardStatusDto(
    val enabled: Boolean = false,
    /** Sidecar link state: `connected` / `disconnected` / … */
    val status: String? = null,
    /** Whether the in-game plugin is currently connected to the sidecar. */
    val pluginConnected: Boolean = false,
    /** ISO timestamp of the last ingested event, or null. */
    val lastEventAt: String? = null,
    val onlineCount: Int = 0,
    val economy: EconomySampleDto? = null,
) {
    /** True when the shard is live (link enabled and the plugin is connected). */
    val isOnline: Boolean get() = enabled && pluginConnected
}

/**
 * `GET /public/shard/feed` — a stored notable event. The domain fields live under
 * [payload]; the live SSE frames carry those same fields at the top level (see
 * `ShardEventText`).
 */
@Serializable
data class FeedEventDto(
    val id: Long = 0,
    val kind: String = "",
    val t: Long? = null,
    val bootId: String? = null,
    val payload: JsonObject? = null,
    val createdAt: String? = null,
)

/**
 * `GET /public/shard/online` — a staff member currently in-world. Location is only
 * present for privileged viewers server-side; anonymous/app callers see name+serial.
 */
@Serializable
data class OnlineStaffDto(
    val serial: String? = null,
    val name: String? = null,
    val map: String? = null,
    val x: Int? = null,
    val y: Int? = null,
    val z: Int? = null,
)

/**
 * A house on the public IDOC board (`GET /public/shard/houses`) — location only.
 * Owner/price/decay detail is staff-only and never reaches the app.
 */
@Serializable
data class HouseDto(
    val serial: String = "",
    val name: String? = null,
    val region: String? = null,
    val map: String? = null,
    val x: Int? = null,
    val y: Int? = null,
    val z: Int? = null,
    val isIdoc: Boolean = false,
)

/**
 * A champion-spawn board entry (`GET /public/shard/champs` + live `champ.update`).
 * Three families share the board (`category`: champion / mini / sea); the
 * category-specific fields are all nullable.
 */
@Serializable
data class ChampDto(
    val serial: String = "",
    val category: String? = null,
    val type: String? = null,
    val name: String? = null,
    val status: String? = null,
    val active: Boolean = false,
    val map: String? = null,
    val x: Int? = null,
    val y: Int? = null,
    val z: Int? = null,
    val bossUp: Boolean = false,
    val boss: String? = null,
    val level: Int? = null,
    val maxLevel: Int? = null,
    val kills: Int? = null,
    val maxKills: Int? = null,
    val hits: Long? = null,
    val hitsMax: Long? = null,
    val restartAt: String? = null,
    val t: Long? = null,
)

/** A guild board entry (`GET /public/shard/guilds` + live `guild.update`). */
@Serializable
data class GuildDto(
    val id: Long = 0,
    val name: String? = null,
    val abbr: String? = null,
    val members: Int? = null,
    val online: Int? = null,
    val alliance: String? = null,
    val leader: ActorDto? = null,
    val t: Long? = null,
)

/** A town-governor board entry (`GET /public/shard/governors` + live `city.update`). */
@Serializable
data class GovernorDto(
    val city: String = "",
    val governor: ActorDto? = null,
    val governorElect: ActorDto? = null,
    val electionPhase: String? = null,
    val t: Long? = null,
)

/** One term in a city's governor ledger (`GET /public/shard/governors/:city/history`). */
@Serializable
data class GovernorTermDto(
    val city: String? = null,
    val governor: ActorDto? = null,
    val startedAt: Long? = null,
    val endedAt: Long? = null,
    val votes: Int? = null,
)

/** `GET /public/shard/presence` — the online-population aggregate (live `presence.online`). */
@Serializable
data class PresenceDto(
    val count: Int = 0,
    val byFacet: Map<String, Int> = emptyMap(),
    val byRegion: Map<String, Int> = emptyMap(),
    val t: Long? = null,
)
