/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui.page

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.runicgateway.app.data.api.dto.BlockDto
import com.runicgateway.app.ui.LocalAssetResolver
import com.runicgateway.app.ui.components.HtmlText
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

/**
 * Renders the block-based CMS page body (PLAN.md §6.1). Dispatches on the block
 * `type` from the server registry (`heading | rich_text | image | quote | cta |
 * divider | two_column`); unknown or invisible blocks are skipped so a new
 * server-side block type never breaks an older app.
 */
@Composable
fun BlockList(blocks: List<BlockDto>, modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        blocks.filter { it.visible }.forEach { block ->
            BlockView(block)
            Spacer(Modifier.height(12.dp))
        }
    }
}

@Composable
private fun BlockView(block: BlockDto) {
    val props = block.props
    when (block.type) {
        "heading" -> {
            val text = props.str("text").orEmpty()
            val level = props.int("level") ?: 2
            Text(text = text, style = headingStyle(level))
        }

        "rich_text" -> {
            props.str("html")?.takeIf { it.isNotBlank() }?.let {
                HtmlText(it, modifier = Modifier.fillMaxWidth())
            }
        }

        "image" -> {
            val resolveAsset = LocalAssetResolver.current
            resolveAsset(props.str("src"))?.let { url ->
                AsyncImage(
                    model = url,
                    contentDescription = props.str("alt"),
                    contentScale = ContentScale.FillWidth,
                    modifier = Modifier.fillMaxWidth(),
                )
            }
            props.str("caption")?.takeIf { it.isNotBlank() }?.let { caption ->
                Text(
                    text = caption,
                    style = MaterialTheme.typography.labelSmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                    modifier = Modifier.padding(top = 4.dp),
                )
            }
        }

        "quote" -> {
            Column(Modifier.padding(start = 12.dp)) {
                Text(
                    text = props.str("text").orEmpty(),
                    style = MaterialTheme.typography.titleMedium.copy(fontStyle = FontStyle.Italic),
                )
                props.str("attribution")?.takeIf { it.isNotBlank() }?.let { attribution ->
                    Text(
                        text = "— $attribution",
                        style = MaterialTheme.typography.labelMedium,
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                        modifier = Modifier.padding(top = 4.dp),
                    )
                }
            }
        }

        "cta" -> {
            val resolveAsset = LocalAssetResolver.current
            val uriHandler = LocalUriHandler.current
            val label = props.str("text").orEmpty()
            val url = resolveAsset(props.str("url"))
            val onClick: () -> Unit = { url?.let { runCatching { uriHandler.openUri(it) } } }
            if (props.str("style") == "secondary") {
                OutlinedButton(onClick = onClick) { Text(label) }
            } else {
                Button(onClick = onClick) { Text(label) }
            }
        }

        "divider" -> HorizontalDivider()

        "two_column" -> {
            // Stack the two columns vertically on a phone (§2.1 functional pass).
            BlockList(props.blocks("left"))
            BlockList(props.blocks("right"))
        }

        else -> Unit // unknown block type — skip rather than crash
    }
}

@Composable
private fun headingStyle(level: Int) = when (level) {
    1 -> MaterialTheme.typography.headlineMedium
    2 -> MaterialTheme.typography.headlineSmall
    3 -> MaterialTheme.typography.titleLarge
    else -> MaterialTheme.typography.titleMedium
}

// ── Prop readers (tolerant of missing/typed values) ──────────────────────
private val blockJson = Json {
    ignoreUnknownKeys = true
    explicitNulls = false
    coerceInputValues = true
}

private fun JsonObject.str(key: String): String? =
    (this[key] as? JsonPrimitive)?.takeIf { it.isString }?.content

private fun JsonObject.int(key: String): Int? =
    (this[key] as? JsonPrimitive)?.content?.toIntOrNull()

private fun JsonObject.blocks(key: String): List<BlockDto> {
    val array = this[key] as? JsonArray ?: return emptyList()
    return runCatching {
        blockJson.decodeFromJsonElement(ListSerializer(BlockDto.serializer()), array)
    }.getOrDefault(emptyList())
}
