import { __decorate } from 'tslib';
import { ɵɵdefineInjectable, Injectable } from '@angular/core';

class PolyfillStorage {
    constructor(_storage) {
        this._storage = _storage;
        // State.
        this._isSupported = false;
        this.encode = (value, attributes) => {
            // Make sure the expires attribute is a date and not a number.
            const data = { value };
            if (typeof attributes.expires === 'number') {
                attributes.expires = new Date(attributes.expires);
            }
            if (attributes.expires) {
                data['expires'] = attributes.expires;
            }
            return JSON.stringify(data);
        };
        this.testIfSupported = () => {
            if (typeof this._storage === 'undefined') {
                return false;
            }
            try {
                const x = '__storage_test__';
                this._storage.setItem(x, x);
                this._storage.removeItem(x);
                return true;
            }
            catch (error) {
                return error instanceof DOMException
                    && (error.code === 22 ||
                        error.code === 1014 ||
                        error.name === 'QuotaExceededError' ||
                        error.name === 'NS_ERROR_DOM_QUOTA_REACHED')
                    && (this._storage && this._storage.length !== 0);
            }
        };
        // Validate the storage was passed.
        if (!this._storage) {
            throw new Error('No polyfill storage was passed.');
        }
        // Check if the storage is supported.
        this._isSupported = this.testIfSupported();
    }
    get(token) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return undefined;
        }
        return this.decode(token);
    }
    set(token, value, attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        // Stringify the data.
        const rawData = this.encode(value, Object.assign({}, attributes));
        // Set the raw data in the storage.
        this._storage.setItem(token, rawData);
    }
    remove(token, attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        // Remove the item in the storage.
        this._storage.removeItem(token);
    }
    clear(attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        // Clear the storage.
        this._storage.clear();
    }
    isSupported() {
        return this._isSupported;
    }
    decode(token) {
        // Retrieve the raw data from the storage.
        const rawData = this._storage.getItem(token);
        // Try to parse the data.
        let parsedData = undefined;
        // Only parse the data when it is not null.
        if (rawData !== null) {
            try {
                const { value, expires } = JSON.parse(rawData);
                if (expires !== undefined) {
                    const now = new Date().getTime();
                    const expireDate = new Date(expires).getTime();
                    if (expireDate <= now) {
                        this.remove(token);
                        return undefined;
                    }
                }
                parsedData = value;
            }
            catch (_a) {
                // We dont do anything with failed parsing.
            }
        }
        // Return the parsed data.
        return parsedData;
    }
}

class CookieStorage {
    constructor() {
        // State.
        this._isSupported = false;
        this.decode = (cookieString) => {
            const result = {};
            const cookies = cookieString ? cookieString.split('; ') : [];
            const rdecode = /(%[0-9A-Z]{2})+/g;
            for (let index = 0; index < cookies.length; index++) {
                const parts = cookies[index].split('=');
                let cookie = parts.slice(1).join('=');
                if (cookie.charAt(0) === '"') {
                    cookie = cookie.slice(1, -1);
                }
                try {
                    const name = parts[0].replace(rdecode, decodeURIComponent);
                    const value = cookie.replace(rdecode, decodeURIComponent);
                    result[name] = this.jsonParse(value);
                }
                catch (_a) {
                    // Ignore cookies with an invalid name/value encoding.
                }
            }
            return result;
        };
        this.jsonParse = (rawValue) => {
            // Try to parse the data.
            let parsedValue = undefined;
            // Only parse the value when it is not null.
            if (rawValue !== undefined) {
                try {
                    parsedValue = JSON.parse(rawValue);
                }
                catch (_a) {
                    // The string could not be parsed to JSON.
                }
            }
            // Return the parsed value.
            return parsedValue;
        };
        this.stringifyAttribute = (token, value) => {
            if (!value) {
                return '';
            }
            const stringified = `; ${token}`;
            // Boolean attributes should not have a value.
            if (value === true) {
                return stringified;
            }
            return stringified + `= ${value}`;
        };
        this.testIfSupported = () => {
            return (navigator && navigator.cookieEnabled) || false;
        };
        // Check if the storage is supported.
        this._isSupported = this.testIfSupported();
    }
    get(token) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return undefined;
        }
        // Get all values and pick one out.
        const values = this.all();
        return values[token];
    }
    set(token, value, attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        // Write a new cookie.
        document.cookie = this.encode(token, value, Object.assign({ path: '/' }, attributes));
    }
    remove(token, attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        this.set(token, '', Object.assign(Object.assign({}, attributes), { expires: -1 }));
    }
    clear(attributes) {
        // Validate that the storage is supported.
        if (!this._isSupported) {
            return;
        }
        const items = this.all();
        Object.keys(items)
            .forEach(token => this.remove(token, attributes));
    }
    isSupported() {
        return this._isSupported;
    }
    all() {
        return this.decode(document.cookie);
    }
    encode(token, value, attributes) {
        // Encode the token.
        const encodedToken = encodeURIComponent(token)
            .replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent)
            .replace(/\(/g, '%28').replace(/\)/g, '%29');
        const rawValue = JSON.stringify(value);
        // Encode the value.
        const encodedValue = encodeURIComponent(rawValue)
            .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
        // Return the completed encoded string.
        return [encodedToken, '=', encodedValue, this.stringifyCookie(attributes)].join('');
    }
    stringifyCookie(attributes) {
        // Make sure the expires attribute is a date and not a number.
        if (typeof attributes.expires === 'number') {
            const expires = new Date();
            expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
            attributes.expires = expires;
        }
        // Stringify all attributes.
        return this.stringifyAttribute('Expires', attributes.expires ? attributes.expires.toUTCString() : '')
            + this.stringifyAttribute('Domain', attributes.domain)
            + this.stringifyAttribute('Path', attributes.path)
            + this.stringifyAttribute('Secure', attributes.secure)
            + this.stringifyAttribute('SameSite', attributes.sameSite);
    }
}

class MemoryStorage {
    constructor() {
        // State.
        this._storage = {};
        this.encode = (value, attributes) => {
            const data = { value };
            // Make sure the expires is a date object.
            if (typeof attributes.expires === 'number') {
                attributes.expires = new Date(attributes.expires);
            }
            // Add the expire date if it was set.
            if (attributes.expires) {
                data['expires'] = attributes.expires;
            }
            // Return the encoded data.
            return data;
        };
    }
    get(token) {
        // Grab the data from the memory storage.
        const result = this._storage[token];
        // Validate the data exists.
        if (result === undefined) {
            return undefined;
        }
        // Extract the value and expire date.
        const { value, expires } = result;
        // Validate the value is still valid.
        if (expires !== undefined) {
            const now = new Date().getTime();
            const expireDate = new Date(expires).getTime();
            // Remove the value if it is expired.
            if (expireDate <= now) {
                this.remove(token);
                return undefined;
            }
        }
        // Return the value.
        return value;
    }
    set(token, value, attributes) {
        // Save the encoded value with the attributes.
        this._storage[token] = this.encode(value, Object.assign({}, attributes));
    }
    remove(token, attributes) {
        delete this._storage[token];
    }
    clear(attributes) {
        this._storage = {};
    }
    isSupported() {
        // The memory storage is always supported.
        return true;
    }
}

let Storage = class Storage {
    constructor() {
        // All available strategies (sorted best to last).
        this._strategies = {
            local: new PolyfillStorage(localStorage),
            cookie: new CookieStorage(),
            session: new PolyfillStorage(sessionStorage),
            memory: new MemoryStorage()
        };
        // Current selected storage strategy.
        this._current = null;
        this._current = this.findBestStorageStrategy();
    }
    get local() {
        return this._strategies.local;
    }
    get cookie() {
        return this._strategies.cookie;
    }
    get session() {
        return this._strategies.session;
    }
    get memory() {
        return this._strategies.memory;
    }
    get(token) {
        return this._current.get(token);
    }
    set(token, value, attributes) {
        this._current.set(token, value, attributes);
    }
    remove(token, attributes) {
        this._current.remove(token, attributes);
    }
    clear(attributes) {
        this._current.clear(attributes);
    }
    isSupported() {
        return !!this._current;
    }
    findBestStorageStrategy() {
        const types = Object.keys(this._strategies);
        // Loop through all available strategies to find the best.
        for (let index = 0; index < types.length; index++) {
            const type = types[index];
            const strategy = this._strategies[type];
            // The first supported strategy is the best.
            if (strategy.isSupported()) {
                return strategy;
            }
        }
        // If no strategy was returned we emit an error.
        throw new Error('No storage strategy was available.');
    }
};
Storage.ɵprov = ɵɵdefineInjectable({ factory: function Storage_Factory() { return new Storage(); }, token: Storage, providedIn: "root" });
Storage = __decorate([
    Injectable({
        providedIn: 'root'
    })
], Storage);

/**
 * Generated bundle index. Do not edit.
 */

export { CookieStorage, MemoryStorage, PolyfillStorage, Storage };
//# sourceMappingURL=bravobit-bb-foundation-storage.js.map
