'use strict';

const { assert } = require('@hapi/hoek');

const Any = require('./any');
const Common = require('../common');
const Values = require('../values');


const internals = {};


internals.isBool = function (value) {

    return typeof value === 'boolean';
};


module.exports = Any.extend({

    type: 'boolean',

    flags: {

        sensitive: { default: false }
    },

    terms: {

        falsy: {
            init: null,
            manifest: 'values'
        },

        truthy: {
            init: null,
            manifest: 'values'
        }
    },

    coerce(value, { schema }) {

        if (typeof value === 'boolean') {
            return;
        }

        if (typeof value === 'string') {
            const trimmedValue = value.trim();
            const normalized = schema._flags.sensitive ? trimmedValue : trimmedValue.toLowerCase();
            value = normalized === 'true' ? true : (normalized === 'false' ? false : value);
        }

        if (typeof value !== 'boolean') {
            value = schema.$_terms.truthy && schema.$_terms.truthy.has(value, null, null, !schema._flags.sensitive) ||
                (schema.$_terms.falsy && schema.$_terms.falsy.has(value, null, null, !schema._flags.sensitive) ? false : value);
        }

        return { value };
    },

    validate(value, { error }) {

        if (typeof value !== 'boolean') {
            return { value, errors: error('boolean.base') };
        }
    },

    rules: {
        truthy: {
            method(...values) {

                Common.verifyFlat(values, 'truthy');

                const obj = this.clone();
                obj.$_terms.truthy = obj.$_terms.truthy || new Values();

                for (let i = 0; i < values.length; ++i) {
                    const value = values[i];

                    assert(value !== undefined, 'Cannot call truthy with undefined');
                    obj.$_terms.truthy.add(value);
                }

                return obj;
            }
        },

        falsy: {
            method(...values) {

                Common.verifyFlat(values, 'falsy');

                const obj = this.clone();
                obj.$_terms.falsy = obj.$_terms.falsy || new Values();

                for (let i = 0; i < values.length; ++i) {
                    const value = values[i];

                    assert(value !== undefined, 'Cannot call falsy with undefined');
                    obj.$_terms.falsy.add(value);
                }

                return obj;
            }
        },

        sensitive: {
            method(enabled = true) {

                return this.$_setFlag('sensitive', enabled);
            }
        }
    },

    cast: {
        number: {
            from: internals.isBool,
            to(value, helpers) {

                return value ? 1 : 0;
            }
        },
        string: {
            from: internals.isBool,
            to(value, helpers) {

                return value ? 'true' : 'false';
            }
        }
    },

    manifest: {

        build(obj, desc) {

            if (desc.truthy) {
                obj = obj.truthy(...desc.truthy);
            }

            if (desc.falsy) {
                obj = obj.falsy(...desc.falsy);
            }

            return obj;
        }
    },

    messages: {
        'boolean.base': '{{#label}} must be a boolean'
    }
});
