import { Reviver } from 'comment-json';

declare class JSONError extends Error {

  /**
   * Visual representation of the error encountered
   */
  codeframe: string;
  /**
   * The line number of the error within the source
   */
  line: number;
  /**
   * The column number of the error within the source
   */
  column: number;

}

interface SortOptions {
  /**
   * Whether or not objects should be sorted in the structure
   *
   *
   * @example
   *
   * // Defaults to true
   * { sortObjects: false }
   *
   * // BEFORE
   *
   * {
   *  b: ['b','c','a'],
   *  c: { b: 2, c: 3, a: 1 }
   *  a: [2,1]
   * }
   *
   * // AFTER
   *
   * {
   *  a: [1,2]
   *  b: ['a','b','c'],
   *  c: { b: 2, c: 3, a: 1 } // preserved when false
   * }
   */
  objects?: boolean;
  /**
   * Whether or not arrays should be sorted
   *
   * @default false
   *
   * @example
   *
   * // Defaults to false
   * { sortArrays: true }
   *
   * // BEFORE
   *
   * {
   *  a: ['b','c','a'],
   *  b: [2,1]
   * }
   *
   * // AFTER
   *
   * {
   *  a: ['a','b','c'], // sorted
   *  b: [1,2] // sorted
   * }
   */
  arrays?: boolean;
  /**
   * String list of property names that should apply sorting. Only these entries will sorted.
   * You can target deep structures using `.` separators.
   *
   * @default []
   *
   * @example
   *
   * // TARGET
   *
   * ['d.c']
   *
   * // BEFORE
   *
   * {
   *  a: { b: 'x' },
   *  c: true,
   *  d: { d: '2', c: { f: 'x', e: 'x' }, b: '1', a: '0' },
   *  b: [2,1],
   * }
   *
   * // AFTER
   *
   * {
   *  a: { b: 'x' },
   *  c: true,
   *  d: { a: '0', b: '1', c: { e: 'x', f: 'x' }, d: '2' }, // sorted
   *  b: [2,1],
   * }
   */
  target?: string[];
  /**
   * String list of property names whos value is either an object or array
   * that should be excluded from sorting.
   *
   * @default []
   *
   * @example
   *
   * // EXCLUDE
   *
   * ['b', 'c']
   *
   * // BEFORE
   *
   * {
   *  a: { b: 'x' },
   *  c: true,
   *  d: { c: { f: 'x', e: 'x' } },
   *  b: [2,1],
   * }
   *
   * // AFTER
   *
   * {
   *  a: { b: 'x' },
   *  b: [2,1],  // b prop was sorted but value was not
   *  c: true,
   *  d: { c: { e: 'x', f: 'x' } } // c was sorted
   * }
   */
  exclude?: string[];
}

interface EvaluateOptions {
  /**
   * Whether or not stringify should use tab `\t` characters
   *
   * @default false
   */
  useTab?: boolean;
  /**
   * The indentation size. When `useTab` is `true` will apply at division of `2`
   *
   * @default 2
   */
  indentSize?: number;
  /**
   * CRLF Line endings, When `true` will apply `\r\n` line enders, otherwise `\n`
   *
   * @default false
   */
  crlf?: boolean;
  /**
   * Controls structure sorting of the returned `string`. When `false` then
   * structure will not be sorted. By default, no sorting is applied.
   *
   * @default false
   */
  sorting?: boolean | SortOptions;
  /**
   * Controls structure sorting to be applied to checksum hash. By default,
   * sorting is applied to objects, but not arrays.
   *
   * @default
   * {
   *   arrays: false,
   *   objects: true,
   *   exclude: []
   * }
   */
  hashSort?: boolean | SortOptions
}

interface FormatOptions {
  /**
   * Whether or not stringify should use tab `\t` characters
   *
   * @default false
   */
  useTab?: boolean;
  /**
   * The indentation size. When `useTab` is `true` will apply at division of `2`
   *
   * @default 2
   */
  indentSize?: number;
  /**
   * CRLF Line endings, When `true` will apply `\r\n` line enders, otherwise `\n`
   *
   * @default false
   */
  crlf?: boolean;
  /**
   * Whether or not to remove comments from the structure
   *
   * @default false
   */
  removeComments?: boolean;
  /**
   * Sorting options to apply to the JSON structure
   */
  sorting?: SortOptions
}

interface StringifyOptions extends EvaluateOptions {
  /**
   * A function that transforms the results or an array of strings and numbers that acts as a approved
   * list for selecting the object properties that will be stringified.
   *
   * @default null
   */
  replacer?: Reviver | null;
}

interface ParseEvaluate<T = any> {
  /**
   * The original `source` JSON string passed into the function, this
   * is untouched and will be identical to the `evaluate(source)` value.
   */
  source: string;
  /**
   * The processed `source` JSON string with applied format and sorting changes.
   *
   * > This is the value you'd use when writing to disk.
   */
  string: string;
  /**
   * The `source` JSON parsed into a workable format.
   */
  parsed: T;
  /**
   * A **checksum** string or the processed `source` with `hashSort` options applied.
   * The checksum generated using a version of the `source` JSON which has comments
   * omitted along with newlines, indentation and whitespace stripped.
   *
   * > This is the value you'd used for diffing purposes.
   */
  hashed: string;
}

/**
 * Deep alpha-numeric sorting of an object or array structure.
 *
 * @param source
 * An object or array to be sorted
 *
 * @param options
 * Elementary level control over sorting operations
 */
declare const sort: <T = any>(source: T, options?: SortOptions) => T;

/**
 * Performs a deep equality comparison check against 2 structures. Returns a boolean
 * value indicating whether or not strucures match.
 *
 * @param actual
 * Either a json string, object or array
 *
 * @param expected
 * Either a json string, object or array
 *
 * @param options
 * Elementary level control over sorting operations
 *
 * @example
 *
 * const matches = equality(actual, expected, {
 *    objects: true,      // Sort objects before equality check (optional)
 *    arrays: false,      // Sort objects before equality check (optional)
 *    exclude: []         // Property names to excude before equality check
 * })
 */
declare const equality: (actual: unknown, expected: unknown, options?: boolean | SortOptions) => boolean;

/**
 * Evaluate returns model representing the various structures produced
 *
 * @example
 *
 * const value = `{
 *   a: {
 *     b: [
 *       // line comment
 *       { c: 'string' }
 *     ]
 *   }
 * }`
 *
 * // Evaluate parses the value and returns the following variations
 *
 * const {
 *   string,               // (getter) Writeable string with comments
 *   parsed,               // (getter) The parsed JSON object
 *   source,               // (getter) The original source value
 *   hashed                // (getter) Checksum MD5 hash
 * } = evaluate(value, {
 *   crlf: false,          // Use CRLF line endings (optional)
 *   useTab: false,        // Use Tab spacing (optional)
 *   indentSize: 2,        // The indentation size (optional)
 *   sorting: {
 *     objects: true,      // Sort objects in structure (optional)
 *     arrays: false,      // Sort Arrays in structure (optional)
 *     exclude: []         // Property names to excude in sorting
 *   },
 *   hashSort: {
 *     objects: true,      // Sort objects for the checksum hash (optional)
 *     arrays: false,      // Sort Arrays for the checksum hash (optional)
 *     exclude: []         // Key names to exclude in sort for checksum hash (optional)
 *   }
 * })
 */
declare const evaluate: <T = any>(source: string, options?: EvaluateOptions) => ParseEvaluate<T>;

/**
 * Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
 */
declare const stringify: (value: unknown, options?: StringifyOptions) => string;

/**
 * Parses and formats a JSON structure. Returns the formatted result as a string
 */
declare const format: (value: unknown, options?: FormatOptions) => string;

/**
 * Detects the indentation of a JSON (or other) structure.
 * Determines indent by the frequency occurrence and returns a basic model.
 *
 * @param string
 * JSON string structure
 *
 * @example
 *
 * const detect = getIndent(`
 * {
 *   "foo": 1,
 *   "bar": 2,
 *   "baz": 3,
 * }
 * `)
 *
 * detect.type        // => 'space'
 * detect.indent      // => '  '
 * detect.indentSize  // => 2
 * detect.indentChar  // => ' '
 */
declare const getIndent: (value: string) => {
  /**
   * The type of indentation
   */
  type: 'space' | 'tab';
  /**
   * String representation of the indentation
   */
  indent: string;
  /**
   * Indentation Size
   */
  indentSize: number;
  /**
   * The indentation character, e.g:`\t` or ` `
   */
  indentChar: string;
};

/**
 * Converts a JavaScript Object Notation (JSON) string into an object, removes comments and
 * trailing comma occurences. Supports JSON5 syntax.
 *
 * @param value
 * A valid JSON string.

 * @param
 * Reviver function, same as `JSON.parse({}, () => {})
 */
declare const parse: <T = any>(value: string, reviver?: Reviver | null) => T;

export { type EvaluateOptions, type FormatOptions, JSONError, type ParseEvaluate, type SortOptions, type StringifyOptions, equality, evaluate, format, getIndent, parse, sort, stringify };
