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

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement

/**
 * DTOs for the public site/identity endpoints. Shapes mirror the backend
 * responses (website `public.controller.js` + `settings.model.js`); unknown
 * keys are ignored by the JSON parser so additive backend fields never break
 * decoding (PLAN.md §8 "additive, v1").
 */

/** `GET /public/version` and the `version` block embedded in `/public/status`. */
@Serializable
data class VersionDto(
    val service: String = "",
    val api: String = "",
    val server: String = "",
)

/** `GET /public/status` — site mode + version for the first-run probe (§3). */
@Serializable
data class StatusDto(
    val mode: String = "live",
    @SerialName("status_message") val statusMessage: String = "",
    val version: VersionDto = VersionDto(),
) {
    val isMaintenance: Boolean get() = mode.equals("maintenance", ignoreCase = true)
}

/** Per-shard branding block the app themes itself from (§3, §6.1). */
@Serializable
data class BrandDto(
    val name: String = "",
    val shortName: String = "",
    val tagline: String = "",
    val description: String? = null,
    val contactEmail: String = "",
    val url: String = "",
    /** Seed/accent color as a hex string, e.g. "#7f99bd". */
    val accent: String = "",
    /** Asset URL or site-relative path; empty = none. Resolve against the base URL. */
    val logo: String = "",
    val hero: String = "",
    val favicon: String = "",
)

/** Derived, public-safe registration availability flags. */
@Serializable
data class RegistrationFlagsDto(
    val password: Boolean = false,
    val sso: Boolean = false,
)

/**
 * Push-notification relay config (M7). [ntfyUrl] is the client-facing ntfy base
 * URL the app's embedded distributor registers its device topic against; null (or
 * absent, on an older backend) means push isn't configured for this shard and the
 * Notifications screen shows it as unavailable.
 */
@Serializable
data class PushConfigDto(
    val ntfyUrl: String? = null,
)

/**
 * `GET /public/settings` — whitelisted settings + branding. Only the keys the
 * app consumes are modeled; other whitelisted keys are ignored.
 */
@Serializable
data class SettingsDto(
    @SerialName("site_title") val siteTitle: String? = null,
    @SerialName("status_message") val statusMessage: String? = null,
    @SerialName("maintenance_message") val maintenanceMessage: String? = null,
    val registration: RegistrationFlagsDto = RegistrationFlagsDto(),
    val gameAccountSignup: Boolean = false,
    val brand: BrandDto = BrandDto(),
    /** Push relay config (M7); default (null ntfyUrl) on a backend that predates it. */
    val push: PushConfigDto = PushConfigDto(),
    /**
     * The admin's **resolved** theme tokens — the CSS custom properties the site
     * paints, already layered `:root ← preset ← custom` by the server
     * (THEMING_AND_NAV.md §3). Absent when no `theme_visual` row exists, which
     * means "the shipped defaults" and is the untouched-instance path.
     *
     * Held as a raw [JsonElement] rather than a `Map<String, String>` on
     * purpose: a single unexpected value must not fail the decode of the whole
     * settings payload and take `brand` and `push` down with it. It is coerced
     * field-by-field by `SiteAppearance.from`.
     *
     * The raw `theme_visual` / `brand_assets` rows ride along in this same
     * response and are deliberately **not** modeled — they are inputs, and
     * re-deriving a palette from them would be a second `resolveThemeTokens` in
     * Kotlin, guaranteed to drift (§3).
     */
    val theme: JsonElement? = null,
    /**
     * The public nav overrides, as the raw JSON **string** stored in
     * `settings.value` (TEXT) — so it is parsed a second time, exactly as the web
     * client's `parseJsonSetting` does. Absent when the admin never edited the
     * nav.
     */
    @SerialName("nav_public") val navPublic: String? = null,
)
