import { lexGrl, type GrlToken } from "../lexer/index.js";
import type {
  GrlDataDeclaration,
  GrlDeclarationStorage,
  GrlFunctionDeclaration,
  GrlImportDeclaration,
  GrlLanguageDeclaration,
  GrlModuleDeclaration,
  GrlOperationActionBlock,
  GrlOperationDeclaration,
  GrlOperationItem,
  GrlOperationProcessBlock,
  GrlPathDeclaration,
  GrlPathDefaultsBlock,
  GrlPathEvent,
  GrlPathItem,
  GrlPathPoint,
  GrlPathProperty,
  GrlPathSourceBlock,
  GrlProcedureDeclaration,
  GrlProgram,
  GrlRawTopLevelDeclaration,
  GrlTargetDeclaration,
  GrlTopLevelDeclaration
} from "../ast/index.js";
import { GrlParseError } from "./errors.js";
import { parseGrlExpression } from "./expressionParser.js";

const RAW_TOP_LEVEL_KEYWORDS = new Set([
  "trap",
  "task",
  "post_hint"
]);

export function parseGrl(source: string): GrlProgram {
  const tokens = lexGrl(source).filter((token) => token.kind !== "comment");
  return new GrlParser(tokens).parseProgram();
}

class GrlParser {
  private current = 0;

  constructor(private readonly tokens: GrlToken[]) {}

  parseProgram(): GrlProgram {
    const first = this.peek();
    const language = this.matchKeyword("language") ? this.finishLanguageDeclaration(this.previous()) : undefined;
    const module = this.parseModuleDeclaration();
    const eof = this.consume("eof", "Expected end of file after module declaration");

    return {
      kind: "Program",
      ...(language ? { language } : {}),
      module,
      range: {
        start: (language?.range ?? module.range).start,
        end: eof.range.end
      }
    };
  }

  private finishLanguageDeclaration(languageToken: GrlToken): GrlLanguageDeclaration {
    const languageName = this.consumeIdentifierLike("Expected language name after language");
    if (languageName.raw !== "grl") {
      throw new GrlParseError("Only language grl is supported", languageName);
    }

    const version = this.consume("number", "Expected GRL language version");
    return {
      kind: "LanguageDeclaration",
      language: "grl",
      version: version.raw,
      range: {
        start: languageToken.range.start,
        end: version.range.end
      }
    };
  }

  private parseModuleDeclaration(): GrlModuleDeclaration {
    const moduleToken = this.consumeKeyword("module", "Expected module declaration");
    const name = this.consume("identifier", "Expected module name");
    const declarations: GrlTopLevelDeclaration[] = [];

    while (!this.checkKeyword("end") && !this.isAtEnd()) {
      declarations.push(this.parseTopLevelDeclaration());
    }

    const end = this.consumeKeyword("end", "Expected end after module declaration");
    return {
      kind: "ModuleDeclaration",
      name: name.raw,
      declarations,
      range: {
        start: moduleToken.range.start,
        end: end.range.end
      }
    };
  }

  private parseTopLevelDeclaration(): GrlTopLevelDeclaration {
    if (this.matchKeyword("import")) {
      return this.finishImportDeclaration(this.previous());
    }

    if (this.matchKeyword("proc")) {
      return this.finishProcedureDeclaration(this.previous());
    }

    if (this.matchKeyword("func")) {
      return this.finishFunctionDeclaration(this.previous());
    }

    if (this.checkDataDeclarationStart()) {
      return this.parseDataDeclaration();
    }

    if (this.matchKeyword("target")) {
      return this.finishTargetDeclaration(this.previous());
    }

    if (this.matchKeyword("path")) {
      return this.finishPathDeclaration(this.previous());
    }

    if (this.matchKeyword("operation")) {
      return this.finishOperationDeclaration(this.previous());
    }

    const token = this.peek();
    if (token.kind === "keyword" && RAW_TOP_LEVEL_KEYWORDS.has(token.raw)) {
      return this.parseRawTopLevelDeclaration();
    }

    throw new GrlParseError("Expected top-level declaration", token);
  }

  private finishImportDeclaration(importToken: GrlToken): GrlImportDeclaration {
    const moduleName = this.consume("identifier", "Expected imported module name");
    return {
      kind: "ImportDeclaration",
      moduleName: moduleName.raw,
      range: {
        start: importToken.range.start,
        end: moduleName.range.end
      }
    };
  }

  private parseDataDeclaration(): GrlDataDeclaration {
    const storageToken = this.advance();
    const storage = storageToken.raw as GrlDeclarationStorage;
    const typeName = this.consumeIdentifierLike("Expected type name in data declaration");
    const name = this.consume("identifier", "Expected variable name in data declaration");
    this.consumeOperator("=", "Expected = in data declaration");
    const initializerTokens = this.collectFlatExpressionTokens();
    const initializer = parseGrlExpression(initializerTokens);

    return {
      kind: "DataDeclaration",
      storage,
      typeName: typeName.raw,
      name: name.raw,
      initializer,
      range: {
        start: storageToken.range.start,
        end: initializer.range.end
      }
    };
  }

  private finishTargetDeclaration(targetToken: GrlToken): GrlTargetDeclaration {
    const name = this.consume("identifier", "Expected target name");
    this.consumeOperator("=", "Expected = in target declaration");
    const targetTokens = this.collectFlatExpressionTokens();
    const target = parseGrlExpression(targetTokens);

    return {
      kind: "TargetDeclaration",
      name: name.raw,
      target,
      range: {
        start: targetToken.range.start,
        end: target.range.end
      }
    };
  }

  private finishPathDeclaration(pathToken: GrlToken): GrlPathDeclaration {
    const name = this.consume("identifier", "Expected path name");
    this.consumePunctuation("{", "Expected { after path name");
    const items: GrlPathItem[] = [];

    while (!this.checkPunctuation("}") && !this.isAtEnd()) {
      if (this.matchKeyword("defaults")) {
        items.push(this.finishPathDefaultsBlock(this.previous()));
        continue;
      }
      if (this.matchKeyword("source")) {
        items.push(this.finishPathSourceBlock(this.previous()));
        continue;
      }
      if (this.matchKeyword("point")) {
        items.push(this.finishPathPoint(this.previous()));
        continue;
      }
      if (this.matchKeyword("event")) {
        items.push(this.finishPathEvent(this.previous()));
        continue;
      }
      throw new GrlParseError("Expected path item", this.peek());
    }

    const end = this.consumePunctuation("}", "Expected } after path declaration");
    return {
      kind: "PathDeclaration",
      name: name.raw,
      items,
      range: {
        start: pathToken.range.start,
        end: end.range.end
      }
    };
  }

  private finishPathDefaultsBlock(start: GrlToken): GrlPathDefaultsBlock {
    const { properties, end } = this.parsePathPropertyBlock("defaults");
    return {
      kind: "PathDefaultsBlock",
      properties,
      range: {
        start: start.range.start,
        end: end.range.end
      }
    };
  }

  private finishPathSourceBlock(start: GrlToken): GrlPathSourceBlock {
    const { properties, end } = this.parsePathPropertyBlock("source");
    return {
      kind: "PathSourceBlock",
      properties,
      range: {
        start: start.range.start,
        end: end.range.end
      }
    };
  }

  private parsePathPropertyBlock(blockName: string): { properties: GrlPathProperty[]; end: GrlToken } {
    this.consumePunctuation("{", `Expected { after path ${blockName}`);
    const properties: GrlPathProperty[] = [];

    while (!this.checkPunctuation("}") && !this.isAtEnd()) {
      const key = this.consumeIdentifierLike(`Expected ${blockName} property name`);
      this.consumePunctuation(":", `Expected : after ${blockName} property name`);
      const valueTokens = this.collectPathPropertyValueTokens();
      const value = parseGrlExpression(valueTokens);
      properties.push({
        key: key.raw,
        value,
        range: {
          start: key.range.start,
          end: value.range.end
        }
      });
      this.matchPunctuation(",");
    }

    const end = this.consumePunctuation("}", `Expected } after path ${blockName}`);
    return { properties, end };
  }

  private finishPathPoint(start: GrlToken): GrlPathPoint {
    const id = this.consume("identifier", "Expected path point id");
    const motionTokens = this.collectPathMotionTokens();
    if (motionTokens.length === 0) {
      throw new GrlParseError("Expected path point motion statement", this.peek());
    }
    return {
      kind: "PathPoint",
      id: id.raw,
      motionTokens,
      range: {
        start: start.range.start,
        end: motionTokens.at(-1)?.range.end ?? id.range.end
      }
    };
  }

  private finishPathEvent(start: GrlToken): GrlPathEvent {
    const timing = this.consumeIdentifierLike("Expected before, after, or at after event");
    if (timing.raw !== "before" && timing.raw !== "after" && timing.raw !== "at") {
      throw new GrlParseError("Expected before, after, or at after event", timing);
    }
    const point = this.consume("identifier", "Expected event point id");
    let distance: GrlPathEvent["distance"];
    if (timing.raw === "at") {
      this.consumeIdentifierValue("distance", "Expected distance in event at");
      const distanceToken = this.peek();
      const distanceTokens = this.collectSignedNumberTokens();
      const distanceExpression = parseGrlExpression(distanceTokens);
      if (distanceExpression.kind !== "NumberLiteral") {
        throw new GrlParseError("Expected numeric event distance", distanceToken);
      }
      distance = distanceExpression;
    }
    const actionTokens = this.collectPathEventActionTokens();
    if (actionTokens.length === 0) {
      throw new GrlParseError("Expected path event action", this.peek());
    }

    return {
      kind: "PathEvent",
      timing: timing.raw,
      pointId: point.raw,
      ...(distance ? { distance } : {}),
      actionTokens,
      range: {
        start: start.range.start,
        end: actionTokens.at(-1)?.range.end ?? point.range.end
      }
    };
  }

  private finishOperationDeclaration(operationToken: GrlToken): GrlOperationDeclaration {
    const name = this.consume("identifier", "Expected operation name");
    this.consumePunctuation("{", "Expected { after operation name");
    let operationKind: string | undefined;
    let pathName: string | undefined;
    const items: GrlOperationItem[] = [];

    while (!this.checkPunctuation("}") && !this.isAtEnd()) {
      if (this.matchIdentifierValue("kind")) {
        this.consumePunctuation(":", "Expected : after operation kind");
        operationKind = this.consumeIdentifierLike("Expected operation kind").raw;
        this.matchPunctuation(",");
        continue;
      }
      if (this.matchIdentifierValue("path")) {
        this.consumePunctuation(":", "Expected : after operation path");
        pathName = this.consumeIdentifierLike("Expected operation path name").raw;
        this.matchPunctuation(",");
        continue;
      }
      if (this.matchIdentifierValue("process")) {
        items.push(this.finishOperationProcessBlock(this.previous()));
        continue;
      }
      if (this.matchIdentifierValue("start_action")) {
        items.push(this.finishOperationActionBlock(this.previous(), "start_action"));
        continue;
      }
      if (this.matchIdentifierValue("end_action")) {
        items.push(this.finishOperationActionBlock(this.previous(), "end_action"));
        continue;
      }
      throw new GrlParseError("Expected operation item", this.peek());
    }

    const end = this.consumePunctuation("}", "Expected } after operation declaration");
    if (!operationKind) {
      throw new GrlParseError("Operation requires kind", end);
    }
    if (!pathName) {
      throw new GrlParseError("Operation requires path", end);
    }

    return {
      kind: "OperationDeclaration",
      name: name.raw,
      operationKind,
      pathName,
      items,
      range: {
        start: operationToken.range.start,
        end: end.range.end
      }
    };
  }

  private finishOperationProcessBlock(start: GrlToken): GrlOperationProcessBlock {
    const { properties, end } = this.parsePathPropertyBlock("process");
    return {
      kind: "OperationProcessBlock",
      properties,
      range: {
        start: start.range.start,
        end: end.range.end
      }
    };
  }

  private finishOperationActionBlock(
    start: GrlToken,
    actionKind: "start_action" | "end_action"
  ): GrlOperationActionBlock {
    this.consumePunctuation(":", `Expected : after ${actionKind}`);
    const actionTokens = this.collectOperationActionTokens();
    if (actionTokens.length === 0) {
      throw new GrlParseError(`Expected ${actionKind} action`, this.peek());
    }
    return {
      kind: "OperationActionBlock",
      actionKind,
      actionTokens,
      range: {
        start: start.range.start,
        end: actionTokens.at(-1)?.range.end ?? start.range.end
      }
    };
  }

  private finishProcedureDeclaration(procToken: GrlToken): GrlProcedureDeclaration {
    const name = this.consume("identifier", "Expected procedure name");
    this.consumePunctuation("(", "Expected ( after procedure name");

    const params: GrlToken[] = [];
    while (!this.checkPunctuation(")") && !this.isAtEnd()) {
      params.push(this.advance());
    }
    this.consumePunctuation(")", "Expected ) after procedure parameters");

    const bodyTokens: GrlToken[] = [];
    let nestedBlocks = 0;
    while (!this.isAtEnd()) {
      if (this.checkKeyword("end") && nestedBlocks === 0) {
        break;
      }

      const token = this.advance();
      bodyTokens.push(token);

      if (token.kind === "keyword" && token.raw === "end" && nestedBlocks > 0) {
        nestedBlocks -= 1;
      } else if (token.kind === "keyword" && ["if", "while", "for", "switch", "try"].includes(token.raw)) {
        nestedBlocks += 1;
      }
    }

    const end = this.consumeKeyword("end", "Expected end after procedure declaration");

    return {
      kind: "ProcedureDeclaration",
      name: name.raw,
      params,
      bodyTokens,
      range: {
        start: procToken.range.start,
        end: end.range.end
      }
    };
  }

  private finishFunctionDeclaration(funcToken: GrlToken): GrlFunctionDeclaration {
    const returnType = this.consumeIdentifierLike("Expected function return type");
    const name = this.consume("identifier", "Expected function name");
    this.consumePunctuation("(", "Expected ( after function name");

    const params: GrlToken[] = [];
    while (!this.checkPunctuation(")") && !this.isAtEnd()) {
      params.push(this.advance());
    }
    this.consumePunctuation(")", "Expected ) after function parameters");

    const bodyTokens: GrlToken[] = [];
    let nestedBlocks = 0;
    while (!this.isAtEnd()) {
      if (this.checkKeyword("end") && nestedBlocks === 0) {
        break;
      }

      const token = this.advance();
      bodyTokens.push(token);

      if (token.kind === "keyword" && token.raw === "end" && nestedBlocks > 0) {
        nestedBlocks -= 1;
      } else if (token.kind === "keyword" && ["if", "while", "for", "switch", "try"].includes(token.raw)) {
        nestedBlocks += 1;
      }
    }

    const end = this.consumeKeyword("end", "Expected end after function declaration");

    return {
      kind: "FunctionDeclaration",
      returnType: returnType.raw,
      name: name.raw,
      params,
      bodyTokens,
      range: {
        start: funcToken.range.start,
        end: end.range.end
      }
    };
  }

  private parseRawTopLevelDeclaration(): GrlRawTopLevelDeclaration {
    const first = this.advance();
    const tokens: GrlToken[] = [first];

    if (["path", "operation"].includes(first.raw)) {
      this.collectBalancedBlock(tokens);
    } else if (["trap", "task"].includes(first.raw)) {
      this.collectUntilMatchingEnd(tokens);
    } else {
      this.collectFlatDeclaration(tokens);
    }

    return {
      kind: "RawTopLevelDeclaration",
      declarationType: first.raw,
      tokens,
      range: {
        start: first.range.start,
        end: tokens.at(-1)?.range.end ?? first.range.end
      }
    };
  }

  private collectBalancedBlock(tokens: GrlToken[]): void {
    let braceDepth = 0;
    while (!this.isAtEnd()) {
      const token = this.advance();
      tokens.push(token);
      if (token.kind === "punctuation" && token.raw === "{") {
        braceDepth += 1;
      } else if (token.kind === "punctuation" && token.raw === "}") {
        braceDepth -= 1;
        if (braceDepth === 0) {
          return;
        }
      }
    }
  }

  private collectUntilMatchingEnd(tokens: GrlToken[]): void {
    let nestedBlocks = 0;
    while (!this.isAtEnd()) {
      const token = this.advance();
      tokens.push(token);
      if (token.kind === "keyword" && token.raw === "end") {
        if (nestedBlocks === 0) {
          return;
        }
        nestedBlocks -= 1;
      } else if (token.kind === "keyword" && ["if", "while", "for", "switch", "try"].includes(token.raw)) {
        nestedBlocks += 1;
      }
    }
  }

  private collectFlatDeclaration(tokens: GrlToken[]): void {
    while (!this.isAtEnd()) {
      if (this.checkKeyword("end") || this.startsTopLevelDeclaration(this.peek())) {
        return;
      }
      tokens.push(this.advance());
    }
  }

  private collectFlatExpressionTokens(): GrlToken[] {
    const tokens: GrlToken[] = [];
    let braceDepth = 0;
    let bracketDepth = 0;
    let parenDepth = 0;

    while (!this.isAtEnd()) {
      if (
        braceDepth === 0 &&
        bracketDepth === 0 &&
        parenDepth === 0 &&
        (this.checkKeyword("end") || this.startsTopLevelDeclaration(this.peek()) || this.startsNextDataDeclaration())
      ) {
        break;
      }

      const token = this.advance();
      tokens.push(token);

      if (token.kind === "punctuation") {
        if (token.raw === "{") {
          braceDepth += 1;
        } else if (token.raw === "}") {
          braceDepth -= 1;
        } else if (token.raw === "[") {
          bracketDepth += 1;
        } else if (token.raw === "]") {
          bracketDepth -= 1;
        } else if (token.raw === "(") {
          parenDepth += 1;
        } else if (token.raw === ")") {
          parenDepth -= 1;
        }
      }
    }

    if (tokens.length === 0) {
      throw new GrlParseError("Expected expression", this.peek());
    }
    return tokens;
  }

  private collectPathPropertyValueTokens(): GrlToken[] {
    return this.collectUntil((token, depth) =>
      depth.brace === 0 &&
      depth.bracket === 0 &&
      depth.paren === 0 &&
      ((token.kind === "punctuation" && (token.raw === "," || token.raw === "}")) ||
        this.isPathItemStart(token) ||
        this.isPathPropertyStart())
    );
  }

  private collectPathMotionTokens(): GrlToken[] {
    return this.collectUntil((token, depth) =>
      depth.brace === 0 &&
      depth.bracket === 0 &&
      depth.paren === 0 &&
      ((token.kind === "punctuation" && token.raw === "}") || this.isPathItemStart(token))
    );
  }

  private collectPathEventActionTokens(): GrlToken[] {
    return this.collectUntil((token, depth) =>
      depth.brace === 0 &&
      depth.bracket === 0 &&
      depth.paren === 0 &&
      ((token.kind === "punctuation" && token.raw === "}") || this.isPathItemStart(token))
    );
  }

  private collectOperationActionTokens(): GrlToken[] {
    return this.collectUntil((token, depth) =>
      depth.brace === 0 &&
      depth.bracket === 0 &&
      depth.paren === 0 &&
      ((token.kind === "punctuation" && token.raw === "}") || this.isOperationItemStart(token))
    );
  }

  private collectSignedNumberTokens(): GrlToken[] {
    const tokens: GrlToken[] = [];
    if (this.peek().kind === "operator" && this.peek().raw === "-") {
      tokens.push(this.advance());
    }
    tokens.push(this.consume("number", "Expected numeric value"));
    return tokens;
  }

  private collectUntil(
    shouldStop: (
      token: GrlToken,
      depth: { brace: number; bracket: number; paren: number }
    ) => boolean
  ): GrlToken[] {
    const tokens: GrlToken[] = [];
    const depth = { brace: 0, bracket: 0, paren: 0 };

    while (!this.isAtEnd() && !shouldStop(this.peek(), depth)) {
      const token = this.advance();
      tokens.push(token);

      if (token.kind === "punctuation") {
        if (token.raw === "{") {
          depth.brace += 1;
        } else if (token.raw === "}") {
          depth.brace -= 1;
        } else if (token.raw === "[") {
          depth.bracket += 1;
        } else if (token.raw === "]") {
          depth.bracket -= 1;
        } else if (token.raw === "(") {
          depth.paren += 1;
        } else if (token.raw === ")") {
          depth.paren -= 1;
        }
      }
    }

    if (tokens.length === 0) {
      throw new GrlParseError("Expected expression", this.peek());
    }
    return tokens;
  }

  private isPathItemStart(token: GrlToken): boolean {
    return (
      token.kind === "keyword" &&
      (token.raw === "defaults" || token.raw === "source" || token.raw === "point" || token.raw === "event")
    );
  }

  private isOperationItemStart(token: GrlToken): boolean {
    return (
      ((token.kind === "keyword" || token.kind === "identifier") &&
        (token.raw === "kind" || token.raw === "path" || token.raw === "process")) ||
      ((token.kind === "identifier" || token.kind === "keyword") &&
        (token.raw === "start_action" || token.raw === "end_action"))
    );
  }

  private isPathPropertyStart(): boolean {
    const token = this.peek();
    const next = this.peek(1);
    return (
      (token.kind === "keyword" || token.kind === "identifier") &&
      next.kind === "punctuation" &&
      next.raw === ":"
    );
  }

  private startsNextDataDeclaration(): boolean {
    const current = this.peek();
    const next = this.peekNext();
    const following = this.peek(2);

    if (current.kind !== "identifier" && current.kind !== "keyword") {
      return false;
    }
    if (next.kind !== "identifier" && next.kind !== "keyword") {
      return false;
    }
    return following.kind === "operator" && following.raw === "=";
  }

  private startsTopLevelDeclaration(token: GrlToken): boolean {
    if (token.kind !== "keyword") {
      return false;
    }
    if (token.raw === "target") {
      const name = this.peek(1);
      const equals = this.peek(2);
      return name.kind === "identifier" && equals.kind === "operator" && equals.raw === "=";
    }

    return (
      token.raw === "import" ||
      token.raw === "proc" ||
      token.raw === "path" ||
      token.raw === "operation" ||
      token.raw === "persistent" ||
      token.raw === "const" ||
      token.raw === "var" ||
      RAW_TOP_LEVEL_KEYWORDS.has(token.raw)
    );
  }

  private checkDataDeclarationStart(): boolean {
    return this.checkKeyword("persistent") || this.checkKeyword("const") || this.checkKeyword("var");
  }

  private consumeIdentifierLike(message: string): GrlToken {
    const token = this.peek();
    if (token.kind === "identifier" || token.kind === "keyword") {
      return this.advance();
    }
    throw new GrlParseError(message, token);
  }

  private consume(kind: GrlToken["kind"], message: string): GrlToken {
    if (this.check(kind)) {
      return this.advance();
    }
    throw new GrlParseError(message, this.peek());
  }

  private consumeKeyword(keyword: string, message: string): GrlToken {
    if (this.checkKeyword(keyword)) {
      return this.advance();
    }
    throw new GrlParseError(message, this.peek());
  }

  private consumeIdentifierValue(value: string, message: string): GrlToken {
    const token = this.peek();
    if ((token.kind === "keyword" || token.kind === "identifier") && token.raw === value) {
      return this.advance();
    }
    throw new GrlParseError(message, token);
  }

  private consumePunctuation(value: string, message: string): GrlToken {
    if (this.checkPunctuation(value)) {
      return this.advance();
    }
    throw new GrlParseError(message, this.peek());
  }

  private consumeOperator(value: string, message: string): GrlToken {
    const token = this.peek();
    if (token.kind === "operator" && token.raw === value) {
      return this.advance();
    }
    throw new GrlParseError(message, token);
  }

  private matchKeyword(keyword: string): boolean {
    if (this.checkKeyword(keyword)) {
      this.advance();
      return true;
    }
    return false;
  }

  private matchIdentifierValue(value: string): boolean {
    const token = this.peek();
    if ((token.kind === "keyword" || token.kind === "identifier") && token.raw === value) {
      this.advance();
      return true;
    }
    return false;
  }

  private matchPunctuation(value: string): boolean {
    if (this.checkPunctuation(value)) {
      this.advance();
      return true;
    }
    return false;
  }

  private check(kind: GrlToken["kind"]): boolean {
    return this.peek().kind === kind;
  }

  private checkKeyword(keyword: string): boolean {
    const token = this.peek();
    return token.kind === "keyword" && token.raw === keyword;
  }

  private checkPunctuation(value: string): boolean {
    const token = this.peek();
    return token.kind === "punctuation" && token.raw === value;
  }

  private advance(): GrlToken {
    if (!this.isAtEnd()) {
      this.current += 1;
    }
    return this.previous();
  }

  private isAtEnd(): boolean {
    return this.peek().kind === "eof";
  }

  private peek(distance = 0): GrlToken {
    return this.tokens[this.current + distance] ?? this.tokens[this.tokens.length - 1]!;
  }

  private peekNext(): GrlToken {
    return this.peek(1);
  }

  private previous(): GrlToken {
    return this.tokens[this.current - 1] ?? this.tokens[0]!;
  }
}
