package com.beancount.mobile.accessibility

import android.content.Context
import android.graphics.Color
import android.util.Log
import com.facebook.react.bridge.ReadableMap
import org.json.JSONObject

/**
 * 浮层 UI 颜色契约（docs/ui-redesign-p6-plan.md「FloatingUiConfig 契约」）。
 * 字段名（camelCase）与 src/services/floatingUiConfig.ts 的 FloatingUiConfig.colors 一一对应。
 * 默认值 = 现状硬编码颜色（JS 未推送时行为不变），注释标明出处。
 */
data class FloatingUiConfigColors(
    /** 按钮/选中态背景。 */           val accent: String = "#FF5E6AD2",     // 原 FloatingBillView saveBg / 分段选中态
    /** accent 上的文字色。 */          val accentFg: String = "#FFFFFFFF",   // 原 FloatingBillView 按钮文字色
    /** 卡片底色（原 0x8C050506 半透明，默认实色化到 F0 保证可读）。 */
    val cardBg: String = "#F0050506",                                       // 原 FloatingBillView containerBg 0x8C050506
    /** 输入框/未选中 chip 底色。 */     val inputBg: String = "#4012131A",    // 原 FloatingBillView 输入框底 0x4012131A
    val fgPrimary: String = "#FFFFFFFF",                                    // 原 FloatingBillView 输入框文字色
    val fgSecondary: String = "#FF9CA3AF",                                  // 原 FloatingBillView 未选中 tab/chip 文字色
    val border: String = "#80222433",                                       // 原 FloatingBillView 输入框/chip 描边 0x80222433
    /** financial.income。 */           val income: String = "#FF10B981",     // 原 FloatingBillView 收入/转账 chip 选中色
    /** financial.expense。 */          val expense: String = "#FFE11D48",    // 原 FloatingBillView 支出账户 chip 选中色
    /** financial.transfer。 */         val transfer: String = "#FF10B981",   // 原 FloatingBillView 转账转入 chip 选中色
)

/**
 * 浮层 UI 文案契约。
 * 字段名（camelCase）与 src/services/floatingUiConfig.ts 的 FloatingUiConfig.labels 一一对应。
 * 默认值 = 现状中文硬编码文案（去 emoji）。
 */
data class FloatingUiConfigLabels(
    /** 浮窗标题。 */               val billTitle: String = "调整交易草稿",
    val dirExpense: String = "支出",
    val dirIncome: String = "收入",
    val dirTransfer: String = "转账",
    val amountLabel: String = "金额",
    val payeeLabel: String = "交易对手",
    val narrationLabel: String = "描述/备注",
    val narrationHint: String = "输入交易叙述",
    /** 支出方向分类行标签。 */       val categoryExpense: String = "交易分类",
    /** 收入方向分类行标签。 */       val categoryIncome: String = "收入分类",
    /** 转账方向第一行标签。 */       val transferTarget: String = "转入账户",
    /** 支出方向账户行标签。 */       val accountExpense: String = "资金来源",
    /** 收入方向账户行标签。 */       val accountIncome: String = "存入账户",
    /** 转账方向账户行标签。 */       val accountTransfer: String = "转出账户",
    val openApp: String = "打开应用",
    val dismiss: String = "忽略",
    val confirm: String = "确认入账",
    /** 悬浮球「识别账单」。 */       val ballOcr: String = "识别账单",
    /** 悬浮球「记住此页」。 */       val ballRemember: String = "记住此页",
    /** 记住页面成功提示（不含 emoji）。 */ val rememberSuccess: String = "已将当前页面加入识别白名单",
    /** 失败提示前缀（原生拼接 ': ' + e.message）。 */ val rememberFail: String = "记录失败",
    /** SelectToSpeakService Toast「已记住页面签名」前缀。 */ val pageRemembered: String = "已记住页面签名",
    /** 「该页面签名已存在」前缀。 */ val pageSignatureExists: String = "该页面签名已存在",
)

/** 原生浮层 UI 配置（JS 经 AccessibilityBridge.setFloatingUiConfig 下发）。 */
data class FloatingUiConfig(
    val colors: FloatingUiConfigColors = FloatingUiConfigColors(),
    val labels: FloatingUiConfigLabels = FloatingUiConfigLabels(),
)

/**
 * FloatingUiConfig 的 SharedPreferences 持久化（plan「原生持久化」节）。
 *
 * - 文件名 floating_ui_config，单键 config_json 存整个 JSON。
 * - save 采用合并策略：先 load 出现存 JSON，再覆盖传入键，避免部分更新丢字段。
 * - load 逐字段 optString 缺省回退 data class 默认值；任何异常返回全默认。
 */
object FloatingUiConfigStore {
    private const val TAG = "FloatingUiConfigStore"
    private const val PREFS = "floating_ui_config"
    private const val KEY = "config_json"

    /** 契约内已知 colors 键（save 时只写这些键）。 */
    private val COLOR_KEYS = listOf(
        "accent", "accentFg", "cardBg", "inputBg", "fgPrimary",
        "fgSecondary", "border", "income", "expense", "transfer",
    )

    /** 契约内已知 labels 键（save 时只写这些键）。 */
    private val LABEL_KEYS = listOf(
        "billTitle", "dirExpense", "dirIncome", "dirTransfer", "amountLabel",
        "payeeLabel", "narrationLabel", "narrationHint", "categoryExpense",
        "categoryIncome", "transferTarget", "accountExpense", "accountIncome",
        "accountTransfer", "openApp", "dismiss", "confirm", "ballOcr",
        "ballRemember", "rememberSuccess", "rememberFail", "pageRemembered",
        "pageSignatureExists",
    )

    /** 保存（合并）：先读出现存 JSON，再覆盖传入的已知键。 */
    fun save(context: Context, map: ReadableMap) {
        val root = readRawJson(context)

        if (map.hasKey("colors")) {
            val colorsMap = map.getMap("colors")
            val colorsJson = root.optJSONObject("colors") ?: JSONObject().also { root.put("colors", it) }
            if (colorsMap != null) {
                for (key in COLOR_KEYS) {
                    if (colorsMap.hasKey(key) && !colorsMap.isNull(key)) {
                        colorsJson.put(key, colorsMap.getString(key))
                    }
                }
            }
        }

        if (map.hasKey("labels")) {
            val labelsMap = map.getMap("labels")
            val labelsJson = root.optJSONObject("labels") ?: JSONObject().also { root.put("labels", it) }
            if (labelsMap != null) {
                for (key in LABEL_KEYS) {
                    if (labelsMap.hasKey(key) && !labelsMap.isNull(key)) {
                        labelsJson.put(key, labelsMap.getString(key))
                    }
                }
            }
        }

        context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
            .edit()
            .putString(KEY, root.toString())
            .apply()
    }

    /** 读取配置；缺失字段回退默认值，任何异常返回全默认。 */
    fun load(context: Context): FloatingUiConfig {
        return try {
            val root = readRawJson(context)
            val defaultColors = FloatingUiConfigColors()
            val defaultLabels = FloatingUiConfigLabels()
            val colorsJson = root.optJSONObject("colors")
            val labelsJson = root.optJSONObject("labels")

            val colors = FloatingUiConfigColors(
                accent = colorsJson.optStringOr("accent", defaultColors.accent),
                accentFg = colorsJson.optStringOr("accentFg", defaultColors.accentFg),
                cardBg = colorsJson.optStringOr("cardBg", defaultColors.cardBg),
                inputBg = colorsJson.optStringOr("inputBg", defaultColors.inputBg),
                fgPrimary = colorsJson.optStringOr("fgPrimary", defaultColors.fgPrimary),
                fgSecondary = colorsJson.optStringOr("fgSecondary", defaultColors.fgSecondary),
                border = colorsJson.optStringOr("border", defaultColors.border),
                income = colorsJson.optStringOr("income", defaultColors.income),
                expense = colorsJson.optStringOr("expense", defaultColors.expense),
                transfer = colorsJson.optStringOr("transfer", defaultColors.transfer),
            )
            val labels = FloatingUiConfigLabels(
                billTitle = labelsJson.optStringOr("billTitle", defaultLabels.billTitle),
                dirExpense = labelsJson.optStringOr("dirExpense", defaultLabels.dirExpense),
                dirIncome = labelsJson.optStringOr("dirIncome", defaultLabels.dirIncome),
                dirTransfer = labelsJson.optStringOr("dirTransfer", defaultLabels.dirTransfer),
                amountLabel = labelsJson.optStringOr("amountLabel", defaultLabels.amountLabel),
                payeeLabel = labelsJson.optStringOr("payeeLabel", defaultLabels.payeeLabel),
                narrationLabel = labelsJson.optStringOr("narrationLabel", defaultLabels.narrationLabel),
                narrationHint = labelsJson.optStringOr("narrationHint", defaultLabels.narrationHint),
                categoryExpense = labelsJson.optStringOr("categoryExpense", defaultLabels.categoryExpense),
                categoryIncome = labelsJson.optStringOr("categoryIncome", defaultLabels.categoryIncome),
                transferTarget = labelsJson.optStringOr("transferTarget", defaultLabels.transferTarget),
                accountExpense = labelsJson.optStringOr("accountExpense", defaultLabels.accountExpense),
                accountIncome = labelsJson.optStringOr("accountIncome", defaultLabels.accountIncome),
                accountTransfer = labelsJson.optStringOr("accountTransfer", defaultLabels.accountTransfer),
                openApp = labelsJson.optStringOr("openApp", defaultLabels.openApp),
                dismiss = labelsJson.optStringOr("dismiss", defaultLabels.dismiss),
                confirm = labelsJson.optStringOr("confirm", defaultLabels.confirm),
                ballOcr = labelsJson.optStringOr("ballOcr", defaultLabels.ballOcr),
                ballRemember = labelsJson.optStringOr("ballRemember", defaultLabels.ballRemember),
                rememberSuccess = labelsJson.optStringOr("rememberSuccess", defaultLabels.rememberSuccess),
                rememberFail = labelsJson.optStringOr("rememberFail", defaultLabels.rememberFail),
                pageRemembered = labelsJson.optStringOr("pageRemembered", defaultLabels.pageRemembered),
                pageSignatureExists = labelsJson.optStringOr("pageSignatureExists", defaultLabels.pageSignatureExists),
            )
            FloatingUiConfig(colors, labels)
        } catch (e: Exception) {
            Log.e(TAG, "读取浮层 UI 配置失败，回退默认值: ${e.message}")
            FloatingUiConfig()
        }
    }

    /** 解析颜色字符串；失败/为空时解析 fallbackHex（fallback 也不合法则返回 Color.BLACK）。 */
    fun parseColorOr(value: String, fallbackHex: String): Int {
        if (value.isNotBlank()) {
            try {
                return Color.parseColor(value)
            } catch (_: Exception) {}
        }
        return try {
            Color.parseColor(fallbackHex)
        } catch (_: Exception) {
            Color.BLACK
        }
    }

    /** 读出现存 JSON；无数据或解析失败返回空 JSONObject。 */
    private fun readRawJson(context: Context): JSONObject {
        return try {
            val raw = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
                .getString(KEY, null)
            if (raw.isNullOrBlank()) JSONObject() else JSONObject(raw)
        } catch (_: Exception) {
            JSONObject()
        }
    }

    /** optString 包装：JSON 为 null / 键缺失 / 空串时回退 default。 */
    private fun JSONObject?.optStringOr(key: String, default: String): String {
        if (this == null) return default
        val v = optString(key, default)
        return if (v.isBlank()) default else v
    }
}
