Code/Core/Wire.cs
namespace Nodebox;

public sealed class Wire : GraphElement, IMeta {
    private Node _source = null;
    [JsonIgnore]
    public Node Source {
        get => _source;
        set {
            if (_source == value) {
                return;
            }

            Disconnect();
            _source = value;
            Connect();
        }
    }

    private PinIndex _sourceIndex = PinIndex.Invalid;
    [JsonIgnore]
    public PinIndex SourceIndex {
        get => _sourceIndex;
        set {
            if (_sourceIndex == value) {
                return;
            }

            Disconnect();
            _sourceIndex = value;
            Connect();
        }
    }

    private Node _destination = null;
    [JsonIgnore]
    public Node Destination {
        get => _destination;
        set {
            if (_destination == value) {
                return;
            }

            Disconnect();
            _destination = value;
            Connect();
        }
    }

    private PinIndex _destinationIndex = PinIndex.Invalid;
    [JsonIgnore]
    public PinIndex DestinationIndex {
        get => _destinationIndex;
        set {
            if (_destinationIndex == value) {
                return;
            }

            Disconnect();
            _destinationIndex = value;
            Connect();
        }
    }

    [JsonIgnore]
    public Pin SourcePin => _source.OutputPins[_sourceIndex];
    [JsonIgnore]
    public Pin DestinationPin => _destination.InputPins[_destinationIndex];


    [JsonIgnore]
    public Type SourceType => SourcePin.Type;
    [JsonIgnore]
    public Type DestinationType => DestinationPin.Type;

    [JsonIgnore]
    public override Type DirectoryType => typeof(Wire);
    public Dictionary<string, object> Meta { get; private set; } = [];

    private bool _valid = false;
    [JsonIgnore]
    public override bool IsValid => _valid && base.IsValid && _source.IsValid && _destination.IsValid;

    public Wire() : base() { }

    public Wire(Node source, PinIndex sourceIndex, Node destination, PinIndex destinationIndex) : this() {
        Check(source, sourceIndex, destination, destinationIndex);

        _source = source;
        _sourceIndex = sourceIndex;
        _destination = destination;
        _destinationIndex = destinationIndex;

        Connect();
    }

    public static void Check(Node source, PinIndex sourceIndex, Node destination, PinIndex destinationIndex) {
        Assert.IsValid(source);
        Assert.IsValid(destination);

        ArgumentNullOrInvalidException.ThrowIfNullOrNotValid(source, nameof(source));
        ArgumentNullOrInvalidException.ThrowIfNullOrNotValid(destination, nameof(destination));

        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(sourceIndex, source.OutputPins.Count, nameof(sourceIndex));
        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(destinationIndex, destination.InputPins.Count, nameof(destinationIndex));
    }

    private void CheckSelf() => Check(_source, _sourceIndex, _destination, _destinationIndex);

    private void Connect() {
        CheckSelf();

        // var sourcePin = _source.outputPins[_sourceIndex];
        // var destinationPin = _destination.inputPins[_sourceIndex];
        _source.AddOutputWire(_sourceIndex, this);
        _destination.AddInputWire(_destinationIndex, this);

        _valid = true;
    }

    private void Disconnect() {
        _source?.RemoveOutputWire(_sourceIndex, this);
        _destination?.RemoveInputWire(_destinationIndex, this);
    }

    protected override void Term() {
        Disconnect();
        Meta.Clear();
        base.Term();
        _valid = false;
    }

    public override string ToString() {
        return $"{DisplayInfo.ForGenericType(Source.GetType()).Name}[{SourceIndex}] -> {DisplayInfo.ForGenericType(Destination.GetType()).Name}[{DestinationIndex}]";
    }

    ~Wire() {
        Destroy();
    }
}