///|
/// MoonBit parsing utilities - translated from OCaml parsing_util.ml
// Translated from src/ast/parsing_util.ml

// Global counter for generating unique variable names
let counter : Ref[Int] = Ref(0)

///|
/// Create constant expression with location
pub fn make_constant_expr(loc~ : Location, c : Constant) -> Expr {
  Constant(c~, loc~)
}

///|
/// Create identifier expression
pub fn make_ident_expr(loc~ : Location, id : Var) -> Expr {
  Ident(id~, loc~)
}

///|
/// Create tuple expression
pub fn make_tuple_expr(loc~ : Location, exprs : @list.List[Expr]) -> Expr {
  Tuple(exprs~, loc~)
}

///|
/// Create array expression with possible spread elements
pub fn make_array_expr(
  loc~ : Location,
  is_iter~ : Bool,
  elems : @list.List[SpreadableElem],
) -> Expr {
  // Try to convert to regular array if no spread elements
  let mut acc : @list.List[Expr] = @list.empty()
  for elem in elems {
    match elem {
      Regular(e) => acc = acc.add(e)
      Spread(_) => return ArraySpread(elems~, is_iter~, loc~)
    }
  } nobreak {
    return Array(
      // If we have only regular elements, create a simple array
      exprs=acc.rev(),
      is_iter~,
      loc~,
    )
  }
}

///|
/// Create interpolation elements from literal components
pub fn make_interps(c : @tokens.InterpLiteral) -> @list.List[InterpElem] {
  let result = c.map(fn(lit) {
    match lit {
      InterpLit(repr~, loc~) => InterpElem::Literal(repr~, loc~)
      InterpSource(s) => Source(s)
    }
  })
  List(result)
}

///|
/// Create interpolation expression
pub fn make_interp_expr(loc~ : Location, c : @tokens.InterpLiteral) -> Expr {
  let elems = make_interps(c)
  Interp(elems~, loc~)
}

///|
pub fn make_bytes_interp_expr(
  loc~ : Location,
  c : @tokens.InterpLiteral,
) -> Expr {
  let elems = make_interps(c)
  BytesInterp(elems~, loc~)
}

///|
/// Create record expression
pub fn make_record_expr(
  loc~ : Location,
  trailing~ : TrailingMark,
  type_name : TypeName?,
  fields : @list.List[FieldDef],
) -> Expr {
  Record(type_name~, fields~, trailing~, loc~)
}

///|
/// Create alias pattern
pub fn make_alias_pattern(
  loc~ : Location,
  pat : Pattern,
  alias_ : Binder,
) -> Pattern {
  Alias(pat~, alias_~, loc~)
}

///|
/// Create constant pattern
pub fn make_constant_pattern(loc~ : Location, c : Constant) -> Pattern {
  Constant(c~, loc~)
}

///|
/// Create tuple pattern
pub fn make_tuple_pattern(
  loc~ : Location,
  pats : @list.List[Pattern],
) -> Pattern {
  Tuple(pats~, loc~)
}

///|
/// Create constructor pattern
pub fn make_constr_pattern(
  loc~ : Location,
  constr : Constructor,
  args : @list.List[ConstrPatArg]?,
  is_open : Bool,
) -> Pattern {
  Constr(constr~, args~, is_open~, loc~)
}

///|
/// Create tuple type
pub fn make_tuple_type(loc~ : Location, tys : @list.List[Type]) -> Type {
  Tuple(tys~, loc~)
}

///|
/// Create option type
pub fn make_option_type(
  loc~ : Location,
  constr_loc~ : Location,
  ty : Type,
) -> Type {
  Option(ty~, loc~, question_loc=constr_loc)
}

///|
/// Create field pattern
pub fn make_field_pat(
  loc~ : Location,
  label : Label,
  pattern : Pattern,
  is_pun : Bool,
) -> FieldPat {
  { label, pattern, is_pun, loc }
}

///|
/// Create unary plus expression
pub fn make_uplus(loc~ : Location, name : String, arg : Expr) -> Expr {
  match (name, arg) {
    ("+", Constant(c=Int(_), loc=_)) => arg
    ("+", Constant(c=Double(_), loc=_)) => arg
    _ => {
      let op = Var::{ name: Ident(name="~+"), loc }
      Apply(
        func=make_ident_expr(loc~, op),
        args=@list.singleton({ value: arg, kind: Positional }),
        loc~,
      )
    }
  }
}

///|
/// Create unary minus expression
pub fn make_uminus(loc~ : Location, name : String, arg : Expr) -> Expr {
  match (name, arg) {
    ("-", Constant(c=Int(x), loc=_)) => {
      let new_val = match x {
        ['-', .. num] => num.to_owned()
        _ => "-\{x}"
      }
      make_constant_expr(loc~, Int(new_val))
    }
    ("-", Constant(c=Int64(x), loc=_)) => {
      let new_val = match x {
        ['-', .. num] => num.to_owned()
        _ => "-\{x}"
      }
      make_constant_expr(loc~, Int64(new_val))
    }
    ("-", Constant(c=Double(x), loc=_)) => {
      let new_val = match x {
        ['-', .. num] => num.to_owned()
        _ => "-\{x}"
      }
      make_constant_expr(loc~, Double(new_val))
    }
    _ => {
      let op = Var::{ name: Ident(name~), loc }
      Unary(op~, expr=arg, loc~)
    }
  }
}

///|
/// Create logical not expression
pub fn make_unot(loc~ : Location, arg : Expr) -> Expr {
  let op = Var::{ name: Ident(name="!"), loc }
  Unary(op~, expr=arg, loc~)
}

///|
pub fn bracket_loc(index : Expr) -> Location {
  let loc = loc_of_expression(index)
  let start = shift_col(loc.start, -1)
  let end = shift_col(loc.end, 1)
  { start, end }
}

///|
/// Desugar array get operation to method call
pub fn desugar_array_get(loc~ : Location, obj : Expr, index : Expr) -> Expr {
  DotApply(
    self=obj,
    method_name={
      name: operators_table.get("_[_]").unwrap(),
      loc: bracket_loc(index),
    },
    args=List([{ value: index, kind: Positional }]),
    return_self=false,
    loc~,
  )
}

///|
/// Desugar array set operation to method call
pub fn desugar_array_set(
  loc~ : Location,
  obj : Expr,
  index : Expr,
  value : Expr,
) -> Expr {
  DotApply(
    self=obj,
    method_name={
      name: operators_table.get("_[_]=_").unwrap(),
      loc: bracket_loc(index),
    },
    args=List([{ value: index, kind: Positional }, { value, kind: Positional }]),
    return_self=false,
    loc~,
  )
}

///|
/// Desugar augmented array assignment
pub fn desugar_array_augmented_set(
  loc : Location,
  op : Var,
  array : Expr,
  index : Expr,
  value : Expr,
) -> Expr {

  // Generate unique variable names for array and index
  // Use a simple counter instead of random for reproducibility
  let arr_name = "*array_\{counter.val}"
  counter.val = counter.val + 1
  let arr_loc = loc_of_expression(array)
  let arr_var = Var::{ name: Ident(name=arr_name), loc: arr_loc }
  let arr_ident = make_ident_expr(loc=arr_loc, arr_var)
  let idx_name = "*index_\{counter.val}"
  counter.val = counter.val + 1
  let idx_loc = loc_of_expression(index)
  let idx_var = Var::{ name: Ident(name=idx_name), loc: idx_loc }
  let idx_ident = make_ident_expr(loc=idx_loc, idx_var)
  Let(
    pattern=Var({ name: arr_name, loc: no_location }),
    expr=array,
    body=Let(
      pattern=Var({ name: idx_name, loc: no_location }),
      expr=index,
      body=desugar_array_set(
        loc~,
        arr_ident,
        idx_ident,
        Infix(
          op~,
          lhs=desugar_array_get(loc~, arr_ident, idx_ident),
          rhs=value,
          loc~,
        ),
      ),
      loc~,
    ),
    loc~,
  )
}

///|
/// Try to create assignment expression
pub fn make_assign_opt(loc~ : Location, lhs : Expr, rhs : Expr) -> Expr? {
  match lhs {
    Ident(id~, loc=_) =>
      Some(Assign(var_=id, expr=rhs, augmented_by=None, loc~))
    Field(record~, accessor~, loc=_) =>
      Some(Mutate(record~, accessor~, field=rhs, augmented_by=None, loc~))
    ArrayGet(array~, index~, loc=_) =>
      Some(ArraySet(array~, index~, value=rhs, loc~))
    _ => None
  }
}

///|
/// Try to create augmented assignment expression
pub fn make_augmented_assign_opt(
  loc~ : Location,
  op : Var,
  lhs : Expr,
  rhs : Expr,
) -> Expr? {
  match lhs {
    Ident(id~, loc=_) =>
      Some(Assign(var_=id, expr=rhs, augmented_by=Some(op), loc~))
    Field(record~, accessor~, loc=_) =>
      Some(Mutate(record~, accessor~, field=rhs, augmented_by=Some(op), loc~))
    ArrayGet(array~, index~, loc=_) =>
      Some(ArrayAugmentedSet(op~, array~, index~, value=rhs, loc~))
    _ => None
  }
}

///|
/// Create field definition
pub fn make_field_def(
  loc~ : Location,
  label : Label,
  expr : Expr,
  is_pun : Bool,
) -> FieldDef {
  { label, expr, is_pun, loc }
}

///|
/// Convert label to expression
pub fn label_to_expr(loc~ : Location, label : Label) -> Expr {
  make_ident_expr(loc~, { name: Ident(name=label.name), loc })
}

///|
/// Convert label to pattern
pub fn label_to_pat(loc~ : Location, label : Label) -> Pattern {
  Var({ name: label.name, loc })
}

///|
/// Parse integer literal with suffix
pub fn make_int(i : String) -> Constant {
  if i.has_suffix("L") {
    let base = i.sub(end=i.length() - 1)
    if base.has_suffix("U") {
      let u64 = base.sub(end=base.length() - 1)
      UInt64(u64.to_owned())
    } else {
      Int64(base.to_owned())
    }
  } else if i.has_suffix("U") {
    let u32 = i.sub(end=i.length() - 1)
    UInt(u32.to_owned())
  } else if i.has_suffix("N") {
    let bigint = i.sub(end=i.length() - 1)
    BigInt(bigint.to_owned())
  } else {
    Int(i)
  }
}

///|
/// Create double constant
pub fn make_double(f : String) -> Constant {
  Double(f)
}

///|
/// Create float constant
pub fn make_float(f : String) -> Constant {
  if f.has_suffix("F") {
    let base = f.sub(end=f.length() - 1)
    Float(base.to_owned())
  } else {
    abort("make_float: string must end with F")
  }
}

///|
/// Arrow function parameter type
pub(all) enum ArrowFnParam {
  Named(Binder)
  Unnamed(Location)
} derive(Debug)

///|
/// Convert binder to expression
pub fn binder_to_expr(p : Binder) -> Expr {
  make_ident_expr(loc=p.loc, { name: Ident(name=p.name), loc: p.loc })
}

///|
/// Convert arrow function parameter to expression
pub fn arrow_fn_param_to_expr(p : ArrowFnParam) -> Expr {
  match p {
    Named(b) => binder_to_expr(b)
    Unnamed(loc) => Hole(loc~, kind=Incomplete)
  }
}

///|
/// Create arrow function
pub fn make_arrow_fn(
  params : @list.List[(ArrowFnParam, Type?)],
  body : Expr,
  params_loc~ : Location,
  loc~ : Location,
) -> Func {
  let parameters = params.map(fn(p) {
    let (param, ty) = p
    match param {
      Named(binder) => Parameter::Positional(binder~, ty~)
      Unnamed(loc) => DiscardPositional(ty~, loc~)
    }
  })
  {
    parameters,
    params_loc,
    body,
    return_type: None,
    error_type: NoErrorType,
    kind: Arrow,
    is_async: None,
    loc,
  }
}

///|
pub fn Expr::loc(self : Self) -> Location {
  self |> loc_of_expression
}

///|
pub fn Type::loc(self : Type) -> Location {
  match self {
    Option(loc~, ..)
    | Name(loc~, ..)
    | Tuple(loc~, ..)
    | Arrow(loc~, ..)
    | Any(loc~)
    | Object({ loc, .. }) => loc
  }
}

// Helper functions that need to be defined elsewhere

///|
pub fn loc_of_expression(expr : Expr) -> Location {
  match expr {
    Apply(func=_, args=_, loc~) => loc
    Infix(op=_, lhs=_, rhs=_, loc~) => loc
    Unary(op=_, expr=_, loc~) => loc
    Array(exprs=_, is_iter=_, loc~) => loc
    ArraySpread(elems=_, is_iter=_, loc~) => loc
    ListComprehension(kind=_, guard_=_, body=_, is_iter=_, loc~) => loc
    ArrayGet(array=_, index=_, loc~) => loc
    ArrayGetSlice(array=_, start_index=_, end_index=_, index_loc=_, loc~) => loc
    ArraySet(array=_, index=_, value=_, loc~) => loc
    ArrayAugmentedSet(op=_, array=_, index=_, value=_, loc~) => loc
    Constant(c=_, loc~) => loc
    MultilineString(elems=_, loc~) => loc
    Interp(elems=_, loc~) => loc
    BytesInterp(elems=_, loc~) => loc
    Constraint(expr=_, ty=_, loc~) => loc
    Constr(constr=_, loc~) => loc
    LabelledBlock(label=_, body=_, loc~) => loc
    While(loop_cond=_, loop_body=_, while_else=_, label=_, loc~) => loc
    Function(func=_, loc~) => loc
    Ident(id=_, loc~) => loc
    If(cond=_, ifso=_, ifnot=_, loc~) => loc
    Guard(loc~, ..) => loc
    Is(expr=_, pat=_, loc~) => loc
    RegexMatch(expr=_, pat=_, bindings=_, loc~) => loc
    Defer(loc~, ..) => loc
    LetFn(name=_, func=_, body=_, loc~) => loc
    LetAnd(bindings=_, body=_, loc~) => loc
    Let(pattern=_, expr=_, body=_, loc~) => loc
    Sequence(exprs=_, last_expr=_, loc~) => loc
    Tuple(exprs=_, loc~) => loc
    Record(type_name=_, fields=_, trailing=_, loc~) => loc
    RecordUpdate(type_name=_, record=_, fields=_, loc~) => loc
    Field(record=_, accessor=_, loc~) => loc
    Method(type_name=_, method_name=_, loc~) => loc
    DotApply(self=_, method_name=_, args=_, return_self=_, loc~) => loc
    As(expr=_, trait_=_, loc~) => loc
    Mutate(record=_, accessor=_, field=_, augmented_by=_, loc~) => loc
    Match(expr=_, cases=_, match_loc=_, loc~) => loc
    LexMatch(strategy=_, expr=_, match_loc=_, cases=_, loc~) => loc
    LexScan(loc~, ..) => loc
    LetMut(binder=_, ty=_, expr=_, body=_, loc~) => loc
    Pipe(lhs=_, rhs=_, loc~) => loc
    RevPipe(lhs=_, rhs=_, loc~) => loc
    Assign(var_=_, expr=_, augmented_by=_, loc~) => loc
    Hole(loc~, kind=_) => loc
    Return(return_value=_, loc~) => loc
    Raise(err_value=_, loc~) => loc
    Quantifier(kind=_, binder=_, binder_ty=_, body=_, loc~) => loc
    Implies(lhs=_, rhs=_, loc~) => loc
    ProofAssert(expr=_, loc~) => loc
    ProofLet(binder=_, expr=_, loc~) => loc
    Unit(loc~, faked=_) => loc
    Break(arg=_, label=_, loc~) => loc
    Continue(args=_, label=_, loc~) => loc
    For(
      binders=_,
      condition=_,
      continue_block=_,
      body=_,
      for_else=_,
      label=_,
      loc~,
      where_clause=_
    ) => loc
    ForEach(
      binders=_,
      expr=_,
      init=_,
      continue_block=_,
      body=_,
      else_block=_,
      where_clause=_,
      label=_,
      loc~
    ) => loc
    Try(
      body=_,
      catch_=_,
      try_else=_,
      has_try=_,
      try_loc=_,
      catch_loc=_,
      else_loc=_,
      loc~
    ) => loc
    TryOperator(loc~, ..) => loc
    Map(elems=_, loc~) => loc
    Group(expr=_, group=_, loc~) => loc
    TemplateWriting(expr=_, template=_, is_conditional=_, loc~) => loc
    StaticAssert(asserts=_, body=_) => no_location
  }
}

///|
pub fn Pattern::loc(self : Self) -> Location {
  match self {
    Map(loc~, ..)
    | Record(loc~, ..)
    | Var({ loc, .. })
    | Tuple(loc~, ..)
    | Or(loc~, ..)
    | Constr(loc~, ..)
    | Constraint(loc~, ..)
    | Constant(loc~, ..)
    | Array(loc~, ..)
    | Any(loc~)
    | Alias(loc~, ..)
    | Range(loc~, ..)
    | With(loc~, ..)
    | SpecialConstr(loc~, ..) => loc
  }
}

///|
pub fn Impl::loc(self : Self) -> Location {
  match self {
    TopTypeDef({ loc, .. })
    | TopFuncDef(loc~, ..)
    | TopLetDef(loc~, ..)
    | TopExpr(loc~, ..)
    | TopImplRelation(loc~, ..)
    | TopTest(loc~, ..)
    | TopTrait({ loc, .. })
    | TopView(loc~, ..)
    | TopImpl(loc~, ..)
    | TopUsing(loc~, ..)
    | TopExtend(loc~, ..)
    | TopImport(loc~, ..) => loc
  }
}

///|
pub fn Parameter::loc(self : Self) -> Location {
  match self {
    Optional(binder~, ty=_, default~) => binder.loc.merge(default.loc())
    Labelled(binder~, ty~)
    | Positional(binder~, ty~)
    | QuestionOptional(binder~, ty~) =>
      match ty {
        None => binder.loc
        Some(ty) => binder.loc.merge(ty.loc())
      }
    DiscardPositional(ty=_, loc~) => loc
  }
}

///|
pub fn AliasTarget::loc(self : AliasTarget) -> Location {
  match self.target {
    None => self.binder.loc
    Some(target) => self.binder.loc.merge(target.loc)
  }
}

///|
fn shift_col(pos : Position, offset : Int) -> Position {
  {
    lnum: pos.lnum,
    fname: pos.fname,
    bol: pos.bol + offset,
    cnum: pos.cnum + offset,
  }
}