using System;
using System.IO;
using System.Collections.Generic;
using Server.Collections;
using Server.Items;
using Server.Gumps;
using Server.Text;

namespace Server.Commands;

public class AddonGenerator
{
    private static readonly string _outputDirectory = Path.Combine(Core.BaseDirectory, "Data", "Generated Addons");

    public delegate void PickerCallbackDelegate(Mobile from, Map map, Point3D start, Point3D end, object state);

    public static void PickerCallbackWrapper(Mobile from, Map map, Point3D start, Point3D end, PickerCallbackDelegate callback, object state)
    {
        callback(from, map, start, end, state);
    }

    private const string _template =
        """
        /////////////////////////////////////////////////
        //
        // Automatically generated by the
        // AddonGenerator script for ModernUO
        //
        /////////////////////////////////////////////////
        using System;
        using Server;{serverItemsNamespace}

        namespace {namespace};

        [SerializationGenerator(0)]
        public class {name}Addon : BaseAddon
        {
            public override BaseAddonDeed Deed => {name}AddonDeed();

            [Constructible]
            public {name}Addon()
            {
        {components}
            }
        }

        [SerializationGenerator(0)]
        public class {name}AddonDeed : BaseAddonDeed
        {
            public override BaseAddon Addon => new {name}Addon();
            public override string DefaultName => "{name}";

            [Constructible]
            public {name}AddonDeed()
            {
            }
        }
        """;

    // TODO: .NET 9 - Use search values
    // private static SearchValues<string> _templatePlaceholders = SearchValues.Create("{serverItemsNamespace}", "{namespace}", "{name}", "{components}");

    public static void Configure()
    {
        CommandSystem.Register("AddonGen", AccessLevel.Administrator, OnAddonGen);
    }

    [Usage("AddonGen [<name> [namespace]]")]
    [Description("Brings up the addon script generator gump. Specify a name to skip the gump and go directly to picking an area.")]
    private static void OnAddonGen(CommandEventArgs e)
    {
        var pickerState = new PickerState
        {
            Name = "",
            Namespace = "Server.Items",
            Items = true,
            Statics = true,
            Range = false,
            Min = sbyte.MinValue,
            Max = sbyte.MaxValue
        };

        if (e.Arguments.Length <= 0)
        {
            // Send gump
            e.Mobile.SendGump(new InternalGump(e.Mobile, pickerState));
            return;
        }

        pickerState.Name = e.Arguments[0];
        pickerState.Namespace = e.Arguments.Length > 1 ? e.Arguments[1] : "Server.Items";

        BoundingBoxPicker.Begin(
            e.Mobile,
            (map, start, end) => PickerCallbackWrapper(e.Mobile, map, start, end, PickerCallback, pickerState)
        );
    }

    private static void PickerCallback(Mobile from, Map map, Point3D start, Point3D end, object state)
    {
        if (state is not PickerState pickerState)
        {
            return;
        }

        if (start.X > end.X)
        {
            (start.X, end.X) = (end.X, start.X);
        }

        if (start.Y > end.Y)
        {
            (start.Y, end.Y) = (end.Y, start.Y);
        }

        var startPoint2D = new Point2D(start.X, start.Y);
        var endPoint2D = new Point2D(end.X, end.Y);
        var bounds = new Rectangle2D(startPoint2D, endPoint2D);

        var name = pickerState.Name;
        var ns = pickerState.Namespace;
        var items = pickerState.Items;
        var statics = pickerState.Statics;
        var range = pickerState.Range;
        var min = pickerState.Min;
        var max = pickerState.Max;

        // Get center
        var center = new Point3D();
        center.Z = 127;

        var x1 = bounds.End.X;
        var y1 = bounds.End.Y;
        var x2 = bounds.Start.X;
        var y2 = bounds.Start.Y;

        var tiles = new Dictionary<Point2D, List<StaticTile>>();

        if (statics)
        {
            for (var x = start.X; x <= end.X; x++)
            {
                for (var y = start.Y; y <= end.Y; y++)
                {
                    var tileMatrix = map.Tiles;
                    List<StaticTile> list = [];

                    if (items)
                    {
                        foreach (var item in map.GetItemsAt(x, y))
                        {
                            if (range && item.Z >= min && item.Z <= max)
                            {
                                list.Add(new StaticTile((ushort)item.ItemID, (sbyte)item.Z));
                            }
                        }
                    }

                    foreach (var staticTile in tileMatrix.GetStaticTiles(x, y))
                    {
                        if (range && staticTile.Z >= min && staticTile.Z <= max)
                        {
                            list.Add(staticTile);
                        }
                    }

                    if (list is { Count: > 0 })
                    {
                        tiles[new Point2D(x, y)] = list;
                    }
                }
            }
        }

        using var target = PooledRefList<Item>.Create();

        foreach (var s in map.GetItemsInBounds<Static>(bounds))
        {
            if (!range || s.Z >= min && s.Z <= max)
            {
                target.Add(s);
            }
        }

        if (target.Count == 0 && tiles.Keys.Count == 0)
        {
            from.SendMessage(0x40, "No items have been selected");
            return;
        }

        // Get correct bounds
        foreach (var item in target)
        {
            if (item.Z < center.Z)
            {
                center.Z = item.Z;
            }

            x1 = Math.Min(x1, item.X);
            y1 = Math.Min(y1, item.Y);
            x2 = Math.Max(x2, item.X);
            y2 = Math.Max(y2, item.Y);
        }

        foreach (var p in tiles.Keys)
        {
            var list = tiles[p];

            foreach (var t in list)
            {
                if (t.Z < center.Z)
                {
                    center.Z = t.Z;
                }
            }

            x1 = Math.Min(x1, p.X);
            y1 = Math.Min(y1, p.Y);
            x2 = Math.Max(x2, p.X);
            y2 = Math.Max(y2, p.Y);
        }

        center.X = x1 + (x2 - x1) / 2;
        center.Y = y1 + (y2 - y1) / 2;

        using var sb = ValueStringBuilder.Create();

        foreach (var p in tiles.Keys)
        {
            var list = tiles[p];

            var xOffset = p.X - center.X;
            var yOffset = p.Y - center.Y;

            foreach (var t in list)
            {
                var zOffset = t.Z - center.Z;
                var id = t.ID - 16384;

                    sb.Append($"            AddComponent(new AddonComponent({id}), {xOffset}, {yOffset}, {zOffset});\n");
            }
        }

        foreach (var item in target)
        {
            var xOffset = item.X - center.X;
            var yOffset = item.Y - center.Y;
            var zOffset = item.Z - center.Z;

            var light = (item.ItemData.Flags & TileFlag.LightSource) == TileFlag.LightSource ? item.Light : LightType.Empty;
            var hue = item.Hue;

            sb.Append("            AddComponent(\n");
            if (hue != 0 || light != LightType.Empty)
            {
                sb.Append($"                new AddonComponent({item.ItemID})\n");
                sb.Append("                    {\n");
                if (light != LightType.Empty)
                {
                    sb.Append($"                        Light = LightType.{light},\n");
                }
                if (hue != 0)
                {
                    sb.Append($"                        Hue = {hue},\n");
                }
                sb.Append("                    },\n");
            }
            else
            {
                sb.Append($"                new AddonComponent({item.ItemID}),\n");
            }
            sb.Append($"                {xOffset},\n");
            sb.Append($"                {yOffset},\n");
            sb.Append($"                {zOffset},\n");
            sb.Append("            )\n");
        }

        var output = _template.Replace("{name}", name);
        output = output.Replace("{namespace}", ns);
        output = output.Replace("{components}", sb.ToString());

        var path = Path.Combine(_outputDirectory, $"{name}Addon.cs");

        var folder = Path.GetDirectoryName(path);
        PathUtility.EnsureDirectory(folder);

        using var writer = new StreamWriter(path, false);
        writer.Write(output);

        from.SendMessage(0x40, $"Script saved to {path}");
    }

    private class PickerState
    {
        public string Name { get; set; }
        public string Namespace { get; set; }
        public bool Items { get; set; }
        public bool Statics { get; set; }
        public bool Range { get; set; }
        public sbyte Min { get; set; }
        public sbyte Max { get; set; }
    }

    private class InternalGump : Gump
    {
        private const int LabelHue = 0x480;
        private const int GreenHue = 0x40;
        private readonly PickerState _state;

        public override bool Singleton => true;

        public InternalGump(Mobile m, PickerState state) : base(100, 50)
        {
            _state = state;
            MakeGump();
        }

        private void MakeGump()
        {
            Closable = true;
            Disposable = true;
            Draggable = true;
            Resizable = false;
            AddPage(0);
            AddBackground(0, 0, 280, 225, 9270);
            AddAlphaRegion(10, 10, 260, 205);
            AddLabel(64, 15, GreenHue, "Addon Script Generator");

            AddLabel(20, 40, LabelHue, "Name");
            AddImageTiled(95, 55, 165, 1, 9304);
            AddTextEntry(95, 35, 165, 20, LabelHue, 0, _state.Name);

            AddLabel(20, 60, LabelHue, "Namespace");
            AddImageTiled(95, 75, 165, 1, 9304);
            AddTextEntry(95, 55, 165, 20, LabelHue, 1, _state.Namespace);

            AddCheck(20, 85, 2510, 2511, _state.Items, 0);
            AddLabel(40, 85, LabelHue, "Export Items");

            AddCheck(20, 110, 2510, 2511, _state.Statics, 1);
            AddLabel(40, 110, LabelHue, "Export Statics");

            AddCheck(20, 135, 2510, 2511, _state.Range, 2);
            AddLabel(40, 135, LabelHue, "Specify Z Range");

            AddLabel(50, 160, LabelHue, "Max");
            AddImageTiled(85, 175, 60, 1, 9304);
            AddTextEntry(85, 155, 60, 20, LabelHue, 2, _state.Min.ToString());

            AddLabel(160, 160, LabelHue, "Min");
            AddImageTiled(200, 175, 60, 1, 9304);
            AddTextEntry(200, 155, 60, 20, LabelHue, 3, _state.Max.ToString());

            AddButton(20, 185, 4020, 4021, 0);
            AddLabel(55, 185, LabelHue, "Cancel");

            AddButton(155, 185, 4005, 4006, 1);
            AddLabel(195, 185, LabelHue, "Generate");
        }

        public override void OnResponse(Network.NetState sender, in RelayInfo info)
        {
            if (info.ButtonID == 0)
            {
                return;
            }

            _state.Name = info.GetTextEntry(0);
            _state.Namespace = info.GetTextEntry(1);

            _state.Items = info.IsSwitched(0);
            _state.Statics = info.IsSwitched(1);
            _state.Range = info.IsSwitched(2);

            var fail = !_state.Items && !_state.Statics;

            if (!sbyte.TryParse(info.GetTextEntry(2), out var min))
            {
                min = sbyte.MinValue;
                fail = true;
            }

            if (!sbyte.TryParse(info.GetTextEntry(3), out var max))
            {
                max = sbyte.MaxValue;
                fail = true;
            }

            if (max < min)
            {
                (max, min) = (min, max);
            }

            _state.Min = min;
            _state.Max = max;

            if (fail)
            {
                sender.Mobile.SendMessage(0x40, "Please review the generation parameters, some are invalid.");
                sender.Mobile.SendGump(new InternalGump(sender.Mobile, _state));
                return;
            }

            BoundingBoxPicker.Begin(
                sender.Mobile,
                (map, start, end) => PickerCallbackWrapper(sender.Mobile, map, start, end, PickerCallback, _state)
            );
        }
    }
}
