package wang.yaojia.webterm.push

import java.util.UUID

/**
 * The DATA-ONLY FCM payload the server sends (plan §4.5). It carries ONLY three fields:
 *
 *  - [sessionId] — which session the signal is about,
 *  - [cls] — the notify class (`"needs-input"` = a held gate, `"done"` = finished),
 *  - [token] — the single-use `/hook/decision` capability token, present for **needs-input only**.
 *
 * It NEVER carries cwd / command / terminal bytes (content minimization, plan §4.5 / §8). The raw
 * `Map<String,String>` from `RemoteMessage.getData()` is untrusted external input, so [parse]
 * validates at the boundary and returns null on anything malformed rather than trusting it.
 *
 * Pure Kotlin (no Android imports) so the parse boundary is JVM-unit-testable.
 */
public data class PushPayload(
    public val sessionId: UUID,
    public val cls: String,
    public val token: String?,
) {
    /** True only for a gate signal that actually carries a usable capability token. */
    public val isActionableGate: Boolean
        get() = cls == CLASS_NEEDS_INPUT && !token.isNullOrEmpty()

    public companion object {
        public const val KEY_SESSION_ID: String = "sessionId"
        public const val KEY_CLS: String = "cls"
        public const val KEY_TOKEN: String = "token"

        /** A held gate awaiting Allow/Deny — carries the single-use token. */
        public const val CLASS_NEEDS_INPUT: String = "needs-input"

        /** An informational "session finished" signal — no token, no actions. */
        public const val CLASS_DONE: String = "done"

        /**
         * Boundary validation. Returns null when [sessionId] is absent or not a UUID, or when
         * [cls] is absent/blank. [token] stays optional (present only for `needs-input`); it is
         * kept verbatim and NEVER logged — see [PushDecisionSubmitter].
         */
        public fun parse(data: Map<String, String>): PushPayload? {
            val rawId = data[KEY_SESSION_ID]?.trim().orEmptyToNull() ?: return null
            val sessionId = runCatching { UUID.fromString(rawId) }.getOrNull() ?: return null
            val cls = data[KEY_CLS]?.trim().orEmptyToNull() ?: return null
            val token = data[KEY_TOKEN]?.orEmptyToNull()
            return PushPayload(sessionId = sessionId, cls = cls, token = token)
        }

        private fun String?.orEmptyToNull(): String? = this?.takeIf { it.isNotEmpty() }
    }
}
