'use strict';

const Hoek = require('@hapi/hoek');

const Any = require('../any');


const internals = {};


internals.Lazy = class extends Any {

    constructor() {

        super();
        this._type = 'lazy';
        this._flags.once = true;
        this._cache = null;
    }

    _init(fn, options) {

        return this.set(fn, options);
    }

    _base(value, state, options) {

        let schema;
        if (this._cache) {
            schema = this._cache;
        }
        else {
            const result = { value };
            const lazy = this._flags.lazy;

            if (!lazy) {
                result.errors = this.createError('lazy.base', null, state, options);
                return result;
            }

            schema = lazy();

            if (!(schema instanceof Any)) {
                result.errors = this.createError('lazy.schema', { schema }, state, options);
                return result;
            }

            if (this._flags.once) {
                this._cache = schema;
            }
        }

        return schema._validate(value, state, options);
    }

    set(fn, options) {

        Hoek.assert(typeof fn === 'function', 'You must provide a function as first argument');
        Hoek.assert(options === undefined || (options && typeof options === 'object' && !Array.isArray(options)), `Options must be an object`);

        if (options) {
            const unknownOptions = Object.keys(options).filter((key) => !['once'].includes(key));
            Hoek.assert(unknownOptions.length === 0, `Options contain unknown keys: ${unknownOptions}`);
            Hoek.assert(options.once === undefined || typeof options.once === 'boolean', 'Option "once" must be a boolean');
        }

        const obj = this.clone();
        obj._flags.lazy = fn;

        if (options && options.once !== obj._flags.once) {
            obj._flags.once = options.once;
        }

        return obj;
    }

};

module.exports = new internals.Lazy();
