#nullable enable

using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;

namespace Barotrauma
{
    internal partial class CircuitBoxWire : CircuitBoxSelectable, ICircuitBoxIdentifiable
    {
        public CircuitBoxConnection From, To;
        public readonly Option<Item> BackingWire;

        public readonly Color Color;
        public readonly ItemPrefab UsedItemPrefab;

        public ushort ID { get; }

        public CircuitBoxWire(CircuitBox circuitBox, ushort Id, Option<Item> backingItem, CircuitBoxConnection from, CircuitBoxConnection to, ItemPrefab prefab)
        {
            ID = Id;
            From = from;
            To = to;
            BackingWire = backingItem;
            Color = prefab.SpriteColor;
            UsedItemPrefab = prefab;
#if CLIENT
            Renderer = new CircuitBoxWireRenderer(Option.Some(this), to.AnchorPoint, from.AnchorPoint, Color, circuitBox.WireSprite);
#endif
            EnsureWireConnected();
        }

        public XElement Save()
        {
            XElement element = new XElement("Wire",
                new XAttribute("id", ID),
                new XAttribute("backingitemid", BackingWire.TryUnwrap(out var item) ? ItemSlotIndexPair.Serialize(item) : string.Empty),
                new XAttribute("prefab", UsedItemPrefab.Identifier));

            XElement fromElement = CircuitBoxConnectorIdentifier.FromConnection(From).Save("From"),
                     toElement = CircuitBoxConnectorIdentifier.FromConnection(To).Save("To");

            element.Add(fromElement);
            element.Add(toElement);

            return element;
        }

        public static Option<CircuitBoxWire> TryLoadFromXML(ContentXElement element, CircuitBox circuitBox)
        {
            ushort id = element.GetAttributeUInt16("id", ICircuitBoxIdentifiable.NullComponentID);
            var backingItemIdOption = ItemSlotIndexPair.TryDeserializeFromXML(element, "backingitemid");
            Identifier usedPrefabIdentifier = element.GetAttributeIdentifier("prefab", Identifier.Empty);

            if (!ItemPrefab.Prefabs.TryGet(usedPrefabIdentifier, out var itemPrefab))
            {
                DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:PrefabNotFound",
                    $"Failed to find prefab used to create wire with identifier {usedPrefabIdentifier} for CircuitBoxWire with ID {id}");
                return Option.None;
            }

            Option<Item> backingItem = Option.None;
            if (backingItemIdOption.TryUnwrap(out var backingItemIdPair))
            {
                if (backingItemIdPair.FindItemInContainer(circuitBox.WireContainer) is { } item)
                {
                    backingItem = Option.Some(item);
                }
                else
                {
                    DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:IdNotFound",
                        $"Failed to find item with ID {backingItemIdPair} for CircuitBoxWire with ID {id}");
                    return Option.None;
                }
            }

            Option<CircuitBoxConnection> From = Option.None,
                                         To = Option.None;

            foreach (ContentXElement subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "from":
                        var fromIdentifier = CircuitBoxConnectorIdentifier.Load(subElement);
                        if (fromIdentifier.FindConnection(circuitBox).TryUnwrap(out var fromConnection))
                        {
                            From = Option.Some(fromConnection);
                        }
                        break;
                    case "to":
                        var toIdentifier = CircuitBoxConnectorIdentifier.Load(subElement);
                        if (toIdentifier.FindConnection(circuitBox).TryUnwrap(out var toConnection))
                        {
                            To = Option.Some(toConnection);
                        }
                        break;
                }
            }

            if (From.TryUnwrap(out var from) && To.TryUnwrap(out var to))
            {
                return Option.Some(new CircuitBoxWire(circuitBox, id, backingItem, from, to, itemPrefab));
            }

            DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:MissingFromOrTo",
                $"Failed to load CircuitBoxWire with ID {id}, missing \"From\" or \"To\" connection.");

            return Option.None;
        }

        public void EnsureWireConnected()
        {
            EnsureExternalConnection(From, To);
            EnsureExternalConnection(To, From);

            if (!BackingWire.TryUnwrap(out var item) || item.GetComponent<Wire>() is not { } wire) { return; }

            wire.DropOnConnect = false;

            From.Connection.ConnectWire(wire);
            To.Connection.ConnectWire(wire);

            wire.Connect(From.Connection, 0, addNode: false, sendNetworkEvent: false);
            wire.Connect(To.Connection, 1, addNode: false, sendNetworkEvent: false);

            static void EnsureExternalConnection(CircuitBoxConnection one, CircuitBoxConnection two)
            {
                switch (one)
                {
                    case CircuitBoxInputConnection input:
                    {
                        if (input.ExternallyConnectedTo.Contains(two)) { break; }
                        input.ExternallyConnectedTo.Add(two);
                        break;
                    }
                    case CircuitBoxOutputConnection output:
                    {
                        if (output.ExternallyConnectedFrom.Contains(two)) { break; }
                        output.ExternallyConnectedFrom.Add(two);
                        break;
                    }
                    case CircuitBoxNodeConnection node when two is CircuitBoxOutputConnection output:
                    {
                        if (node.Connection.CircuitBoxConnections.Contains(output)) { break; }
                        node.Connection.CircuitBoxConnections.Add(output);
                        break;
                    }
                    case CircuitBoxNodeConnection node when two is CircuitBoxInputConnection input:
                    {
                        if (!node.Connection.CircuitBoxConnections.Contains(input))
                        {
                            node.Connection.CircuitBoxConnections.Add(input);
                        }
                        if (!node.ExternallyConnectedFrom.Contains(input))
                        {
                            node.ExternallyConnectedFrom.Add(input);
                        }
                        break;
                    }
                }
            }
        }

        public void Remove()
        {
            // client should not remove wires
            if (GameMain.NetworkMember is { IsClient: true }) { return; }

            if (!BackingWire.TryUnwrap(out var wireItem)) { return; }

            if (Entity.Spawner is { } spawner && Screen.Selected is not { IsEditor: true })
            {
                spawner.AddEntityToRemoveQueue(wireItem);
                return;
            }

            Wire? wire = wireItem.GetComponent<Wire>();
            if (wire is not null)
            {
                From.Connection.DisconnectWire(wire);
                To.Connection.DisconnectWire(wire);
            }
            // if EntitySpawner is not available
            if (!wireItem.Removed)
            {
                wireItem.Remove();
            }
        }

        public static ItemPrefab DefaultWirePrefab => ItemPrefab.Prefabs[Tags.RedWire];
        public static ItemPrefab SelectedWirePrefab = DefaultWirePrefab;
        public static readonly Color DefaultWireColor = DefaultWirePrefab.SpriteColor;
    }
}