using System;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace MajacloudSdk.Editor
{
    /// <summary>Procedural uGUI + TextMeshPro helpers used to generate the Starter sample.</summary>
    internal static class UiBuilder
    {
        private static Sprite _fallbackRounded;

        /// <summary>The generated soft rounded sprite, or Unity's built-in one if it is missing.</summary>
        public static Sprite RoundedSprite
        {
            get
            {
                if (SoftArt.Rounded != null) return SoftArt.Rounded;
                if (_fallbackRounded == null) _fallbackRounded = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Background.psd");
                return _fallbackRounded;
            }
        }

        public static Canvas CreateCanvas(string name, Vector2 reference, float match = 0.5f)
        {
            var go = new GameObject(name, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
            var canvas = go.GetComponent<Canvas>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;
            var scaler = go.GetComponent<CanvasScaler>();
            scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
            scaler.referenceResolution = reference;
            scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
            scaler.matchWidthOrHeight = match;
            return canvas;
        }

        /// <summary>EventSystem without an input module: InputModuleBootstrap adds the right one at runtime.</summary>
        public static GameObject CreateEventSystem()
        {
            var go = new GameObject("EventSystem", typeof(EventSystem));
            AddSampleComponent(go, "InputModuleBootstrap");
            return go;
        }

        public static GameObject CreateRect(string name, Transform parent)
        {
            var go = new GameObject(name, typeof(RectTransform));
            go.transform.SetParent(parent, false);
            return go;
        }

        /// <summary>A filled panel. <paramref name="radius"/> is the corner radius in canvas units (0 = square corners).</summary>
        public static Image CreatePanel(string name, Transform parent, Color fill, float radius = UiTheme.RadiusCard)
        {
            var go = CreateRect(name, parent);
            var image = go.AddComponent<Image>();
            image.color = fill;
            if (radius > 0f)
            {
                image.sprite = RoundedSprite;
                image.type = Image.Type.Sliced;
                image.pixelsPerUnitMultiplier = Mathf.Max(0.01f, SoftArt.CornerPixels / radius);
            }
            image.raycastTarget = false;
            return image;
        }

        public static Image CreateImage(string name, Transform parent, Sprite sprite)
        {
            var go = CreateRect(name, parent);
            var image = go.AddComponent<Image>();
            image.sprite = sprite;
            image.preserveAspect = true;
            image.raycastTarget = false;
            if (sprite == null) image.color = Color.clear;
            return image;
        }

        public static TextMeshProUGUI CreateText(string name, Transform parent, string text, int size, Color color, TextAlignmentOptions alignment = TextAlignmentOptions.Left, FontStyles style = FontStyles.Normal)
        {
            var go = CreateRect(name, parent);
            var tmp = go.AddComponent<TextMeshProUGUI>();
            var font = TmpResources.DefaultFont();
            if (font != null) tmp.font = font;
            tmp.text = text;
            tmp.fontSize = size;
            tmp.color = color;
            tmp.alignment = alignment;
            tmp.fontStyle = style;
            tmp.raycastTarget = false;
            tmp.overflowMode = TextOverflowModes.Ellipsis;
#if UNITY_2023_2_OR_NEWER
            tmp.textWrappingMode = TextWrappingModes.Normal;
#else
            tmp.enableWordWrapping = true;
#endif
            return tmp;
        }

        /// <summary>A button with a centred label; pass a null label for a button you fill yourself.</summary>
        public static Button CreateButton(string name, Transform parent, string label, Color background, Color foreground, int fontSize = UiTheme.Body, float radius = UiTheme.RadiusControl)
        {
            var image = CreatePanel(name, parent, background, radius);
            image.raycastTarget = true;
            var button = image.gameObject.AddComponent<Button>();
            var colors = button.colors;
            colors.normalColor = Color.white;
            colors.highlightedColor = new Color(0.94f, 0.94f, 0.94f);
            colors.pressedColor = new Color(0.85f, 0.85f, 0.85f);
            colors.disabledColor = new Color(1f, 1f, 1f, 0.5f);
            button.colors = colors;
            button.targetGraphic = image;
            if (label == null) return button;
            var text = CreateText("Label", image.transform, label, fontSize, foreground, TextAlignmentOptions.Center, FontStyles.Bold);
            Stretch(text.rectTransform, 16, 8);
            return button;
        }

        public static TMP_InputField CreateInput(string name, Transform parent, string placeholder, TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard, string value = null)
        {
            var image = CreatePanel(name, parent, UiTheme.Panel2, UiTheme.RadiusControl);
            image.raycastTarget = true;
            var input = image.gameObject.AddComponent<TMP_InputField>();
            input.targetGraphic = image;
            input.contentType = contentType;
            var area = CreateRect("Text Area", image.transform);
            Stretch((RectTransform)area.transform, 24, 12);
            area.AddComponent<RectMask2D>();
            var placeholderText = CreateText("Placeholder", area.transform, placeholder, UiTheme.Body, new Color(UiTheme.Muted.r, UiTheme.Muted.g, UiTheme.Muted.b, 0.8f));
            Stretch(placeholderText.rectTransform, 0, 0);
            placeholderText.alignment = TextAlignmentOptions.MidlineLeft;
            var text = CreateText("Text", area.transform, string.Empty, UiTheme.Body, UiTheme.Text);
            Stretch(text.rectTransform, 0, 0);
            text.alignment = TextAlignmentOptions.MidlineLeft;
            text.overflowMode = TextOverflowModes.Overflow;
            input.textViewport = (RectTransform)area.transform;
            input.textComponent = text;
            input.placeholder = placeholderText;
            input.fontAsset = text.font;
            input.pointSize = UiTheme.Body;
            if (!string.IsNullOrEmpty(value)) input.text = value;
            return input;
        }

        public static (ScrollRect scroll, RectTransform content) CreateScrollList(string name, Transform parent, float spacing = 8f)
        {
            var root = CreateRect(name, parent);
            var scroll = root.AddComponent<ScrollRect>();
            var viewportGo = CreateRect("Viewport", root.transform);
            var viewportImage = viewportGo.AddComponent<Image>();
            viewportImage.color = new Color(1, 1, 1, 0.01f);
            viewportGo.AddComponent<RectMask2D>();
            Stretch((RectTransform)viewportGo.transform, 0, 0);
            var content = CreateRect("Content", viewportGo.transform);
            var contentRect = (RectTransform)content.transform;
            contentRect.anchorMin = new Vector2(0, 1);
            contentRect.anchorMax = new Vector2(1, 1);
            contentRect.pivot = new Vector2(0.5f, 1);
            contentRect.offsetMin = Vector2.zero;
            contentRect.offsetMax = Vector2.zero;
            var layout = content.AddComponent<VerticalLayoutGroup>();
            layout.spacing = spacing;
            layout.padding = new RectOffset(0, 0, 0, 0);
            layout.childControlWidth = true;
            layout.childControlHeight = true;
            layout.childForceExpandWidth = true;
            layout.childForceExpandHeight = false;
            layout.childAlignment = TextAnchor.UpperCenter;
            var fitter = content.AddComponent<ContentSizeFitter>();
            fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
            scroll.content = contentRect;
            scroll.viewport = (RectTransform)viewportGo.transform;
            scroll.horizontal = false;
            scroll.vertical = true;
            scroll.movementType = ScrollRect.MovementType.Clamped;
            scroll.scrollSensitivity = 30f;
            return (scroll, contentRect);
        }

        public static VerticalLayoutGroup AddVertical(GameObject go, float spacing, int padding, TextAnchor alignment = TextAnchor.UpperCenter, bool expandHeight = false)
        {
            var layout = go.AddComponent<VerticalLayoutGroup>();
            layout.spacing = spacing;
            layout.padding = new RectOffset(padding, padding, padding, padding);
            layout.childControlWidth = true;
            layout.childControlHeight = true;
            layout.childForceExpandWidth = true;
            layout.childForceExpandHeight = expandHeight;
            layout.childAlignment = alignment;
            return layout;
        }

        public static HorizontalLayoutGroup AddHorizontal(GameObject go, float spacing, int padding, TextAnchor alignment = TextAnchor.MiddleLeft)
        {
            var layout = go.AddComponent<HorizontalLayoutGroup>();
            layout.spacing = spacing;
            layout.padding = new RectOffset(padding, padding, padding, padding);
            layout.childControlWidth = true;
            layout.childControlHeight = true;
            layout.childForceExpandWidth = false;
            layout.childForceExpandHeight = true;
            layout.childAlignment = alignment;
            return layout;
        }

        public static LayoutElement SetSize(GameObject go, float preferredHeight = -1, float preferredWidth = -1, float flexibleWidth = -1, float flexibleHeight = -1)
        {
            var element = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>();
            // A fixed height means "don't stretch": rows with a layout group would otherwise report
            // flexible height (childForceExpandHeight) and soak up the space meant for lists.
            if (preferredHeight >= 0) { element.preferredHeight = preferredHeight; element.minHeight = preferredHeight; if (flexibleHeight < 0) element.flexibleHeight = 0; }
            if (preferredWidth >= 0) { element.preferredWidth = preferredWidth; element.minWidth = preferredWidth; }
            if (flexibleWidth >= 0) element.flexibleWidth = flexibleWidth;
            if (flexibleHeight >= 0) element.flexibleHeight = flexibleHeight;
            return element;
        }

        public static void Stretch(RectTransform rect, float horizontalPadding, float verticalPadding)
        {
            rect.anchorMin = Vector2.zero;
            rect.anchorMax = Vector2.one;
            rect.offsetMin = new Vector2(horizontalPadding, verticalPadding);
            rect.offsetMax = new Vector2(-horizontalPadding, -verticalPadding);
        }

        /// <summary>Add a component from the samples assembly by name, without a hard assembly reference.</summary>
        public static Component AddSampleComponent(GameObject go, string typeName)
        {
            var type = Type.GetType("MajacloudSdk.Samples.Starter." + typeName + ", Majacloud.Samples.Starter");
            if (type == null) throw new InvalidOperationException("Starter sample script " + typeName + " was not found. Is the Majacloud.Samples.Starter assembly compiled?");
            return go.AddComponent(type);
        }

        public static void SetField(UnityEngine.Object target, string property, UnityEngine.Object value)
        {
            var serialized = new SerializedObject(target);
            var found = serialized.FindProperty(property);
            if (found == null) throw new InvalidOperationException(target.GetType().Name + " has no serialized field '" + property + "'.");
            found.objectReferenceValue = value;
            serialized.ApplyModifiedPropertiesWithoutUndo();
        }

        public static void SetField(UnityEngine.Object target, string property, string value)
        {
            var serialized = new SerializedObject(target);
            var found = serialized.FindProperty(property);
            if (found == null) throw new InvalidOperationException(target.GetType().Name + " has no serialized field '" + property + "'.");
            found.stringValue = value;
            serialized.ApplyModifiedPropertiesWithoutUndo();
        }

        public static void SetField(UnityEngine.Object target, string property, int value)
        {
            var serialized = new SerializedObject(target);
            var found = serialized.FindProperty(property);
            if (found == null) throw new InvalidOperationException(target.GetType().Name + " has no serialized field '" + property + "'.");
            found.intValue = value;
            serialized.ApplyModifiedPropertiesWithoutUndo();
        }

        public static void SetField(UnityEngine.Object target, string property, bool value)
        {
            var serialized = new SerializedObject(target);
            var found = serialized.FindProperty(property);
            if (found == null) throw new InvalidOperationException(target.GetType().Name + " has no serialized field '" + property + "'.");
            found.boolValue = value;
            serialized.ApplyModifiedPropertiesWithoutUndo();
        }

        public static void SetArray(UnityEngine.Object target, string property, UnityEngine.Object[] values)
        {
            var serialized = new SerializedObject(target);
            var found = serialized.FindProperty(property);
            if (found == null) throw new InvalidOperationException(target.GetType().Name + " has no serialized field '" + property + "'.");
            found.arraySize = values.Length;
            for (int i = 0; i < values.Length; i++) found.GetArrayElementAtIndex(i).objectReferenceValue = values[i];
            serialized.ApplyModifiedPropertiesWithoutUndo();
        }

        public static GameObject SavePrefab(GameObject instance, string path)
        {
            MajacloudEditorPaths.EnsureFolder(System.IO.Path.GetDirectoryName(path)?.Replace('\\', '/'));
            return PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
        }
    }
}
