package com.xfestudio.xfeservermanager.core.content;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import static com.xfestudio.xfeservermanager.core.content.ContentDefinition.Kind.*;

/** The server-owned capability and validation schema shared by every supported platform. */
public record ContentCatalog(int schemaVersion, List<Archetype> archetypes, List<Event> events,
                             List<Action> actions, List<Template> templates) {
    public ContentCatalog {
        archetypes = List.copyOf(archetypes);
        events = List.copyOf(events);
        actions = List.copyOf(actions);
        templates = List.copyOf(templates);
    }
    public record Property(String key, String nameZh, String nameEn, String descriptionZh, String descriptionEn,
                           String type, String defaultValue, Double min, Double max, List<String> options,
                           boolean requiresRestart) {
        public Property { options = List.copyOf(options); }
    }
    public record Archetype(String id, ContentDefinition.Kind kind, String nameZh, String nameEn,
                            String descriptionZh, String descriptionEn, List<Property> properties) {
        public Archetype { properties = List.copyOf(properties); }
    }
    public record Event(String id, String nameZh, String nameEn, String descriptionZh, String descriptionEn,
                        List<ContentDefinition.Kind> kinds, List<String> archetypes, boolean cancellable) {
        public Event { kinds = List.copyOf(kinds); archetypes = List.copyOf(archetypes); }
    }
    public record Action(String id, String nameZh, String nameEn, String descriptionZh, String descriptionEn,
                         List<Property> parameters, List<String> events, List<ContentDefinition.Kind> kinds) {
        public Action { parameters = List.copyOf(parameters); events = List.copyOf(events); kinds = List.copyOf(kinds); }
    }
    public record Template(String id, String nameZh, String nameEn, String descriptionZh, String descriptionEn,
                           ContentDefinition definition) { }

    private static final class Holder { private static final ContentCatalog BASE = createBase(); }

    public static ContentCatalog builtIn() {
        ContentCatalog base = Holder.BASE;
        return new ContentCatalog(1, base.archetypes(), base.events(), base.actions(), List.of(
                new Template("chair", "可坐座椅", "Seat", "右键坐下，潜行离开；使用矮方块碰撞。", "Right-click to sit; sneak to dismount. Uses a low collision box.",
                        new ContentDefinition("xfesmcontent:chair", BLOCK, "座椅", "右键坐下，潜行离开", "solid",
                                Map.of("texture", "minecraft:block/oak_planks", "shape_max_y", "8"),
                                List.of(new ContentDefinition.Behavior("sit", "right_click", "sit", Map.of("height", "0.5"), 10, true)))),
                new Template("music_player", "音乐播放器", "Music player", "右键播放唱片音乐，可替换为上传的 OGG 资源。", "Right-click to play music; replace the sound with an uploaded OGG resource.",
                        new ContentDefinition("xfesmcontent:music_player", BLOCK, "音乐播放器", "右键播放音乐", "solid",
                                Map.of("texture", "minecraft:block/jukebox_side"),
                                List.of(new ContentDefinition.Behavior("music", "right_click", "play_sound",
                                        Map.of("sound", "minecraft:music_disc.cat", "volume", "1", "pitch", "1"), 100, true))))));
    }

    public static Archetype archetype(ContentDefinition.Kind kind, String id) {
        return Holder.BASE.archetypes().stream().filter(value -> value.kind() == kind && value.id().equals(id)).findFirst()
                .orElseThrow(() -> new IllegalArgumentException("unsupported content archetype: " + kind + "/" + id));
    }

    public static void validate(ContentDefinition.Kind kind, String archetype, Map<String, String> properties,
                                List<ContentDefinition.Behavior> behaviors) {
        Archetype descriptor = archetype(kind, archetype);
        validateValues(properties, descriptor.properties(), "property", false);
        if (kind == ITEM && Integer.parseInt(properties.getOrDefault("durability", defaultValue(descriptor, "durability", "0"))) > 0
                && Integer.parseInt(properties.getOrDefault("max_stack_size", defaultValue(descriptor, "max_stack_size", "64"))) != 1) {
            throw new IllegalArgumentException("durable items must have max_stack_size=1");
        }
        if (kind == BLOCK) {
            for (String axis : List.of("x", "y", "z")) {
                if (Double.parseDouble(properties.getOrDefault("shape_min_" + axis, "0"))
                        >= Double.parseDouble(properties.getOrDefault("shape_max_" + axis, "16"))) {
                    throw new IllegalArgumentException("shape_min_" + axis + " must be smaller than shape_max_" + axis);
                }
            }
        }
        for (var behavior : behaviors) {
            Event event = Holder.BASE.events().stream().filter(value -> value.id().equals(behavior.event())).findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("unsupported behavior event: " + behavior.event()));
            if (!event.kinds().contains(kind) || (!event.archetypes().isEmpty() && !event.archetypes().contains(archetype))) {
                throw new IllegalArgumentException("event " + event.id() + " is not available on " + archetype);
            }
            if (behavior.cancelVanilla() && !event.cancellable()) throw new IllegalArgumentException("event cannot cancel vanilla behavior: " + event.id());
            Action action = Holder.BASE.actions().stream().filter(value -> value.id().equals(behavior.action())).findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("unsupported behavior action: " + behavior.action()));
            if (!action.events().contains(event.id()) || !action.kinds().contains(kind)) {
                throw new IllegalArgumentException("action " + action.id() + " is not available for this event/content kind");
            }
            validateValues(behavior.parameters(), action.parameters(), "action parameter", true);
        }
    }

    private static String defaultValue(Archetype archetype, String key, String fallback) {
        return archetype.properties().stream().filter(value -> value.key().equals(key)).findFirst().map(Property::defaultValue).orElse(fallback);
    }

    private static void validateValues(Map<String, String> values, List<Property> schema, String label, boolean required) {
        for (var entry : values.entrySet()) {
            Property descriptor = schema.stream().filter(value -> value.key().equals(entry.getKey())).findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("unsupported " + label + ": " + entry.getKey()));
            validateValue(descriptor, entry.getValue());
        }
        if (required) {
            for (Property descriptor : schema) {
                String value = values.getOrDefault(descriptor.key(), descriptor.defaultValue());
                validateValue(descriptor, value);
            }
        }
    }

    private static void validateValue(Property descriptor, String value) {
        try {
            switch (descriptor.type()) {
                case "int", "number" -> {
                    if (descriptor.type().equals("int") && !value.matches("-?[0-9]+")) throw new IllegalArgumentException("expected an integer");
                    BigDecimal number = new BigDecimal(value);
                    if (descriptor.type().equals("int")) number.intValueExact();
                    if ((descriptor.min() != null && number.compareTo(BigDecimal.valueOf(descriptor.min())) < 0)
                            || (descriptor.max() != null && number.compareTo(BigDecimal.valueOf(descriptor.max())) > 0)) {
                        throw new IllegalArgumentException("out of range");
                    }
                }
                case "boolean" -> { if (!value.equals("true") && !value.equals("false")) throw new IllegalArgumentException("expected true or false"); }
                case "select" -> { if (!descriptor.options().contains(value)) throw new IllegalArgumentException("unknown choice"); }
                case "resource" -> {
                    if (!value.matches("[a-z0-9_.-]+:[a-z0-9_./-]+") || value.contains("..") || value.contains(":/") || value.endsWith("/")) {
                        throw new IllegalArgumentException("expected namespaced resource id");
                    }
                }
                case "menu_ref", "trigger_ref", "uuid" -> {
                    if (!UUID.fromString(value).toString().equals(value)) throw new IllegalArgumentException("expected canonical UUID");
                }
                default -> throw new IllegalArgumentException("unsupported property schema type");
            }
        } catch (IllegalArgumentException | ArithmeticException invalid) {
            throw new IllegalArgumentException("invalid " + descriptor.key() + ": " + invalid.getMessage(), invalid);
        }
    }

    private static ContentCatalog createBase() {
        List<Archetype> archetypes = new ArrayList<>();
        String[] ids = {"generic", "food", "sword", "pickaxe", "axe", "shovel", "hoe", "bow", "crossbow", "shield", "helmet", "chestplate", "leggings", "boots"};
        String[] names = {"普通物品", "食物", "剑", "镐", "斧", "锹", "锄", "弓", "弩", "盾牌", "头盔", "胸甲", "护腿", "靴子"};
        for (int index = 0; index < ids.length; index++) {
            String id = ids[index];
            boolean generic = id.equals("generic"), food = id.equals("food");
            boolean tool = List.of("sword", "pickaxe", "axe", "shovel", "hoe").contains(id);
            boolean armor = List.of("helmet", "chestplate", "leggings", "boots").contains(id);
            List<Property> properties = new ArrayList<>();
            String itemTexture = generic ? "paper" : food ? "apple" : tool || armor ? "iron_" + id : id;
            properties.add(resource("texture", "物品贴图", "Item texture", "minecraft:item/" + itemTexture));
            properties.add(integer("max_stack_size", "最大堆叠数", "Maximum stack size", generic || food ? 64 : 1, 1, generic || food ? 64 : 1));
            if (!food) properties.add(integer("durability", "耐久", "Durability", switch (id) {
                case "generic" -> 0; case "bow" -> 384; case "crossbow" -> 465; case "shield" -> 336;
                case "helmet" -> 165; case "chestplate" -> 240; case "leggings" -> 225; case "boots" -> 195; default -> 250;
            }, generic ? 0 : 1, 100_000));
            properties.add(bool("fire_resistant", "防火", "Fire resistant", false));
            properties.add(choice("rarity", "稀有度", "Rarity", "common", List.of("common", "uncommon", "rare", "epic")));
            if (tool) {
                properties.add(choice("tier", "工具材质等级", "Tool tier", "iron", List.of("wood", "stone", "iron", "diamond", "gold", "netherite")));
                Property damage = List.of("sword", "pickaxe", "hoe").contains(id)
                        ? integer("attack_damage", "额外攻击伤害", "Additional attack damage", id.equals("sword") ? 3 : id.equals("pickaxe") ? 1 : 0, 0, 2043)
                        : number("attack_damage", "额外攻击伤害", "Additional attack damage", id.equals("axe") ? 6 : 1.5, 0, 2043);
                properties.add(new Property(damage.key(), damage.nameZh(), damage.nameEn(),
                        damage.descriptionZh() + "此值叠加于材质与玩家基础攻击力；最终伤害还受玩家属性、附魔等影响。",
                        damage.descriptionEn() + "Added to the material and player base attack; final damage also depends on player attributes, enchantments and other modifiers.",
                        damage.type(), damage.defaultValue(), damage.min(), damage.max(), damage.options(), damage.requiresRestart()));
                properties.add(number("attack_speed", "攻击速度修正", "Attack speed modifier", switch (id) { case "sword" -> -2.4; case "axe" -> -3.1; case "pickaxe" -> -2.8; case "shovel" -> -3; default -> -1; }, -4, 100));
            }
            if (armor) properties.add(choice("armor_material", "护甲材质（防御与韧性预设）", "Armor material (defense and toughness preset)", "iron", List.of("leather", "chainmail", "iron", "gold", "diamond", "turtle", "netherite")));
            if (food) {
                properties.add(integer("nutrition", "饥饿回复", "Nutrition", 4, 0, 100));
                properties.add(number("saturation", "饱和度系数", "Saturation modifier", 0.3, 0, 100));
                properties.add(bool("always_edible", "饱食时仍可食用", "Always edible", false));
            }
            archetypes.add(new Archetype(id, ITEM, names[index], id.replace('_', ' '), "使用原版行为原型；修改后需同步内容包并重启客户端和服务器。", "Uses a vanilla behavior archetype; synchronize the pack and restart client and server after changes.", properties));
        }
        List<Property> block = new ArrayList<>(List.of(resource("texture", "方块贴图", "Block texture", "minecraft:block/stone"),
                number("hardness", "硬度（-1 为不可破坏）", "Hardness (-1 is unbreakable)", 1.5, -1, 10_000),
                number("resistance", "爆炸抗性", "Blast resistance", 6, 0, 3_600_000), integer("light_level", "发光等级", "Light level", 0, 0, 15),
                number("friction", "摩擦系数", "Friction", 0.6, 0, 1), bool("requires_correct_tool", "需要正确工具", "Requires correct tool", false),
                bool("no_collision", "无碰撞", "No collision", false)));
        for (String axis : List.of("x", "y", "z")) {
            block.add(number("shape_min_" + axis, "形状 " + axis.toUpperCase() + " 最小值", "Shape " + axis.toUpperCase() + " minimum", 0, 0, 16));
            block.add(number("shape_max_" + axis, "形状 " + axis.toUpperCase() + " 最大值", "Shape " + axis.toUpperCase() + " maximum", 16, 0, 16));
        }
        archetypes.add(new Archetype("solid", BLOCK, "可配置方块", "Configurable block", "轴对齐长方体形状；可组合右键交互行为。不包含方块实体容器或红石逻辑。", "Axis-aligned box shape with interaction behaviors. Does not implement block-entity inventories or redstone logic.", block));
        for (String id : List.of("zombie", "skeleton", "cow", "pig")) {
            String zh = Map.of("zombie", "僵尸", "skeleton", "骷髅", "cow", "牛", "pig", "猪").get(id);
            boolean hostile = id.equals("zombie") || id.equals("skeleton");
            List<Property> properties = new ArrayList<>(List.of(number("max_health", "最大生命值", "Maximum health", hostile ? 20 : 10, 1, 1024),
                    number("movement_speed", "移动速度", "Movement speed", id.equals("zombie") ? 0.23 : id.equals("skeleton") ? 0.25 : 0.2, 0, 10),
                    number("armor", "护甲值", "Armor", id.equals("zombie") ? 2 : 0, 0, 30),
                    number("follow_range", "跟随范围", "Follow range", hostile ? 35 : 16, 1, 2048),
                    number("knockback_resistance", "击退抗性", "Knockback resistance", 0, 0, 1)));
            if (hostile) properties.add(number("attack_damage", "攻击伤害", "Attack damage", id.equals("zombie") ? 3 : 2, 0, 2048));
            archetypes.add(new Archetype(id, ENTITY, zh + "原型", id + " archetype", "复用原版 AI、模型与动画，支持列出的属性；不会新增自然生成规则。", "Reuses vanilla AI, model and animation with the listed attributes. Does not add natural spawning rules.", properties));
        }
        List<Event> events = List.of(
                event("right_click", "玩家右键", "Player right-click", List.of(ITEM, BLOCK, ENTITY), true),
                event("attack", "持物攻击", "Attack with item", List.of(ITEM), true),
                event("consume", "食用完成", "Food consumed", List.of(ITEM), false),
                event("break", "玩家破坏方块", "Player breaks block", List.of(BLOCK), true),
                event("place", "玩家放置方块", "Player places block", List.of(BLOCK), true));
        List<String> allEvents = events.stream().map(Event::id).toList();
        List<Action> actions = List.of(
                new Action("trigger", "调用触发器", "Call trigger", "使用此次交互的玩家与对象上下文调用已保存的触发器。", "Calls a saved trigger with the interaction player and object context.", List.of(ref("trigger_id", "触发器", "Trigger", "trigger_ref")), allEvents, List.of(ITEM, BLOCK, ENTITY)),
                new Action("open_menu", "打开菜单", "Open menu", "向交互玩家打开已保存的菜单，需要兼容客户端。", "Opens a saved menu for the interacting player; requires a compatible client.", List.of(ref("menu_id", "菜单", "Menu", "menu_ref")), allEvents, List.of(ITEM, BLOCK, ENTITY)),
                new Action("sit", "坐在方块上", "Sit on block", "创建座位，玩家潜行离开；仅适用于右键方块。", "Creates a seat; sneak to dismount. Available only on block right-click.", List.of(number("height", "座位高度", "Seat height", 0.5, 0, 2)), List.of("right_click"), List.of(BLOCK)),
                new Action("play_sound", "播放音乐或音效", "Play music or sound", "播放原版声音或内容包中的 OGG 音频。", "Plays a vanilla sound or an OGG asset from the content pack.", List.of(resource("sound", "声音资源", "Sound resource", ""), number("volume", "音量", "Volume", 1, 0, 4), number("pitch", "音调", "Pitch", 1, 0.5, 2)), allEvents, List.of(ITEM, BLOCK, ENTITY)),
                new Action("stop_sound", "停止指定声音", "Stop selected sound", "只停止指定声音，不影响其他声音。", "Stops only the selected sound without affecting other audio.", List.of(resource("sound", "声音资源", "Sound resource", "")), allEvents, List.of(ITEM, BLOCK, ENTITY)));
        return new ContentCatalog(1, archetypes, events, actions, List.of());
    }

    private static Event event(String id, String zh, String en, List<ContentDefinition.Kind> kinds, boolean cancellable) {
        return new Event(id, zh, en, zh + "时执行绑定的行为。", "Runs attached behaviors on " + en.toLowerCase(java.util.Locale.ROOT) + ".", kinds, id.equals("consume") ? List.of("food") : List.of(), cancellable);
    }
    private static Property integer(String key, String zh, String en, int value, int min, int max) {
        return new Property(key, zh, en, "整数，范围 " + min + " 至 " + max + "。", "Integer from " + min + " to " + max + ".", "int", Integer.toString(value), (double) min, (double) max, List.of(), true);
    }
    private static Property number(String key, String zh, String en, double value, double min, double max) {
        return new Property(key, zh, en, "数值，范围 " + min + " 至 " + max + "。", "Number from " + min + " to " + max + ".", "number", BigDecimal.valueOf(value).stripTrailingZeros().toPlainString(), min, max, List.of(), true);
    }
    private static Property bool(String key, String zh, String en, boolean value) {
        return new Property(key, zh, en, "启用或关闭此属性。", "Enable or disable this property.", "boolean", Boolean.toString(value), null, null, List.of(), true);
    }
    private static Property choice(String key, String zh, String en, String value, List<String> options) {
        return new Property(key, zh, en, "使用所选原版预设。", "Uses the selected vanilla preset.", "select", value, null, null, options, true);
    }
    private static Property resource(String key, String zh, String en, String value) {
        return new Property(key, zh, en, "原版资源 ID 或已上传内容资源 ID。", "A vanilla resource ID or an uploaded content resource ID.", "resource", value, null, null, List.of(), true);
    }
    private static Property ref(String key, String zh, String en, String type) {
        return new Property(key, zh, en, "选择已保存的" + zh + "，不接受任意脚本或命令。", "Select a saved " + en.toLowerCase(java.util.Locale.ROOT) + "; arbitrary scripts or commands are not accepted.", type, "", null, null, List.of(), true);
    }
}
