// This file is adapted from the `json-logic-js` type definitions in the DefinitelyTyped
// repository. Function declarations and declaration-specific lines have been removed.
// See https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f8e745a63ae4890edbc871f2adf8c292f313c7cf/types/json-logic-js/index.d.ts.

type RenameToIn<T> = {
  [K in keyof T as K extends `in${Uppercase<string>}${Lowercase<string>}` ? `in` : K]: T[K]
}

/**
 * This is a utility type used below for the "if" operation.
 * Original: https://stackoverflow.com/a/68373774/765987
 */
type MAXIMUM_ALLOWED_BOUNDARY = 80
type Mapped<
  Tuple extends unknown[],
  Result extends unknown[] = [],
  Count extends ReadonlyArray<number> = []
> = Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
  ? Result
  : Tuple extends []
    ? []
    : Result extends []
      ? Mapped<Tuple, Tuple, [...Count, 1]>
      : Mapped<Tuple, Result | [...Result, ...Tuple], [...Count, 1]>
/**
 * Used for the "if" operation, which takes an array of odd length
 * and a minimum of three (3) elements.
 */
type AnyArrayOfOddLengthMin3 = [any, ...Mapped<[any, any]>]

export type ReservedOperations =
  | 'var'
  | 'missing'
  | 'missing_some'
  | 'if'
  | '=='
  | '==='
  | '!='
  | '!=='
  | '!'
  | '!!'
  | 'or'
  | 'and'
  | '>'
  | '>='
  | '<'
  | '<='
  | 'max'
  | 'min'
  | '+'
  | '-'
  | '*'
  | '/'
  | '%'
  | 'map'
  | 'filter'
  | 'reduce'
  | 'all'
  | 'none'
  | 'some'
  | 'merge'
  | 'in'
  | 'cat'
  | 'substr'
  | 'log'

/**
 * This can be an object with any key except the reserved keys.
 * TODO: Find a way to limit this type to exactly one (1) key, since
 * json-logic-js enforces it. See:
 * https://github.com/jwadhams/json-logic-js/blob/2.0.2/logic.js#L180
 */
export type AdditionalOperation = Partial<Record<ReservedOperations, never>> & { [k: string]: any }

interface AllReservedOperationsInterface<AddOps extends AdditionalOperation = never> {
  var: RulesLogic<AddOps> | [RulesLogic<AddOps>] | [RulesLogic<AddOps>, any] | [RulesLogic<AddOps>, any]
  missing: RulesLogic<AddOps> | any[]
  missing_some: [RulesLogic<AddOps>, RulesLogic<AddOps> | any[]]
  if: AnyArrayOfOddLengthMin3
  '==': [any, any]
  '===': [any, any]
  '!=': [any, any]
  '!==': [any, any]
  '!': any
  '!!': any
  or: Array<RulesLogic<AddOps>>
  and: Array<RulesLogic<AddOps>>
  '>': [RulesLogic<AddOps>, RulesLogic<AddOps>]
  '>=': [RulesLogic<AddOps>, RulesLogic<AddOps>]
  '<': [RulesLogic<AddOps>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>]
  '<=': [RulesLogic<AddOps>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>]
  max: Array<RulesLogic<AddOps>>
  min: Array<RulesLogic<AddOps>>
  '+': Array<RulesLogic<AddOps>> | RulesLogic<AddOps>
  '-': Array<RulesLogic<AddOps>> | RulesLogic<AddOps>
  '*': Array<RulesLogic<AddOps>> | RulesLogic<AddOps>
  '/': Array<RulesLogic<AddOps>> | RulesLogic<AddOps>
  '%': [RulesLogic<AddOps>, RulesLogic<AddOps>]
  map: [RulesLogic<AddOps>, RulesLogic<AddOps>]
  filter: [RulesLogic<AddOps>, RulesLogic<AddOps>]
  reduce: [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>]
  all: [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>]
  none: [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>]
  some: [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>]
  merge: Array<Array<RulesLogic<AddOps>> | RulesLogic<AddOps>>
  inArray: [RulesLogic<AddOps>, Array<RulesLogic<AddOps>>]
  inString: [RulesLogic<AddOps>, RulesLogic<AddOps>]
  cat: Array<RulesLogic<AddOps>>
  substr: [RulesLogic<AddOps>, RulesLogic<AddOps>] | [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>]
  log: RulesLogic<AddOps>
}

export type JsonLogicVar<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'var'
>
export type JsonLogicMissing<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'missing'
>
export type JsonLogicMissingSome<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'missing_some'
>
export type JsonLogicIf = Pick<AllReservedOperationsInterface, 'if'>
export type JsonLogicEqual = Pick<AllReservedOperationsInterface, '=='>
export type JsonLogicStrictEqual = Pick<AllReservedOperationsInterface, '==='>
export type JsonLogicNotEqual = Pick<AllReservedOperationsInterface, '!='>
export type JsonLogicStrictNotEqual = Pick<AllReservedOperationsInterface, '!=='>
export type JsonLogicNegation = Pick<AllReservedOperationsInterface, '!'>
export type JsonLogicDoubleNegation = Pick<AllReservedOperationsInterface, '!!'>
export type JsonLogicOr<AddOps extends AdditionalOperation = never> = Pick<AllReservedOperationsInterface<AddOps>, 'or'>
export type JsonLogicAnd<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'and'
>
export type JsonLogicGreaterThan<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '>'
>
export type JsonLogicGreaterThanOrEqual<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '>='
>
export type JsonLogicLessThan<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '<'
>
export type JsonLogicLessThanOrEqual<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '<='
>
export type JsonLogicMax<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'max'
>
export type JsonLogicMin<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'min'
>
export type JsonLogicSum<AddOps extends AdditionalOperation = never> = Pick<AllReservedOperationsInterface<AddOps>, '+'>
export type JsonLogicDifference<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '-'
>
export type JsonLogicProduct<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '*'
>
export type JsonLogicQuotient<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '/'
>
export type JsonLogicRemainder<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  '%'
>
export type JsonLogicMap<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'map'
>
export type JsonLogicFilter<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'filter'
>
export type JsonLogicReduce<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'reduce'
>
export type JsonLogicAll<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'all'
>
export type JsonLogicNone<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'none'
>
export type JsonLogicSome<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'some'
>
export type JsonLogicMerge<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'merge'
>
export type JsonLogicInArray<AddOps extends AdditionalOperation = never> = RenameToIn<
  Pick<AllReservedOperationsInterface<AddOps>, 'inArray'>
>
export type JsonLogicInString<AddOps extends AdditionalOperation = never> = RenameToIn<
  Pick<AllReservedOperationsInterface<AddOps>, 'inString'>
>
export type JsonLogicCat<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'cat'
>
export type JsonLogicSubstr<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'substr'
>
export type JsonLogicLog<AddOps extends AdditionalOperation = never> = Pick<
  AllReservedOperationsInterface<AddOps>,
  'log'
>

export type RulesLogic<AddOps extends AdditionalOperation = never> =
  | boolean
  | string
  | number

  // Accessing Data - https://jsonlogic.com/operations.html#accessing-data
  | JsonLogicVar<AddOps>
  | JsonLogicMissing<AddOps>
  | JsonLogicMissingSome<AddOps>

  // Logic and Boolean Operations - https://jsonlogic.com/operations.html#logic-and-boolean-operations
  | JsonLogicIf
  | JsonLogicEqual
  | JsonLogicStrictEqual
  | JsonLogicNotEqual
  | JsonLogicStrictNotEqual
  | JsonLogicNegation
  | JsonLogicDoubleNegation
  | JsonLogicOr<AddOps>
  | JsonLogicAnd<AddOps>

  // Numeric Operations - https://jsonlogic.com/operations.html#numeric-operations
  | JsonLogicGreaterThan<AddOps>
  | JsonLogicGreaterThanOrEqual<AddOps>
  | JsonLogicLessThan<AddOps>
  | JsonLogicLessThanOrEqual<AddOps>
  | JsonLogicMax<AddOps>
  | JsonLogicMin<AddOps>
  | JsonLogicSum<AddOps>
  | JsonLogicDifference<AddOps>
  | JsonLogicProduct<AddOps>
  | JsonLogicQuotient<AddOps>
  | JsonLogicRemainder<AddOps>

  // Array Operations - https://jsonlogic.com/operations.html#array-operations
  | JsonLogicMap<AddOps>
  | JsonLogicFilter<AddOps>
  | JsonLogicReduce<AddOps>
  | JsonLogicAll<AddOps>
  | JsonLogicNone<AddOps>
  | JsonLogicSome<AddOps>
  | JsonLogicMerge<AddOps>
  | JsonLogicInArray<AddOps>

  // String Operations - https://jsonlogic.com/operations.html#string-operations
  | JsonLogicInString<AddOps>
  | JsonLogicCat<AddOps>
  | JsonLogicSubstr<AddOps>

  // Miscellaneous - https://jsonlogic.com/operations.html#miscellaneous
  | JsonLogicLog<AddOps>

  // Adding Operations (https://jsonlogic.com/add_operation.html)
  | AddOps
