import { UCDFile } from './ucdFile.js';

/** Provides information pointing to a location within a source. */
interface Location {
  /** Line in the parsed source (1-based). */
  readonly line: number;
  /** Column in the parsed source (1-based). */
  readonly column: number;
  /** Offset in the parsed source (0-based). */
  readonly offset: number;
}

/**
 * Anything that can successfully be converted to a string with `String()`
 * so that it can be used in error messages.
 *
 * The GrammarLocation class in Peggy is a good example.
 */
interface GrammarSourceObject {
  readonly toString: () => string;

  /**
   * If specified, allows the grammar source to be embedded in a larger file
   * at some offset.
   */
  readonly offset?: undefined | ((loc: Location) => Location);
}

/**
 * Most often, you just use a string with the file name.
 */
type GrammarSource = string | GrammarSourceObject;

/** The `start` and `end` position's of an object within the source. */
interface LocationRange {
  /**
   * A string or object that was supplied to the `parse()` call as the
   * `grammarSource` option.
   */
  readonly source: GrammarSource;
  /** Position at the beginning of the expression. */
  readonly start: Location;
  /** Position after the end of the expression. */
  readonly end: Location;
}

/**
 * Expected a literal string, like `"foo"i`.
 */
interface LiteralExpectation {
  readonly type: "literal";
  readonly text: string;
  readonly ignoreCase: boolean;
}

/**
 * Range of characters, like `a-z`
 */
type ClassRange = [
  start: string,
  end: string,
]

interface ClassParts extends Array<string | ClassRange> {
}

/**
 * Expected a class, such as `[^acd-gz]i`
 */
interface ClassExpectation {
  readonly type: "class";
  readonly parts: ClassParts;
  readonly inverted: boolean;
  readonly ignoreCase: boolean;
}

/**
 * Expected any character, with `.`
 */
interface AnyExpectation {
  readonly type: "any";
}

/**
 * Expected the end of input.
 */
interface EndExpectation {
  readonly type: "end";
}

/**
 * Expected some other input.  These are specified with a rule's
 * "human-readable name", or with the `expected(message, location)`
 * function.
 */
interface OtherExpectation {
  readonly type: "other";
  readonly description: string;
}

type Expectation =
  | AnyExpectation
  | ClassExpectation
  | EndExpectation
  | LiteralExpectation
  | OtherExpectation;

/**
 * Pass an array of these into `SyntaxError.prototype.format()`
 */
interface SourceText {
  /**
   * Identifier of an input that was used as a grammarSource in parse().
   */
  readonly source: GrammarSource;
  /** Source text of the input. */
  readonly text: string;
}

declare class SyntaxError extends globalThis.SyntaxError {
  /**
   * Constructs the human-readable message from the machine representation.
   *
   * @param expected Array of expected items, generated by the parser
   * @param found Any text that will appear as found in the input instead of
   *   expected
   */
  static buildMessage(expected: Expectation[], found?: string | null | undefined): string;
  readonly expected: Expectation[];
  readonly found: string | null | undefined;
  readonly location: LocationRange;
  readonly name: string;
  constructor(
    message: string,
    expected: Expectation[],
    found: string | null,
    location: LocationRange,
  );

  /**
   * With good sources, generates a feature-rich error message pointing to the
   * error in the input.
   * @param sources List of {source, text} objects that map to the input.
   */
  format(sources: SourceText[]): string;
}

/**
 * Trace execution of the parser.
 */
interface ParserTracer {
  trace: (event: ParserTracerEvent) => void;
}

type ParserTracerEvent
  = {
      readonly type: "rule.enter";
      readonly rule: string;
      readonly location: LocationRange
    }
  | {
      readonly type: "rule.fail";
      readonly rule: string;
      readonly location: LocationRange
    }
  | {
      readonly type: "rule.match";
      readonly rule: string;
      readonly location: LocationRange
      /** Return value from the rule. */
      readonly result: unknown;
    };

type StartRuleNames = "data_file";
interface ParseOptions<T extends StartRuleNames = "data_file"> {
  /**
   * String or object that will be attached to the each `LocationRange` object
   * created by the parser. For example, this can be path to the parsed file
   * or even the File object.
   */
  readonly grammarSource?: GrammarSource;
  readonly startRule?: T;
  readonly tracer?: ParserTracer;

  // Internal use only:
  readonly peg$library?: boolean;
  // Internal use only:
  peg$currPos?: number;
  // Internal use only:
  peg$silentFails?: number;
  // Internal use only:
  peg$maxFailExpected?: Expectation[];
  // Extra application-specific properties
  [key: string]: unknown;
}

declare const StartRules: StartRuleNames[];
declare const parse: typeof ParseFunction;

// Overload of ParseFunction for each allowedStartRule

declare function ParseFunction<Options extends ParseOptions<"data_file">>(
  input: string,
  options?: Options,
): UCDFile;

declare function ParseFunction<Options extends ParseOptions<StartRuleNames>>(
  input: string,
  options?: Options,
): UCDFile;

export { type AnyExpectation, type ClassExpectation, type ClassParts, type ClassRange, type EndExpectation, type Expectation, type GrammarSource, type GrammarSourceObject, type LiteralExpectation, type Location, type LocationRange, type OtherExpectation, type ParseOptions, type ParserTracer, type ParserTracerEvent, type SourceText, type StartRuleNames, StartRules, SyntaxError, parse };
