// VRC Messages — génère un clavier tactile (contacts) + écran de texte sur un avatar VRChat (SDK3).
//
// Menu : Tools > VRC Messages > Générer le clavier sur l'avatar sélectionné
//
// Ce que ça crée :
//  - un GameObject "VRCM_Keyboard" (désactivé par défaut) avec ~45 touches (Contact Receivers),
//    un écran de texte (shader + atlas) et un fond ;
//  - un Contact Sender sur le bout de chaque index (tag "VRCM_Finger") ;
//  - des paramètres locaux KB_* (non synchronisés) dans l'Expression Parameters et l'Animator FX ;
//  - 2 layers FX : "VRCM_Toggle" (affiche/cache le clavier) et "VRCM_Display" (texte renvoyé par l'app) ;
//  - un Toggle "Clavier messages" dans ton menu.
//
// Tout est local (rien n'est synchronisé aux autres joueurs). Relancer le menu régénère proprement.
#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Rendering;
using VRC.Dynamics;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using VRC.SDK3.Dynamics.Contact.Components;

public static class VRCMessagesKeyboardSetup
{
    const string Root = "Assets/VRCMessages";
    const string AtlasPath = Root + "/Textures/vrcm_font_atlas.png";
    const string GenDir = Root + "/Generated";
    const string ClipsDir = GenDir + "/Clips";

    const string KeyboardName = "VRCM_Keyboard";
    const string SenderName = "VRCM_FingerSender";
    const string FingerTag = "VRCM_Finger";
    const string MenuLabel = "Clavier messages";

    const int Slots = 24;              // cases de l'écran (12 x 2) = KB_SLOTS côté app
    const float Pitch = 0.036f;        // pas entre les touches (m)
    const float KeySize = 0.032f;      // taille visuelle d'une touche
    const float ReceiverRadius = 0.013f;
    const float SenderRadius = 0.006f;
    const float TiltDegrees = 25f;

    class Key
    {
        public string param;
        public int glyph;     // case dans l'atlas (voir l'app : KB_CHARS)
        public float width;   // en unités de touche
        public Color color;
    }

    // ------------------------------------------------------------------ menu
    [MenuItem("Tools/VRC Messages/Générer le clavier sur l'avatar sélectionné")]
    static void Generate()
    {
        var desc = GetSelectedDescriptor();
        if (desc == null) return;
        GenerateFor(desc);
    }

    [MenuItem("CONTEXT/VRCAvatarDescriptor/VRC Messages : ajouter le clavier")]
    static void GenerateFromContext(MenuCommand cmd)
    {
        var desc = cmd.context as VRCAvatarDescriptor;
        if (desc != null) GenerateFor(desc);
    }

    public static void GenerateFor(VRCAvatarDescriptor desc)
    {
        if (desc == null) return;
        var avatar = desc.gameObject;
        var animator = avatar.GetComponent<Animator>();
        if (animator == null || !animator.isHuman)
        {
            Info("L'avatar doit avoir un Animator avec un rig Humanoid (pour trouver les doigts et la poitrine).");
            return;
        }

        var atlas = PrepareAtlas();
        if (atlas == null) return;

        var keysShader = Shader.Find("VRCMessages/Keys");
        var displayShader = Shader.Find("VRCMessages/Display");
        if (keysShader == null || displayShader == null)
        {
            Info("Shaders introuvables. Vérifie que VRCM_Keys.shader et VRCM_Display.shader sont dans Assets/VRCMessages/Shaders/ et qu'ils compilent sans erreur.");
            return;
        }

        var fx = GetOrCreateFxController(desc);
        if (fx == null) return;

        // Nettoyage d'une ancienne génération
        RemoveFromAvatar(desc, fx);

        EnsureFolder(GenDir);
        EnsureFolder(ClipsDir);

        var layout = BuildLayout();

        // --- Matériaux et meshes
        var keysMat = CreateMaterial(GenDir + "/VRCM_Keys.mat", keysShader, atlas);
        var displayMat = CreateMaterial(GenDir + "/VRCM_Display.mat", displayShader, atlas);
        var keysMesh = SaveMesh(BuildKeysMesh(layout), GenDir + "/VRCM_KeysMesh.asset");
        var displayMesh = SaveMesh(BuildDisplayMesh(layout), GenDir + "/VRCM_DisplayMesh.asset");

        // --- Hiérarchie
        float s = Mathf.Max(0.0001f, avatar.transform.lossyScale.y);
        var root = new GameObject(KeyboardName);
        Undo.RegisterCreatedObjectUndo(root, "VRC Messages keyboard");
        root.transform.SetParent(avatar.transform, false);

        Transform anchor = animator.GetBoneTransform(HumanBodyBones.UpperChest);
        if (anchor == null) anchor = animator.GetBoneTransform(HumanBodyBones.Chest);
        if (anchor == null) anchor = animator.GetBoneTransform(HumanBodyBones.Spine);
        Vector3 basePos = anchor != null ? anchor.position : avatar.transform.position + Vector3.up * 1.2f * s;
        root.transform.position = basePos + avatar.transform.forward * 0.30f * s + Vector3.down * 0.05f * s;
        root.transform.rotation = avatar.transform.rotation * Quaternion.Euler(TiltDegrees, 0f, 0f);

        MakeMeshObject("Keys", root.transform, keysMesh, keysMat, Vector3.zero);
        var displayGo = MakeMeshObject("Display", root.transform, displayMesh, displayMat, Vector3.zero);

        foreach (var row in RowsWithPositions(layout))
            foreach (var kp in row)
                AddReceiver(root.transform, kp.key, kp.x, kp.y);

        AddFingerSender(animator, HumanBodyBones.LeftIndexDistal, HumanBodyBones.LeftIndexIntermediate, avatar.transform, s);
        AddFingerSender(animator, HumanBodyBones.RightIndexDistal, HumanBodyBones.RightIndexIntermediate, avatar.transform, s);

        root.SetActive(false);

        // --- Animator FX
        string displayPath = AnimationUtility.CalculateTransformPath(displayGo.transform, avatar.transform);
        BuildAnimator(fx, layout, displayPath);

        // --- Paramètres + menu
        AddExpressionParameters(desc, layout);
        bool menuOk = AddMenuToggle(desc);

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        Selection.activeGameObject = root;

        Info("Clavier généré sur « " + avatar.name + " ».\n\n" +
             "• Active le Toggle « " + MenuLabel + " » dans ton menu pour l'afficher.\n" +
             "• Ouvre l'app Messages VRChat (OSC activé dans VRChat).\n" +
             (menuOk ? "" : "\n⚠ Ton menu a déjà 8 contrôles : ajoute manuellement un Toggle sur le paramètre KB_Active.\n") +
             "\nPense à ré-uploader l'avatar. Les touches comptent dans le rang de performance (Contacts).");
    }

    [MenuItem("Tools/VRC Messages/Retirer le clavier de l'avatar sélectionné")]
    static void Remove()
    {
        var desc = GetSelectedDescriptor();
        if (desc == null) return;
        var fx = GetFxController(desc);
        RemoveFromAvatar(desc, fx);
        AssetDatabase.SaveAssets();
        Info("Clavier retiré de « " + desc.gameObject.name + " ».");
    }

    // --------------------------------------------------------------- sélection
    static VRCAvatarDescriptor GetSelectedDescriptor()
    {
        var sel = Selection.activeGameObject;
        var desc = sel != null ? sel.GetComponentInParent<VRCAvatarDescriptor>() : null;
        if (desc == null) Info("Sélectionne ton avatar (l'objet qui porte le VRC Avatar Descriptor) dans la Hierarchy, puis relance.");
        return desc;
    }

    static void Info(string msg)
    {
        EditorUtility.DisplayDialog("VRC Messages", msg, "OK");
    }

    // ------------------------------------------------------------------ layout
    static Key K(string param, int glyph, float width = 1f, Color? color = null)
    {
        return new Key { param = param, glyph = glyph, width = width, color = color ?? new Color(0.20f, 0.20f, 0.32f) };
    }

    static List<List<Key>> BuildLayout()
    {
        var rows = new List<List<Key>>();

        var r1 = new List<Key>();
        foreach (char c in "1234567890") r1.Add(K("KB_" + c, 27 + (c - '0')));
        rows.Add(r1);

        var r2 = new List<Key>();
        foreach (char c in "QWERTYUIOP") r2.Add(K("KB_" + c, 1 + (c - 'A')));
        rows.Add(r2);

        var r3 = new List<Key>();
        foreach (char c in "ASDFGHJKL") r3.Add(K("KB_" + c, 1 + (c - 'A')));
        r3.Add(K("KB_Apos", 41));
        rows.Add(r3);

        var r4 = new List<Key>();
        foreach (char c in "ZXCVBNM") r4.Add(K("KB_" + c, 1 + (c - 'A')));
        r4.Add(K("KB_Comma", 38));
        r4.Add(K("KB_Dot", 37));
        r4.Add(K("KB_Excl", 39));
        r4.Add(K("KB_Quest", 40));
        rows.Add(r4);

        var r5 = new List<Key>();
        r5.Add(K("KB_Clear", 44, 1.5f, new Color(0.55f, 0.35f, 0.10f)));
        r5.Add(K("KB_Space", 45, 6f));
        r5.Add(K("KB_Back", 42, 1.5f, new Color(0.55f, 0.18f, 0.20f)));
        r5.Add(K("KB_Send", 43, 2f, new Color(0.12f, 0.50f, 0.28f)));
        rows.Add(r5);

        return rows;
    }

    struct KeyPos { public Key key; public float x; public float y; }

    static List<List<KeyPos>> RowsWithPositions(List<List<Key>> layout)
    {
        var result = new List<List<KeyPos>>();
        for (int r = 0; r < layout.Count; r++)
        {
            float total = 0f;
            foreach (var k in layout[r]) total += k.width;
            float cursor = -total * Pitch * 0.5f;
            var row = new List<KeyPos>();
            foreach (var k in layout[r])
            {
                float cx = cursor + k.width * Pitch * 0.5f;
                row.Add(new KeyPos { key = k, x = cx, y = -r * Pitch });
                cursor += k.width * Pitch;
            }
            result.Add(row);
        }
        return result;
    }

    static float MaxRowWidth(List<List<Key>> layout)
    {
        float max = 0f;
        foreach (var row in layout)
        {
            float t = 0f;
            foreach (var k in row) t += k.width;
            if (t > max) max = t;
        }
        return max * Pitch;
    }

    // ------------------------------------------------------------------- meshes
    static void GlyphUV(int glyph, out float u0, out float v0, out float u1, out float v1)
    {
        int cx = glyph % 8;
        int cy = glyph / 8;
        u0 = cx / 8f;
        u1 = (cx + 1) / 8f;
        v1 = 1f - cy / 8f;
        v0 = 1f - (cy + 1) / 8f;
    }

    static void AddQuad(List<Vector3> verts, List<Vector2> uvs, List<Color> cols, List<int> tris,
                        float cx, float cy, float z, float w, float h,
                        float u0, float v0, float u1, float v1, Color color)
    {
        int i = verts.Count;
        float hx = w * 0.5f, hy = h * 0.5f;
        verts.Add(new Vector3(cx - hx, cy - hy, z)); uvs.Add(new Vector2(u0, v0)); cols.Add(color);
        verts.Add(new Vector3(cx - hx, cy + hy, z)); uvs.Add(new Vector2(u0, v1)); cols.Add(color);
        verts.Add(new Vector3(cx + hx, cy + hy, z)); uvs.Add(new Vector2(u1, v1)); cols.Add(color);
        verts.Add(new Vector3(cx + hx, cy - hy, z)); uvs.Add(new Vector2(u1, v0)); cols.Add(color);
        tris.Add(i); tris.Add(i + 1); tris.Add(i + 2);
        tris.Add(i); tris.Add(i + 2); tris.Add(i + 3);
    }

    static Mesh BuildKeysMesh(List<List<Key>> layout)
    {
        var verts = new List<Vector3>();
        var uvs = new List<Vector2>();
        var cols = new List<Color>();
        var tris = new List<int>();

        float u0, v0, u1, v1;
        GlyphUV(0, out u0, out v0, out u1, out v1);   // case vide (alpha 0)

        // Plaque de fond
        float plateW = MaxRowWidth(layout) + 0.012f;
        float plateH = layout.Count * Pitch + 0.012f;
        AddQuad(verts, uvs, cols, tris, 0f, -(layout.Count - 1) * Pitch * 0.5f, 0.0008f, plateW, plateH,
                u0, v0, u1, v1, new Color(0.05f, 0.05f, 0.09f));

        float gap = Pitch - KeySize;
        foreach (var row in RowsWithPositions(layout))
        {
            foreach (var kp in row)
            {
                // fond de la touche
                AddQuad(verts, uvs, cols, tris, kp.x, kp.y, 0f, kp.key.width * Pitch - gap, KeySize,
                        u0, v0, u1, v1, kp.key.color);
                // symbole, légèrement devant (le spectateur est du côté -Z)
                float gu0, gv0, gu1, gv1;
                GlyphUV(kp.key.glyph, out gu0, out gv0, out gu1, out gv1);
                AddQuad(verts, uvs, cols, tris, kp.x, kp.y, -0.0005f, KeySize, KeySize,
                        gu0, gv0, gu1, gv1, kp.key.color);
            }
        }

        var mesh = new Mesh { name = "VRCM_KeysMesh" };
        mesh.SetVertices(verts);
        mesh.SetUVs(0, uvs);
        mesh.SetColors(cols);
        mesh.SetTriangles(tris, 0);
        mesh.RecalculateBounds();
        return mesh;
    }

    static Mesh BuildDisplayMesh(List<List<Key>> layout)
    {
        var verts = new List<Vector3>();
        var uvs = new List<Vector2>();
        var cols = new List<Color>();
        var tris = new List<int>();

        float width = MaxRowWidth(layout);
        float height = width / 12f * 2f;                    // cases carrées : 12 x 2
        float plateTop = Pitch * 0.5f + 0.006f;
        float cy = plateTop + 0.008f + height * 0.5f;
        AddQuad(verts, uvs, cols, tris, 0f, cy, 0f, width, height, 0f, 0f, 1f, 1f, Color.white);

        var mesh = new Mesh { name = "VRCM_DisplayMesh" };
        mesh.SetVertices(verts);
        mesh.SetUVs(0, uvs);
        mesh.SetColors(cols);
        mesh.SetTriangles(tris, 0);
        mesh.RecalculateBounds();
        return mesh;
    }

    static Mesh SaveMesh(Mesh mesh, string path)
    {
        AssetDatabase.DeleteAsset(path);
        AssetDatabase.CreateAsset(mesh, path);
        return AssetDatabase.LoadAssetAtPath<Mesh>(path);
    }

    static GameObject MakeMeshObject(string name, Transform parent, Mesh mesh, Material mat, Vector3 localPos)
    {
        var go = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.transform.localPosition = localPos;
        go.AddComponent<MeshFilter>().sharedMesh = mesh;
        var r = go.AddComponent<MeshRenderer>();
        r.sharedMaterial = mat;
        r.shadowCastingMode = ShadowCastingMode.Off;
        r.receiveShadows = false;
        r.lightProbeUsage = LightProbeUsage.Off;
        r.reflectionProbeUsage = ReflectionProbeUsage.Off;
        return go;
    }

    // ----------------------------------------------------------- textures / mats
    const string GenAtlasPath = GenDir + "/vrcm_font_atlas_gen.png";

    static Texture2D PrepareAtlas()
    {
        string path = AtlasPath;
        var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);

        if (tex == null)
        {
            foreach (var guid in AssetDatabase.FindAssets("vrcm_font_atlas t:Texture2D"))
            {
                var p = AssetDatabase.GUIDToAssetPath(guid);
                var t = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
                if (t != null) { tex = t; path = p; break; }
            }
        }

        if (tex == null)
        {
            // Texture absente ou mal importée : on la recrée depuis le script.
            EnsureFolder(GenDir);
            File.WriteAllBytes(GenAtlasPath, System.Convert.FromBase64String(AtlasPngBase64));
            AssetDatabase.ImportAsset(GenAtlasPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
            path = GenAtlasPath;
            tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
        }

        if (tex == null)
        {
            Info("Impossible de créer la texture des lettres (" + GenAtlasPath + ").");
            return null;
        }

        var imp = AssetImporter.GetAtPath(path) as TextureImporter;
        if (imp != null)
        {
            bool changed = false;
            if (imp.textureType != TextureImporterType.Default) { imp.textureType = TextureImporterType.Default; changed = true; }
            if (imp.mipmapEnabled) { imp.mipmapEnabled = false; changed = true; }
            if (imp.textureCompression != TextureImporterCompression.Uncompressed) { imp.textureCompression = TextureImporterCompression.Uncompressed; changed = true; }
            if (imp.wrapMode != TextureWrapMode.Clamp) { imp.wrapMode = TextureWrapMode.Clamp; changed = true; }
            if (!imp.alphaIsTransparency) { imp.alphaIsTransparency = true; changed = true; }
            if (imp.alphaSource != TextureImporterAlphaSource.FromInput) { imp.alphaSource = TextureImporterAlphaSource.FromInput; changed = true; }
            if (changed)
            {
                imp.SaveAndReimport();
                tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
            }
        }
        return tex;
    }

    const string AtlasPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAWfmNhQlgAABZ+anVtYgAAAB5qdW1kYzJwYQARABCAAACqADibcQNjMnBhAAAAFlhqdW1iAAAAR2p1bWRjMm1hABEAEIAAAKoAOJtxA3VybjpjMnBhOmIyNTU1MDU5LTI0OGItNDhjMC1hYzE4LTZlZTEyOGRlYWUzOAAAAAOTanVtYgAAAClqdW1kYzJhcwARABCAAACqADibcQNjMnBhLmFzc2VydGlvbnMAAAAAuGp1bWIAAABEanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5pbmdyZWRpZW50LnYzAAAAABhjMnNoBSypjzbdlJ7vN33qxAD0xwAAAGxjYm9yo2lkYzpmb3JtYXRpaW1hZ2UvcG5namluc3RhbmNlSUR4LHhtcDppaWQ6N2Q1MTBmNmMtYzkyYy00YzM2LTgyNzAtY2MwOWY2MWQzYzdhbHJlbGF0aW9uc2hpcGhwYXJlbnRPZgAAAeJqdW1iAAAAQWp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuYWN0aW9ucy52MgAAAAAYYzJzaKLNDqGH7+lINWyULkL68igAAAGZY2JvcqJnYWN0aW9uc4KiZmFjdGlvbmtjMnBhLm9wZW5lZGpwYXJhbWV0ZXJzoWtpbmdyZWRpZW50c4GiY3VybHgtc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5pbmdyZWRpZW50LnYzZGhhc2hYIMzm5LwtJYuytziM9JIYIhwE3IvE9XKK2TMtaJA2Dp2dpGZhY3Rpb254HWNvbS5hbnRocm9waWMuY2xhdWRlLnByb3ZpZGVkanBhcmFtZXRlcnOheB9jb20uYW50aHJvcGljLm9yaWdpbi1jb25maWRlbmNlZ3Vua25vd25rZGVzY3JpcHRpb254ZkNsYXVkZSBwcm92aWRlZCB0aGlzIGZpbGUgYXQgdGhlIHJlcXVlc3Qgb2YgYSB1c2VyIGFuZCBtYXkgaGF2ZSBjcmVhdGVkIG9yIG1vZGlmaWVkIHRoZSBmaWxlIGNvbnRlbnRzLm1zb2Z0d2FyZUFnZW50oWRuYW1lZkNsYXVkZXJhbGxBY3Rpb25zSW5jbHVkZWT1AAAAyGp1bWIAAABAanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5oYXNoLmRhdGEAAAAAGGMyc2jH01DtJdcoZhbzjjRiov0yAAAAgGNib3KlY2FsZ2ZzaGEyNTZjcGFkTQAAAAAAAAAAAAAAAABkaGFzaFggX5BRZn8CNfGWHgCm0GY6p3DfA00FUAdyqKr3gDanUd9kbmFtZW5qdW1iZiBtYW5pZmVzdGpleGNsdXNpb25zgaJlc3RhcnQYIWZsZW5ndGgZFooAAAI+anVtYgAAACdqdW1kYzJjbAARABCAAACqADibcQNjMnBhLmNsYWltLnYyAAAAAg9jYm9ypWNhbGdmc2hhMjU2aXNpZ25hdHVyZXhNc2VsZiNqdW1iZj0vYzJwYS91cm46YzJwYTpiMjU1NTA1OS0yNDhiLTQ4YzAtYWMxOC02ZWUxMjhkZWFlMzgvYzJwYS5zaWduYXR1cmVqaW5zdGFuY2VJRHgseG1wOmlpZDo1YjA4M2IzZS1kYjZhLTQ2MDMtODZlMi02MjhkZmQ4ZTg5M2ZyY3JlYXRlZF9hc3NlcnRpb25zg6JjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggzObkvC0li7K3OIz0khgiHATci8T1corZMy1okDYOnZ2iY3VybHgqc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5hY3Rpb25zLnYyZGhhc2hYIIGeir7do5csKkeZZuhTmupluCMUqNe+VwxmO0ACpsDLomN1cmx4KXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaGFzaC5kYXRhZGhhc2hYIDNnpA2D4Q0hFkY46kEi9XVVPlwYNQG1RdsjY6eO2oAmdGNsYWltX2dlbmVyYXRvcl9pbmZvo2RuYW1lb0FudGhyb3BpYyBGaWxlc2d2ZXJzaW9uZTEuMC4wa3NwZWNWZXJzaW9uZTIuNC4wAAAQOGp1bWIAAAAoanVtZGMyY3MAEQAQgAAAqgA4m3EDYzJwYS5zaWduYXR1cmUAAAAQCGNib3LShFkCEqIBJhghWQIKMIICBjCCAY2gAwIBAgIUQOWgCu7COdC+uIP6BkIFPWdVEwAwCgYIKoZIzj0EAwMwSTEXMBUGA1UEChMOQW50aHJvcGljLCBQQkMxLjAsBgNVBAMTJUFudGhyb3BpYyBDb250ZW50IENyZWRlbnRpYWxzIFJvb3QgQ0EwHhcNMjYwODA3MTg0MzU2WhcNMjgwODA2MTk0MzU2WjBEMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEpMCcGA1UEAxMgQW50aHJvcGljIENsYXVkZSBDb250ZW50IFNpZ25pbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASYegpry1AYBRTVNL1CpTlbROnY3dey+UrsF9C3phYrATN3ZHf93Mo8RQN0KOUuOn19P4oWNFWe5n2/She9N7eTo1gwVjAOBgNVHQ8BAf8EBAMCB4AwFQYDVR0lBA4wDAYKKwYBBAGD6F4CATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFM5R4gSBTmRbI/jjxM+aPpzB11zCMAoGCCqGSM49BAMDA2cAMGQCMDFzHRSeAXrSy1WOzkbhPZ6Km2wGTmZ/2gK18k8BQGXyqz88Rdrz6CTX9flAnYNVxgIwcF9c3fVhqmJKpi+UhasNUMko69cyX6STPfta3Q8EjyzDjzoyrol46FP6VFHhvUcJoWNwYWRZDZ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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Jo1VAEB7Q3V0iOM9ANbECADQDh0PgEkgAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFIgAAACAAhEAAABQIAIAAAAKRAAAAECBCAAAACgQAQAAAAUiAAAAoEAEAAAAFOj/AcTO+rPkOOuxAAAAAElFTkSuQmCC";

    static Material CreateMaterial(string path, Shader shader, Texture2D atlas)
    {
        AssetDatabase.DeleteAsset(path);
        var mat = new Material(shader);
        mat.SetTexture("_Atlas", atlas);
        AssetDatabase.CreateAsset(mat, path);
        return AssetDatabase.LoadAssetAtPath<Material>(path);
    }

    static void EnsureFolder(string path)
    {
        if (AssetDatabase.IsValidFolder(path)) return;
        string parent = Path.GetDirectoryName(path).Replace('\\', '/');
        string leaf = Path.GetFileName(path);
        EnsureFolder(parent);
        AssetDatabase.CreateFolder(parent, leaf);
    }

    // ---------------------------------------------------------------- contacts
    static void AddReceiver(Transform keyboardRoot, Key key, float x, float y)
    {
        var go = new GameObject("Key_" + key.param);
        go.transform.SetParent(keyboardRoot, false);
        go.transform.localPosition = new Vector3(x, y, 0f);

        var r = go.AddComponent<VRCContactReceiver>();
        r.rootTransform = go.transform;
        r.radius = ReceiverRadius;
        if (key.width > 1.01f)
        {
            r.shapeType = ContactBase.ShapeType.Capsule;
            r.height = key.width * Pitch - 0.004f;
            r.rotation = Quaternion.Euler(0f, 0f, 90f);   // capsule couchée selon X
        }
        else
        {
            r.shapeType = ContactBase.ShapeType.Sphere;
        }
        r.collisionTags = new List<string> { FingerTag };
        r.receiverType = ContactReceiver.ReceiverType.Constant;
        r.parameter = key.param;
        r.allowSelf = true;
        r.allowOthers = false;
        r.localOnly = true;
    }

    static void AddFingerSender(Animator animator, HumanBodyBones distal, HumanBodyBones intermediate, Transform avatar, float scale)
    {
        var d = animator.GetBoneTransform(distal);
        if (d == null) return;
        var m = animator.GetBoneTransform(intermediate);
        Vector3 dir = m != null ? (d.position - m.position).normalized : avatar.forward;

        var go = new GameObject(SenderName);
        go.transform.SetParent(d, false);
        go.transform.position = d.position + dir * 0.012f * scale;   // vers le bout du doigt

        var s = go.AddComponent<VRCContactSender>();
        s.rootTransform = go.transform;
        s.shapeType = ContactBase.ShapeType.Sphere;
        s.radius = SenderRadius;
        s.collisionTags = new List<string> { FingerTag };
        s.localOnly = true;
    }

    // ---------------------------------------------------------------- animator
    internal static AnimatorController GetFxController(VRCAvatarDescriptor desc)
    {
        var layers = desc.baseAnimationLayers;
        if (layers == null) return null;
        for (int i = 0; i < layers.Length; i++)
            if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.FX)
                return layers[i].animatorController as AnimatorController;
        return null;
    }

    static AnimatorController GetOrCreateFxController(VRCAvatarDescriptor desc)
    {
        var layers = desc.baseAnimationLayers;
        int idx = -1;
        if (layers != null)
            for (int i = 0; i < layers.Length; i++)
                if (layers[i].type == VRCAvatarDescriptor.AnimLayerType.FX) { idx = i; break; }
        if (idx < 0)
        {
            Info("Aucun layer FX trouvé dans le descriptor. Dans le VRC Avatar Descriptor, ouvre « Playable Layers », coche « Customize » puis relance.");
            return null;
        }

        EnsureFolder(GenDir);
        var ctrl = layers[idx].animatorController as AnimatorController;

        if (ctrl == null)
        {
            string p = AssetDatabase.GenerateUniqueAssetPath(GenDir + "/" + desc.gameObject.name + "_FX.controller");
            ctrl = AnimatorController.CreateAnimatorControllerAtPath(p);
        }
        else
        {
            string path = AssetDatabase.GetAssetPath(ctrl);
            if (path.StartsWith("Packages/") || layers[idx].isDefault)
            {
                string copy = AssetDatabase.GenerateUniqueAssetPath(GenDir + "/" + desc.gameObject.name + "_FX.controller");
                if (AssetDatabase.CopyAsset(path, copy)) ctrl = AssetDatabase.LoadAssetAtPath<AnimatorController>(copy);
            }
            else
            {
                string backup = path.Replace(".controller", "_backup_before_VRCM.controller");
                if (!File.Exists(backup)) AssetDatabase.CopyAsset(path, backup);
            }
        }

        layers[idx].isDefault = false;
        layers[idx].isEnabled = true;
        layers[idx].animatorController = ctrl;
        desc.customizeAnimationLayers = true;
        desc.baseAnimationLayers = layers;
        EditorUtility.SetDirty(desc);
        return ctrl;
    }

    static bool MajorityWriteDefaults(AnimatorController c)
    {
        int on = 0, off = 0;
        foreach (var l in c.layers)
        {
            if (l.stateMachine == null || l.name.StartsWith("VRCM_")) continue;
            foreach (var cs in l.stateMachine.states)
            {
                if (cs.state.writeDefaultValues) on++; else off++;
            }
        }
        return on > off;
    }

    static void EnsureAnimParam(AnimatorController c, string name, AnimatorControllerParameterType type, float def)
    {
        foreach (var p in c.parameters) if (p.name == name) return;
        c.AddParameter(new AnimatorControllerParameter { name = name, type = type, defaultFloat = def, defaultBool = false });
    }

    static AnimationClip MakeClip(string name, string path, System.Type type, string prop, float value)
    {
        var clip = new AnimationClip { name = name };
        clip.SetCurve(path, type, prop, AnimationCurve.Constant(0f, 1f / 60f, value));
        AssetDatabase.CreateAsset(clip, ClipsDir + "/" + name + ".anim");
        return clip;
    }

    static void BuildAnimator(AnimatorController ctrl, List<List<Key>> layout, string displayPath)
    {
        bool wd = MajorityWriteDefaults(ctrl);
        string ctrlPath = AssetDatabase.GetAssetPath(ctrl);

        // Paramètres
        EnsureAnimParam(ctrl, "KB_Active", AnimatorControllerParameterType.Bool, 0f);
        EnsureAnimParam(ctrl, "VRCM_One", AnimatorControllerParameterType.Float, 1f);
        foreach (var row in layout)
            foreach (var k in row)
                EnsureAnimParam(ctrl, k.param, AnimatorControllerParameterType.Bool, 0f);
        for (int i = 0; i < Slots; i++)
            EnsureAnimParam(ctrl, "KB_Char" + i, AnimatorControllerParameterType.Float, 0f);

        // Layer 1 : afficher / cacher le clavier
        var clipOff = MakeClip("VRCM_KB_Off", KeyboardName, typeof(GameObject), "m_IsActive", 0f);
        var clipOn = MakeClip("VRCM_KB_On", KeyboardName, typeof(GameObject), "m_IsActive", 1f);

        var sm1 = new AnimatorStateMachine { name = "VRCM_Toggle", hideFlags = HideFlags.HideInHierarchy };
        AssetDatabase.AddObjectToAsset(sm1, ctrlPath);
        var layer1 = new AnimatorControllerLayer { name = "VRCM_Toggle", defaultWeight = 1f, stateMachine = sm1 };
        ctrl.AddLayer(layer1);

        var stOff = sm1.AddState("VRCM_Off", new Vector3(260, 0, 0));
        var stOn = sm1.AddState("VRCM_On", new Vector3(260, 80, 0));
        stOff.motion = clipOff; stOff.writeDefaultValues = wd;
        stOn.motion = clipOn; stOn.writeDefaultValues = wd;
        sm1.defaultState = stOff;

        var tOn = stOff.AddTransition(stOn);
        tOn.hasExitTime = false; tOn.duration = 0f;
        tOn.AddCondition(AnimatorConditionMode.If, 0f, "KB_Active");
        var tOff = stOn.AddTransition(stOff);
        tOff.hasExitTime = false; tOff.duration = 0f;
        tOff.AddCondition(AnimatorConditionMode.IfNot, 0f, "KB_Active");

        // Layer 2 : écran de texte (24 cases pilotées par KB_Char0..23)
        var direct = new BlendTree { name = "VRCM_DisplayTree", blendType = BlendTreeType.Direct, hideFlags = HideFlags.HideInHierarchy };
        AssetDatabase.AddObjectToAsset(direct, ctrlPath);

        var children = new List<ChildMotion>();
        for (int i = 0; i < Slots; i++)
        {
            string prop = "material._C" + i;
            var lo = MakeClip("VRCM_Char" + i + "_lo", displayPath, typeof(MeshRenderer), prop, 0f);
            var hi = MakeClip("VRCM_Char" + i + "_hi", displayPath, typeof(MeshRenderer), prop, 63f);

            var sub = new BlendTree
            {
                name = "VRCM_Char" + i,
                blendType = BlendTreeType.Simple1D,
                blendParameter = "KB_Char" + i,
                useAutomaticThresholds = false,
                hideFlags = HideFlags.HideInHierarchy
            };
            AssetDatabase.AddObjectToAsset(sub, ctrlPath);
            sub.AddChild(lo, 0f);
            sub.AddChild(hi, 1f);   // l'app envoie indice / 63 (VRChat limite les Float à [-1, 1])

            children.Add(new ChildMotion { motion = sub, timeScale = 1f, directBlendParameter = "VRCM_One" });
        }
        direct.children = children.ToArray();

        var sm2 = new AnimatorStateMachine { name = "VRCM_Display", hideFlags = HideFlags.HideInHierarchy };
        AssetDatabase.AddObjectToAsset(sm2, ctrlPath);
        var layer2 = new AnimatorControllerLayer { name = "VRCM_Display", defaultWeight = 1f, stateMachine = sm2 };
        ctrl.AddLayer(layer2);
        var stDisplay = sm2.AddState("VRCM_Display", new Vector3(260, 0, 0));
        stDisplay.motion = direct;
        stDisplay.writeDefaultValues = wd;
        sm2.defaultState = stDisplay;

        EditorUtility.SetDirty(ctrl);
    }

    // ------------------------------------------------- paramètres d'expression / menu
    static void AddExpressionParameters(VRCAvatarDescriptor desc, List<List<Key>> layout)
    {
        var ep = desc.expressionParameters;
        if (ep == null)
        {
            ep = ScriptableObject.CreateInstance<VRCExpressionParameters>();
            ep.parameters = new VRCExpressionParameters.Parameter[0];
            string p = AssetDatabase.GenerateUniqueAssetPath(GenDir + "/VRCM_Parameters.asset");
            AssetDatabase.CreateAsset(ep, p);
            desc.expressionParameters = ep;
        }
        desc.customExpressions = true;

        var list = new List<VRCExpressionParameters.Parameter>(ep.parameters ?? new VRCExpressionParameters.Parameter[0]);
        list.RemoveAll(p => p != null && p.name != null && p.name.StartsWith("KB_"));

        list.Add(LocalParam("KB_Active", VRCExpressionParameters.ValueType.Bool));
        foreach (var row in layout)
            foreach (var k in row)
                list.Add(LocalParam(k.param, VRCExpressionParameters.ValueType.Bool));
        for (int i = 0; i < Slots; i++)
            list.Add(LocalParam("KB_Char" + i, VRCExpressionParameters.ValueType.Float));

        ep.parameters = list.ToArray();
        EditorUtility.SetDirty(ep);
        EditorUtility.SetDirty(desc);
    }

    static VRCExpressionParameters.Parameter LocalParam(string name, VRCExpressionParameters.ValueType type)
    {
        return new VRCExpressionParameters.Parameter
        {
            name = name,
            valueType = type,
            defaultValue = 0f,
            saved = false,
            networkSynced = false   // local seulement : ne coûte rien en mémoire synchronisée
        };
    }

    static bool AddMenuToggle(VRCAvatarDescriptor desc)
    {
        var menu = desc.expressionsMenu;
        if (menu == null)
        {
            menu = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
            menu.controls = new List<VRCExpressionsMenu.Control>();
            string p = AssetDatabase.GenerateUniqueAssetPath(GenDir + "/VRCM_Menu.asset");
            AssetDatabase.CreateAsset(menu, p);
            desc.expressionsMenu = menu;
        }
        desc.customExpressions = true;

        menu.controls.RemoveAll(c => c != null && c.name == MenuLabel);
        if (menu.controls.Count >= 8) return false;

        menu.controls.Add(new VRCExpressionsMenu.Control
        {
            name = MenuLabel,
            type = VRCExpressionsMenu.Control.ControlType.Toggle,
            parameter = new VRCExpressionsMenu.Control.Parameter { name = "KB_Active" },
            value = 1f
        });
        EditorUtility.SetDirty(menu);
        EditorUtility.SetDirty(desc);
        return true;
    }

    // -------------------------------------------------------------------- nettoyage
    internal static void RemoveFromAvatar(VRCAvatarDescriptor desc, AnimatorController fx)
    {
        var avatar = desc.transform;

        // Objets
        var toDestroy = new List<GameObject>();
        foreach (var t in avatar.GetComponentsInChildren<Transform>(true))
            if (t != null && (t.name == KeyboardName || t.name == SenderName)) toDestroy.Add(t.gameObject);
        foreach (var go in toDestroy)
            if (go != null) Object.DestroyImmediate(go);

        // Animator
        if (fx != null)
        {
            for (int i = fx.layers.Length - 1; i >= 0; i--)
                if (fx.layers[i].name.StartsWith("VRCM_")) fx.RemoveLayer(i);

            foreach (var p in fx.parameters)
                if (p.name.StartsWith("KB_") || p.name == "VRCM_One") fx.RemoveParameter(p);

            string path = AssetDatabase.GetAssetPath(fx);
            foreach (var o in AssetDatabase.LoadAllAssetsAtPath(path))
            {
                if (o == null || o == fx) continue;
                if ((o is AnimatorStateMachine || o is BlendTree || o is AnimatorState) && o.name.StartsWith("VRCM_"))
                    Object.DestroyImmediate(o, true);
            }
            EditorUtility.SetDirty(fx);
        }

        // Paramètres d'expression
        var ep = desc.expressionParameters;
        if (ep != null && ep.parameters != null)
        {
            var list = new List<VRCExpressionParameters.Parameter>(ep.parameters);
            list.RemoveAll(p => p != null && p.name != null && p.name.StartsWith("KB_"));
            ep.parameters = list.ToArray();
            EditorUtility.SetDirty(ep);
        }

        // Menu
        var menu = desc.expressionsMenu;
        if (menu != null && menu.controls != null)
        {
            menu.controls.RemoveAll(c => c != null && c.name == MenuLabel);
            EditorUtility.SetDirty(menu);
        }

        // Fichiers générés
        AssetDatabase.DeleteAsset(ClipsDir);
        AssetDatabase.DeleteAsset(GenDir + "/VRCM_KeysMesh.asset");
        AssetDatabase.DeleteAsset(GenDir + "/VRCM_DisplayMesh.asset");
        AssetDatabase.DeleteAsset(GenDir + "/VRCM_Keys.mat");
        AssetDatabase.DeleteAsset(GenDir + "/VRCM_Display.mat");
    }
}
#endif
