///|
/// MoonBit AST Definition — P0-005.
/// Pure MoonBit language structure types. No sqlc/IR/database semantics.
/// Used by code generators (P0-008, P0-009) to produce MoonBit source,
/// and by the emitter (P0-006) to render formatted output.
//
// Copyright 2026 Mairzzcllo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

///|
/// A type expression — used in struct fields, function signatures, etc.
pub enum TypeExpr {
  /// Simple named type: `Int`, `String`, `Bool`
  Named(String)
  /// Parameterized type: `Array[Int]`, `Map[String, Int]`
  Parameterized(String, Array[TypeExpr])
  /// Option type: `Option[Int]`
  Option(TypeExpr)
  /// Wildcard / inferred: `_`
  Wildcard
}

///|
impl Debug for TypeExpr with fn to_repr(self) {
  Repr::literal(self.to_string())
}

///|
impl Show for TypeExpr with fn to_string(self) -> String {
  match self {
    Named(s) => "Named(\"" + s + "\")"
    Parameterized(n, args) => {
      let mut s = "Parameterized(\"" + n + "\", ["
      let mut first = true
      for a in args {
        if !first {
          s = s + ", "
        }
        s = s + a.to_string()
        first = false
      }
      s + "])"
    }
    Option(inner) => "Option(" + inner.to_string() + ")"
    Wildcard => "Wildcard"
  }
}

///|
/// A struct field definition.
pub struct FieldDef {
  name : String // MoonBit field identifier (after rename/escape)
  col_name : String // Original database column name (for index_of lookup)
  ty : TypeExpr
  doc : String?
  json_tag : String?
  db_tag : String?
}

///|
/// Struct definition.
pub struct StructDef {
  name : String
  fields : Array[FieldDef]
  doc : String?
  is_pub : Bool
}

///|
/// Enum variant definition.
pub struct VariantDef {
  name : String
  doc : String?
}

///|
/// Enum definition.
pub struct EnumDef {
  name : String
  variants : Array[VariantDef]
  doc : String?
  is_pub : Bool
}

///|
/// Function parameter definition.
pub struct ParamDef {
  name : String
  ty : TypeExpr
}

///|
/// Expression in generated function bodies.
pub enum Expr {
  /// Sequential block: `stmt1; stmt2`
  Block(Array[Expr])
  /// Variable reference: `sql`
  Ident(String)
  /// Function call: `db.query_many(sql, lambda)`
  Call(String, Array[Expr])
  /// Let binding: `let sql = "SELECT ..."`
  Let(String, Expr)
  /// String literal: `"SELECT ..."`
  StrLit(String)
  /// Integer literal: `0`, `42`
  IntLit(Int)
  /// Array literal: `[1, 2, 3]`
  ArrLit(Array[Expr])
  /// Array/slice index: `arr[i]`
  Index(Expr, Expr)
  /// Lambda: `fn(row: Row) { Type::decode(row) }`
  Lambda(Array[ParamDef], Expr)
  /// Binary operation: `a > b`, `a != b`
  BinOp(String, Expr, Expr)
  /// If-else expression: `if cond { then } else { else }`
  If(Expr, Expr, Expr)
  /// Return expression: `return expr`
  Return(Expr)
  /// Pattern match: `match expr { pat1 => body1; pat2 => body2 }`
  /// Each arm: (pattern_string, body_expr)
  Match(Expr, Array[(String, Expr)])
  /// For loop: `for var in iterable { body_statements }`
  ForLoop(String, Expr, Array[Expr])
  /// Unit / noop
  Unit
}

///|
/// Function definition.
pub struct FnDef {
  name : String
  params : Array[ParamDef]
  return_ty : TypeExpr
  body : Array[Expr]
  doc : String?
  is_pub : Bool
}

///|
/// Type alias definition.
pub struct TypeAliasDef {
  name : String
  underlying : TypeExpr
  doc : String?
}

///|
/// Import statement.
pub struct ImportDef {
  path : String
  import_alias : String?
}

///|
/// Top-level item in a MoonBit source file.
pub enum TopLevel {
  Struct(StructDef)
  Enum(EnumDef)
  Fn(FnDef)
  TypeAlias(TypeAliasDef)
  Import(ImportDef)
}

///|
/// A MoonBit source file — ordered list of top-level items.
pub struct SourceFile {
  items : Array[TopLevel]
}

// === Tests ===

///|
test "TypeExpr::Named construction" {
  let t : TypeExpr = Named("Int")
  inspect(t, content="Named(\"Int\")")
}

///|
test "TypeExpr::Parameterized construction" {
  let t : TypeExpr = Parameterized("Array", [Named("Int")])
  inspect(t, content="Parameterized(\"Array\", [Named(\"Int\")])")
}

///|
test "TypeExpr::Option construction" {
  let t : TypeExpr = Option(Named("String"))
  inspect(t, content="Option(Named(\"String\"))")
}

///|
test "TypeExpr::Wildcard construction" {
  let t : TypeExpr = Wildcard
  inspect(t, content="Wildcard")
}

///|
test "StructDef with fields" {
  let s : StructDef = {
    name: "User",
    fields: [
      {
        name: "id",
        col_name: "id",
        ty: Named("Int"),
        doc: None,
        json_tag: None,
        db_tag: None,
      },
      {
        name: "email",
        col_name: "email",
        ty: Named("String"),
        doc: Some("user email"),
        json_tag: None,
        db_tag: None,
      },
    ],
    doc: Some("A user record"),
    is_pub: true,
  }
  inspect(s.name, content="User")
  inspect(s.fields.length(), content="2")
  inspect(s.fields[0].name, content="id")
  inspect(s.fields[1].name, content="email")
  debug_inspect(s.fields[1].doc, content="Some(\"user email\")")
  inspect(s.is_pub, content="true")
}

///|
test "EnumDef with variants" {
  let e : EnumDef = {
    name: "Color",
    variants: [
      { name: "Red", doc: None },
      { name: "Blue", doc: Some("the blue variant") },
    ],
    doc: None,
    is_pub: true,
  }
  inspect(e.name, content="Color")
  inspect(e.variants.length(), content="2")
  inspect(e.variants[0].name, content="Red")
  inspect(e.variants[1].name, content="Blue")
}

///|
test "FnDef with body" {
  let f : FnDef = {
    name: "greet",
    params: [{ name: "name", ty: Named("String") }],
    return_ty: Named("String"),
    body: [Ident("name")],
    doc: Some("Greet someone"),
    is_pub: true,
  }
  inspect(f.name, content="greet")
  inspect(f.params.length(), content="1")
  inspect(f.params[0].name, content="name")
  inspect(f.is_pub, content="true")
}

///|
test "ImportDef with alias" {
  let i : ImportDef = {
    path: "moonbitlang/core/builtin",
    import_alias: Some("builtin"),
  }
  inspect(i.path, content="moonbitlang/core/builtin")
  debug_inspect(i.import_alias, content="Some(\"builtin\")")
}

///|
test "ImportDef without alias" {
  let i : ImportDef = { path: "moonbitlang/core/array", import_alias: None }
  debug_inspect(i.import_alias, content="None")
}

///|
test "Construct Call expression" {
  let e : Expr = Call("query_many", [Ident("db"), Ident("sql")])
  let s = match e {
    Call(name, _) => name
    _ => ""
  }
  inspect(s, content="query_many")
}

///|
test "Construct Let expression" {
  let e : Expr = Let("sql", StrLit("SELECT * FROM users"))
  let s = match e {
    Let(name, _) => name
    _ => ""
  }
  inspect(s, content="sql")
}

///|
test "Lambda expression" {
  let call : Expr = Call("User::decode", [Ident("row")])
  let lambda : Expr = Lambda([{ name: "row", ty: Named("Row") }], call)
  let is_lambda = match lambda {
    Lambda(_, _) => true
    _ => false
  }
  inspect(is_lambda, content="true")
}

///|
test "Block expression" {
  let e : Expr = Block([
    Let("sql", StrLit("SELECT 1")),
    Call("db.query_many", [Ident("sql")]),
  ])
  let count = match e {
    Block(exprs) => exprs.length()
    _ => 0
  }
  inspect(count, content="2")
}

///|
test "TypeAliasDef with simple type" {
  let t : TypeAliasDef = {
    name: "Json",
    underlying: Named("String"),
    doc: Some("JSON encoded as string"),
  }
  inspect(t.name, content="Json")
  inspect(t.underlying, content="Named(\"String\")")
}

///|
test "TopLevel::Fn wrapper" {
  let f : FnDef = {
    name: "f",
    params: [],
    return_ty: Named("Unit"),
    body: [Unit],
    doc: None,
    is_pub: false,
  }
  let top : TopLevel = Fn(f)
  let name = match top {
    Fn(fn_def) => fn_def.name
    _ => ""
  }
  inspect(name, content="f")
}

///|
test "SourceFile with multiple items" {
  let s : StructDef = { name: "S", fields: [], doc: None, is_pub: true }
  let f : FnDef = {
    name: "f",
    params: [],
    return_ty: Named("Unit"),
    body: [Unit],
    doc: None,
    is_pub: false,
  }
  let src : SourceFile = { items: [Struct(s), Fn(f)] }
  inspect(src.items.length(), content="2")
  let first_is_struct = match src.items[0] {
    Struct(_) => true
    _ => false
  }
  inspect(first_is_struct, content="true")
}