const ls = {
    // parse JSON without throwing an error.
    parseJSON: function (str) {
        try {
            return JSON.parse(str);
        } catch (err) {
            return;
        }
    },

    // check if the datestamp is from before now and if so return true.
    isExpired: function (stamp) {
        return new Date(stamp) < new Date();
    },

    // return the localStorage key that is bound to a version of a key.
    formGetter: function (version, name) {
        return "ls__" + version + "__" + name;
    },

    // create a formData object to put in the data, a signature that it was
    // created with this library, and when it expires (if ever).
    formData: function (data, expires) {
        return {
            data: data,
            __valid: true,
            expires: new Date().getTime() + expires,
        };
    },

    getData: function (version, name) {
        const key = this.formGetter(version, name);
        let data = localStorage.getItem(key);
        data = ls.parseJSON(data);

        if (data) {
            if (data.__valid) {
                // JSON forms of data with `Infinity` turns into `null`,
                // so if null then it hasn't expired since nothing was specified.
                if (!ls.isExpired(data.expires) || data.expires === null) {
                    return data;
                }
            }
        }
    },

    // set the wrapped version of the data into localStorage.
    setData: function (version, name, data, expires) {
        const key = this.formGetter(version, name);
        const val = this.formData(data, expires);

        localStorage.setItem(key, JSON.stringify(val));
    },

    // remove the key from localStorage and from memory.
    removeData: function (version, name) {
        const key = this.formGetter(version, name);

        localStorage.removeItem(key);
    },

    // Remove keys which match a regex.
    removeDataRegex: function (version, regex) {
        const key_regex = new RegExp(this.formGetter(version, regex));
        const keys = Object.keys(localStorage).filter(function (key) {
            return key_regex.test(key);
        });

        keys.forEach(function (key) {
            localStorage.removeItem(key);
        });
    },

    // migrate from an older version of a data src to a newer one with a
    // specified callback function.
    migrate: function (name, v1, v2, callback) {
        const old = this.getData(v1, name);
        this.removeData(v1, name);

        if (old && old.__valid) {
            const data = callback(old.data);
            this.setData(v2, name, data, Infinity);

            return data;
        }
    },
};

// return a new function instance that has instance-scoped variables.
const localstorage = function () {
    const _data = {
        VERSION: 1,
        expires: Infinity,
        expiresIsGlobal: false,
    };

    const prototype = {
        // `expires` should be a Number that represents the number of ms from
        // now that this should expire in.
        // this allows for it to either be set only once or permanently.
        setExpiry: function (expires, isGlobal) {
            _data.expires = expires;
            _data.expiresIsGlobal = isGlobal || false;

            return this;
        },

        get: function (name) {
            const data = ls.getData(_data.VERSION, name);

            if (data) {
                return data.data;
            }
        },

        set: function (name, data) {
            if (typeof _data.VERSION !== "undefined") {
                ls.setData(_data.VERSION, name, data, _data.expires);

                // if the expires attribute was not set as a global, then
                // make sure to return it back to Infinity to not impose
                // constraints on the next key.
                if (!_data.expiresIsGlobal) {
                    _data.expires = Infinity;
                }

                return true;
            }

            return false;
        },

        // remove a key with a given version.
        remove: function (name) {
            ls.removeData(_data.VERSION, name);
        },

        // Remove keys which match the pattern given by name.
        removeRegex: function (name) {
            ls.removeDataRegex(_data.VERSION, name);
        },

        migrate: function (name, v1, v2, callback) {
            return ls.migrate(name, v1, v2, callback);
        },
    };

    // set a new master version for the LocalStorage instance.
    Object.defineProperty(prototype, "version", {
        get: function () {
            return _data.VERSION;
        },
        set: function (version) {
            _data.VERSION = version;

            return prototype;
        },
    });

    return prototype;
};

let warned_of_localstorage = false;

localstorage.supported = function supports_localstorage() {
    try {
        return window.hasOwnProperty('localStorage') && window.localStorage !== null;
    } catch (e) {
        if (!warned_of_localstorage) {
            blueslip.error("Client browser does not support local storage, will lose socket message on reload");
            warned_of_localstorage = true;
        }
        return false;
    }
};

module.exports = localstorage;
window.localstorage = localstorage;
