"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseModelRequiredFilters = exports.parseFilters = exports.parseOperator = void 0;
const tslib_1 = require("tslib");
const peg = tslib_1.__importStar(require("pegjs"));
const uuid_1 = require("uuid");
const errors_1 = require("./errors");
const filter_1 = require("./filter");
const filterGrammar = `ROOT
= EXPRESSION / EMPTY_STRING
EMPTY_STRING = '' {
    return {
      type: '${filter_1.FilterOperator.EQUALS}',
      values: [],
      is: true,
    }
  }

EXPRESSION
= BETWEEN_DATE / BETWEEN_NUMERICAL / NUMERICAL / DATE_RESTRICTION / LIST / TERM

BETWEEN_DATE = SPACE_SYMBOL* ("between"i / "BETWEEN") SPACE_SYMBOL+ min:DATE_STRING SPACE_SYMBOL+ ("and"i / "AND") SPACE_SYMBOL+ max:DATE_STRING {
    return {
        type: '${filter_1.FilterOperator.IN_BETWEEN}',
        values: [min, max],
        is: true
    }
   }

BETWEEN_NUMERICAL = SPACE_SYMBOL* ("between"i / "BETWEEN") SPACE_SYMBOL+ min:NUMBER SPACE_SYMBOL+ ("and"i / "AND") SPACE_SYMBOL+ max:NUMBER {
    return {
        type: '${filter_1.FilterOperator.IN_BETWEEN}',
        values: [min, max],
        is: true
    }
   }

NUMERICAL = SPACE_SYMBOL* operator:OPERATOR SPACE_SYMBOL* value:NUMBER {
    return {
        type: operator,
        values: [value]
    }
   }

OPERATOR = '>=' / '<=' / '>' / '<'

DATE_STRING
  = quotation_mark date:ISO_DATE quotation_mark { return date }
  / ISO_DATE

ISO_DATE
  = year:YEAR "-" month:MONTH "-" day:DAY time:("T" TIME "Z"?)? {
      return text();
    }

YEAR = [0-9][0-9][0-9][0-9]
MONTH = ("0" [1-9]) / ("1" [0-2])
DAY = ("0" [1-9]) / ([1-2] [0-9]) / ("3" [0-1])
TIME = [0-9][0-9] ":" [0-9][0-9] ":" [0-9][0-9] ("." [0-9]+)?

DATE_RESTRICTION = SPACE_SYMBOL* operator:DATE_OPERATOR SPACE_SYMBOL* value:NUMBER SPACE_SYMBOL* interval:DATE_INTERVAL {
    return {
        type: operator,
        values: [value],
        date_interval: interval
    }
   }

DATE_OPERATOR = 'inThePast' / 'inTheNext'
DATE_INTERVAL = 'milliseconds' / 'seconds' / 'minutes' / 'hours' / 'days' / 'weeks' / 'months' / 'years'

NUMBER
  = '-'? FLOAT ([Ee] [+-]? INTEGER)?
    { return Number(text()) }

FLOAT
  = INTEGER '.'? INTEGER?

INTEGER
  = [0-9]+

LIST
= left:TERM COMMA right:EXPRESSION {
 return {
     type: ',',
       left: left,
       right: right
   }
}

TERM
=  not:(NOT)? term:(PCT / KEYWORDS / MATCH) {
    term.is = not ? false : true
    return term
   }



KEYWORDS = ("EMPTY" / "empty") {
    return {
        type: 'blank',
    }
}
/ ("NULL" / "null") {
    return {
        type: 'null',
    }
}
MATCH
= quotation_mark sequence:(char  / COMMA / UNDERSCORE / CARET)+ quotation_mark {
       return {
           type:'${filter_1.FilterOperator.EQUALS}',
           values: [sequence.join('')]
       }
   }
   / sequence:(char  / COMMA / UNDERSCORE / CARET)+ {
    return {
        type:'${filter_1.FilterOperator.EQUALS}',
        values: [sequence.join('')]
    }
}
PCT
=  CONTAINS / STARTS_WITH / ENDS_WITH
CONTAINS
= PCT_SYMBOL value:(char / UNDERSCORE)+ PCT_SYMBOL !(string / PCT_SYMBOL / UNDERSCORE)  {
  return {
      type: '${filter_1.FilterOperator.INCLUDE}',
      values: [value.join('')]
    }
}
STARTS_WITH
= value:(char / UNDERSCORE)+ PCT_SYMBOL !(string / PCT_SYMBOL / UNDERSCORE) {
      return {
      type: '${filter_1.FilterOperator.STARTS_WITH}',
      values: [value.join('')]
  }
}
ENDS_WITH
=  PCT_SYMBOL value:(char / UNDERSCORE)+ !(PCT_SYMBOL ) {
return {
     type: '${filter_1.FilterOperator.ENDS_WITH}',
     values: [value.join('')]
 }
}
NOT = '!'
COMMA = COMMA_SYMBOL
raw_string "string"
= str:char_sequence { return str }
string "string"
= str:char_sequence { return str ? str.trim():str}
/* ----- 7. Strings ----- */
char_sequence "string"
= quotation_mark chars:(valid_char / escaped_quotation_mark)* quotation_mark {
  return '"' + chars.join("") + '"'
}
/
chars:char+ {return chars.join('') }
char "character"
=  ESCAPE_CARET
/ escape symbol:SYMBOLS { return symbol}
/ escape
sequence:(
      '"'
  / "\\\\"
  / "/"
  / "b" { return "\\b"; }
  / "f" { return "\\f"; }
  / "n" { return "\\n"; }
  / "r" { return "\\r"; }
  / "t" { return "\\t"; }
  / "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG) {
      return String.fromCharCode(parseInt(digits, 16));
    }
)
{ return sequence }
/ unescaped
/ NOT_SYMBOL
/* Returns a single space for double escaped space at the end, or before a comma
to reverse escapeWithDoubleLastEscape */
ESCAPE_CARET
= CARET (SPACE_SYMBOL ! FOLLOWING_SPACE_END / ESCAPED_DOUBLE_SPACE) {
  return " "
}
/
CARET symbol:(PCT_SYMBOL / UNDERSCORE / COMMA_SYMBOL / CARET / NOT_SYMBOL) {
  return symbol
}
SYMBOLS = PCT_SYMBOL / COMMA_SYMBOL / NOT_SYMBOL / UNDERSCORE / CARET
PCT_SYMBOL 		        = '%'
UNDERSCORE 		        = '_'
COMMA_SYMBOL   	        = ","
NOT_SYMBOL	  	        = "-"
SPACE_SYMBOL            = " "
FOLLOWING_SPACE_END     = "^ " (&COMMA_SYMBOL / !.)
ESCAPED_DOUBLE_SPACE    = " ^ "
CARET 	   		        = "^"
escape         	        = "\\\\"
escaped_quotation_mark  = '\\\\"'
quotation_mark 	        = '"'
unescaped      	        = !SYMBOLS char:char_range { return char;}
valid_char              = char:char_range { return char;}
char_range              = [^\\0-\\x1F\\x22\\x5C]
HEXDIG                  = [0-9a-f]i


`;
const parseOperator = (operator, isTrue, fieldName) => {
    switch (operator) {
        case filter_1.FilterOperator.EQUALS:
            return isTrue ? filter_1.FilterOperator.EQUALS : filter_1.FilterOperator.NOT_EQUALS;
        case filter_1.FilterOperator.INCLUDE:
            return isTrue ? filter_1.FilterOperator.INCLUDE : filter_1.FilterOperator.NOT_INCLUDE;
        case filter_1.FilterOperator.STARTS_WITH:
            return filter_1.FilterOperator.STARTS_WITH;
        case filter_1.FilterOperator.ENDS_WITH:
            return filter_1.FilterOperator.ENDS_WITH;
        case '>':
            return filter_1.FilterOperator.GREATER_THAN;
        case '>=':
            return filter_1.FilterOperator.GREATER_THAN_OR_EQUAL;
        case '<':
            return filter_1.FilterOperator.LESS_THAN;
        case '<=':
            return filter_1.FilterOperator.LESS_THAN_OR_EQUAL;
        case filter_1.FilterOperator.IN_THE_PAST:
            return filter_1.FilterOperator.IN_THE_PAST;
        case filter_1.FilterOperator.IN_THE_NEXT:
            return filter_1.FilterOperator.IN_THE_NEXT;
        case filter_1.FilterOperator.IN_BETWEEN:
            return isTrue
                ? filter_1.FilterOperator.IN_BETWEEN
                : filter_1.FilterOperator.NOT_IN_BETWEEN;
        case 'null':
        case 'NULL':
            return isTrue ? filter_1.FilterOperator.NULL : filter_1.FilterOperator.NOT_NULL;
        default:
            throw new errors_1.UnexpectedServerError(`${fieldName} uses invalid filter operator type ${operator}`);
    }
};
exports.parseOperator = parseOperator;
const parseFilters = (rawFilters) => {
    if (!rawFilters || rawFilters.length === 0) {
        return [];
    }
    const parser = peg.generate(filterGrammar);
    return rawFilters.reduce((acc, filter) => {
        if (Object.entries(filter).length !== 1)
            return acc;
        const [key, value] = Object.entries(filter)[0];
        if (value === null) {
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: { fieldRef: key },
                    operator: filter_1.FilterOperator.NULL,
                    values: [1],
                },
            ];
        }
        if (typeof value === 'string') {
            const parsedFilter = parser.parse(value);
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: { fieldRef: key },
                    operator: (0, exports.parseOperator)(parsedFilter.type, !!parsedFilter.is, key),
                    values: parsedFilter.values || [1],
                    ...(parsedFilter.date_interval
                        ? {
                            settings: {
                                unitOfTime: parsedFilter.date_interval,
                            },
                        }
                        : null),
                },
            ];
        }
        if (typeof value === 'object') {
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: { fieldRef: key },
                    operator: filter_1.FilterOperator.EQUALS,
                    values: value,
                },
            ];
        }
        return [
            ...acc,
            {
                id: (0, uuid_1.v4)(),
                target: { fieldRef: key },
                operator: filter_1.FilterOperator.EQUALS,
                values: [value],
            },
        ];
    }, []);
};
exports.parseFilters = parseFilters;
const parseModelRequiredFilters = ({ requiredFilters, defaultFilters, }) => {
    const rawFilters = [...(requiredFilters || []), ...(defaultFilters || [])];
    if (!rawFilters || rawFilters.length === 0) {
        return [];
    }
    const parser = peg.generate(filterGrammar);
    return rawFilters.reduce((acc, filter) => {
        const parseFilter = () => {
            const requiredDefault = requiredFilters?.includes(filter) ?? false;
            const filterHasMultipleKeys = Object.keys(filter).length > 1;
            if (filterHasMultipleKeys) {
                // Require is a special property that is not part of the filter grammar
                const { required, ...filterRule } = filter; // Remove require from object
                // we default to true for backwards compatibility
                return [
                    required === undefined ? requiredDefault : required,
                    filterRule,
                ];
            }
            // If there is only one key, we still want to return it as it is.
            // This is to cover the case where the filter is just { required: true }, when required is used as a field.
            // We currently don't support a "required" field which is "not" required (as in: required: false), because those keys will clash
            return [requiredDefault, filter];
        };
        const [required, filterRule] = parseFilter();
        if (Object.entries(filterRule).length !== 1)
            return acc;
        const [key, value] = Object.entries(filterRule)[0];
        if (acc.map((a) => a.target.fieldRef).includes(key)) {
            // eslint-disable-next-line no-console
            console.warn(`Duplicate filter key "${key}" in default filters`);
            return acc;
        }
        const fieldRefParts = key.split('.');
        const filterTarget = fieldRefParts.length !== 2
            ? { fieldRef: key }
            : { fieldRef: key, tableName: fieldRefParts[0] };
        if (value === null) {
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: filterTarget,
                    operator: filter_1.FilterOperator.NULL,
                    values: [1],
                    required,
                },
            ];
        }
        if (typeof value === 'string') {
            const parsedFilter = parser.parse(value);
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: filterTarget,
                    operator: (0, exports.parseOperator)(parsedFilter.type, !!parsedFilter.is, key),
                    values: parsedFilter.values || [1],
                    ...(parsedFilter.date_interval
                        ? {
                            settings: {
                                unitOfTime: parsedFilter.date_interval,
                            },
                        }
                        : null),
                    required,
                },
            ];
        }
        if (typeof value === 'object') {
            return [
                ...acc,
                {
                    id: (0, uuid_1.v4)(),
                    target: filterTarget,
                    operator: filter_1.FilterOperator.EQUALS,
                    values: value,
                    required,
                },
            ];
        }
        return [
            ...acc,
            {
                id: (0, uuid_1.v4)(),
                target: filterTarget,
                operator: filter_1.FilterOperator.EQUALS,
                values: [value],
                required,
            },
        ];
    }, []);
};
exports.parseModelRequiredFilters = parseModelRequiredFilters;
exports.default = filterGrammar;
//# sourceMappingURL=filterGrammar.js.map