namespace NubLang.Diagnostics;

public readonly struct SourceSpan : IEquatable<SourceSpan>, IComparable<SourceSpan>
{
    public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
    {
        var spanArray = spans as SourceSpan[] ?? spans.ToArray();
        if (spanArray.Length == 0)
        {
            return new SourceSpan(string.Empty, new SourceLocation(0, 0), new SourceLocation(0, 0));
        }

        var minStart = spanArray.Min(s => s.Start);
        var maxEnd = spanArray.Max(s => s.End);

        return new SourceSpan(spanArray[0].FilePath, minStart, maxEnd);
    }

    public SourceSpan(string filePath, SourceLocation start, SourceLocation end)
    {
        if (start > end)
        {
            throw new ArgumentException("Start location cannot be after end location");
        }

        FilePath = filePath;
        Start = start;
        End = end;
    }

    public string FilePath { get; }
    public SourceLocation Start { get; }
    public SourceLocation End { get; }

    public override string ToString()
    {
        if (Start == End)
        {
            return $"{FilePath}:{Start}";
        }

        if (Start.Line == End.Line)
        {
            return Start.Column == End.Column ? $"{FilePath}:{Start}" : $"{FilePath}:{Start.Line}:{Start.Column}-{End.Column}";
        }

        return $"{FilePath}:{Start}-{End}";
    }

    public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;
    public override bool Equals(object? obj) => obj is SourceSpan other && Equals(other);
    public override int GetHashCode() => HashCode.Combine(typeof(SourceSpan), Start, End);

    public static bool operator ==(SourceSpan left, SourceSpan right) => Equals(left, right);
    public static bool operator !=(SourceSpan left, SourceSpan right) => !Equals(left, right);

    public static bool operator <(SourceSpan left, SourceSpan right) => left.CompareTo(right) < 0;
    public static bool operator <=(SourceSpan left, SourceSpan right) => left.CompareTo(right) <= 0;
    public static bool operator >(SourceSpan left, SourceSpan right) => left.CompareTo(right) > 0;
    public static bool operator >=(SourceSpan left, SourceSpan right) => left.CompareTo(right) >= 0;

    public int CompareTo(SourceSpan other)
    {
        var startComparison = Start.CompareTo(other.Start);
        return startComparison != 0 ? startComparison : End.CompareTo(other.End);
    }
}

public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
{
    public SourceLocation(int line, int column)
    {
        Line = line;
        Column = column;
    }

    public int Line { get; }
    public int Column { get; }

    public override string ToString()
    {
        return $"{Line}:{Column}";
    }

    public override bool Equals(object? obj)
    {
        return obj is SourceLocation other && Equals(other);
    }

    public bool Equals(SourceLocation other)
    {
        return Line == other.Line && Column == other.Column;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(typeof(SourceLocation), Line, Column);
    }

    public static bool operator ==(SourceLocation left, SourceLocation right) => Equals(left, right);
    public static bool operator !=(SourceLocation left, SourceLocation right) => !Equals(left, right);
    public static bool operator <(SourceLocation left, SourceLocation right) => left.Line < right.Line || (left.Line == right.Line && left.Column < right.Column);
    public static bool operator >(SourceLocation left, SourceLocation right) => left.Line > right.Line || (left.Line == right.Line && left.Column > right.Column);
    public static bool operator <=(SourceLocation left, SourceLocation right) => left.Line <= right.Line || (left.Line == right.Line && left.Column <= right.Column);
    public static bool operator >=(SourceLocation left, SourceLocation right) => left.Line >= right.Line || (left.Line == right.Line && left.Column >= right.Column);

    public int CompareTo(SourceLocation other)
    {
        var lineComparison = Line.CompareTo(other.Line);
        return lineComparison != 0 ? lineComparison : Column.CompareTo(other.Column);
    }
}