package net.hareworks.ghostdisplays.command import java.time.format.DateTimeFormatter import java.util.Locale import java.util.UUID import net.hareworks.ghostdisplays.display.DisplayManager import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException import net.hareworks.ghostdisplays.display.DisplayKind import net.hareworks.ghostdisplays.display.EditSessionManager import net.hareworks.kommand_lib.KommandLib import net.hareworks.kommand_lib.kommand import net.hareworks.permits_lib.bukkit.MutationSession import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.Material import org.bukkit.block.data.BlockData import org.bukkit.command.CommandSender import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import org.bukkit.permissions.PermissionDefault import org.bukkit.plugin.java.JavaPlugin object CommandRegistrar { fun register( plugin: JavaPlugin, manager: DisplayManager, editSessions: EditSessionManager, permissionSession: MutationSession ): KommandLib = kommand(plugin) { permissions { namespace = "ghostdisplays" defaultValue = PermissionDefault.OP wildcard = true session(permissionSession) } command("ghostdisplay", listOf("gdisplay", "gdisp")) { description = "Manage GhostDisplays entities" permission = "ghostdisplays.command" executes { sender.showUsage() } literal("help") { executes { sender.showUsage() } } literal("create") { literal("text") { string("id") { executes { val player = sender.requirePlayer() ?: return@executes val id = argument("id") val location = player.anchorLocation() try { val display = manager.createTextDisplay(id, location, "", player) editSessions.begin(player, display.id) sender.success("Text display '${display.id}' created at ${describe(location)}. Type text in chat (or 'cancel') to set content.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to create text display.") } } } } literal("block") { string("id") { string("state") { executes { val player = sender.requirePlayer() ?: return@executes val id = argument("id") val state = argument("state") try { val blockData = parseBlockData(state) val display = manager.createBlockDisplay(id, player.anchorLocation(), blockData, player) sender.success("Block display '${display.id}' created with $state at ${describe(display.location)}.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to create block display.") } catch (ex: IllegalArgumentException) { sender.failure("Invalid block data '$state': ${ex.message}") } } } } } literal("item") { string("id") { string("material") { executes { val player = sender.requirePlayer() ?: return@executes val id = argument("id") val materialToken = argument("material") val material = Material.matchMaterial(materialToken.uppercase(Locale.ROOT)) ?: return@executes sender.failure("Unknown material '$materialToken'.") if (!material.isItem) { return@executes sender.failure("$materialToken cannot be used as an item display.") } try { val item = ItemStack(material) val display = manager.createItemDisplay(id, player.anchorLocation(), item, player) sender.success("Item display '${display.id}' created with ${material.name.lowercase()} at ${describe(display.location)}.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to create item display.") } } } } } } literal("delete") { string("id") { executes { val id = argument("id") try { manager.delete(id) sender.success("Display '$id' removed.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to delete '$id'.") } } } } literal("list") { executes { val entries = manager.listDisplays() if (entries.isEmpty()) { sender.info("No displays registered.") return@executes } sender.info("Displays (${entries.size}):") entries.forEach { sender.info(" - ${it.id} [${it.kind}] @ ${describe(it.location)} viewers=${it.controller.viewerIds().size}") } } } literal("info") { string("id") { executes { val id = argument("id") val display = manager.findDisplay(id) if (display == null) { sender.failure("Display '$id' not found.") return@executes } sender.info("Display '${display.id}' (${display.kind})") sender.info(" Location: ${describe(display.location)}") sender.info(" Created: ${DATE_FORMAT.format(display.createdAt)} by ${display.createdBy?.let { Bukkit.getOfflinePlayer(it).name ?: it } ?: "unknown"}") val viewers = display.controller.viewerIds() sender.info(" Viewers (${viewers.size}): ${resolveNames(viewers).ifEmpty { "none" }}") val audiences = display.audienceBindings() sender.info(" Audiences (${audiences.size}): ${if (audiences.isEmpty()) "none" else audiences.joinToString { it.description }}") sender.info(" Content: ${manager.describeContent(display)}") } } } literal("viewer") { literal("add") { string("id") { players("targets") { executes { val id = argument("id") val targets = argument>("targets") try { manager.showToPlayers(id, targets) sender.success("Showing '$id' to ${targets.size} player(s).") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update viewers.") } } } } } literal("remove") { string("id") { players("targets") { executes { val id = argument("id") val targets = argument>("targets") try { manager.hideFromPlayers(id, targets) sender.success("Hid '$id' from ${targets.size} player(s).") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update viewers.") } } } } } literal("clear") { string("id") { executes { val id = argument("id") try { manager.clearViewers(id) sender.success("Cleared active viewers for '$id'.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to clear viewers.") } } } } } literal("text") { literal("edit") { string("id") { executes { val player = sender.requirePlayer() ?: return@executes val id = argument("id") val display = manager.findDisplay(id) if (display == null || display.kind != DisplayKind.TEXT) { sender.failure("Text display '$id' not found.") return@executes } editSessions.begin(player, display.id) sender.info("Enter new MiniMessage text in chat for '${display.id}'. Type 'cancel' to abort.") } } } literal("set") { string("id") { string("content") { executes { val id = argument("id") val token = argument("content") val raw = token.replace('_', ' ').replace("\\n", "\n") try { manager.updateText(id, raw) sender.success("Updated text for '$id'. Use /ghostdisplay text edit $id for multi-line content.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update text.") } } } } } literal("cancel") { executes { val player = sender.requirePlayer() ?: return@executes if (editSessions.cancel(player)) { sender.success("Editing session cancelled.") } else { sender.failure("You are not editing any display.") } } } } literal("block") { literal("set") { string("id") { string("state") { executes { val id = argument("id") val state = argument("state") try { val blockData = parseBlockData(state) manager.updateBlock(id, blockData) sender.success("Block data for '$id' updated to $state.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update block display.") } catch (ex: IllegalArgumentException) { sender.failure("Invalid block data '$state': ${ex.message}") } } } } } } literal("item") { literal("set") { string("id") { string("material") { executes { val id = argument("id") val materialToken = argument("material") val material = Material.matchMaterial(materialToken.uppercase(Locale.ROOT)) ?: return@executes sender.failure("Unknown material '$materialToken'.") if (!material.isItem) { return@executes sender.failure("$materialToken cannot be used as an item display.") } try { manager.updateItem(id, ItemStack(material)) sender.success("Item for '$id' set to ${material.name.lowercase()}.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update item display.") } } } } } } literal("audience") { literal("permission") { literal("add") { string("id") { string("permission") { executes { val id = argument("id") val perm = argument("permission") try { manager.addPermissionAudience(id, perm) sender.success("Permission audience '$perm' added to '$id'.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to add permission audience.") } } } } } literal("remove") { string("id") { string("permission") { executes { val id = argument("id") val perm = argument("permission") val removed = runCatching { manager.removePermissionAudience(id, perm) } .onFailure { sender.failure(it.message ?: "Failed to remove permission audience.") } .getOrNull() if (removed == true) { sender.success("Permission audience '$perm' removed from '$id'.") } else if (removed == false) { sender.failure("Permission audience '$perm' not found for '$id'.") } } } } } } literal("near") { literal("set") { string("id") { float("radius", min = 1.0) { executes { val id = argument("id") val radius = argument("radius") try { manager.setNearAudience(id, radius) sender.success("Radius audience for '$id' set to ${String.format("%.1f", radius)} block(s).") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to update radius audience.") } } } } } } literal("clear") { string("id") { executes { val id = argument("id") try { manager.clearAudiences(id) sender.success("Audiences cleared for '$id'.") } catch (ex: DisplayOperationException) { sender.failure(ex.message ?: "Failed to clear audiences.") } } } } } } } } private fun CommandSender.showUsage() { info("GhostDisplays commands:") info(" /ghostdisplay create ...") info(" /ghostdisplay text edit - edit text via chat") info(" /ghostdisplay viewer ...") info(" /ghostdisplay audience ...") info(" /ghostdisplay list | info | delete ") } private fun Player.anchorLocation(): Location = eyeLocation.clone().add(direction.normalize().multiply(1.5)) private fun parseBlockData(state: String): BlockData = Bukkit.createBlockData(state) private fun describe(location: Location): String = "${location.world?.name ?: "unknown"} @ ${location.blockX},${location.blockY},${location.blockZ}" private fun CommandSender.requirePlayer(): Player? { val player = this as? Player if (player == null) { failure("This command can only be used by players.") } return player } private fun CommandSender.info(message: String) { sendMessage("§7$message") } private fun CommandSender.success(message: String) { sendMessage("§a$message") } private fun CommandSender.failure(message: String) { sendMessage("§c$message") } private fun resolveNames(ids: Collection): String { if (ids.isEmpty()) return "" return ids.joinToString { Bukkit.getOfflinePlayer(it).name ?: it.toString() } } private val DATE_FORMAT: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT