package com.tiedup.remake.dialogue;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.personality.PersonalityType;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import net.minecraft.server.packs.resources.ResourceManager;
import org.jetbrains.annotations.Nullable;

/**
 * Central manager for the data-driven dialogue system.
 * Loads dialogues from JSON and provides selection based on context.
 *
 * New structure: dialogues are organized by personality type.
 * Each personality has its own complete set of dialogues,
 * with default dialogues as fallback.
 *
 * Usage:
 * 1. Call reload() on server/resource reload to load JSON files
 * 2. Call getDialogue(dialogueId, context) to get appropriate dialogue text
 *
 * Personality System: Data-driven dialogue
 */
public class DialogueManager {

    private static final Random RANDOM = new Random();

    /**
     * Loaded dialogues organized by personality.
     * Map: PersonalityType -> DialogueId -> List of matching entries
     */
    private static Map<
        PersonalityType,
        Map<String, List<DialogueEntry>>
    > dialogues = new EnumMap<>(PersonalityType.class);

    /** Current language code */
    private static String currentLang = "en_us";

    /** Whether dialogues have been loaded */
    private static boolean initialized = false;

    /**
     * Reload dialogues from resource manager.
     * Should be called on server start and on /reload.
     *
     * @param resourceManager Minecraft's resource manager
     */
    public static void reload(ResourceManager resourceManager) {
        reload(resourceManager, "en_us");
    }

    /**
     * Reload dialogues with specified language.
     *
     * @param resourceManager Minecraft's resource manager
     * @param lang Language code
     */
    public static void reload(ResourceManager resourceManager, String lang) {
        TiedUpMod.LOGGER.debug(
            "[DialogueManager] Reloading dialogues for lang: {}",
            lang
        );

        currentLang = lang;
        dialogues = DialogueLoader.loadDialogues(resourceManager, lang);
        initialized = true;

        int totalEntries = dialogues
            .values()
            .stream()
            .flatMap(m -> m.values().stream())
            .mapToInt(List::size)
            .sum();

        TiedUpMod.LOGGER.debug(
            "[DialogueManager] Loaded {} personalities, {} total entries",
            dialogues.size(),
            totalEntries
        );
    }

    /**
     * Get a dialogue string for the given ID and context.
     * Uses the personality from context to select the appropriate dialogue set.
     *
     * @param dialogueId The dialogue identifier (e.g., "command.follow.accept")
     * @param context Current dialogue context (contains personality)
     * @return Formatted dialogue text, or null if no match
     */
    @Nullable
    public static String getDialogue(
        String dialogueId,
        DialogueContext context
    ) {
        if (!initialized) {
            TiedUpMod.LOGGER.warn(
                "[DialogueManager] Attempted to get dialogue before initialization"
            );
            return null;
        }

        PersonalityType personality = context.getPersonality();

        // Get dialogues for this personality
        Map<String, List<DialogueEntry>> personalityDialogues = dialogues.get(
            personality
        );
        if (personalityDialogues == null) {
            TiedUpMod.LOGGER.warn(
                "[DialogueManager] No dialogues loaded for personality: {}",
                personality
            );
            return null;
        }

        List<DialogueEntry> entries = personalityDialogues.get(dialogueId);
        if (entries == null || entries.isEmpty()) {
            TiedUpMod.LOGGER.warn(
                "[DialogueManager] No entries for dialogue ID: '{}' (personality: {}). Available IDs: {}",
                dialogueId,
                personality,
                personalityDialogues
                    .keySet()
                    .stream()
                    .filter(k -> k.startsWith("action."))
                    .toList()
            );
            return null;
        }

        // Find all matching entries
        List<DialogueEntry> matching = new ArrayList<>();
        for (DialogueEntry entry : entries) {
            if (entry.matches(context)) {
                matching.add(entry);
            }
        }

        if (matching.isEmpty()) {
            TiedUpMod.LOGGER.debug(
                "[DialogueManager] No matching entries for: {} (conditions didn't match)",
                dialogueId
            );
            return null;
        }

        // Select from matching entries (first match = highest priority)
        DialogueEntry selected = matching.get(0);

        return selected.getFormattedDialogue(context);
    }

    /**
     * Get a dialogue with fallback to a default message.
     *
     * @param dialogueId The dialogue identifier
     * @param context Current dialogue context
     * @param fallback Fallback message if no match found
     * @return Dialogue text or fallback
     */
    public static String getDialogueOrDefault(
        String dialogueId,
        DialogueContext context,
        String fallback
    ) {
        String result = getDialogue(dialogueId, context);
        return result != null ? result : context.substituteVariables(fallback);
    }

    /**
     * Get a random dialogue from a category pattern.
     * Useful for getting any dialogue matching "command.*.accept" pattern.
     *
     * @param pattern Dialogue ID pattern (use * as wildcard)
     * @param context Current dialogue context
     * @return Matching dialogue or null
     */
    @Nullable
    public static String getRandomDialogue(
        String pattern,
        DialogueContext context
    ) {
        if (!initialized) return null;

        PersonalityType personality = context.getPersonality();
        Map<String, List<DialogueEntry>> personalityDialogues = dialogues.get(
            personality
        );
        if (personalityDialogues == null) return null;

        String regexPattern = pattern.replace(".", "\\.").replace("*", ".*");
        List<DialogueEntry> allMatching = new ArrayList<>();

        for (Map.Entry<
            String,
            List<DialogueEntry>
        > entry : personalityDialogues.entrySet()) {
            if (entry.getKey().matches(regexPattern)) {
                for (DialogueEntry dialogueEntry : entry.getValue()) {
                    if (dialogueEntry.matches(context)) {
                        allMatching.add(dialogueEntry);
                    }
                }
            }
        }

        if (allMatching.isEmpty()) return null;

        DialogueEntry selected = allMatching.get(
            RANDOM.nextInt(allMatching.size())
        );
        return selected.getFormattedDialogue(context);
    }

    /**
     * Check if a dialogue ID exists for the given personality.
     *
     * @param dialogueId The dialogue identifier
     * @param personality Personality type
     * @return true if at least one entry exists for this ID
     */
    public static boolean hasDialogue(
        String dialogueId,
        PersonalityType personality
    ) {
        Map<String, List<DialogueEntry>> personalityDialogues = dialogues.get(
            personality
        );
        return (
            personalityDialogues != null &&
            personalityDialogues.containsKey(dialogueId) &&
            !personalityDialogues.get(dialogueId).isEmpty()
        );
    }

    /**
     * Check if a dialogue ID exists (for any personality).
     */
    public static boolean hasDialogue(String dialogueId) {
        for (Map<
            String,
            List<DialogueEntry>
        > personalityDialogues : dialogues.values()) {
            if (
                personalityDialogues.containsKey(dialogueId) &&
                !personalityDialogues.get(dialogueId).isEmpty()
            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get all dialogue IDs matching a prefix for a personality.
     *
     * @param prefix Prefix to match (e.g., "command.")
     * @param personality Personality type
     * @return List of matching dialogue IDs
     */
    public static List<String> getDialogueIds(
        String prefix,
        PersonalityType personality
    ) {
        List<String> result = new ArrayList<>();
        Map<String, List<DialogueEntry>> personalityDialogues = dialogues.get(
            personality
        );
        if (personalityDialogues != null) {
            for (String id : personalityDialogues.keySet()) {
                if (id.startsWith(prefix)) {
                    result.add(id);
                }
            }
        }
        return result;
    }

    /**
     * Get count of loaded personalities.
     */
    public static int getPersonalityCount() {
        return dialogues.size();
    }

    /**
     * Get total count of dialogue entries across all personalities.
     */
    public static int getEntryCount() {
        return dialogues
            .values()
            .stream()
            .flatMap(m -> m.values().stream())
            .mapToInt(List::size)
            .sum();
    }

    /**
     * Get count of unique dialogue IDs (across all personalities).
     */
    public static int getDialogueCount() {
        return (int) dialogues
            .values()
            .stream()
            .flatMap(m -> m.keySet().stream())
            .distinct()
            .count();
    }

    /**
     * Check if dialogues have been loaded.
     */
    public static boolean isInitialized() {
        return initialized;
    }

    /**
     * Clear all loaded dialogues.
     */
    public static void clear() {
        dialogues.clear();
        initialized = false;
    }

    /**
     * Get current language code.
     */
    public static String getCurrentLang() {
        return currentLang;
    }

    // --- Convenience methods for common dialogue categories ---

    /**
     * Get command accept dialogue.
     */
    @Nullable
    public static String getCommandAccept(
        String commandName,
        DialogueContext context
    ) {
        return getDialogue(
            "command." + commandName.toLowerCase() + ".accept",
            context
        );
    }

    /**
     * Get command refuse dialogue.
     */
    @Nullable
    public static String getCommandRefuse(
        String commandName,
        DialogueContext context
    ) {
        return getDialogue(
            "command." + commandName.toLowerCase() + ".refuse",
            context
        );
    }

    /**
     * Get command hesitate dialogue.
     */
    @Nullable
    public static String getCommandHesitate(
        String commandName,
        DialogueContext context
    ) {
        return getDialogue(
            "command." + commandName.toLowerCase() + ".hesitate",
            context
        );
    }

    /**
     * Get capture dialogue.
     */
    @Nullable
    public static String getCaptureDialogue(
        String phase,
        DialogueContext context
    ) {
        return getDialogue("capture." + phase, context);
    }

    /**
     * Get struggle dialogue.
     */
    @Nullable
    public static String getStruggleDialogue(
        String type,
        DialogueContext context
    ) {
        return getDialogue("struggle." + type, context);
    }

    /**
     * Get mood expression dialogue.
     */
    @Nullable
    public static String getMoodDialogue(String mood, DialogueContext context) {
        return getDialogue("mood." + mood, context);
    }

    /**
     * Get needs dialogue (hungry, tired, etc.).
     */
    @Nullable
    public static String getNeedsDialogue(
        String need,
        DialogueContext context
    ) {
        return getDialogue("needs." + need, context);
    }

    /**
     * Get idle/random dialogue.
     */
    @Nullable
    public static String getIdleDialogue(DialogueContext context) {
        return getRandomDialogue("idle.*", context);
    }
}
