import { LRParser as LezerParser } from "@lezer/lr";
import { debugEnabled } from "../util/logger.ts";
import { Text } from "@codemirror/state";
import type { Input } from "@lezer/common";
import { SyntacticParseError } from "../ParseError.ts";

// Generated by Lezer from the SDL grammar
import { parser } from "./parser.ts";
import { getContextTracker } from "./contextTracker.ts";

let lenientSdlParser: LezerParser | undefined;
let strictSdlParser: LezerParser | undefined;

/**
 * Create an in memory lenient Lezer based parser using the SDL grammar and store it as a "singleton".
 */
export function createLenientSdlParser(): LezerParser {
  if (!lenientSdlParser) {
    if (debugEnabled) {
      lenientSdlParser = parser.configure({
        contextTracker: getContextTracker(parser),
      });
    } else {
      lenientSdlParser = parser;
    }
  }

  return lenientSdlParser;
}

/**
 * Create an in memory strict Lezer based parser using the SDL grammar and store it as a "singleton".
 */
export function createStrictSdlParser(): LezerParser {
  if (!strictSdlParser) {
    strictSdlParser = createLenientSdlParser().configure({
      strict: true,
    });

    // Wrap the parser's parse method to catch errors and wrap them in ParseError
    const originalParseFunc = strictSdlParser.parse.bind(strictSdlParser);

    strictSdlParser.parse = (input: Input | string, ...args) => {
      try {
        return originalParseFunc(input, ...args);
      } catch (err) {
        // parse position out of error message which takes the form "No parse at 0"
        const errorMessage = (err as Error).message;
        const match = errorMessage.match(/No parse at (\d+)/);
        let position = 0;
        if (match) {
          const parsed = parseInt(match[1], 10);
          position = isNaN(parsed) ? 0 : parsed;
        }

        let inputText: string;

        if (typeof input === "string") {
          inputText = input;
        } else {
          inputText = input.read(0, input.length);
        }

        const text = Text.of(
          inputText.split("\n"),
        );

        throw SyntacticParseError.fromTextAndPosition(text, position);
      }
    };
  }

  return strictSdlParser;
}
