'use strict';

const Assert = require('@hapi/hoek/lib/assert');

const Base = require('../base');
const Common = require('../common');
const Messages = require('../messages');


const internals = {};


module.exports = Base.extend({

    type: 'any',

    flags: {

        only: { default: false }
    },

    terms: {

        alterations: { init: null },
        examples: { init: null },
        externals: { init: null },
        metas: { init: [] },
        notes: { init: [] },
        shared: { init: null },
        tags: { init: [] },
        whens: { init: null }
    },

    rules: {

        custom: {
            method(method, description) {

                Assert(typeof method === 'function', 'Method must be a function');
                Assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');

                return this.$_addRule({ name: 'custom', args: { method, description } });
            },
            validate(value, helpers, { method }) {

                try {
                    return method(value, helpers);
                }
                catch (err) {
                    return helpers.error('any.custom', { error: err });
                }
            },
            args: ['method', 'description'],
            multi: true
        },

        messages: {
            method(messages) {

                return this.prefs({ messages });
            }
        },

        shared: {
            method(schema) {

                Assert(Common.isSchema(schema) && schema._flags.id, 'Schema must be a schema with an id');

                const obj = this.clone();
                obj.$_terms.shared = obj.$_terms.shared || [];
                obj.$_terms.shared.push(schema);
                obj.$_mutateRegister(schema);
                return obj;
            }
        },

        warning: {
            method(code, local) {

                Assert(code && typeof code === 'string', 'Invalid warning code');

                return this.$_addRule({ name: 'warning', args: { code, local }, warn: true });
            },
            validate(value, helpers, { code, local }) {

                return helpers.error(code, local);
            },
            args: ['code', 'local'],
            multi: true
        }
    },

    modifiers: {

        keep(rule, enabled = true) {

            rule.keep = enabled;
        },

        message(rule, message) {

            rule.message = Messages.compile(message);
        },

        warn(rule, enabled = true) {

            rule.warn = enabled;
        }
    },

    manifest: {

        build(obj, desc) {

            for (const key in desc) {
                const values = desc[key];

                if (['examples', 'externals', 'metas', 'notes', 'tags'].includes(key)) {
                    for (const value of values) {
                        obj = obj[key.slice(0, -1)](value);
                    }

                    continue;
                }

                if (key === 'alterations') {
                    const alter = {};
                    for (const { target, adjuster } of values) {
                        alter[target] = adjuster;
                    }

                    obj = obj.alter(alter);
                    continue;
                }

                if (key === 'whens') {
                    for (const value of values) {
                        const { ref, is, not, then, otherwise, concat } = value;
                        if (concat) {
                            obj = obj.concat(concat);
                        }
                        else if (ref) {
                            obj = obj.when(ref, { is, not, then, otherwise, switch: value.switch, break: value.break });
                        }
                        else {
                            obj = obj.when(is, { then, otherwise, break: value.break });
                        }
                    }

                    continue;
                }

                if (key === 'shared') {
                    for (const value of values) {
                        obj = obj.shared(value);
                    }
                }
            }

            return obj;
        }
    },

    messages: {
        'any.custom': '{{#label}} failed custom validation because {{#error.message}}',
        'any.default': '{{#label}} threw an error when running default method',
        'any.failover': '{{#label}} threw an error when running failover method',
        'any.invalid': '{{#label}} contains an invalid value',
        'any.only': '{{#label}} must be {if(#valids.length == 1, "", "one of ")}{{#valids}}',
        'any.ref': '{{#label}} {{#arg}} references {{:#ref}} which {{#reason}}',
        'any.required': '{{#label}} is required',
        'any.unknown': '{{#label}} is not allowed'
    }
});
