using System;
using System.Collections.Generic;
using System.Reflection;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Runtime.Helpers;

namespace MCPForUnity.Editor.Tools.Cameras
{
    internal static class CameraCreate
    {
        private static readonly Dictionary<string, (string body, string aim)> Presets = new(StringComparer.OrdinalIgnoreCase)
        {
            ["follow"]        = ("CinemachineFollow",              "CinemachineRotationComposer"),
            ["third_person"]  = ("CinemachineThirdPersonFollow",   "CinemachineRotationComposer"),
            ["freelook"]      = ("CinemachineOrbitalFollow",       "CinemachineRotationComposer"),
            ["dolly"]         = ("CinemachineSplineDolly",         "CinemachineRotationComposer"),
            ["static"]        = (null,                              "CinemachineHardLookAt"),
            ["top_down"]      = ("CinemachineFollow",              null),
            ["side_scroller"] = ("CinemachinePositionComposer",    null),
        };

        internal static object CreateBasicCamera(JObject @params)
        {
            var props = CameraHelpers.ExtractProperties(@params) ?? new JObject();
            string name = ParamCoercion.CoerceString(props["name"], null) ?? "Camera";
            float fov = ParamCoercion.CoerceFloat(props["fieldOfView"], 60f);
            float near = ParamCoercion.CoerceFloat(props["nearClipPlane"], 0.3f);
            float far = ParamCoercion.CoerceFloat(props["farClipPlane"], 1000f);

            var go = new GameObject(name);
            Undo.RegisterCreatedObjectUndo(go, $"Create Camera '{name}'");
            var cam = go.AddComponent<UnityEngine.Camera>();
            cam.fieldOfView = fov;
            cam.nearClipPlane = near;
            cam.farClipPlane = far;

            // Position near follow target if provided
            string follow = ParamCoercion.CoerceString(props["follow"], null);
            if (follow != null)
            {
                var target = CameraHelpers.ResolveGameObjectRef(follow);
                if (target != null)
                    go.transform.position = target.transform.position + new Vector3(0, 5, -10);
            }

            // Look at target if provided
            string lookAt = ParamCoercion.CoerceString(props["lookAt"] ?? props["look_at"], null);
            if (lookAt != null)
            {
                var target = CameraHelpers.ResolveGameObjectRef(lookAt);
                if (target != null)
                    go.transform.LookAt(target.transform);
            }

            CameraHelpers.MarkDirty(go);

            return new
            {
                success = true,
                message = $"Created basic Camera '{name}' (Cinemachine not installed — using Unity Camera).",
                data = new
                {
                    instanceID = go.GetInstanceIDCompat(),
                    cinemachine = false,
                    hint = "Install com.unity.cinemachine for presets, blending, and virtual camera features."
                }
            };
        }

        internal static object CreateCinemachineCamera(JObject @params)
        {
            var props = CameraHelpers.ExtractProperties(@params) ?? new JObject();
            string name = ParamCoercion.CoerceString(props["name"], null) ?? "CM Camera";
            string preset = ParamCoercion.CoerceString(props["preset"], null) ?? "follow";
            int priority = ParamCoercion.CoerceInt(props["priority"], 10);

            if (!Presets.TryGetValue(preset, out var presetDef))
            {
                return new ErrorResponse(
                    $"Unknown preset '{preset}'. Valid presets: {string.Join(", ", Presets.Keys)}.");
            }

            var go = new GameObject(name);
            Undo.RegisterCreatedObjectUndo(go, $"Create CinemachineCamera '{name}'");

            // Add CinemachineCamera component
            var cmType = CameraHelpers.CinemachineCameraType;
            var cmCamera = go.AddComponent(cmType);

            // PrioritySettings is a struct with Enabled + m_Value — use SerializedProperty
            using (var so = new SerializedObject(cmCamera))
            {
                var priorityProp = so.FindProperty("Priority");
                if (priorityProp != null)
                {
                    var enabledProp = priorityProp.FindPropertyRelative("Enabled");
                    var valueProp = priorityProp.FindPropertyRelative("m_Value");
                    if (enabledProp != null) enabledProp.boolValue = true;
                    if (valueProp != null) valueProp.intValue = priority;
                    so.ApplyModifiedProperties();
                }
                else
                {
                    CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority);
                }
            }

            // Add Body component
            string bodyName = null;
            if (presetDef.body != null)
            {
                var bodyType = CameraHelpers.ResolveComponentType(presetDef.body);
                if (bodyType != null)
                {
                    go.AddComponent(bodyType);
                    bodyName = presetDef.body;
                }
            }

            // Add Aim component
            string aimName = null;
            if (presetDef.aim != null)
            {
                var aimType = CameraHelpers.ResolveComponentType(presetDef.aim);
                if (aimType != null)
                {
                    go.AddComponent(aimType);
                    aimName = presetDef.aim;
                }
            }

            // Set Follow target
            var followToken = props["follow"];
            if (followToken != null && followToken.Type != JTokenType.Null)
                CameraHelpers.SetTransformTarget(cmCamera, "Follow", followToken);

            // Set LookAt target
            var lookAtToken = props["lookAt"] ?? props["look_at"];
            if (lookAtToken != null && lookAtToken.Type != JTokenType.Null)
                CameraHelpers.SetTransformTarget(cmCamera, "LookAt", lookAtToken);

            CameraHelpers.MarkDirty(go);

            return new
            {
                success = true,
                message = $"Created CinemachineCamera '{name}' with preset '{preset}'.",
                data = new
                {
                    instanceID = go.GetInstanceIDCompat(),
                    cinemachine = true,
                    preset,
                    priority,
                    body = bodyName,
                    aim = aimName
                }
            };
        }

        internal static object EnsureBrain(JObject @params)
        {
            var props = CameraHelpers.ExtractProperties(@params) ?? new JObject();

            // Check if Brain already exists
            var existingBrain = CameraHelpers.FindBrain();
            if (existingBrain != null)
            {
                return new
                {
                    success = true,
                    message = $"CinemachineBrain already exists on '{existingBrain.gameObject.name}'.",
                    data = new
                    {
                        instanceID = existingBrain.gameObject.GetInstanceIDCompat(),
                        alreadyExisted = true
                    }
                };
            }

            // Find target camera
            string cameraRef = ParamCoercion.CoerceString(props["camera"], null);
            UnityEngine.Camera cam;
            if (cameraRef != null)
            {
                var camGo = CameraHelpers.ResolveGameObjectRef(cameraRef);
                cam = camGo != null ? camGo.GetComponent<UnityEngine.Camera>() : null;
            }
            else
            {
                cam = CameraHelpers.FindMainCamera();
            }

            if (cam == null)
                return new ErrorResponse("No Camera found to add CinemachineBrain to.");

            var brainType = CameraHelpers.CinemachineBrainType;
            Undo.RecordObject(cam.gameObject, "Add CinemachineBrain");
            var brain = cam.gameObject.AddComponent(brainType);

            // Configure default blend if provided
            string blendStyle = ParamCoercion.CoerceString(props["defaultBlendStyle"] ?? props["default_blend_style"], null);
            float blendDuration = ParamCoercion.CoerceFloat(props["defaultBlendDuration"] ?? props["default_blend_duration"], -1f);

            if (blendStyle != null || blendDuration >= 0)
            {
                // Set via SerializedProperty for the DefaultBlend struct
                using var so = new SerializedObject(brain);
                var defaultBlendProp = so.FindProperty("DefaultBlend") ?? so.FindProperty("m_DefaultBlend");
                if (defaultBlendProp != null)
                {
                    if (blendStyle != null)
                    {
                        var styleProp = defaultBlendProp.FindPropertyRelative("Style")
                                     ?? defaultBlendProp.FindPropertyRelative("m_Style");
                        if (styleProp != null)
                        {
                            int idx = Array.FindIndex(styleProp.enumNames,
                                n => n.Equals(blendStyle, StringComparison.OrdinalIgnoreCase));
                            if (idx >= 0)
                                styleProp.enumValueIndex = idx;
                        }
                    }
                    if (blendDuration >= 0)
                    {
                        var timeProp = defaultBlendProp.FindPropertyRelative("Time")
                                    ?? defaultBlendProp.FindPropertyRelative("m_Time");
                        if (timeProp != null)
                            timeProp.floatValue = blendDuration;
                    }
                    so.ApplyModifiedProperties();
                }
            }

            CameraHelpers.MarkDirty(cam.gameObject);

            return new
            {
                success = true,
                message = $"CinemachineBrain added to '{cam.gameObject.name}'.",
                data = new
                {
                    instanceID = cam.gameObject.GetInstanceIDCompat(),
                    alreadyExisted = false
                }
            };
        }
    }
}
