///|
/// Recursive-descent parser for the WIT 1.0 core subset.
///
/// Parses: `package`, `interface`, `world`, `use`, `import`/`export`,
/// `record`/`variant`/`enum`/`flags`/`resource`/`type` aliases and `func`
/// signatures, including named results and inline interfaces in worlds.
/// Raises `WitError` with 1-based line/column positions on malformed input.
/// Design inspired by bytecodealliance/wit-bindgen.

///|
priv struct Parser {
  tokens : Array[Token]
  mut idx : Int
}

///|
fn Parser::make(tokens : Array[Token]) -> Parser {
  { tokens, idx: 0 }
}

///|
fn Parser::peek(self : Parser, offset : Int) -> Token {
  let i = self.idx + offset
  if i >= self.tokens.length() {
    self.tokens[self.tokens.length() - 1]
  } else {
    self.tokens[i]
  }
}

///|
fn Parser::advance(self : Parser) -> Unit {
  if self.peek(0).kind != Eof {
    self.idx += 1
  }
}

///|
fn Parser::check_kw(self : Parser, kw : String) -> Bool {
  match self.peek(0).kind {
    Ident(text) => text == kw
    _ => false
  }
}

///|
fn Parser::error_here(self : Parser, message : String) -> WitError {
  let tok = self.peek(0)
  WitError::make(tok.line, tok.col, message)
}

///|
fn Parser::expect_ident(self : Parser) -> String raise WitError {
  match self.peek(0).kind {
    Ident(text) => {
      self.advance()
      text
    }
    _ =>
      raise self.error_here(
        "expected identifier, found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::expect_kw(self : Parser, kw : String) -> Unit raise WitError {
  match self.peek(0).kind {
    Ident(text) if text == kw => self.advance()
    _ =>
      raise self.error_here(
        "expected keyword '\{kw}', found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::expect_punct(
  self : Parser,
  kind : TokenKind,
  what : String,
) -> Unit raise WitError {
  if self.peek(0).kind == kind {
    self.advance()
  } else {
    raise self.error_here("expected \{what}, found \{self.peek(0).to_string()}")
  }
}

///|
/// Parse a WIT document into a [`WitPackage`].
pub fn parse(src : String) -> WitPackage raise WitError {
  let tokens = tokenize(src)
  let p = Parser::make(tokens)
  p.parse_package()
}

///|
fn Parser::parse_package(self : Parser) -> WitPackage raise WitError {
  let mut ns = "root"
  let mut pkg_name = "package"
  let mut version : String? = None
  if self.check_kw("package") {
    self.advance()
    ns = self.expect_ident()
    self.expect_punct(Colon, "':'")
    pkg_name = self.expect_ident()
    if self.peek(0).kind == At {
      self.advance()
      match self.peek(0).kind {
        Version(v) => {
          version = Some(v)
          self.advance()
        }
        _ => raise self.error_here("expected a version after '@'")
      }
    }
    self.expect_punct(Semi, "';'")
  }
  let items : Array[WitItem] = Array::new()
  while self.peek(0).kind != Eof {
    match self.peek(0).kind {
      Ident(text) if text == "interface" =>
        items.push(Interface(self.parse_interface()))
      Ident(text) if text == "world" => items.push(World(self.parse_world()))
      Ident(text) if text == "use" => items.push(Use(self.parse_use()))
      _ =>
        raise self.error_here(
          "expected 'interface', 'world' or 'use', found \{self.peek(0).to_string()}",
        )
    }
  }
  { ns, name: pkg_name, version, items }
}

///|
fn Parser::parse_interface(self : Parser) -> Interface raise WitError {
  self.expect_kw("interface")
  let name = self.expect_ident()
  match self.peek(0).kind {
    Semi => {
      self.advance()
      { name, items: Array::new() }
    }
    LBrace => {
      self.advance()
      let items = self.parse_interface_items()
      self.expect_punct(RBrace, "'}'")
      { name, items }
    }
    _ =>
      raise self.error_here(
        "expected '{{' or ';' after interface name, found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::parse_interface_items(
  self : Parser,
) -> Array[InterfaceItem] raise WitError {
  let items : Array[InterfaceItem] = Array::new()
  while self.peek(0).kind != RBrace && self.peek(0).kind != Eof {
    items.push(self.parse_interface_item())
  }
  items
}

///|
fn Parser::parse_interface_item(self : Parser) -> InterfaceItem raise WitError {
  match self.peek(0).kind {
    Ident(text) if text == "use" => Use(self.parse_use())
    Ident(text) if text == "type" => Type(self.parse_type_alias())
    Ident(text) if text == "record" => Type(self.parse_record())
    Ident(text) if text == "variant" => Type(self.parse_variant())
    Ident(text) if text == "enum" => Type(self.parse_enum())
    Ident(text) if text == "flags" => Type(self.parse_flags())
    Ident(text) if text == "resource" => Type(self.parse_resource())
    Ident(_) => Func(self.parse_func())
    _ =>
      raise self.error_here(
        "expected an interface item, found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::parse_world(self : Parser) -> World raise WitError {
  self.expect_kw("world")
  let name = self.expect_ident()
  match self.peek(0).kind {
    Semi => {
      self.advance()
      { name, items: Array::new() }
    }
    LBrace => {
      self.advance()
      let items : Array[WorldItem] = Array::new()
      while self.peek(0).kind != RBrace && self.peek(0).kind != Eof {
        match self.peek(0).kind {
          Ident(text) if text == "use" => items.push(Use(self.parse_use()))
          Ident(text) if text == "include" => {
            let (path, names) = self.parse_include()
            items.push(Include(path, names))
          }
          Ident(text) if text == "import" =>
            if self.check_world_func("import") {
              items.push(ImportFunc(self.parse_world_func("import")))
            } else {
              items.push(Import(self.parse_impexp("import")))
            }
          Ident(text) if text == "export" =>
            if self.check_world_func("export") {
              items.push(ExportFunc(self.parse_world_func("export")))
            } else {
              items.push(Export(self.parse_impexp("export")))
            }
          _ =>
            raise self.error_here(
              "expected 'include', 'import', 'export' or 'use', found \{self.peek(0).to_string()}",
            )
        }
      }
      self.expect_punct(RBrace, "'}'")
      { name, items }
    }
    _ =>
      raise self.error_here(
        "expected '{{' or ';' after world name, found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::parse_include(
  self : Parser,
) -> (String, Array[UseName]) raise WitError {
  self.expect_kw("include")
  let mut path = ""
  let mut saw_segment = false
  for ;; {
    match self.peek(0).kind {
      Ident("with") if saw_segment => break
      Ident(text) => {
        path = path + text
        saw_segment = true
        self.advance()
      }
      Colon => {
        path = path + ":"
        self.advance()
      }
      Slash => {
        path = path + "/"
        self.advance()
      }
      At => {
        path = path + "@"
        self.advance()
        match self.peek(0).kind {
          Version(v) => {
            path = path + v
            self.advance()
          }
          _ => raise self.error_here("expected a version after '@'")
        }
      }
      Dot =>
        match self.peek(1).kind {
          Ident("with") => break
          _ => {
            path = path + "."
            self.advance()
          }
        }
      _ => break
    }
  }
  if !saw_segment {
    raise self.error_here("expected a world path after 'include'")
  }
  let names : Array[UseName] = Array::new()
  if self.check_kw("with") {
    self.advance()
    self.expect_punct(LBrace, "'{' after 'with'")
    while self.peek(0).kind != RBrace {
      let source = self.expect_ident()
      self.expect_kw("as")
      let target = self.expect_ident()
      names.push({ name: source, asName: Some(target) })
      if self.peek(0).kind == Comma {
        self.advance()
      } else {
        break
      }
    }
    self.expect_punct(RBrace, "'}'")
  }
  self.expect_punct(Semi, "';'")
  (path, names)
}

///|
fn Parser::check_world_func(self : Parser, kw : String) -> Bool {
  if !self.check_kw(kw) {
    return false
  }
  match (self.peek(1).kind, self.peek(2).kind, self.peek(3).kind) {
    (Ident(_), Colon, Ident("func")) => true
    _ => false
  }
}

///|
fn Parser::parse_world_func(
  self : Parser,
  kw : String,
) -> FuncSig raise WitError {
  self.expect_kw(kw)
  let name = self.expect_ident()
  self.expect_punct(Colon, "':'")
  self.parse_func_after_name(name)
}

///|
fn Parser::parse_impexp(self : Parser, kw : String) -> ImpExp raise WitError {
  self.expect_kw(kw)
  let name = self.parse_impexp_path()
  let as_name = if self.check_kw("as") {
    self.advance()
    Some(self.expect_ident())
  } else {
    None
  }
  let inline = if self.peek(0).kind == Colon {
    self.advance()
    self.expect_kw("interface")
    match self.peek(0).kind {
      Semi => {
        self.advance()
        Some(({ name: "", items: Array::new() } : Interface))
      }
      LBrace => {
        self.advance()
        let items = self.parse_interface_items()
        self.expect_punct(RBrace, "'}'")
        Some(({ name: "", items } : Interface))
      }
      _ =>
        raise self.error_here(
          "expected ';' or '{{' after 'interface', found \{self.peek(0).to_string()}",
        )
    }
  } else {
    None
  }
  if inline is None {
    self.expect_punct(Semi, "';'")
  }
  { name, asName: as_name, inline }
}

///|
fn Parser::parse_impexp_path(self : Parser) -> String raise WitError {
  let mut path = self.expect_ident()
  for ;; {
    match self.peek(0).kind {
      Colon =>
        match self.peek(1).kind {
          Ident("interface") => break
          _ => {
            path = path + ":"
            self.advance()
          }
        }
      Slash => {
        path = path + "/"
        self.advance()
      }
      Dot => {
        path = path + "."
        self.advance()
      }
      At => {
        path = path + "@"
        self.advance()
        match self.peek(0).kind {
          Version(v) => {
            path = path + v
            self.advance()
          }
          _ => raise self.error_here("expected a version after '@'")
        }
      }
      Ident(text) if text != "as" => {
        path = path + text
        self.advance()
      }
      _ => break
    }
  }
  path
}

///|
fn Parser::parse_use(self : Parser) -> UseDecl raise WitError {
  self.expect_kw("use")
  let path = self.parse_use_path()
  self.expect_punct(Dot, "'.'")
  let names : Array[UseName] = Array::new()
  match self.peek(0).kind {
    Star =>
      // `use foo.*` — re-export everything; kept as an empty name list.
      self.advance()
    LBrace => {
      self.advance()
      while self.peek(0).kind != RBrace {
        let n = self.expect_ident()
        let as_name = if self.check_kw("as") {
          self.advance()
          Some(self.expect_ident())
        } else {
          None
        }
        names.push({ name: n, asName: as_name })
        if self.peek(0).kind == Comma {
          self.advance()
        } else {
          break
        }
      }
      self.expect_punct(RBrace, "'}'")
    }
    _ =>
      raise self.error_here(
        "expected '.{{' or '.*' after use path, found \{self.peek(0).to_string()}",
      )
  }
  self.expect_punct(Semi, "';'")
  { path, names }
}

///|
/// Collect a `use` path such as `wasi:io/streams` from identifier / `:` / `/` /
/// `.` tokens, stopping before the final `.` that precedes `{` or `*`.
fn Parser::parse_use_path(self : Parser) -> String {
  let mut s = ""
  for ;; {
    match self.peek(0).kind {
      Ident(text) => {
        s = s + text
        self.advance()
      }
      Colon => {
        s = s + ":"
        self.advance()
      }
      Slash => {
        s = s + "/"
        self.advance()
      }
      At => {
        s = s + "@"
        self.advance()
        match self.peek(0).kind {
          Version(v) => {
            s = s + v
            self.advance()
          }
          _ => return s
        }
      }
      Dot =>
        match self.peek(1).kind {
          LBrace | Star => break
          _ => {
            s = s + "."
            self.advance()
          }
        }
      _ => break
    }
  }
  s
}

///|
fn Parser::parse_type_alias(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("type")
  let name = self.expect_ident()
  self.expect_punct(Eq, "'='")
  let ty = self.parse_type()
  self.expect_punct(Semi, "';'")
  { name, kind: Alias(ty) }
}

///|
fn Parser::parse_record(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("record")
  let name = self.expect_ident()
  self.expect_punct(LBrace, "'{'")
  let fields : Array[Field] = Array::new()
  while self.peek(0).kind != RBrace {
    let fname = self.expect_ident()
    self.expect_punct(Colon, "':'")
    let ty = self.parse_type()
    fields.push({ name: fname, ty })
    if self.peek(0).kind == Comma {
      self.advance()
    }
  }
  self.expect_punct(RBrace, "'}'")
  { name, kind: Record(fields) }
}

///|
fn Parser::parse_variant(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("variant")
  let name = self.expect_ident()
  self.expect_punct(LBrace, "'{'")
  let cases : Array[Case] = Array::new()
  while self.peek(0).kind != RBrace {
    let cname = self.expect_ident()
    let ty = if self.peek(0).kind == LParen {
      self.advance()
      let t = self.parse_type()
      self.expect_punct(RParen, "')'")
      Some(t)
    } else {
      None
    }
    cases.push({ name: cname, ty })
    if self.peek(0).kind == Comma {
      self.advance()
    }
  }
  self.expect_punct(RBrace, "'}'")
  { name, kind: Variant(cases) }
}

///|
fn Parser::parse_name_list(self : Parser) -> Array[String] raise WitError {
  let names : Array[String] = Array::new()
  while self.peek(0).kind != RBrace {
    names.push(self.expect_ident())
    if self.peek(0).kind == Comma {
      self.advance()
    }
  }
  names
}

///|
fn Parser::parse_enum(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("enum")
  let name = self.expect_ident()
  self.expect_punct(LBrace, "'{'")
  let names = self.parse_name_list()
  self.expect_punct(RBrace, "'}'")
  { name, kind: Enum(names) }
}

///|
fn Parser::parse_flags(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("flags")
  let name = self.expect_ident()
  self.expect_punct(LBrace, "'{'")
  let names = self.parse_name_list()
  self.expect_punct(RBrace, "'}'")
  { name, kind: Flags(names) }
}

///|
fn Parser::parse_resource(self : Parser) -> TypeDef raise WitError {
  self.expect_kw("resource")
  let name = self.expect_ident()
  if self.peek(0).kind == Semi {
    self.advance()
    return { name, kind: Resource(Array::new()) }
  }
  self.expect_punct(LBrace, "'{'")
  let funcs : Array[ResourceFunc] = Array::new()
  while self.peek(0).kind != RBrace {
    let kind = if self.check_kw("constructor") {
      self.advance()
      Constructor
    } else if self.check_kw("static") {
      self.advance()
      Static
    } else {
      Method
    }
    let sig = if kind is Constructor {
      self.parse_func_signature("new")
    } else {
      self.parse_func()
    }
    funcs.push({ kind, sig })
  }
  self.expect_punct(RBrace, "'}'")
  { name, kind: Resource(funcs) }
}

///|
fn Parser::parse_func(self : Parser) -> FuncSig raise WitError {
  let name = self.expect_ident()
  self.expect_punct(Colon, "':'")
  self.parse_func_after_name(name)
}

///|
fn Parser::parse_func_after_name(
  self : Parser,
  name : String,
) -> FuncSig raise WitError {
  self.expect_kw("func")
  self.parse_func_signature(name)
}

///|
fn Parser::parse_func_signature(
  self : Parser,
  name : String,
) -> FuncSig raise WitError {
  self.expect_punct(LParen, "'('")
  let params : Array[Field] = Array::new()
  while self.peek(0).kind != RParen {
    let pname = self.expect_ident()
    self.expect_punct(Colon, "':'")
    let ty = self.parse_type()
    params.push({ name: pname, ty })
    if self.peek(0).kind == Comma {
      self.advance()
    } else {
      break
    }
  }
  self.expect_punct(RParen, "')'")
  let results = if self.peek(0).kind == Arrow {
    self.advance()
    self.parse_results()
  } else {
    Array::new()
  }
  if self.peek(0).kind == Semi {
    self.advance()
  }
  { name, params, results }
}

///|
fn Parser::parse_results(self : Parser) -> Array[FuncResult] raise WitError {
  if self.peek(0).kind == LParen {
    // `-> (t1, t2)` or `-> (a: t1, b: t2)` or `-> ()`
    self.advance()
    let results : Array[FuncResult] = Array::new()
    if self.peek(0).kind != RParen {
      for ;; {
        if self.peek(1).kind == Colon {
          let n = self.expect_ident()
          self.expect_punct(Colon, "':'")
          let t = self.parse_type()
          results.push(Named({ name: n, ty: t }))
        } else {
          let t = self.parse_type()
          results.push(Unnamed(t))
        }
        if self.peek(0).kind == Comma {
          self.advance()
        } else {
          break
        }
      }
    }
    self.expect_punct(RParen, "')'")
    results
  } else {
    // single unnamed result: `-> t`
    let t = self.parse_type()
    [Unnamed(t)]
  }
}

///|
fn Parser::parse_type(self : Parser) -> WitType raise WitError {
  match self.peek(0).kind {
    Ident(text) =>
      match text {
        "u8" => {
          self.advance()
          U8
        }
        "u16" => {
          self.advance()
          U16
        }
        "u32" => {
          self.advance()
          U32
        }
        "u64" => {
          self.advance()
          U64
        }
        "s8" => {
          self.advance()
          S8
        }
        "s16" => {
          self.advance()
          S16
        }
        "s32" => {
          self.advance()
          S32
        }
        "s64" => {
          self.advance()
          S64
        }
        "f32" => {
          self.advance()
          F32
        }
        "f64" => {
          self.advance()
          F64
        }
        "char" => {
          self.advance()
          TChar
        }
        "string" => {
          self.advance()
          TString
        }
        "bool" => {
          self.advance()
          TBool
        }
        "list" => {
          self.advance()
          List(self.parse_angle_arg())
        }
        "option" => {
          self.advance()
          Option(self.parse_angle_arg())
        }
        "result" => {
          self.advance()
          self.parse_result_type()
        }
        "tuple" => {
          self.advance()
          self.parse_tuple_type()
        }
        "own" => {
          self.advance()
          Own(self.parse_resource_ref("own"))
        }
        "borrow" => {
          self.advance()
          Borrow(self.parse_resource_ref("borrow"))
        }
        "future" => {
          self.advance()
          Future(self.parse_optional_angle_arg())
        }
        "stream" => {
          self.advance()
          Stream(self.parse_optional_angle_arg())
        }
        _ => {
          self.advance()
          Name(text)
        }
      }
    _ =>
      raise self.error_here(
        "expected a type, found \{self.peek(0).to_string()}",
      )
  }
}

///|
fn Parser::parse_optional_angle_arg(self : Parser) -> WitType? raise WitError {
  self.expect_punct(Lt, "'<'")
  let value = match self.peek(0).kind {
    Ident("_") => {
      self.advance()
      None
    }
    _ => Some(self.parse_type())
  }
  self.expect_punct(Gt, "'>'")
  value
}

///|
fn Parser::parse_angle_arg(self : Parser) -> WitType raise WitError {
  self.expect_punct(Lt, "'<'")
  let t = self.parse_type()
  self.expect_punct(Gt, "'>'")
  t
}

///|
fn Parser::parse_result_type(self : Parser) -> WitType raise WitError {
  self.expect_punct(Lt, "'<'")
  let ok = self.parse_result_slot()
  let err = if self.peek(0).kind == Comma {
    self.advance()
    self.parse_result_slot()
  } else {
    None
  }
  self.expect_punct(Gt, "'>'")
  Result(ok, err)
}

///|
fn Parser::parse_result_slot(self : Parser) -> WitType? raise WitError {
  match self.peek(0).kind {
    Ident("_") => {
      self.advance()
      None
    }
    Ident("nil") => {
      self.advance()
      None
    }
    _ => Some(self.parse_type())
  }
}

///|
fn Parser::parse_tuple_type(self : Parser) -> WitType raise WitError {
  self.expect_punct(Lt, "'<'")
  let items : Array[WitType] = Array::new()
  items.push(self.parse_type())
  while self.peek(0).kind == Comma {
    self.advance()
    items.push(self.parse_type())
  }
  self.expect_punct(Gt, "'>'")
  Tuple(items)
}

///|
/// The resource type name inside `own<...>` / `borrow<...>`.
fn Parser::parse_resource_ref(
  self : Parser,
  what : String,
) -> String raise WitError {
  self.expect_punct(Lt, "'<'")
  let t = self.parse_type()
  self.expect_punct(Gt, "'>'")
  match t {
    Name(n) => n
    _ => raise self.error_here("expected a type name inside '\{what}<>'")
  }
}