package com.tiedup.remake.dialogue;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.personality.PersonalityType;
import java.io.Reader;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.jetbrains.annotations.Nullable;

/**
 * Loads dialogue entries from JSON files.
 *
 * New structure (per-personality dialogues):
 * dialogue/{lang}/default/   - Base dialogues (fallback)
 * dialogue/{lang}/{personality}/  - Personality-specific dialogues
 *
 * Each personality folder contains the same categories:
 * - idle.json, struggle.json, capture.json, commands.json,
 *   needs.json, mood.json, jobs.json, combat.json
 *
 * Personality System: Data-driven dialogue
 */
public class DialogueLoader {

    private static final Gson GSON = new GsonBuilder()
        .setPrettyPrinting()
        .create();

    /**
     * Dialogue file categories to load.
     */
    private static final String[] DIALOGUE_CATEGORIES = {
        "commands",
        "capture",
        "struggle",
        "jobs",
        "mood",
        "combat",
        "needs",
        "idle",
        "actions",
        "fear",
        "reaction",
        "environment",
        "discipline",
        "home",
        "leash",
        "resentment",
        "personality",
        "conversation",
        // Master NPC dialogues
        "punishment",
        "purchase",
        "inspection",
        "petplay",
        // Camp NPC dialogues (guard, maid)
        "guard_labor",
        "maid_labor",
        // Kidnapper-specific dialogues
        "guard",
        "dogwalk",
        "patrol",
        "punish",
    };

    /**
     * Speaker-type specific dialogue folders to load.
     * These are loaded as additional dialogues for all personalities.
     */
    private static final String[] SPEAKER_TYPE_FOLDERS = {
        "master",
        "kidnapper",
        "maid",
        "trader",
        "guard",
        "kidnapper_archer",
        "kidnapper_elite",
        "merchant",
    };

    /**
     * Load all dialogues organized by personality.
     *
     * @param resourceManager Resource manager
     * @param lang Language code (e.g., "en_us")
     * @return Map of personality -> dialogue ID -> entries
     */
    public static Map<
        PersonalityType,
        Map<String, List<DialogueEntry>>
    > loadDialogues(ResourceManager resourceManager, String lang) {
        Map<PersonalityType, Map<String, List<DialogueEntry>>> allDialogues =
            new EnumMap<>(PersonalityType.class);

        TiedUpMod.LOGGER.info(
            "[DialogueLoader] Loading dialogues for lang: {}",
            lang
        );

        // Load default dialogues (stored with null key in a temporary holder)
        Map<String, List<DialogueEntry>> defaultDialogues = new HashMap<>();
        loadDialoguesForPersonality(
            resourceManager,
            lang,
            "default",
            null,
            defaultDialogues
        );

        int totalEntries = defaultDialogues
            .values()
            .stream()
            .mapToInt(List::size)
            .sum();
        TiedUpMod.LOGGER.info(
            "[DialogueLoader] Loaded {} default dialogue IDs ({} entries)",
            defaultDialogues.size(),
            totalEntries
        );

        // Load personality-specific dialogues
        for (PersonalityType personality : PersonalityType.values()) {
            // Start with a copy of default dialogues
            Map<String, List<DialogueEntry>> personalityDialogues =
                new HashMap<>();

            // Copy default entries (deep copy the lists)
            for (Map.Entry<
                String,
                List<DialogueEntry>
            > entry : defaultDialogues.entrySet()) {
                personalityDialogues.put(
                    entry.getKey(),
                    new ArrayList<>(entry.getValue())
                );
            }

            // Load and merge personality-specific dialogues (these take priority)
            String folderName = personality.name().toLowerCase();
            loadDialoguesForPersonality(
                resourceManager,
                lang,
                folderName,
                personality,
                personalityDialogues
            );

            allDialogues.put(personality, personalityDialogues);
        }

        // Load speaker-type specific dialogues (e.g., master/)
        // These are added to ALL personality maps so they're always available
        for (String speakerFolder : SPEAKER_TYPE_FOLDERS) {
            loadSpeakerTypeDialogues(
                resourceManager,
                lang,
                speakerFolder,
                allDialogues
            );
        }

        TiedUpMod.LOGGER.info(
            "[DialogueLoader] Loaded dialogues for {} personalities",
            allDialogues.size()
        );

        return allDialogues;
    }

    /**
     * Load speaker-type specific dialogues and add them to all personality maps.
     * This allows speaker-specific dialogues (e.g., master/) to be available
     * regardless of personality context.
     *
     * @param resourceManager Resource manager
     * @param lang Language code
     * @param speakerFolder Speaker type folder name (e.g., "master")
     * @param allDialogues Map to populate
     */
    private static void loadSpeakerTypeDialogues(
        ResourceManager resourceManager,
        String lang,
        String speakerFolder,
        Map<PersonalityType, Map<String, List<DialogueEntry>>> allDialogues
    ) {
        // Load from speaker/default folder
        Map<String, List<DialogueEntry>> speakerDialogues = new HashMap<>();
        String folderPath = speakerFolder + "/default";

        loadDialoguesForPersonality(
            resourceManager,
            lang,
            folderPath,
            null, // No personality condition - available to all
            speakerDialogues
        );

        if (speakerDialogues.isEmpty()) {
            return;
        }

        int entriesAdded = speakerDialogues
            .values()
            .stream()
            .mapToInt(List::size)
            .sum();
        TiedUpMod.LOGGER.info(
            "[DialogueLoader] Loaded {} speaker-type dialogues from '{}'",
            entriesAdded,
            folderPath
        );

        // Add to all personality maps
        for (PersonalityType personality : PersonalityType.values()) {
            Map<String, List<DialogueEntry>> personalityDialogues =
                allDialogues.get(personality);
            if (personalityDialogues != null) {
                for (Map.Entry<
                    String,
                    List<DialogueEntry>
                > entry : speakerDialogues.entrySet()) {
                    personalityDialogues
                        .computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
                        .addAll(entry.getValue());
                }
            }
        }
    }

    /**
     * Load dialogues for a specific personality folder.
     *
     * @param resourceManager Resource manager
     * @param lang Language code
     * @param folderName Folder name (e.g., "default", "timid", "fierce")
     * @param personality Personality type (null for default)
     * @param dialogues Map to populate (personality-specific entries are added at front for priority)
     */
    private static void loadDialoguesForPersonality(
        ResourceManager resourceManager,
        String lang,
        String folderName,
        @Nullable PersonalityType personality,
        Map<String, List<DialogueEntry>> dialogues
    ) {
        int loadedFiles = 0;
        int loadedEntries = 0;

        for (String category : DIALOGUE_CATEGORIES) {
            String path =
                "dialogue/" +
                lang +
                "/" +
                folderName +
                "/" +
                category +
                ".json";
            ResourceLocation location = ResourceLocation.fromNamespaceAndPath(
                TiedUpMod.MOD_ID,
                path
            );

            try {
                Resource resource = resourceManager
                    .getResource(location)
                    .orElse(null);
                if (resource == null) continue;

                try (Reader reader = resource.openAsReader()) {
                    JsonObject json = GSON.fromJson(reader, JsonObject.class);
                    int count = parseDialogueFile(json, personality, dialogues);
                    loadedFiles++;
                    loadedEntries += count;
                }
            } catch (Exception e) {
                TiedUpMod.LOGGER.warn(
                    "[DialogueLoader] Failed to load {}: {}",
                    location,
                    e.getMessage()
                );
            }
        }

        if (loadedFiles > 0) {
            TiedUpMod.LOGGER.debug(
                "[DialogueLoader] Loaded {} files ({} entries) for '{}'",
                loadedFiles,
                loadedEntries,
                folderName
            );
        }
    }

    /**
     * Parse a dialogue JSON file.
     *
     * @param json JSON object
     * @param personality Personality context (null for default, used to set conditions)
     * @param dialogues Map to populate
     * @return Number of entries loaded
     */
    private static int parseDialogueFile(
        JsonObject json,
        @Nullable PersonalityType personality,
        Map<String, List<DialogueEntry>> dialogues
    ) {
        if (!json.has("entries")) return 0;

        JsonArray entries = json.getAsJsonArray("entries");
        int count = 0;

        for (JsonElement element : entries) {
            DialogueEntry entry = parseEntry(
                element.getAsJsonObject(),
                personality
            );
            if (entry != null && entry.hasVariants()) {
                // For personality-specific dialogues, add at front (higher priority)
                List<DialogueEntry> list = dialogues.computeIfAbsent(
                    entry.getId(),
                    k -> new ArrayList<>()
                );

                if (personality != null) {
                    // Personality-specific: add at front for priority
                    list.add(0, entry);
                } else {
                    // Default: add at end
                    list.add(entry);
                }
                count++;
            }
        }

        return count;
    }

    /**
     * Parse a single dialogue entry from JSON.
     *
     * @param json JSON object for entry
     * @param personality Personality context (for automatic condition)
     * @return Parsed entry or null
     */
    @Nullable
    private static DialogueEntry parseEntry(
        JsonObject json,
        @Nullable PersonalityType personality
    ) {
        if (!json.has("id")) return null;

        String id = json.get("id").getAsString();

        // Build condition
        DialogueCondition.Builder conditionBuilder =
            DialogueCondition.builder();

        // If loading for a specific personality, add that as a condition
        if (personality != null) {
            conditionBuilder.personalities(personality);
        }

        // Apply additional conditions from JSON
        if (json.has("conditions")) {
            applyConditions(
                conditionBuilder,
                json.getAsJsonObject("conditions"),
                personality
            );
        }

        List<DialogueVariant> variants = parseVariants(
            json.getAsJsonArray("variants")
        );

        if (variants.isEmpty()) {
            return null;
        }

        return new DialogueEntry(id, conditionBuilder.build(), variants);
    }

    /**
     * Apply conditions from JSON to builder.
     */
    private static void applyConditions(
        DialogueCondition.Builder builder,
        JsonObject json,
        @Nullable PersonalityType forcedPersonality
    ) {
        // Personality filter (only if not already forced)
        if (forcedPersonality == null && json.has("personality")) {
            Set<PersonalityType> personalities = EnumSet.noneOf(
                PersonalityType.class
            );
            JsonArray arr = json.getAsJsonArray("personality");
            for (JsonElement e : arr) {
                try {
                    personalities.add(
                        PersonalityType.valueOf(e.getAsString().toUpperCase())
                    );
                } catch (IllegalArgumentException ex) {
                    TiedUpMod.LOGGER.warn(
                        "[DialogueLoader] Unknown personality: {}",
                        e.getAsString()
                    );
                }
            }
            if (!personalities.isEmpty()) {
                builder.personalities(personalities);
            }
        }

        // Training level range - gracefully ignored (training system removed)
        // JSON files may still contain training_min/training_max but they have no effect

        // Mood range
        if (json.has("mood_min")) {
            builder.moodMin(json.get("mood_min").getAsInt());
        }
        if (json.has("mood_max")) {
            builder.moodMax(json.get("mood_max").getAsInt());
        }

        // Relationship type - gracefully ignored (relationship system removed)
        // JSON files may still contain "relationship" but it has no effect
    }

    /**
     * Parse variants array from JSON.
     */
    private static List<DialogueVariant> parseVariants(
        @Nullable JsonArray json
    ) {
        List<DialogueVariant> variants = new ArrayList<>();
        if (json == null) return variants;

        for (JsonElement element : json) {
            JsonObject variantJson = element.getAsJsonObject();

            if (!variantJson.has("text")) continue;

            String text = variantJson.get("text").getAsString();
            int weight = variantJson.has("weight")
                ? variantJson.get("weight").getAsInt()
                : 10;
            boolean isAction =
                variantJson.has("is_action") &&
                variantJson.get("is_action").getAsBoolean();

            variants.add(new DialogueVariant(text, weight, isAction));
        }

        return variants;
    }
}
