using System.Security.Cryptography;
using System.Text;

namespace NubLang.Ast;

public abstract class NubType : IEquatable<NubType>
{
    public override bool Equals(object? obj) => obj is NubType other && Equals(other);
    public abstract bool Equals(NubType? other);

    public abstract override int GetHashCode();
    public abstract override string ToString();

    public static bool operator ==(NubType? left, NubType? right) => Equals(left, right);
    public static bool operator !=(NubType? left, NubType? right) => !Equals(left, right);
}

public class NubVoidType : NubType
{
    public override string ToString() => "void";
    public override bool Equals(NubType? other) => other is NubVoidType;
    public override int GetHashCode() => HashCode.Combine(typeof(NubVoidType));
}

public sealed class NubIntType(bool signed, int width) : NubType
{
    public bool Signed { get; } = signed;
    public int Width { get; } = width;

    public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
    public override bool Equals(NubType? other) => other is NubIntType @int && @int.Width == Width && @int.Signed == Signed;
    public override int GetHashCode() => HashCode.Combine(typeof(NubIntType), Signed, Width);
}

public sealed class NubFloatType(int width) : NubType
{
    public int Width { get; } = width;

    public override string ToString() => $"f{Width}";
    public override bool Equals(NubType? other) => other is NubFloatType @float && @float.Width == Width;
    public override int GetHashCode() => HashCode.Combine(typeof(NubFloatType), Width);
}

public class NubBoolType : NubType
{
    public override string ToString() => "bool";
    public override bool Equals(NubType? other) => other is NubBoolType;
    public override int GetHashCode() => HashCode.Combine(typeof(NubBoolType));
}

public sealed class NubPointerType(NubType baseType) : NubType
{
    public NubType BaseType { get; } = baseType;

    public override string ToString() => "^" + BaseType;
    public override bool Equals(NubType? other) => other is NubPointerType pointer && BaseType.Equals(pointer.BaseType);
    public override int GetHashCode() => HashCode.Combine(typeof(NubPointerType), BaseType);
}

public class NubFuncType(List<NubType> parameters, NubType returnType) : NubType
{
    public List<NubType> Parameters { get; } = parameters;
    public NubType ReturnType { get; } = returnType;

    public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
    public override bool Equals(NubType? other) => other is NubFuncType func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);

    public override int GetHashCode()
    {
        var hash = new HashCode();
        hash.Add(typeof(NubFuncType));
        hash.Add(ReturnType);
        foreach (var param in Parameters)
        {
            hash.Add(param);
        }

        return hash.ToHashCode();
    }
}

public class NubStructType(string module, string name, List<NubStructFieldType> fields) : NubType
{
    public string Module { get; } = module;
    public string Name { get; } = name;
    public List<NubStructFieldType> Fields { get; set; } = fields;

    public override string ToString() => $"{Module}::{Name}";
    public override bool Equals(NubType? other) => other is NubStructType structType && Name == structType.Name && Module == structType.Module;
    public override int GetHashCode() => HashCode.Combine(typeof(NubStructType), Module, Name);
}

public class NubStructFieldType(string name, NubType type, bool hasDefaultValue)
{
    public string Name { get; } = name;
    public NubType Type { get; } = type;
    public bool HasDefaultValue { get; } = hasDefaultValue;
}

public class NubSliceType(NubType elementType) : NubType
{
    public NubType ElementType { get; } = elementType;

    public override string ToString() => "[]" + ElementType;
    public override bool Equals(NubType? other) => other is NubSliceType slice && ElementType.Equals(slice.ElementType);
    public override int GetHashCode() => HashCode.Combine(typeof(NubSliceType), ElementType);
}

public class NubConstArrayType(NubType elementType, long size) : NubType
{
    public NubType ElementType { get; } = elementType;
    public long Size { get; } = size;

    public override string ToString() => $"[{Size}]{ElementType}";
    public override bool Equals(NubType? other) => other is NubConstArrayType array && ElementType.Equals(array.ElementType) && Size == array.Size;
    public override int GetHashCode() => HashCode.Combine(typeof(NubConstArrayType), ElementType, Size);
}

public class NubArrayType(NubType elementType) : NubType
{
    public NubType ElementType { get; } = elementType;

    public override string ToString() => $"[?]{ElementType}";
    public override bool Equals(NubType? other) => other is NubArrayType array && ElementType.Equals(array.ElementType);
    public override int GetHashCode() => HashCode.Combine(typeof(NubArrayType), ElementType);
}

public class NubStringType : NubType
{
    public override string ToString() => "string";
    public override bool Equals(NubType? other) => other is NubStringType;
    public override int GetHashCode() => HashCode.Combine(typeof(NubStringType));
}

public static class NameMangler
{
    public static string Mangle(params IEnumerable<NubType> types)
    {
        var readable = string.Join(":", types.Select(EncodeType));
        return ComputeShortHash(readable);
    }

    private static string EncodeType(NubType node) => node switch
    {
        NubVoidType => "V",
        NubBoolType => "B",
        NubIntType i => (i.Signed ? "I" : "U") + i.Width,
        NubFloatType f => "F" + f.Width,
        NubStringType => "S",
        NubArrayType a => $"A({EncodeType(a.ElementType)})",
        NubConstArrayType ca => $"CA({EncodeType(ca.ElementType)})",
        NubSliceType a => $"SL{EncodeType(a.ElementType)}()",
        NubPointerType p => $"P({EncodeType(p.BaseType)})",
        NubFuncType fn => $"FN({string.Join(":", fn.Parameters.Select(EncodeType))}:{EncodeType(fn.ReturnType)})",
        NubStructType st => $"ST({st.Module}:{st.Name})",
        _ => throw new NotSupportedException($"Cannot encode type: {node}")
    };

    private static string ComputeShortHash(string input)
    {
        var bytes = Encoding.UTF8.GetBytes(input);
        var hash = SHA256.HashData(bytes);
        return Convert.ToHexString(hash[..8]).ToLower();
    }
}