import { InjectionToken, Optional, Inject, Injectable, Pipe, APP_INITIALIZER, LOCALE_ID, NgModule } from '@angular/core';
import { __awaiter, __decorate, __param } from 'tslib';
import { Storage } from '@bravobit/bb-foundation/storage';
import { Languages } from '@bravobit/bb-foundation';
import { HttpClient, HttpClientModule } from '@angular/common/http';

//
// The configuration that can be passed when
// the user initializes the localize module.
//
class LocalizeConfig {
    constructor() {
        this.dictionary = [];
    }
}

//
// This is the injection token that will be injected
// into the service so that the all localize functions
// can retrieved and used.
//
const LOCALIZE_FUNCTION = new InjectionToken('localize_function');

//
// This is the injection token that will be injected
// into the service so that the all extra dictionaries
// can retrieved and used.
//
const LOCALIZE_EXTRA_DICTIONARY = new InjectionToken('localize_extra_dictionary');

//
// This function transform the value
// to a lowercase variant of the value.
//
class LocalizeLowercaseFunction {
    keyword() {
        return 'lowercase';
    }
    transform(value) {
        return (value || '').toLowerCase();
    }
}

//
// This function transform the value
// to a uppercase variant of the value.
//
class LocalizeUppercaseFunction {
    keyword() {
        return 'uppercase';
    }
    transform(value) {
        return (value || '').toUpperCase();
    }
}

const ɵ0 = {
    locale: 'en-US',
    data: {
        'form-control-errors': {
            'unknown': 'The field has an unknown error.',
            'required': 'This field is required.',
            'pattern': 'Invalid format.',
            'email': 'The field does not match a valid email address.',
            'emailTaken': 'The e-mail is already taken.',
            'passwordMatch': 'The passwords don\'t match.',
            'confirm': 'The field does not match.',
            'fullNumber': 'The field should be a full number.',
            'min': 'Minimum amount should be {{min}}.',
            'max': 'Maximum amount should be {{max}}.',
            'maxFileSize': 'The file has exceed the maximum amount of {{maxSize}}.',
            'minlength': 'Expected a minimum of {{requiredLength}} characters but got {{actualLength}} characters.',
            'maxlength': 'Expected a maximum of {{requiredLength}} characters but got {{actualLength}} characters.'
        },
        'tables': {
            'search': 'Search...',
            'loading': 'Loading...',
            'empty': 'There is nothing to find here.',
            'page': 'page',
            'error_datasource': 'No data source was provided.',
            'error_unauthorized': 'You need to be authenticated to view this data.',
            'error_forbidden': 'You do not have access to view this data.',
            'error_not_found': 'The data was not found.',
            'error_server': 'A server error occurred while fetching data from the server.',
            'error_unknown': 'A unknown error occurred.'
        },
        'file-picker': {
            'choose': 'Choose file',
            'remove': 'Remove file',
            'drop': 'or drag and drop file here'
        },
        'times': {
            'tomorrow_at': 'Tomorrow at {{date}}',
            'today_at': 'Today at {{date}}',
            'yesterday_at': 'Yesterday at {{date}}'
        }
    }
};
const LOCALIZATION_EXTRA_ENGLISH = {
    provide: LOCALIZE_EXTRA_DICTIONARY,
    useValue: ɵ0,
    multi: true
};

const ɵ0$1 = {
    locale: 'nl-NL',
    data: {
        'form-control-errors': {
            'unknown': 'Het veld heeft een onbekende fout.',
            'required': 'Dit veld is verplicht.',
            'pattern': 'Ongeldig formaat.',
            'email': 'Het veld komt niet overeen met een geldig e-mailadres.',
            'emailTaken': 'Het e-mailadres is al bezet.',
            'passwordMatch': 'De wachtwoorden komen niet overeen.',
            'confirm': 'Het veld komt niet overeen.',
            'fullNumber': 'Het veld moet een volledig getal zijn.',
            'min': 'Minimum hoeveelheid moet {{min}} zijn.',
            'max': 'Maximum hoeveelheid moet {{max}} zijn.',
            'maxFileSize': 'Het bestand heeft de maximum hoeveelheid van {{maxSize}} overschreden.',
            'minlength': 'Verwachtte minimaal {{requiredLength}} tekens, maar kreeg {{actualLength}} tekens.',
            'maxlength': 'Verwachtte maximaal {{requiredLength}} tekens, maar kreeg {{actualLength}} tekens.'
        },
        'tables': {
            'search': 'Zoeken...',
            'loading': 'Aan het laden...',
            'empty': 'Er is hier niets te vinden.',
            'page': 'pagina',
            'error_datasource': 'Er is geen gegevensbron verstrekt.',
            'error_unauthorized': 'U moet ingelogd zijn om deze gegevens te bekijken.',
            'error_forbidden': 'U hebt geen toegang om deze gegevens te bekijken.',
            'error_not_found': 'De gegevens zijn niet gevonden.',
            'error_server': 'Er is een serverfout opgetreden bij het ophalen van gegevens van de server.',
            'error_unknown': 'Er is een onbekende fout opgetreden bij het ophalen van gegevens van de server.'
        },
        'file-picker': {
            'choose': 'Kies bestand',
            'remove': 'Verwijder bestand',
            'drop': 'of sleep het bestand hiernaartoe'
        },
        'times': {
            'tomorrow_at': 'Morgen om {{date}}',
            'today_at': 'Vandaag om {{date}}',
            'yesterday_at': 'Gisteren om {{date}}'
        }
    }
};
const LOCALIZATION_EXTRA_DUTCH = {
    provide: LOCALIZE_EXTRA_DICTIONARY,
    useValue: ɵ0$1,
    multi: true
};

class LocalizeMissingHandler {
    constructor() {
        this.log = (message) => {
            console && console.warn && console.warn(message);
        };
    }
    handle(params, localize) {
        // Log to the console when the token was not found
        // if the user did not mark it as optional.
        if (!params.optional) {
            this.log(`Localize: The token with name: "${params.token}" was not found.`);
        }
        // Return the default token back.
        return params.token;
    }
}

class LocalizeDictionary {
    constructor({ id, data, locale, supportedLanguages }, http) {
        // Dictionary data.
        this._data = {};
        this._url = null;
        this.recursiveSearchValue = (data, value) => {
            if (typeof data === 'undefined' || typeof data === 'string') {
                return undefined;
            }
            return data[value];
        };
        this._id = id;
        this._locale = locale || 'en-US';
        this._supportedLanguages = supportedLanguages || [];
        this._http = http;
        // Set the correct variable to the url or data.
        if (typeof data === 'string') {
            this._url = data;
        }
        else {
            this._data = data;
        }
    }
    get id() {
        return this._id;
    }
    get locale() {
        return this._locale;
    }
    get supportedLanguages() {
        return this._supportedLanguages;
    }
    load() {
        return __awaiter(this, void 0, void 0, function* () {
            // If the url was not set the data was already loaded
            // return true because it was successful.
            if (!this._url) {
                return true;
            }
            try {
                // Try to load the data via a HTTP request.
                const result = yield this._http.get(this._url, { headers: { 'X-No-Authorization': 'yes' } })
                    .toPromise();
                // Set the data.
                this._data = result || {};
                this._url = null;
                // Return true because it succeeded.
                return true;
            }
            catch (_a) {
                // Return false because it failed.
                return false;
            }
        });
    }
    combine(extraDictionary, mode = 'prepend') {
        // Validate that the locale matches.
        if (extraDictionary.locale !== this._locale) {
            return;
        }
        // Extract the extra data.
        const extraData = extraDictionary.data || {};
        // If the selected mode is prepend
        // add the data in front.
        if (mode === 'prepend') {
            this._data = Object.assign(Object.assign({}, extraData), this._data);
            return;
        }
        // If the selected mode is append
        // add the data in the back.
        this._data = Object.assign(Object.assign({}, this._data), extraData);
    }
    search(token) {
        // Check if the token is present in the
        // data, if so return it.
        if (this._data[token]) {
            return this._data[token];
        }
        // Values might be nested in objects this means we need
        // to split them on the dot and search them recursively
        // in the data.
        const value = (token || '')
            .split('.')
            .filter(value => !!value)
            .reduce(this.recursiveSearchValue, this._data);
        // Check if the value is a string
        // if this is true it can be returned.
        if (typeof value === 'string') {
            return value;
        }
        // This means the translation was incorrect.
        return null;
    }
}

let Localize = class Localize {
    constructor(_http, _storage, _languages, _config, _missingHandler, _functions, _extras) {
        this._http = _http;
        this._storage = _storage;
        this._languages = _languages;
        this._config = _config;
        this._missingHandler = _missingHandler;
        this._functions = _functions;
        this._extras = _extras;
        this._storageToken = 'bb-language-dictionary-id';
        this.convertToParams = (tokenOrParams) => {
            if (typeof tokenOrParams !== 'string') {
                // Destruct the params from the object.
                const { token, optional, data } = tokenOrParams;
                // Make sure all parameters are valid
                // by type safe adding them.
                return { token: token, optional: optional || false, data: data || {} };
            }
            // If the parameter is a string fill in the blanks.
            return { token: tokenOrParams, optional: false, data: {} };
        };
        this.getMatches = (value, substitutions) => {
            // Get the matching substitutions.
            const { start, end } = substitutions;
            // Compose a regex that will find the matches.
            const regex = new RegExp(`${start}([^${end}]+)${end}`, 'g');
            // Execute the regex to find matches.
            let match = regex.exec(value);
            const matches = [];
            while (match !== null) {
                // Push a new match.
                matches.push({
                    match: match[0],
                    token: match[1]
                });
                // Try to find more matches.
                match = regex.exec(value);
            }
            // Return the processed value.
            return matches.filter(item => !!item);
        };
        this.reload = () => {
            window && window.location && window.location.reload && window.location.reload();
        };
        // Check if at least one language was passed in the config.
        if (this._config.dictionary.length < 1) {
            throw new Error('Localize: one or more languages should be supplied.');
        }
        // Gather the dictionaries.
        this._dictionaries = this._config.dictionary.map(item => new LocalizeDictionary(item, this._http));
        // Save the best matching current dictionary.
        this._currentDictionary = this.retrieveBestMatchingDictionary();
        // Gather all the localize functions.
        this._functionMiddleware = this._functions.reduce((previous, current) => {
            previous[current.keyword()] = current;
            return previous;
        }, {});
    }
    get dictionaries() {
        return this._dictionaries;
    }
    get current() {
        return this._currentDictionary;
    }
    get locale() {
        return this._currentDictionary.locale;
    }
    initialize() {
        return () => __awaiter(this, void 0, void 0, function* () { return this.load(this._currentDictionary); });
    }
    change(id, reload = true) {
        // Try to fetch the dictionary.
        const dictionary = this._dictionaries.find(item => item.id === id);
        // Validate the dictionary exists.
        if (!dictionary) {
            throw new Error(`Localize: Could not find dictionary with id: "${id}"`);
        }
        // Save the language in the storage.
        this._storage.set(this._storageToken, dictionary.id);
        // Just quit if we don't need to reload.
        if (!reload) {
            return;
        }
        // We need to reload the application for the translations.
        return this.reload();
    }
    clear(reload = true) {
        // Remove the language in the local storage.
        this._storage.remove(this._storageToken);
        // Just quit if we don't need to reload.
        if (!reload) {
            return;
        }
        // We need to reload the application for the translations.
        return this.reload();
    }
    translate(tokenOrParams) {
        // 1. Convert to the params.
        const params = this.convertToParams(tokenOrParams);
        // 2. Try to find the translation in the dictionary.
        const searchResult = this._currentDictionary.search(params.token);
        // 3. Verify the translation was found.
        if (!searchResult) {
            return this._missingHandler.handle(params, this);
        }
        // 4. Find references in the translation that need to be translated as well.
        const processResult = this.findReferencesInTranslation(searchResult);
        // 5. Parse the interpolation data.
        return this.parseInterpolationData(processResult, params.data);
    }
    parseInterpolationData(value, data) {
        // Get the interpolation substitutions.
        const substitutions = this.getSubstitutions('interpolation', ['{{', '}}']);
        // Get all matches in the value.
        const matches = this.getMatches(value, substitutions);
        // Replace all matches with the parsed value.
        return matches.reduce((previous, current) => {
            // Try to parse the token.
            const interpolatedToken = this.interpolateFunctionData(current.token, data);
            // Replace the match with the parsed token.
            return previous.replace(current.match, interpolatedToken);
        }, `${value}`);
    }
    interpolateFunctionData(value, interpolationData) {
        // Get the token and the methods by splitting
        // on the pipe symbol.
        const [token, ...methods] = value.split('|').map(item => item.trim());
        // Get the value for the token from
        // the interpolation data.
        const interpolationValue = interpolationData[token] || token;
        // Loop through all the middleware methods and try to
        // conform the string to the given parameters.
        return methods.reduce((string, middlewareName) => {
            // Try to retrieve the middleware from the collection.
            const middleware = this._functionMiddleware[middlewareName];
            // If the middleware is missing return an error
            // so that the user knows it was not found.
            if (!middleware) {
                return string;
            }
            // Execute the middleware function with the string.
            return middleware.transform(string);
        }, `${interpolationValue}`);
    }
    findReferencesInTranslation(value) {
        // Get the reference substitutions.
        const substitutions = this.getSubstitutions('references', ['#(', ')']);
        // Get all matches in the value.
        const matches = this.getMatches(value, substitutions);
        // Replace all matches with the translated value.
        return matches.reduce((previous, current) => {
            // Try to translate the token.
            const translatedToken = this.translate(current.token);
            // Replace the match with the translated token.
            return previous.replace(current.match, translatedToken);
        }, value);
    }
    getSubstitutions(type, defaultValue) {
        const substitutions = (this._config
            && this._config.substitutions
            && this._config.substitutions[type]);
        // Get the substitutions.
        const value = (!substitutions || substitutions.length !== 2)
            ? defaultValue
            : substitutions;
        // Escape both substitutions so that they
        // can be used.
        const escape = /[.*+?^${}()|[\]\\]/g;
        const start = value[0].replace(escape, '\\$&');
        const end = value[1].replace(escape, '\\$&');
        // Return the substitutions.
        return { start, end };
    }
    retrieveBestMatchingDictionary() {
        // Get the dictionary based on storage.
        const storageDictionary = this.getDictionaryFromStorage();
        // Get the dictionary based on the supported language.
        const browserDictionary = this.getBestBrowserMatchDictionary();
        // Get the dictionary based on the first index.
        const firstDictionary = this._dictionaries[0];
        // Return the best matching dictionary.
        return storageDictionary || browserDictionary || firstDictionary;
    }
    getDictionaryFromStorage() {
        // Get dictionary id from the storage..
        const dictionaryId = this._storage.get(this._storageToken);
        // Return the dictionary based on the storage dictionary id.
        return this._dictionaries.find(item => item.id === dictionaryId);
    }
    getBestBrowserMatchDictionary() {
        // Loop through all browser languages and return the
        // best matching dictionary.
        return this._languages.all.map(({ code, region }) => {
            const languageCode = [code, region].filter(data => !!data).join('-');
            return this._dictionaries.find(item => item.supportedLanguages.includes(languageCode));
        }).filter(item => !!item).find((_, index) => index === 0);
    }
    load(dictionary) {
        return __awaiter(this, void 0, void 0, function* () {
            // Try to load the dictionary data.
            const loaded = yield dictionary.load();
            // Verify the data was loaded.
            if (!loaded) {
                throw new Error(`Localize: Could not correctly fetch the data from: "${dictionary.id}"`);
            }
            // Combine the extra dictionary data.
            (this._extras || []).forEach(extraDictionary => dictionary.combine(extraDictionary));
            // Return the dictionary.
            return dictionary;
        });
    }
};
Localize.ctorParameters = () => [
    { type: HttpClient },
    { type: Storage },
    { type: Languages },
    { type: LocalizeConfig, decorators: [{ type: Optional }] },
    { type: LocalizeMissingHandler },
    { type: Array, decorators: [{ type: Optional }, { type: Inject, args: [LOCALIZE_FUNCTION,] }] },
    { type: Array, decorators: [{ type: Optional }, { type: Inject, args: [LOCALIZE_EXTRA_DICTIONARY,] }] }
];
Localize = __decorate([
    Injectable(),
    __param(3, Optional()),
    __param(5, Optional()), __param(5, Inject(LOCALIZE_FUNCTION)),
    __param(6, Optional()), __param(6, Inject(LOCALIZE_EXTRA_DICTIONARY))
], Localize);

let BbLocalize = class BbLocalize {
    constructor(_localize) {
        this._localize = _localize;
    }
    transform(tokenOrParams, args) {
        // Validate that the localize service
        // exists in the pipe.
        if (!this._localize) {
            return tokenOrParams;
        }
        // Combine the params with the options
        // to form the localize params.
        const params = this.combineParamsWithOptions(tokenOrParams, args);
        // Try to localize the value.
        return this._localize.translate(params);
    }
    combineParamsWithOptions(tokenOrParams, args) {
        // Gather the extras.
        const extras = args || {};
        // Loop through all the keys to find the set options.
        const options = Object.keys(extras).reduce((previous, current) => {
            if (typeof extras[current] === 'undefined') {
                return previous;
            }
            previous[current] = extras[current];
            return previous;
        }, {});
        // Convert the string token to a params object.
        const params = typeof tokenOrParams === 'string'
            ? { token: tokenOrParams, optional: false, data: {} }
            : tokenOrParams;
        // Return the combined params and options.
        return Object.assign(Object.assign({}, params), options);
    }
};
BbLocalize.ctorParameters = () => [
    { type: Localize, decorators: [{ type: Optional }] }
];
BbLocalize = __decorate([
    Pipe({
        name: 'bbLocalize'
    }),
    __param(0, Optional())
], BbLocalize);

var LocalizeModule_1;
let LocalizeModule = LocalizeModule_1 = class LocalizeModule {
    static forRoot(config) {
        return {
            ngModule: LocalizeModule_1,
            providers: [
                // Angular providers.
                { provide: APP_INITIALIZER, deps: [Localize], useFactory: initializeLocale, multi: true },
                { provide: LOCALE_ID, deps: [Localize], useFactory: getLocale },
                // Config provider.
                { provide: LocalizeConfig, useValue: config },
                // Function providers.
                { provide: LOCALIZE_FUNCTION, useClass: LocalizeLowercaseFunction, multi: true },
                { provide: LOCALIZE_FUNCTION, useClass: LocalizeUppercaseFunction, multi: true },
                // Handler providers.
                { provide: LocalizeMissingHandler, useClass: LocalizeMissingHandler },
                // Service providers.
                Localize
            ]
        };
    }
};
LocalizeModule = LocalizeModule_1 = __decorate([
    NgModule({
        imports: [HttpClientModule],
        declarations: [BbLocalize],
        exports: [BbLocalize]
    })
], LocalizeModule);
function initializeLocale(localize) {
    return localize.initialize();
}
function getLocale(localize) {
    return localize.locale;
}

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

export { BbLocalize, LOCALIZATION_EXTRA_DUTCH, LOCALIZATION_EXTRA_ENGLISH, LOCALIZE_EXTRA_DICTIONARY, LOCALIZE_FUNCTION, Localize, LocalizeConfig, LocalizeLowercaseFunction, LocalizeMissingHandler, LocalizeModule, LocalizeUppercaseFunction, getLocale, initializeLocale, ɵ0 };
//# sourceMappingURL=bravobit-bb-foundation-localize.js.map
