package com.estateunified.panel import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonParser import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption internal class ChessBoardStore( private val gson: Gson, private val dataDir: Path, ) { private val lock = Any() fun absolutePath(): String = dataDir.toAbsolutePath().normalize().toString() fun rawByListingId(listingId: String): String? { val safe = safeId(listingId) ?: return null val custom = readCustom(safe) if (custom != null) return custom val resource = "mock-chessboard-$safe.json" val stream = this.javaClass.classLoader.getResourceAsStream(resource) ?: return null return stream.use { it.readBytes().toString(StandardCharsets.UTF_8) } } fun objectByListingId(listingId: String): JsonObject? { val raw = rawByListingId(listingId) ?: return null return runCatching { JsonParser.parseString(raw).asJsonObject }.getOrNull() } fun save(listingId: String, board: JsonObject): JsonObject { val safe = safeId(listingId) ?: error("invalid_listing_id") Files.createDirectories(dataDir) board.addProperty("listingId", listingId) board.addProperty( "updatedAtIso", java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) synchronized(lock) { val file = dataDir.resolve("chessboard-$safe.json") val tmp = dataDir.resolve("chessboard-$safe.json.tmp") Files.writeString(tmp, gson.toJson(board), StandardCharsets.UTF_8) Files.move(tmp, file, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE) } return board } fun setCommonPlan(listingId: String, planUrl: String, planName: String) { val existing = objectByListingId(listingId) ?: defaultBoardFor(listingId) existing.addProperty("commonPlanUrl", planUrl) existing.addProperty("commonPlanName", planName) save(listingId, existing) } fun clearCommonPlan(listingId: String) { val existing = objectByListingId(listingId) ?: return existing.remove("commonPlanUrl") existing.remove("commonPlanName") save(listingId, existing) } fun setLotPlan(listingId: String, lotId: String, planUrl: String) { val existing = objectByListingId(listingId) ?: defaultBoardFor(listingId) val floors = if (existing.has("floors") && existing.get("floors").isJsonArray) existing.getAsJsonArray("floors") else JsonArray().also { existing.add("floors", it) } var found = false floors.forEach { fl -> val flObj = fl.asJsonObject val lots = if (flObj.has("lots") && flObj.get("lots").isJsonArray) flObj.getAsJsonArray("lots") else JsonArray() lots.forEach { lot -> val lo = lot.asJsonObject if (lo.optString("lotId") == lotId) { lo.addProperty("planUrl", planUrl) found = true } } } if (!found) return save(listingId, existing) } fun clearLotPlan(listingId: String, lotId: String) { val existing = objectByListingId(listingId) ?: return if (!existing.has("floors") || !existing.get("floors").isJsonArray) return existing.getAsJsonArray("floors").forEach { fl -> val flObj = fl.asJsonObject if (!flObj.has("lots") || !flObj.get("lots").isJsonArray) return@forEach flObj.getAsJsonArray("lots").forEach { lot -> val lo = lot.asJsonObject if (lo.optString("lotId") == lotId) { lo.remove("planUrl") } } } save(listingId, existing) } fun updateLot(listingId: String, lotId: String, patch: JsonObject) { val existing = objectByListingId(listingId) ?: defaultBoardFor(listingId) if (!existing.has("floors") || !existing.get("floors").isJsonArray) return existing.getAsJsonArray("floors").forEach { fl -> val flObj = fl.asJsonObject if (!flObj.has("lots") || !flObj.get("lots").isJsonArray) return@forEach flObj.getAsJsonArray("lots").forEach { lot -> val lo = lot.asJsonObject if (lo.optString("lotId") == lotId) { patch.entrySet().forEach { (k, v) -> lo.add(k, v) } } } } save(listingId, existing) } private fun readCustom(safeId: String): String? { val path = dataDir.resolve("chessboard-$safeId.json") if (!Files.isRegularFile(path)) return null return runCatching { Files.readString(path, StandardCharsets.UTF_8) }.getOrNull() } private fun defaultBoardFor(listingId: String): JsonObject = JsonObject().apply { addProperty("listingId", listingId) addProperty("complexName", "") addProperty("section", "") addProperty( "updatedAtIso", java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) addProperty("syncMode", "manual") val legend = JsonArray() listOf( Triple("FREE", "Свободно", "#2f6d5b"), Triple("HOLD", "Холд", "#9a6a18"), Triple("BOOKED", "Бронь", "#2d5d8b"), Triple("SOLD", "Продано", "#7d6f6a"), ).forEach { (st, label, color) -> legend.add( JsonObject().apply { addProperty("status", st) addProperty("labelRu", label) addProperty("color", color) }, ) } add("legend", legend) add("floors", JsonArray()) } private fun safeId(listingId: String): String? { val safe = listingId.filter { it.isLetterOrDigit() || it == '-' || it == '_' } if (safe.isEmpty() || safe != listingId) return null return safe } }