using BepInEx;
using BepInEx.Configuration;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

namespace BurglinGnomes.UltraWideFix
{
    /// <summary>
    /// Ultrawide UI fix for Burglin' Gnomes (Unity 6, Mono).
    ///
    /// The camera needs no patching: it is orthographic with a constant
    /// orthographicSize (5.0), so a wider display already shows proportionally
    /// more world (visible width 17.8 -> 35.6 going 16:9 -> 32:9). That is
    /// correct Hor+ behaviour.
    ///
    /// The actual problem is UI scaling:
    ///   * HUD canvases (ScreenSpaceOverlay + ScaleWithScreenSize) ship
    ///     matchWidthOrHeight = 0.453, which is width-biased. At 5120px wide that
    ///     yields scaleFactor 1.948 and a ballooned HUD. Matching HEIGHT (1.0)
    ///     gives 1.333, and raising referenceResolution.y shrinks it further
    ///     while giving the layout MORE logical room (cures overlapping text).
    ///   * World-space label canvases (NamePlate, ...) are NOT affected by
    ///     CanvasScaler.scaleFactor - CanvasScaler.HandleWorldCanvas() overwrites
    ///     it with dynamicPixelsPerUnit every frame. Their size comes from the
    ///     canvas transform's localScale, so that is what we scale.
    /// </summary>
    [BepInPlugin(PluginGuid, PluginName, PluginVersion)]
    public class UltraWideFixPlugin : BaseUnityPlugin
    {
        public const string PluginGuid = "com.programmingpug.burglingnomes.ultrawidefix";
        public const string PluginName = "Burglin' Gnomes UltraWide Fix";
        public const string PluginVersion = "1.0.0";

        private ConfigEntry<bool> _enabled;
        private ConfigEntry<float> _match;
        private ConfigEntry<float> _hudScale;
        private ConfigEntry<float> _worldScale;
        private ConfigEntry<float> _designHeight;
        private ConfigEntry<bool> _verbose;

        // Baselines captured once per canvas so repeated applies never compound.
        private readonly System.Collections.Generic.Dictionary<int, Vector2> _refBaseline
            = new System.Collections.Generic.Dictionary<int, Vector2>();
        private readonly System.Collections.Generic.Dictionary<int, Vector3> _scaleBaseline
            = new System.Collections.Generic.Dictionary<int, Vector3>();

        private float _timer;

        private void Awake()
        {
            _enabled = Config.Bind("General", "Enabled", true,
                "Master switch for the ultrawide UI fix.");
            _match = Config.Bind("HUD", "MatchWidthOrHeight", 1.0f,
                new ConfigDescription(
                    "CanvasScaler match for HUD canvases. 0 = scale by width, 1 = by height. " +
                    "Keep at 1 for ultrawide; lower values re-introduce width scaling and " +
                    "cramp the layout vertically.",
                    new AcceptableValueRange<float>(0f, 1f)));
            _hudScale = Config.Bind("HUD", "HudScale", 0.8f,
                new ConfigDescription(
                    "HUD size multiplier. Below 1 shrinks the HUD AND gives the layout more " +
                    "logical room, which is what cures overlapping text. 1 = stock size.",
                    new AcceptableValueRange<float>(0.4f, 1.5f)));
            _designHeight = Config.Bind("HUD", "DesignHeight", 1080f,
                "The game's design reference height. Do not change unless you know why.");
            _worldScale = Config.Bind("World", "WorldLabelScale", 0.8f,
                new ConfigDescription(
                    "Size multiplier for in-world labels (nameplates). At 32:9 you see twice " +
                    "as much world, so more labels are on screen and can collide; lower this " +
                    "to shrink them. 1 = stock size.",
                    new AcceptableValueRange<float>(0.3f, 1.5f)));
            _verbose = Config.Bind("Debug", "Verbose", false,
                "Log every canvas adjustment.");

            SceneManager.sceneLoaded += (_, __) => Apply();
            Logger.LogInfo($"{PluginName} {PluginVersion} loaded.");
        }

        private void Start() => Apply();

        // Canvases are created lazily (menus, popups), so re-apply periodically.
        // We also re-read the config file each tick: BepInEx does NOT pick up
        // external edits on its own, so without this you'd have to restart the
        // game after every tweak.
        private void Update()
        {
            _timer += Time.unscaledDeltaTime;
            if (_timer < 2f) return;
            _timer = 0f;

            try
            {
                var stamp = System.IO.File.GetLastWriteTimeUtc(Config.ConfigFilePath);
                if (stamp != _configStamp)
                {
                    _configStamp = stamp;
                    Config.Reload();
                    Logger.LogInfo($"Config reloaded: HudScale={_hudScale.Value} " +
                                   $"WorldLabelScale={_worldScale.Value} " +
                                   $"Match={_match.Value} Enabled={_enabled.Value}");
                    // values changed -> re-derive from baselines on this pass
                }
            }
            catch (System.Exception e)
            {
                Logger.LogWarning($"Config reload failed (check the .cfg syntax): {e.Message}");
            }

            Apply();
        }

        private System.DateTime _configStamp;

        private void Apply()
        {
            if (!_enabled.Value) return;

            foreach (var cs in FindObjectsOfType<CanvasScaler>())
            {
                var canvas = cs.GetComponent<Canvas>();
                if (canvas == null) continue;
                int id = cs.GetInstanceID();

                if (canvas.renderMode == RenderMode.WorldSpace)
                {
                    if (Mathf.Approximately(_worldScale.Value, 1f)) continue;

                    var cur = cs.transform.localScale;
                    // Games hide world-space UI by zeroing its scale. Never cache a
                    // zero baseline: if we did, multiplying it would pin the canvas
                    // to zero forever and the UI could never reappear.
                    if (!_scaleBaseline.TryGetValue(id, out var base3))
                    {
                        if (cur.x <= 0.0001f) continue;   // hidden right now; revisit later
                        base3 = cur;
                        _scaleBaseline[id] = base3;
                    }
                    // Respect the game hiding it: leave zeroed canvases alone.
                    if (cur.x <= 0.0001f) continue;

                    var want = base3 * _worldScale.Value;
                    if (cs.transform.localScale != want)
                    {
                        cs.transform.localScale = want;
                        if (_verbose.Value)
                            Logger.LogInfo($"World '{cs.name}' localScale {base3} -> {want}");
                    }
                    continue;
                }

                if (cs.uiScaleMode != CanvasScaler.ScaleMode.ScaleWithScreenSize) continue;

                if (!_refBaseline.TryGetValue(id, out var baseRef))
                {
                    baseRef = cs.referenceResolution;
                    _refBaseline[id] = baseRef;
                }

                float targetH = _designHeight.Value / Mathf.Max(0.01f, _hudScale.Value);
                var targetRef = new Vector2(baseRef.x, targetH);

                if (!Mathf.Approximately(cs.matchWidthOrHeight, _match.Value))
                    cs.matchWidthOrHeight = _match.Value;
                if (cs.referenceResolution != targetRef)
                {
                    cs.referenceResolution = targetRef;
                    // expected scale now; Canvas.scaleFactor lags a frame
                    float exp = Mathf.Pow(Screen.width / targetRef.x, 1f - _match.Value) *
                                Mathf.Pow(Screen.height / targetRef.y, _match.Value);
                    Logger.LogInfo($"HUD '{cs.name}' ref {baseRef} -> {targetRef} " +
                                   $"match={_match.Value} => scale={exp:F3}");
                }
            }
        }
    }
}
