"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ajv_1 = require("ajv");
const codegen_1 = require("ajv/dist/compile/codegen");
const code_1 = require("ajv/dist/compile/codegen/code");
const validate_1 = require("ajv/dist/compile/validate");
const errors_1 = require("ajv/dist/compile/errors");
const names_1 = require("ajv/dist/compile/names");
const keyword = "errorMessage";
const used = new ajv_1.Name("emUsed");
const KEYWORD_PROPERTY_PARAMS = {
    required: "missingProperty",
    dependencies: "property",
    dependentRequired: "property",
};
const INTERPOLATION = /\$\{[^}]+\}/;
const INTERPOLATION_REPLACE = /\$\{([^}]+)\}/g;
const EMPTY_STR = /^""\s*\+\s*|\s*\+\s*""$/g;
function errorMessage(options) {
    return {
        keyword,
        schemaType: ["string", "object"],
        post: true,
        code(cxt) {
            const { gen, data, schema, schemaValue, it } = cxt;
            if (it.createErrors === false)
                return;
            const sch = schema;
            const instancePath = codegen_1.strConcat(names_1.default.instancePath, it.errorPath);
            gen.if(ajv_1._ `${names_1.default.errors} > 0`, () => {
                if (typeof sch == "object") {
                    const [kwdPropErrors, kwdErrors] = keywordErrorsConfig(sch);
                    if (kwdErrors)
                        processKeywordErrors(kwdErrors);
                    if (kwdPropErrors)
                        processKeywordPropErrors(kwdPropErrors);
                    processChildErrors(childErrorsConfig(sch));
                }
                const schMessage = typeof sch == "string" ? sch : sch._;
                if (schMessage)
                    processAllErrors(schMessage);
                if (!options.keepErrors)
                    removeUsedErrors();
            });
            function childErrorsConfig({ properties, items }) {
                const errors = {};
                if (properties) {
                    errors.props = {};
                    for (const p in properties)
                        errors.props[p] = [];
                }
                if (items) {
                    errors.items = {};
                    for (let i = 0; i < items.length; i++)
                        errors.items[i] = [];
                }
                return errors;
            }
            function keywordErrorsConfig(emSchema) {
                let propErrors;
                let errors;
                for (const k in emSchema) {
                    if (k === "properties" || k === "items")
                        continue;
                    const kwdSch = emSchema[k];
                    if (typeof kwdSch == "object") {
                        propErrors || (propErrors = {});
                        const errMap = (propErrors[k] = {});
                        for (const p in kwdSch)
                            errMap[p] = [];
                    }
                    else {
                        errors || (errors = {});
                        errors[k] = [];
                    }
                }
                return [propErrors, errors];
            }
            function processKeywordErrors(kwdErrors) {
                const kwdErrs = gen.const("emErrors", ajv_1.stringify(kwdErrors));
                const templates = gen.const("templates", getTemplatesCode(kwdErrors, schema));
                gen.forOf("err", names_1.default.vErrors, (err) => gen.if(matchKeywordError(err, kwdErrs), () => gen.code(ajv_1._ `${kwdErrs}[${err}.keyword].push(${err})`).assign(ajv_1._ `${err}.${used}`, true)));
                const { singleError } = options;
                if (singleError) {
                    const message = gen.let("message", ajv_1._ `""`);
                    const paramsErrors = gen.let("paramsErrors", ajv_1._ `[]`);
                    loopErrors((key) => {
                        gen.if(message, () => gen.code(ajv_1._ `${message} += ${typeof singleError == "string" ? singleError : ";"}`));
                        gen.code(ajv_1._ `${message} += ${errMessage(key)}`);
                        gen.assign(paramsErrors, ajv_1._ `${paramsErrors}.concat(${kwdErrs}[${key}])`);
                    });
                    errors_1.reportError(cxt, { message, params: ajv_1._ `{errors: ${paramsErrors}}` });
                }
                else {
                    loopErrors((key) => errors_1.reportError(cxt, {
                        message: errMessage(key),
                        params: ajv_1._ `{errors: ${kwdErrs}[${key}]}`,
                    }));
                }
                function loopErrors(body) {
                    gen.forIn("key", kwdErrs, (key) => gen.if(ajv_1._ `${kwdErrs}[${key}].length`, () => body(key)));
                }
                function errMessage(key) {
                    return ajv_1._ `${key} in ${templates} ? ${templates}[${key}]() : ${schemaValue}[${key}]`;
                }
            }
            function processKeywordPropErrors(kwdPropErrors) {
                const kwdErrs = gen.const("emErrors", ajv_1.stringify(kwdPropErrors));
                const templatesCode = [];
                for (const k in kwdPropErrors) {
                    templatesCode.push([
                        k,
                        getTemplatesCode(kwdPropErrors[k], schema[k]),
                    ]);
                }
                const templates = gen.const("templates", gen.object(...templatesCode));
                const kwdPropParams = gen.scopeValue("obj", {
                    ref: KEYWORD_PROPERTY_PARAMS,
                    code: ajv_1.stringify(KEYWORD_PROPERTY_PARAMS),
                });
                const propParam = gen.let("emPropParams");
                const paramsErrors = gen.let("emParamsErrors");
                gen.forOf("err", names_1.default.vErrors, (err) => gen.if(matchKeywordError(err, kwdErrs), () => {
                    gen.assign(propParam, ajv_1._ `${kwdPropParams}[${err}.keyword]`);
                    gen.assign(paramsErrors, ajv_1._ `${kwdErrs}[${err}.keyword][${err}.params[${propParam}]]`);
                    gen.if(paramsErrors, () => gen.code(ajv_1._ `${paramsErrors}.push(${err})`).assign(ajv_1._ `${err}.${used}`, true));
                }));
                gen.forIn("key", kwdErrs, (key) => gen.forIn("keyProp", ajv_1._ `${kwdErrs}[${key}]`, (keyProp) => {
                    gen.assign(paramsErrors, ajv_1._ `${kwdErrs}[${key}][${keyProp}]`);
                    gen.if(ajv_1._ `${paramsErrors}.length`, () => {
                        const tmpl = gen.const("tmpl", ajv_1._ `${templates}[${key}] && ${templates}[${key}][${keyProp}]`);
                        errors_1.reportError(cxt, {
                            message: ajv_1._ `${tmpl} ? ${tmpl}() : ${schemaValue}[${key}][${keyProp}]`,
                            params: ajv_1._ `{errors: ${paramsErrors}}`,
                        });
                    });
                }));
            }
            function processChildErrors(childErrors) {
                const { props, items } = childErrors;
                if (!props && !items)
                    return;
                const isObj = ajv_1._ `typeof ${data} == "object"`;
                const isArr = ajv_1._ `Array.isArray(${data})`;
                const childErrs = gen.let("emErrors");
                let childKwd;
                let childProp;
                const templates = gen.let("templates");
                if (props && items) {
                    childKwd = gen.let("emChildKwd");
                    gen.if(isObj);
                    gen.if(isArr, () => {
                        init(items, schema.items);
                        gen.assign(childKwd, ajv_1.str `items`);
                    }, () => {
                        init(props, schema.properties);
                        gen.assign(childKwd, ajv_1.str `properties`);
                    });
                    childProp = ajv_1._ `[${childKwd}]`;
                }
                else if (items) {
                    gen.if(isArr);
                    init(items, schema.items);
                    childProp = ajv_1._ `.items`;
                }
                else if (props) {
                    gen.if(codegen_1.and(isObj, codegen_1.not(isArr)));
                    init(props, schema.properties);
                    childProp = ajv_1._ `.properties`;
                }
                gen.forOf("err", names_1.default.vErrors, (err) => ifMatchesChildError(err, childErrs, (child) => gen.code(ajv_1._ `${childErrs}[${child}].push(${err})`).assign(ajv_1._ `${err}.${used}`, true)));
                gen.forIn("key", childErrs, (key) => gen.if(ajv_1._ `${childErrs}[${key}].length`, () => {
                    errors_1.reportError(cxt, {
                        message: ajv_1._ `${key} in ${templates} ? ${templates}[${key}]() : ${schemaValue}${childProp}[${key}]`,
                        params: ajv_1._ `{errors: ${childErrs}[${key}]}`,
                    });
                    gen.assign(ajv_1._ `${names_1.default.vErrors}[${names_1.default.errors}-1].instancePath`, ajv_1._ `${instancePath} + "/" + ${key}.replace(/~/g, "~0").replace(/\\//g, "~1")`);
                }));
                gen.endIf();
                function init(children, msgs) {
                    gen.assign(childErrs, ajv_1.stringify(children));
                    gen.assign(templates, getTemplatesCode(children, msgs));
                }
            }
            function processAllErrors(schMessage) {
                const errs = gen.const("emErrs", ajv_1._ `[]`);
                gen.forOf("err", names_1.default.vErrors, (err) => gen.if(matchAnyError(err), () => gen.code(ajv_1._ `${errs}.push(${err})`).assign(ajv_1._ `${err}.${used}`, true)));
                gen.if(ajv_1._ `${errs}.length`, () => errors_1.reportError(cxt, {
                    message: templateExpr(schMessage),
                    params: ajv_1._ `{errors: ${errs}}`,
                }));
            }
            function removeUsedErrors() {
                const errs = gen.const("emErrs", ajv_1._ `[]`);
                gen.forOf("err", names_1.default.vErrors, (err) => gen.if(ajv_1._ `!${err}.${used}`, () => gen.code(ajv_1._ `${errs}.push(${err})`)));
                gen.assign(names_1.default.vErrors, errs).assign(names_1.default.errors, ajv_1._ `${errs}.length`);
            }
            function matchKeywordError(err, kwdErrs) {
                return codegen_1.and(ajv_1._ `${err}.keyword !== ${keyword}`, ajv_1._ `!${err}.${used}`, ajv_1._ `${err}.instancePath === ${instancePath}`, ajv_1._ `${err}.keyword in ${kwdErrs}`, 
                // TODO match the end of the string?
                ajv_1._ `${err}.schemaPath.indexOf(${it.errSchemaPath}) === 0`, ajv_1._ `/^\\/[^\\/]*$/.test(${err}.schemaPath.slice(${it.errSchemaPath.length}))`);
            }
            function ifMatchesChildError(err, childErrs, thenBody) {
                gen.if(codegen_1.and(ajv_1._ `${err}.keyword !== ${keyword}`, ajv_1._ `!${err}.${used}`, ajv_1._ `${err}.instancePath.indexOf(${instancePath}) === 0`), () => {
                    const childRegex = gen.scopeValue("pattern", {
                        ref: /^\/([^/]*)(?:\/|$)/,
                        code: ajv_1._ `new RegExp("^\\\/([^/]*)(?:\\\/|$)")`,
                    });
                    const matches = gen.const("emMatches", ajv_1._ `${childRegex}.exec(${err}.instancePath.slice(${instancePath}.length))`);
                    const child = gen.const("emChild", ajv_1._ `${matches} && ${matches}[1].replace(/~1/g, "/").replace(/~0/g, "~")`);
                    gen.if(ajv_1._ `${child} !== undefined && ${child} in ${childErrs}`, () => thenBody(child));
                });
            }
            function matchAnyError(err) {
                return codegen_1.and(ajv_1._ `${err}.keyword !== ${keyword}`, ajv_1._ `!${err}.${used}`, codegen_1.or(ajv_1._ `${err}.instancePath === ${instancePath}`, codegen_1.and(ajv_1._ `${err}.instancePath.indexOf(${instancePath}) === 0`, ajv_1._ `${err}.instancePath[${instancePath}.length] === "/"`)), ajv_1._ `${err}.schemaPath.indexOf(${it.errSchemaPath}) === 0`, ajv_1._ `${err}.schemaPath[${it.errSchemaPath}.length] === "/"`);
            }
            function getTemplatesCode(keys, msgs) {
                const templatesCode = [];
                for (const k in keys) {
                    const msg = msgs[k];
                    if (INTERPOLATION.test(msg))
                        templatesCode.push([k, templateFunc(msg)]);
                }
                return gen.object(...templatesCode);
            }
            function templateExpr(msg) {
                if (!INTERPOLATION.test(msg))
                    return ajv_1.stringify(msg);
                return new code_1._Code(code_1.safeStringify(msg)
                    .replace(INTERPOLATION_REPLACE, (_s, ptr) => `" + JSON.stringify(${validate_1.getData(ptr, it)}) + "`)
                    .replace(EMPTY_STR, ""));
            }
            function templateFunc(msg) {
                return ajv_1._ `function(){return ${templateExpr(msg)}}`;
            }
        },
        metaSchema: {
            anyOf: [
                { type: "string" },
                {
                    type: "object",
                    properties: {
                        properties: { $ref: "#/$defs/stringMap" },
                        items: { $ref: "#/$defs/stringList" },
                        required: { $ref: "#/$defs/stringOrMap" },
                        dependencies: { $ref: "#/$defs/stringOrMap" },
                    },
                    additionalProperties: { type: "string" },
                },
            ],
            $defs: {
                stringMap: {
                    type: "object",
                    additionalProperties: { type: "string" },
                },
                stringOrMap: {
                    anyOf: [{ type: "string" }, { $ref: "#/$defs/stringMap" }],
                },
                stringList: { type: "array", items: { type: "string" } },
            },
        },
    };
}
const ajvErrors = (ajv, options = {}) => {
    if (!ajv.opts.allErrors)
        throw new Error("ajv-errors: Ajv option allErrors must be true");
    if (ajv.opts.jsPropertySyntax) {
        throw new Error("ajv-errors: ajv option jsPropertySyntax is not supported");
    }
    return ajv.addKeyword(errorMessage(options));
};
exports.default = ajvErrors;
module.exports = ajvErrors;
module.exports.default = ajvErrors;
//# sourceMappingURL=index.js.map