import std/[sequtils, strutils]
import token

type
  TypeKind* = enum
    tkUnknown
    tkVoid
    tkBool
    tkBool8
    tkBool16
    tkBool32
    tkChar8
    tkChar16
    tkChar32
    tkStr
    tkInt8
    tkInt16
    tkInt32
    tkInt64
    tkInt
    tkUInt8
    tkUInt16
    tkUInt32
    tkUInt64
    tkUInt
    tkFloat32
    tkFloat64
    tkPointer
    tkRef
    tkMutRef
    tkDynRef
    tkSlice
    tkRange
    tkTuple
    tkNamed
    tkTypeParam
    tkFunc

  Type* = ref object
    kind*: TypeKind
    name*: string
    inner*: seq[Type]       ## for Pointer(pointee), Slice(element), Tuple(elements), Func(params+ret)

# Factories
proc makeUnknown*(): Type = Type(kind: tkUnknown)
proc makeVoid*(): Type = Type(kind: tkVoid)
proc makeBool*(): Type = Type(kind: tkBool)
proc makeBool8*(): Type = Type(kind: tkBool8)
proc makeBool16*(): Type = Type(kind: tkBool16)
proc makeBool32*(): Type = Type(kind: tkBool32)
proc makeChar8*(): Type = Type(kind: tkChar8)
proc makeChar16*(): Type = Type(kind: tkChar16)
proc makeChar32*(): Type = Type(kind: tkChar32)
proc makeStr*(): Type = Type(kind: tkStr)
proc makeInt8*(): Type = Type(kind: tkInt8)
proc makeInt16*(): Type = Type(kind: tkInt16)
proc makeInt32*(): Type = Type(kind: tkInt32)
proc makeInt64*(): Type = Type(kind: tkInt64)
proc makeInt*(): Type = Type(kind: tkInt)
proc makeUInt8*(): Type = Type(kind: tkUInt8)
proc makeUInt16*(): Type = Type(kind: tkUInt16)
proc makeUInt32*(): Type = Type(kind: tkUInt32)
proc makeUInt64*(): Type = Type(kind: tkUInt64)
proc makeUInt*(): Type = Type(kind: tkUInt)
proc makeFloat32*(): Type = Type(kind: tkFloat32)
proc makeFloat64*(): Type = Type(kind: tkFloat64)

proc makePointer*(pointee: Type): Type =
  Type(kind: tkPointer, inner: @[pointee])
proc makeRef*(pointee: Type): Type =
  Type(kind: tkRef, inner: @[pointee])
proc makeMutRef*(pointee: Type): Type =
  Type(kind: tkMutRef, inner: @[pointee])
proc makeDynRef*(interfaceName: string): Type =
  Type(kind: tkDynRef, name: interfaceName)
proc makeSlice*(element: Type): Type =
  Type(kind: tkSlice, inner: @[element])
proc makeRange*(element: Type): Type =
  Type(kind: tkRange, inner: @[element])
proc makeTuple*(elems: seq[Type]): Type =
  Type(kind: tkTuple, inner: elems)
proc makeNamed*(name: string): Type =
  Type(kind: tkNamed, name: name)
proc makeTypeParam*(name: string): Type =
  Type(kind: tkTypeParam, name: name)
proc makeFunc*(params: seq[Type], ret: Type): Type =
  Type(kind: tkFunc, inner: params & @[ret])

# Predicates
proc isUnknown*(t: Type): bool = t.kind == tkUnknown
proc isVoid*(t: Type): bool = t.kind == tkVoid
proc isBool*(t: Type): bool = t.kind in {tkBool, tkBool8, tkBool16, tkBool32}
proc isNumeric*(t: Type): bool =
  if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
  t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
             tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt,
             tkFloat32, tkFloat64}
proc isInteger*(t: Type): bool =
  if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
  t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
             tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt}
proc isFloat*(t: Type): bool =
  if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
  t.kind in {tkFloat32, tkFloat64}
proc isSigned*(t: Type): bool =
  if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
  t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef, tkDynRef}
proc isRawPointer*(t: Type): bool = t.kind == tkPointer
proc isRef*(t: Type): bool = t.kind == tkRef
proc isMutRef*(t: Type): bool = t.kind == tkMutRef
proc isDynRef*(t: Type): bool = t.kind == tkDynRef
proc isSlice*(t: Type): bool = t.kind == tkSlice

# Comparison
proc `==`*(a, b: Type): bool =
  if a.isNil or b.isNil:
    return a.isNil and b.isNil
  if a.kind != b.kind: return false
  if a.kind in {tkNamed, tkTypeParam} and a.name != b.name: return false
  if a.inner.len != b.inner.len: return false
  for i in 0 ..< a.inner.len:
    if a.inner[i] != b.inner[i]: return false
  return true

proc `!=`*(a, b: Type): bool = not (a == b)

# Assignment compatibility
proc isAssignableTo*(a, b: Type): bool =
  if a.isUnknown or b.isUnknown: return true
  if b.kind == tkTypeParam: return true
  if a == b: return true
  # float32 -> float64
  if a.kind == tkFloat32 and b.kind == tkFloat64: return true
  # int64 <-> int (on x64)
  if a.kind == tkInt64 and b.kind == tkInt: return true
  if a.kind == tkInt and b.kind == tkInt64: return true
  if a.kind == tkUInt64 and b.kind == tkUInt: return true
  if a.kind == tkUInt and b.kind == tkUInt64: return true
  # int32 <-> int (for convenience in bootstrap)
  if a.kind == tkInt32 and b.kind == tkInt: return true
  if a.kind == tkInt and b.kind == tkInt32: return true
  if a.kind == tkUInt32 and b.kind == tkInt: return true
  if a.kind == tkInt and b.kind == tkUInt32: return true
  if a.kind == tkUInt32 and b.kind == tkUInt: return true
  if a.kind == tkUInt and b.kind == tkUInt32: return true
  if a.kind == tkInt32 and b.kind == tkUInt32: return true
  if a.kind == tkUInt32 and b.kind == tkInt32: return true
  # smaller int -> int/uint
  if b.kind == tkInt and a.kind in {tkInt8, tkInt16, tkInt32}: return true
  if b.kind == tkUInt and a.kind in {tkUInt8, tkUInt16, tkUInt32}: return true
  # int <-> uint (for convenience in bootstrap)
  if a.kind == tkInt and b.kind == tkUInt: return true
  if a.kind == tkUInt and b.kind == tkInt: return true
  # *char8 -> String (C string literal to Bux String)
  if a.kind == tkPointer and b.kind == tkStr:
    if a.inner.len > 0 and a.inner[0].kind == tkChar8:
      return true
  # numeric exact match required otherwise
  if a.isNumeric and b.isNumeric: return false
  # bool across widths
  if a.isBool and b.isBool: return true
  # pointer to opaque / null pointer
  if a.isPointer and b.isPointer:
    if a.inner.len > 0 and a.inner[0].isUnknown:
      return true
    if b.inner.len > 0 and b.inner[0].isUnknown:
      return true
    # &mut T -> &T (mutable ref can coerce to shared ref)
    if a.isMutRef and b.isRef:
      if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
        return true
    # &mut T -> *T (mutable ref can coerce to raw pointer)
    if a.isMutRef and b.isRawPointer:
      if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
        return true
    # &T -> *T (shared ref can coerce to raw pointer)
    if a.isRef and b.isRawPointer:
      if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
        return true
    # &Concrete -> &dyn Trait (trait object coercion)
    if b.isDynRef and a.isRef:
      return true
    if b.isDynRef and a.isMutRef:
      return true
  # &mut func(...) -> func(...) (function pointer decay)
  if a.isMutRef and b.kind == tkFunc:
    if a.inner.len > 0 and a.inner[0].kind == tkFunc:
      return true
  return false

# String representation
proc toString*(t: Type): string =
  case t.kind
  of tkUnknown: "?"
  of tkVoid: "void"
  of tkBool: "bool"
  of tkBool8: "bool8"
  of tkBool16: "bool16"
  of tkBool32: "bool32"
  of tkChar8: "char8"
  of tkChar16: "char16"
  of tkChar32: "char32"
  of tkStr: "String"
  of tkInt8: "int8"
  of tkInt16: "int16"
  of tkInt32: "int32"
  of tkInt64: "int64"
  of tkInt: "int"
  of tkUInt8: "uint8"
  of tkUInt16: "uint16"
  of tkUInt32: "uint32"
  of tkUInt64: "uint64"
  of tkUInt: "uint"
  of tkFloat32: "float32"
  of tkFloat64: "float64"
  of tkPointer: "*" & t.inner[0].toString
  of tkRef: "&" & t.inner[0].toString
  of tkMutRef: "&mut " & t.inner[0].toString
  of tkDynRef: "&dyn " & t.name
  of tkSlice:
    if t.inner.len > 0: t.inner[0].toString & "[]"
    else: "Slice<?>"
  of tkRange:
    if t.inner.len > 0: "Range<" & t.inner[0].toString & ">"
    else: "Range<?>"
  of tkTuple:
    "(" & t.inner.mapIt(it.toString).join(", ") & ")"
  of tkNamed: t.name
  of tkTypeParam: t.name
  of tkFunc:
    if t.inner.len == 0: "func()"
    else:
      let params = t.inner[0..^2].mapIt(it.toString).join(", ")
      let ret = t.inner[^1].toString
      "func(" & params & ") -> " & ret
