package com.margelo.nitro.lunardatepicker.services import android.util.LruCache import com.margelo.nitro.lunardatepicker.LDP_ConfigParams import com.margelo.nitro.lunardatepicker.LDP_CustomLanguage import com.margelo.nitro.lunardatepicker.LDP_CustomStyle import com.margelo.nitro.lunardatepicker.LDP_Mode import com.margelo.nitro.lunardatepicker.LDP_PresentParams import com.margelo.nitro.lunardatepicker.models.PickerConfig import java.security.MessageDigest import java.util.Locale import java.util.concurrent.ConcurrentHashMap class ConfigurationCacheService { companion object { private const val TAG = "ConfigurationCache" private const val CONFIG_CACHE_SIZE = 50 private const val TEMPLATE_CACHE_SIZE = 30 } private val configurationCache: LruCache = LruCache(CONFIG_CACHE_SIZE) private val colorCache: ConcurrentHashMap = ConcurrentHashMap() private val templateCache: LruCache = LruCache(TEMPLATE_CACHE_SIZE) fun getOrBuildConfiguration( params: LDP_PresentParams, globalConfig: LDP_ConfigParams? ): PickerConfig { val cacheKey = generateConfigurationKey(params, globalConfig) configurationCache.get(cacheKey)?.let { return it } val config = buildConfigurationWithCache(params, globalConfig) configurationCache.put(cacheKey, config) return config } private fun buildConfigurationWithCache( params: LDP_PresentParams, globalConfig: LDP_ConfigParams? ): PickerConfig { var config = PickerConfig.default.copy() config = config.copy( controller = config.controller.copy( title = params.title, fromText = params.fromText, toText = params.toText, submitText = params.submitText, notSelectedText = params.notSelectedText, showHeader = params.showHeader ?: true, isSingleMode = params.mode == LDP_Mode.SINGLE ) ) globalConfig?.let { globalConf -> globalConf.themes[params.theme]?.let { theme -> globalConf.languages[params.language]?.let { language -> val template = getOrCreateTemplate(theme, language) config = applyTemplate(config, template) } } config = applyTimeZone(globalConf.timeZoneOffset, config) config = applyYearRange(globalConf.yearRangeOffset, config) // Map showSubmitButton from global config config = config.copy( controller = config.controller.copy( showSubmitButton = globalConf.showSubmitButton, fromImage = globalConf.fromImage, toImage = globalConf.toImage, closeImage = globalConf.closeImage ) ) } return config } private fun applyTemplate(config: PickerConfig, template: ConfigurationTemplate): PickerConfig { val normalizedTag = template.localeTag.replace('_', '-') val locale = Locale.forLanguageTag(normalizedTag).let { built -> if (built.language.isNullOrEmpty()) Locale.getDefault() else built } return config.copy( controller = config.controller.copy( backgroundColor = template.backgroundColor, titleColor = template.titleColor, secondaryTextColor = template.secondColor, confirmGradientColors = template.confirmGradientColors, borderColor = template.borderColor ), dayCell = config.dayCell.copy( dateLabelColor = template.dateLabelColor, todayLabelColor = template.todayLabelColor, weekendLabelColor = template.weekendLabelColor, lunarDateLabelColor = template.lunarDateLabelColor, specialDateLabelColor = template.specialDateLabelColor, rangeBackgroundColor = template.rangeBackgroundColor, selectedBackgroundColor = template.selectedBackgroundColor, selectedTextColor = template.selectedTextColor ), monthHeader = config.monthHeader.copy( labelColor = template.monthLabelColor ), weekView = config.weekView.copy( backgroundColor = template.weekViewBackgroundColor, weekendLabelColor = template.weekendLabelColor, weekLabelColor = template.weekLabelColor, weekdayNames = template.weekdayNames ), calendar = config.calendar.copy( locale = locale ) ) } private fun getOrCreateTemplate( theme: LDP_CustomStyle?, language: LDP_CustomLanguage? ): ConfigurationTemplate { val cacheKey = generateTemplateKey(theme, language) templateCache.get(cacheKey)?.let { return it } val template = createConfigurationTemplate(theme, language) templateCache.put(cacheKey, template) return template } private fun createConfigurationTemplate( theme: LDP_CustomStyle?, language: LDP_CustomLanguage? ): ConfigurationTemplate { return ConfigurationTemplate( backgroundColor = theme?.backgroundColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, titleColor = theme?.titleColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, dateLabelColor = theme?.dateLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, todayLabelColor = theme?.todayLabelColor?.let { parseColor(it) } ?: parseColor("#3B82F6"), weekendLabelColor = theme?.weekendLabelColor?.let { parseColor(it) } ?: parseColor("#FF9500"), lunarDateLabelColor = theme?.lunarDateLabelColor?.let { parseColor(it) } ?: parseColor("#8E8E93"), specialDateLabelColor = theme?.specialDayLabelColor?.let { parseColor(it) } ?: parseColor("#FF9500"), rangeBackgroundColor = theme?.rangeBackgroundColor?.let { parseColor(it) } ?: parseColor("#E5F3FF"), selectedBackgroundColor = theme?.selectedBackgroundColor?.let { parseColor(it) } ?: parseColor("#007AFF"), selectedTextColor = theme?.selectedTextColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, monthLabelColor = theme?.monthLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, weekViewBackgroundColor = theme?.weekViewBackgroundColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, weekLabelColor = theme?.dateLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, weekdayNames = language?.weekdayNames ?: arrayOf("T2", "T3", "T4", "T5", "T6", "T7", "CN"), localeTag = language?.locale ?: Locale.getDefault().toLanguageTag(), confirmGradientColors = theme?.gradientColors?.map { parseColor(it) }?.toIntArray() ?: intArrayOf(android.graphics.Color.BLACK, android.graphics.Color.DKGRAY), borderColor = theme?.borderColor?.let { parseColor(it) } ?: parseColor("#E5E7EB"), secondColor = theme?.secondColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE ) } private fun parseColor(hex: String): Int { colorCache[hex]?.let { return it } val color = try { android.graphics.Color.parseColor(hex) } catch (e: IllegalArgumentException) { throw com.margelo.nitro.lunardatepicker.exceptions.LunarDatePickerException.ThemeError("Invalid hex color: $hex") } colorCache[hex] = color return color } private fun applyTimeZone(offset: Double, config: PickerConfig): PickerConfig { val timeZone = java.util.TimeZone.getTimeZone("GMT${if (offset >= 0) "+" else ""}${offset.toInt()}") return config.copy(calendar = config.calendar.copy(timeZone = timeZone)) } private fun applyYearRange(offset: Double, config: PickerConfig): PickerConfig { return config.copy(yearRangeOffset = offset.toInt()) } private fun generateConfigurationKey(params: LDP_PresentParams, globalConfig: LDP_ConfigParams?): String { val keyBuilder = StringBuilder() keyBuilder.append(params.theme).append("|") keyBuilder.append(params.language).append("|") keyBuilder.append(params.title).append("|") keyBuilder.append(params.mode.name).append("|") globalConfig?.let { keyBuilder.append(it.timeZoneOffset).append("|") keyBuilder.append(it.yearRangeOffset).append("|") keyBuilder.append(it.showSubmitButton).append("|") keyBuilder.append(it.fromImage.uri).append("|") keyBuilder.append(it.toImage.uri).append("|") keyBuilder.append(it.closeImage.uri).append("|") } return generateMD5Hash(keyBuilder.toString()) } private fun generateTemplateKey(theme: LDP_CustomStyle?, language: LDP_CustomLanguage?): String { val keyBuilder = StringBuilder() theme?.let { t -> keyBuilder.append(t.backgroundColor).append(",") keyBuilder.append(t.titleColor).append(",") keyBuilder.append(t.dateLabelColor).append(",") keyBuilder.append(t.todayLabelColor).append(",") keyBuilder.append(t.lunarDateLabelColor).append(",") keyBuilder.append(t.selectedTextColor).append(",") keyBuilder.append(t.weekendLabelColor).append(",") keyBuilder.append(t.specialDayLabelColor).append(",") keyBuilder.append(t.monthLabelColor).append(",") keyBuilder.append(t.weekViewBackgroundColor).append(",") keyBuilder.append(t.selectedBackgroundColor).append(",") keyBuilder.append(t.rangeBackgroundColor).append("|") } language?.let { l -> keyBuilder.append(l.weekdayNames.joinToString(",")).append("|") } return generateMD5Hash(keyBuilder.toString()) } private fun generateMD5Hash(input: String): String { val md = MessageDigest.getInstance("MD5") val digest = md.digest(input.toByteArray()) return digest.joinToString("") { "%02x".format(it) } } data class ConfigurationTemplate( val backgroundColor: Int, val titleColor: Int, val dateLabelColor: Int, val todayLabelColor: Int, val weekendLabelColor: Int, val lunarDateLabelColor: Int, val specialDateLabelColor: Int, val rangeBackgroundColor: Int, val selectedBackgroundColor: Int, val selectedTextColor: Int, val monthLabelColor: Int, val weekViewBackgroundColor: Int, val weekLabelColor: Int, val weekdayNames: Array, val localeTag: String, val confirmGradientColors: IntArray, val borderColor: Int, val secondColor: Int ) sealed class ValidationResult { object Valid : ValidationResult() data class Invalid(val error: String) : ValidationResult() } }