package com.tiedup.remake.dialogue;

import static com.tiedup.remake.util.GameConstants.*;

import com.tiedup.remake.dialogue.EmotionalContext.EmotionType;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.util.GagMaterial;
import com.tiedup.remake.util.PhoneticMapper;
import com.tiedup.remake.util.SyllableAnalyzer;
import java.util.List;
import java.util.Random;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;

/**
 *
 * <p>Features:
 * <ul>
 *   <li>Phonetic-based transformation preserving word structure</li>
 *   <li>Syllable-aware processing for natural rhythm</li>
 *   <li>Emotional context detection and expression</li>
 *   <li>Material-specific bleed-through rates</li>
 *   <li>Progressive comprehension (partial understanding)</li>
 * </ul>
 */
public class GagTalkManager {

    private static final Random RANDOM = new Random();

    private static final String[] CRIT_FAIL_SOUNDS = {
        "Mmph!!",
        "Mmmph...",
        "Hmpf!",
        "Mmm...",
        "Mph?!",
        "Nnnnh!",
        "Hffff!",
        "P-pph!",
    };

    /**
     * Process a gagged message for any IBondageState entity.
     *
     * @param kidnapped       The kidnapped entity (Player, EntityDamsel, etc.)
     * @param gagStack        The gag item stack
     * @param originalMessage The original message before gagging
     * @return The muffled message component
     */
    public static Component processGagMessage(
        IBondageState kidnapped,
        ItemStack gagStack,
        String originalMessage
    ) {
        LivingEntity entity = kidnapped.asLivingEntity();
        GagMaterial material = GagMaterial.CLOTH;
        // V2: check data-driven GaggingComponent first
        GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
            gagStack, ComponentType.GAGGING, GaggingComponent.class);
        if (gaggingComp != null && gaggingComp.getMaterial() != null) {
            material = gaggingComp.getMaterial();
        }

        // 1. EFFET DE SUFFOCATION (Si message trop long)
        applySuffocationEffects(entity, originalMessage.length(), material);

        // 2. CHANCE D'ECHEC CRITIQUE
        Component critFailResult = checkCriticalFailure(
            kidnapped,
            gagStack,
            originalMessage.length(),
            material
        );
        if (critFailResult != null) {
            return critFailResult;
        }

        // 3. DETECT OVERALL MESSAGE EMOTION
        EmotionType messageEmotion = EmotionalContext.detectEmotion(
            originalMessage
        );

        // 4. CONSTRUCTION DU MESSAGE V4
        StringBuilder muffled = new StringBuilder();
        String[] words = originalMessage.split("\\s+");

        for (int i = 0; i < words.length; i++) {
            String word = words[i];

            // Progressive comprehension: longer messages get harder to understand
            float positionPenalty = (i > 5) ? 0.05f * (i - 5) : 0.0f;
            float baseComp = material.getComprehension();
            float effectiveComprehension = Math.max(
                0,
                baseComp - positionPenalty
            );

            // Apply emotional intensity modifier
            effectiveComprehension /= EmotionalContext.getIntensityMultiplier(
                messageEmotion
            );

            // Whispering is clearer
            if (EmotionalContext.shouldPreserveMore(messageEmotion)) {
                effectiveComprehension *= 1.5f;
            }

            // Material-specific interjections (rare, natural placement)
            if (RANDOM.nextFloat() < 0.03f && i > 0 && i < words.length - 1) {
                muffled.append(getMaterialInterjection(material)).append(" ");
            }

            // Movement affects clarity
            if (entity.isSprinting() || !entity.onGround()) {
                effectiveComprehension *= 0.7f;
            }

            // Three-tier comprehension:
            // - Full pass: word understood completely
            // - Partial: first letter(s) + muffled rest
            // - None: fully muffled
            float roll = RANDOM.nextFloat();
            if (roll < effectiveComprehension * 0.4f) {
                // Full word passes through
                muffled.append(word);
            } else if (roll < effectiveComprehension) {
                // Partial: first letter(s) visible + muffled rest
                muffled.append(generatePartiallyMuffledWord(word, material));
            } else {
                // Fully muffled
                muffled.append(generateMuffledWord(word, material));
            }

            // Word separator
            if (i < words.length - 1) {
                if (RANDOM.nextFloat() < 0.1f) {
                    muffled.append("-");
                } else {
                    muffled.append(" ");
                }
            }
        }

        // 5. PENSEE INTERNE (Visible seulement pour l'entite)
        kidnapped.sendMessage(
            Component.literal("(")
                .append(
                    Component.literal(originalMessage).withStyle(
                        ChatFormatting.ITALIC
                    )
                )
                .append(")")
                .withStyle(ChatFormatting.GRAY),
            false
        );

        return Component.literal(muffled.toString());
    }

    /**
     * Generate a partially muffled word - first part recognizable, rest muffled.
     */
    private static String generatePartiallyMuffledWord(
        String original,
        GagMaterial material
    ) {
        if (original.isEmpty()) return "";
        if (original.length() <= 2) return original;

        // Find a good split point (after first consonant cluster + vowel)
        int splitPoint = findNaturalSplitPoint(original);

        String visible = original.substring(0, splitPoint);
        String toMuffle = original.substring(splitPoint);

        if (toMuffle.isEmpty()) {
            return visible;
        }

        return visible + generateMuffledWord(toMuffle, material);
    }

    /**
     * Find a natural split point in a word for partial muffling.
     */
    private static int findNaturalSplitPoint(String word) {
        if (word.length() <= 2) return word.length();

        // Try to split after first syllable-ish structure
        boolean foundVowel = false;
        for (int i = 0; i < word.length() && i < 4; i++) {
            char c = Character.toLowerCase(word.charAt(i));
            boolean isVowel = "aeiouy".indexOf(c) >= 0;

            if (isVowel) {
                foundVowel = true;
            } else if (foundVowel) {
                // Found consonant after vowel - good split point
                return i;
            }
        }

        // Default: first 1-2 characters
        return Math.min(2, word.length());
    }

    /**
     * Generate a fully muffled word using phonetic transformation.
     */
    private static String generateMuffledWord(
        String original,
        GagMaterial material
    ) {
        if (original == null || original.isEmpty()) return "";

        // Extract and preserve punctuation
        String punctuation = extractTrailingPunctuation(original);
        String cleanWord = original.replaceAll("[^a-zA-Z]", "");

        if (cleanWord.isEmpty()) {
            return original; // Pure punctuation, keep as-is
        }

        // Preserve original case pattern
        boolean wasAllCaps =
            cleanWord.length() >= 2 &&
            cleanWord.equals(cleanWord.toUpperCase());

        // Split into syllables for rhythm preservation
        List<String> syllables = SyllableAnalyzer.splitIntoSyllables(cleanWord);

        StringBuilder result = new StringBuilder();
        float baseBleed = material.getComprehension();

        for (
            int syllableIdx = 0;
            syllableIdx < syllables.size();
            syllableIdx++
        ) {
            String syllable = syllables.get(syllableIdx);

            // First syllable gets bonus bleed (more recognizable)
            float syllableBleed = (syllableIdx == 0)
                ? baseBleed * 1.3f
                : baseBleed;

            // Stressed syllables are clearer
            if (
                SyllableAnalyzer.isStressedSyllable(
                    syllable,
                    syllableIdx,
                    syllables.size()
                )
            ) {
                syllableBleed *= 1.2f;
            }

            StringBuilder muffledSyllable = new StringBuilder();
            char prevOutput = 0;

            for (int i = 0; i < syllable.length(); i++) {
                char c = syllable.charAt(i);

                // Calculate bleed for this specific phoneme
                float phonemeBleed =
                    syllableBleed * material.getBleedRateFor(c);

                // Apply phonetic mapping
                String mapped = PhoneticMapper.mapPhoneme(
                    c,
                    material,
                    phonemeBleed
                );

                // Avoid excessive repetition (mmmmm -> mm)
                if (shouldSkipRepetition(mapped, prevOutput, muffledSyllable)) {
                    continue;
                }

                muffledSyllable.append(mapped);

                if (!mapped.isEmpty()) {
                    prevOutput = mapped.charAt(mapped.length() - 1);
                }
            }

            result.append(muffledSyllable);

            // Occasional syllable separator for multi-syllable words
            if (
                syllableIdx < syllables.size() - 1 && RANDOM.nextFloat() < 0.2f
            ) {
                result.append("-");
            }
        }

        String muffled = result.toString();

        // Collapse excessive repetitions (max 2 of same letter)
        muffled = collapseRepetitions(muffled, 2);

        // Ensure we have something
        if (muffled.isEmpty()) {
            muffled =
                material.getDominantConsonant() + material.getDominantVowel();
        }

        // Preserve ALL CAPS if original was all caps
        if (wasAllCaps) {
            muffled = muffled.toUpperCase();
        }

        return muffled + punctuation;
    }

    /**
     * Check if we should skip adding a mapped sound to avoid excessive repetition.
     */
    private static boolean shouldSkipRepetition(
        String mapped,
        char prevOutput,
        StringBuilder current
    ) {
        if (mapped.isEmpty()) return true;

        char firstMapped = mapped.charAt(0);

        // Skip if same character repeated more than twice
        if (firstMapped == prevOutput) {
            int repeatCount = 0;
            for (
                int i = current.length() - 1;
                i >= 0 && i >= current.length() - 3;
                i--
            ) {
                if (current.charAt(i) == firstMapped) {
                    repeatCount++;
                } else {
                    break;
                }
            }
            if (repeatCount >= 2) {
                return true;
            }
        }

        return false;
    }

    /**
     * Collapse runs of repeated characters to a maximum count.
     * "uuuuu" with maxRepeat=2 becomes "uu"
     */
    private static String collapseRepetitions(String text, int maxRepeat) {
        if (text == null || text.length() <= maxRepeat) {
            return text;
        }

        StringBuilder result = new StringBuilder();
        char prev = 0;
        int count = 0;

        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            char lower = Character.toLowerCase(c);
            char prevLower = Character.toLowerCase(prev);

            if (lower == prevLower && Character.isLetter(c)) {
                count++;
                if (count <= maxRepeat) {
                    result.append(c);
                }
            } else {
                result.append(c);
                count = 1;
            }
            prev = c;
        }

        return result.toString();
    }

    /**
     * Extract trailing punctuation from a word.
     */
    private static String extractTrailingPunctuation(String word) {
        StringBuilder punct = new StringBuilder();
        for (int i = word.length() - 1; i >= 0; i--) {
            char c = word.charAt(i);
            if (!Character.isLetter(c)) {
                punct.insert(0, c);
            } else {
                break;
            }
        }
        return punct.toString();
    }

    /**
     * Get a material-specific interjection sound.
     */
    private static String getMaterialInterjection(GagMaterial material) {
        return switch (material) {
            case BALL -> "*hhuup*";
            case TAPE -> "*mmn*";
            case STUFFED, SPONGE -> "*mm*";
            case RING -> "*aah*";
            case BITE -> "*ngh*";
            case LATEX -> "*uuh*";
            case BAGUETTE -> "*nom*";
            case PANEL -> "*mmph*";
            default -> "*mph*";
        };
    }

    // SUFFOCATION & CRITICAL FAILURE HELPERS

    /**
     * Apply suffocation effects if message is too long.
     * Long messages through restrictive gags cause slowness and potential blindness.
     *
     * @param entity        The entity speaking
     * @param messageLength The length of the message
     * @param material      The gag material type
     */
    private static void applySuffocationEffects(
        LivingEntity entity,
        int messageLength,
        GagMaterial material
    ) {
        if (
            messageLength > GAG_MAX_MESSAGE_LENGTH_BEFORE_SUFFOCATION &&
            material != GagMaterial.CLOTH
        ) {
            entity.addEffect(
                new MobEffectInstance(
                    MobEffects.MOVEMENT_SLOWDOWN,
                    GAG_SUFFOCATION_SLOWNESS_DURATION,
                    1
                )
            );
            if (RANDOM.nextFloat() < GAG_SUFFOCATION_BLINDNESS_CHANCE) {
                entity.addEffect(
                    new MobEffectInstance(
                        MobEffects.BLINDNESS,
                        GAG_SUFFOCATION_BLINDNESS_DURATION,
                        0
                    )
                );
            }
        }
    }

    /**
     * Check for critical failure when speaking through gag.
     * Longer messages have higher chance of complete muffling.
     *
     * @param kidnapped     The kidnapped entity
     * @param gagStack      The gag item stack
     * @param messageLength The length of the message
     * @param material      The gag material type
     * @return Critical fail Component if failed, null otherwise
     */
    private static Component checkCriticalFailure(
        IBondageState kidnapped,
        ItemStack gagStack,
        int messageLength,
        GagMaterial material
    ) {
        float critChance =
            GAG_BASE_CRITICAL_FAIL_CHANCE +
            (messageLength * GAG_LENGTH_CRITICAL_FACTOR);

        if (RANDOM.nextFloat() < critChance && material != GagMaterial.CLOTH) {
            kidnapped.sendMessage(
                Component.translatable(
                    "chat.tiedup.gag.crit_fail",
                    gagStack.getHoverName()
                ).withStyle(ChatFormatting.RED),
                true
            );
            return Component.literal(
                CRIT_FAIL_SOUNDS[RANDOM.nextInt(CRIT_FAIL_SOUNDS.length)]
            );
        }
        return null;
    }

    // MCA INTEGRATION METHODS

    /**
     * Transform a message to gagged speech without side effects.
     * For use with MCA villagers and AI chat.
     *
     * <p>Unlike {@link #processGagMessage}, this method:
     * <ul>
     *   <li>Does not apply suffocation effects</li>
     *   <li>Does not show internal thoughts</li>
     *   <li>Does not have critical fail chance</li>
     *   <li>Simply returns the muffled text</li>
     * </ul>
     *
     * @param originalMessage The original message to transform
     * @param gagStack        The gag item stack (determines material)
     * @return The muffled message string
     */
    public static String transformToGaggedSpeech(
        String originalMessage,
        ItemStack gagStack
    ) {
        if (originalMessage == null || originalMessage.isEmpty()) {
            return "";
        }

        GagMaterial material = GagMaterial.CLOTH;
        if (gagStack != null && !gagStack.isEmpty()) {
            // V2: check data-driven GaggingComponent first
            GaggingComponent comp = DataDrivenBondageItem.getComponent(
                gagStack, ComponentType.GAGGING, GaggingComponent.class);
            if (comp != null && comp.getMaterial() != null) {
                material = comp.getMaterial();
            }
        }

        StringBuilder muffled = new StringBuilder();
        String[] words = originalMessage.split("\\s+");

        for (int i = 0; i < words.length; i++) {
            String word = words[i];

            // Three-tier comprehension for MCA as well
            float comp = material.getComprehension();
            float roll = RANDOM.nextFloat();

            if (roll < comp * 0.4f) {
                muffled.append(word);
            } else if (roll < comp) {
                muffled.append(generatePartiallyMuffledWord(word, material));
            } else {
                muffled.append(generateMuffledWord(word, material));
            }

            if (i < words.length - 1) {
                muffled.append(RANDOM.nextFloat() < 0.1f ? "-" : " ");
            }
        }

        return muffled.toString();
    }

    /**
     * Transform a message to gagged speech using default cloth gag.
     * Convenience method for when gag item is not available.
     *
     * @param originalMessage The original message to transform
     * @return The muffled message string
     */
    public static String transformToGaggedSpeech(String originalMessage) {
        return transformToGaggedSpeech(originalMessage, ItemStack.EMPTY);
    }
}
