'use strict';

var index = require('./index-C_OnxXhP.js');
var index_esm = require('./index.esm-C1i_NocP.js');
var limetype = require('./limetype-BlG0Apxb.js');

/**
 * Context provider for the info-tile component.
 *
 * This provider contributes contextual information about an info tile
 * to the AI chat feature, including the limetype, filter, aggregation
 * method, and current value.
 *
 * @alpha
 */
class InfoTileContextProvider {
    /**
     * Create an InfoTileContextProvider.
     *
     * @param getState - Function that returns the current tile state
     */
    constructor(getState) {
        this.getState = getState;
        this.contextType = 'info-tile';
    }
    getContext() {
        const { limetype, filterId, filterName, aggregateOperator, propertyName, value, label, } = this.getState();
        if (!limetype || !filterId) {
            console.debug('InfoTileContextProvider: No limetype or filterId available');
            return null;
        }
        const data = {
            widgetType: 'info-tile',
            limetype,
            filterId,
        };
        if (filterName) {
            data.filterName = filterName;
        }
        data.aggregateOperator = aggregateOperator;
        if (propertyName) {
            data.propertyName = propertyName;
        }
        if (value !== undefined) {
            data.value = value;
        }
        if (label) {
            data.label = label;
        }
        return {
            data,
            purpose: 'Shows an aggregated value from a filter. ' +
                'Clicking navigates to the explorer with that filter applied.',
        };
    }
}

class InfoTileLoader {
    constructor(platform, filters = []) {
        this.platform = platform;
        this.filters = filters;
    }
    async load(aggregator, context) {
        let value;
        if (this.isObjectIdentifier(context)) {
            value = await this.loadLimeObjectInfoTile(aggregator, context);
        }
        else {
            value = await this.loadBasicInfoTile(aggregator);
        }
        return value;
    }
    async loadBasicInfoTile(aggregator) {
        const filter = this.getFilter(aggregator.filter);
        if (!filter) {
            throw new Error(`Filter "${aggregator.filter}" not found`);
        }
        const result = await this.query.execute({
            limetype: filter.limetype,
            responseFormat: {
                aggregates: {
                    infotiles: {
                        value: {
                            op: aggregator.operator,
                            key: aggregator.name,
                        },
                    },
                },
            },
            filter: filter.filter,
        });
        return result.aggregates.infotiles[0].value;
    }
    async loadLimeObjectInfoTile(aggregator, identifier) {
        await this.objectRepository.loadObject(identifier.limetype, identifier.id, {
            properties: [aggregator],
        });
        const limeobject = this.objectRepository.getObject(identifier.limetype, identifier.id);
        return limeobject === null || limeobject === void 0 ? void 0 : limeobject.getValue([aggregator.name, aggregator.key].join('.'));
    }
    isObjectIdentifier(context) {
        return Boolean((context === null || context === void 0 ? void 0 : context.limetype) && (context === null || context === void 0 ? void 0 : context.id));
    }
    getFilter(id) {
        return this.filters.find((filter) => filter.id === id);
    }
    get query() {
        return this.platform.get(index_esm.n.Query);
    }
    get objectRepository() {
        return this.platform.get(index_esm.n.LimeObjectRepository);
    }
}

/**
 * Picks a localized string from a record keyed by locale code.
 *
 * Looks up the value for `language` first, then falls back to `en` and
 * `en_us`. Returns an empty string when none of those keys carry a
 * non-empty value, even if other locales are present in the record —
 * matching the behavior consumers rely on today.
 *
 * Useful for resolving values such as `Filter.name`, where strings are
 * provided per locale.
 *
 * @param strings - Record where keys are locale codes and values are
 * the localized strings.
 * @param language - The preferred locale code.
 */
function localizeRecord(strings, language) {
    if (!strings) {
        return '';
    }
    return strings[language] || strings.en || strings.en_us || '';
}

/**
 * Finds a filter set by id in an array of filter sets.
 *
 * Returns `undefined` when either `filters` or `filterId` is missing or
 * when no filter matches — safe to call with optional inputs without a
 * guard.
 *
 * @param filters - The filter sets to search.
 * @param filterId - The id of the filter set to find.
 */
function findFilter(filters, filterId) {
    if (!filters || !filterId) {
        return undefined;
    }
    return filters.find((filter) => filter.id === filterId);
}

/**
 * Build a parsed explorer destination for a filter, optionally
 * narrowing it with extra expressions AND-combined onto the filter's
 * own expression. Use this when navigating imperatively via the
 * `Navigator` service so the host SPA stays on the page (no full
 * reload).
 * @param filter - the filter set whose limetype and id form the
 * explorer path. When `undefined`, the function returns
 * `{ path: '#' }` so callers can wire it into a link without a
 * special-case branch.
 * @param extraExpressions - expressions to AND onto the filter's own
 * expression. The chart passes `{ key: groupBy, op: '=', exp: item }`
 * here, info-tile passes a back-reference constraint when the widget
 * is on an object card. Order matters in the resulting AND: extras
 * come before the filter's own expression.
 * @returns `path` plus, when there's any filter expression to apply,
 * `query.filter` holding the combined expression as a raw object (the
 * navigator stringifies and URL-encodes it when generating the URL).
 */
function buildExplorerLocation(filter, extraExpressions = []) {
    if (!filter) {
        return { path: '#' };
    }
    const path = ['explorer', filter.limetype, filter.id].join('/');
    const combined = combineExpressions(filter.filter, extraExpressions);
    return combined ? { path, query: { filter: combined } } : { path };
}
/**
 * Build a Lime CRM explorer href for a filter, optionally narrowing it
 * with extra expressions AND-combined onto the filter's own expression.
 *
 * Used by widgets like `limebb-info-tile` to render anchor `<a>` links
 * to a filtered list. For imperative navigation prefer
 * {@link buildExplorerLocation} together with the `Navigator` service.
 * @param filter - the filter set whose limetype and id form the
 * explorer href. When `undefined`, the function returns `'#'` so
 * callers can wire it into a link without a special-case branch.
 * @param extraExpressions - expressions to AND onto the filter's own
 * expression, with the same ordering and use-cases as
 * {@link buildExplorerLocation}.
 * @returns an explorer href such as
 * `explorer/{limetype}/{filterId}?filter=<URL-encoded JSON>`, or
 * `'#'` when `filter` is `undefined`.
 */
function buildExplorerHref(filter, extraExpressions = []) {
    const { path, query } = buildExplorerLocation(filter, extraExpressions);
    if (!query) {
        return path;
    }
    const search = Object.entries(query)
        .map(([key, value]) => `${key}=${encodeURIComponent(JSON.stringify(value))}`)
        .join('&');
    return `${path}?${search}`;
}
function combineExpressions(filter, extras) {
    const all = [...extras, filter].filter((exp) => exp !== undefined);
    if (all.length === 0) {
        return undefined;
    }
    if (all.length === 1) {
        return all[0];
    }
    return {
        op: index_esm.nt.AND,
        exp: all,
    };
}

class BasicFormatter {
    constructor(locale) {
        this.locale = locale;
    }
    format(value) {
        let formattedValue = String(value);
        if (typeof value === 'number') {
            formattedValue = new Intl.NumberFormat(this.locale).format(value);
        }
        else if (new Date(value).getTime()) {
            formattedValue = new Intl.DateTimeFormat(this.locale).format(new Date(value));
        }
        return {
            value: formattedValue,
        };
    }
}

class DateTimeFormatter {
    constructor(locale) {
        this.locale = locale;
    }
    format(value, format) {
        const date = new Date(value);
        return {
            prefix: this.formatAffix('prefix', date, format),
            value: this.formatValue(date, format),
            suffix: this.formatAffix('suffix', date, format),
        };
    }
    formatValue(date, format) {
        const options = Object.assign({}, format.options);
        if (format.prefix)
            for (const prefix of format.prefix) {
                delete options[prefix];
            }
        if (format.suffix)
            for (const suffix of format.suffix) {
                delete options[suffix];
            }
        const formatter = new Intl.DateTimeFormat(this.locale, options);
        return formatter.format(date);
    }
    formatAffix(affix, date, format) {
        const options = Object.assign({}, format.options);
        for (const option of Object.keys(options)) {
            if (option === 'hour12') {
                continue;
            }
            if ((format[affix] || []).includes(option)) {
                continue;
            }
            delete options[option];
        }
        if (Object.values(options).filter(Boolean).length === 0) {
            return;
        }
        const formatter = new Intl.DateTimeFormat(this.locale, options);
        return formatter.format(date);
    }
}

class NumberFormatter {
    constructor(locale) {
        this.locale = locale;
    }
    format(value, format) {
        var _a, _b;
        const formatter = new Intl.NumberFormat(this.locale, format.options);
        const parts = formatter.formatToParts(value);
        let prefix = '';
        let number = '';
        let suffix = '';
        for (const part of parts) {
            if ((_a = format.prefix) === null || _a === void 0 ? void 0 : _a.includes(part.type)) {
                prefix += part.value;
            }
            else if ((_b = format.suffix) === null || _b === void 0 ? void 0 : _b.includes(part.type)) {
                suffix += part.value;
            }
            else {
                number += part.value;
            }
        }
        if (!number) {
            number = formatter.format(value);
            prefix = '';
            suffix = '';
        }
        return {
            prefix: prefix,
            value: number,
            suffix: suffix,
        };
    }
}

class RelativeDateFormatter {
    constructor(locale) {
        this.locale = locale;
    }
    format(value, format) {
        const formatter = new Intl.RelativeTimeFormat(this.locale, format.options);
        const { value: relativeTime, unit } = this.getRelativeTime(value);
        const parts = formatter.formatToParts(relativeTime, unit);
        let prefix = '';
        let formattedValue = '';
        let suffix = '';
        for (const part of parts) {
            if (part.type === 'literal' && !formattedValue) {
                prefix += part.value;
            }
            else if (part.type === 'literal' && formattedValue) {
                suffix += part.value;
            }
            else {
                formattedValue += part.value;
            }
        }
        if (!formattedValue) {
            formattedValue = prefix || suffix;
            prefix = '';
            suffix = '';
        }
        return {
            prefix: prefix,
            value: formattedValue,
            suffix: suffix,
        };
    }
    getRelativeTime(value) {
        const date = new Date(value);
        const now = new Date();
        const diffInMs = date.getTime() - now.getTime();
        const times = [
            { unit: 'year', value: 1000 * 60 * 60 * 24 * 365 },
            { unit: 'month', value: 1000 * 60 * 60 * 24 * 30 },
            { unit: 'week', value: 1000 * 60 * 60 * 24 * 7 },
            { unit: 'day', value: 1000 * 60 * 60 * 24 },
            { unit: 'hour', value: 1000 * 60 * 60 },
            { unit: 'minute', value: 1000 * 60 },
            { unit: 'second', value: 1000 },
        ];
        for (const time of times) {
            const diff = diffInMs / time.value;
            if (Math.abs(diff) >= 1) {
                return { value: Math.round(diff), unit: time.unit };
            }
        }
        return { value: 0, unit: 'second' };
    }
}

class InfoTileFormatterFactory {
    constructor(locale) {
        this.locale = locale;
    }
    createFormatter(format, value) {
        if ((format === null || format === void 0 ? void 0 : format.type) === 'number' && typeof value === 'number') {
            return new NumberFormatter(this.locale);
        }
        if ((format === null || format === void 0 ? void 0 : format.type) === 'date' && typeof value === 'string') {
            return new DateTimeFormatter(this.locale);
        }
        if ((format === null || format === void 0 ? void 0 : format.type) === 'relative-date' && typeof value === 'string') {
            return new RelativeDateFormatter(this.locale);
        }
        return new BasicFormatter(this.locale);
    }
}

const infoTileCss = () => `:host(limebb-info-tile){display:block;overflow:visible !important;background-color:transparent !important}limel-info-tile.error{--info-tile-icon-color:rgb(var(--color-red-dark));border:1px dashed rgb(var(--contrast-500));border-radius:var(--info-tile-border-radius, 1rem)}`;

var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
        r = Reflect.decorate(decorators, target, key, desc);
    else
        for (var i = decorators.length - 1; i >= 0; i--)
            if (d = decorators[i])
                r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
const InfoTile = class {
    constructor(hostRef) {
        index.registerInstance(this, hostRef);
        /**
         * Type of aggregation operator to use
         */
        this.aggregateOperator = index_esm.rt.Count;
        this.loading = false;
        this.error = false;
    }
    connectedCallback() {
        this.registerContextProvider();
    }
    disconnectedCallback() {
        var _a;
        (_a = this.unregisterContextProvider) === null || _a === void 0 ? void 0 : _a.call(this);
    }
    componentWillLoad() {
        this.loadInfoTileValue();
    }
    watchFilterId() {
        this.loadInfoTileValue();
    }
    watchPropertyName() {
        this.loadInfoTileValue();
    }
    watchAggregateOperator() {
        this.loadInfoTileValue();
    }
    async loadInfoTileValue() {
        this.loading = true;
        this.error = false;
        const filter = this.getFilter();
        if (!filter) {
            this.error = true;
            return;
        }
        try {
            const aggregator = {
                key: this.getKey(),
                name: this.propertyName,
                operator: this.aggregateOperator,
                filter: this.filterId,
            };
            this.value = await this.loader.load(aggregator, this.context);
        }
        catch (error) {
            console.error(error);
            this.error = true;
        }
        this.loading = false;
    }
    getKey() {
        const RADIX = 36;
        const START = 2;
        // eslint-disable-next-line sonarjs/pseudo-random
        return Math.random().toString(RADIX).slice(START);
    }
    render() {
        var _a, _b;
        const filter = this.getFilter();
        const value = this.getFormattedValue();
        const baseProps = {
            disabled: this.disabled,
            icon: this.icon,
            prefix: ((_a = value === null || value === void 0 ? void 0 : value.prefix) === null || _a === void 0 ? void 0 : _a.trim()) || this.prefix,
            suffix: ((_b = value === null || value === void 0 ? void 0 : value.suffix) === null || _b === void 0 ? void 0 : _b.trim()) || this.suffix,
        };
        const errorProps = Object.assign(Object.assign({}, baseProps), { icon: 'error', prefix: 'ERROR', loading: true, label: this.getLabel(filter) });
        const normalProps = Object.assign(Object.assign({}, baseProps), { label: this.getLabel(filter), link: this.getLink(filter), loading: this.loading, value: value === null || value === void 0 ? void 0 : value.value.trim() });
        const props = !filter || this.error ? errorProps : normalProps;
        return (index.h("limel-info-tile", Object.assign({ key: 'd96335bf647a10a786c7294da2ede4b6bd4ee38f', class: { error: !filter || this.error } }, props)));
    }
    getFormattedValue() {
        if (!this.value && this.value !== 0) {
            return {
                value: '',
            };
        }
        try {
            return this.getFormatter(this.value).format(this.value, this.format);
        }
        catch (_a) {
            const formatter = new BasicFormatter(this.language);
            return formatter.format(this.value);
        }
    }
    getLabel(filter) {
        if (!filter) {
            return 'No filter set is selected';
        }
        if (typeof this.label === 'string') {
            return this.translator.get(this.label);
        }
        if (this.label && typeof this.label === 'object') {
            return localizeRecord(this.label, this.language);
        }
        return localizeRecord(filter.name, this.language);
    }
    getLink(filter) {
        const relationPropertyName = limetype.getRelationPropertyName({
            targetLimetype: filter
                ? this.limetypes[filter.limetype]
                : undefined,
            currentLimetype: this.context.limetype
                ? this.limetypes[this.context.limetype]
                : undefined,
            contextLimetypeName: this.context.limetype,
            contextId: this.context.id,
            propertyName: this.propertyName,
        });
        const extras = relationPropertyName && this.context.id
            ? [
                {
                    key: relationPropertyName,
                    op: index_esm.nt.EQUALS,
                    exp: this.context.id,
                },
            ]
            : [];
        return { href: buildExplorerHref(filter, extras) };
    }
    getFilter() {
        return findFilter(this.filters, this.filterId);
    }
    getFormatter(value) {
        return new InfoTileFormatterFactory(this.language).createFormatter(this.format, value);
    }
    get translator() {
        return this.platform.get(index_esm.n.Translate);
    }
    get language() {
        return this.platform.get(index_esm.n.Application).getLanguage();
    }
    get loader() {
        return new InfoTileLoader(this.platform, this.filters);
    }
    registerContextProvider() {
        const registry = this.platform.get(index_esm.n.AIContextRegistry);
        if (!(registry === null || registry === void 0 ? void 0 : registry.register)) {
            return;
        }
        const provider = new InfoTileContextProvider(() => {
            const filter = this.getFilter();
            return {
                limetype: filter === null || filter === void 0 ? void 0 : filter.limetype,
                filterId: this.filterId,
                filterName: filter
                    ? localizeRecord(filter.name, this.language)
                    : undefined,
                aggregateOperator: this.aggregateOperator,
                propertyName: this.propertyName,
                value: this.value,
                label: this.getLabel(filter),
            };
        });
        this.unregisterContextProvider = registry.register(provider, 
        // This cast is needed because there's a type conflict between
        // `HTMLElement.prefix` and `HTMLLimebbInfoTileElement.prefix`.
        // The problem here is that this info-tile component has a property
        // (`prefix`) with the same name as one of the native HTML attributes.
        this.host);
    }
    get host() { return index.getElement(this); }
    static get watchers() { return {
        "filterId": [{
                "watchFilterId": 0
            }],
        "propertyName": [{
                "watchPropertyName": 0
            }],
        "aggregateOperator": [{
                "watchAggregateOperator": 0
            }]
    }; }
};
__decorate([
    index_esm.lt({})
], InfoTile.prototype, "config", void 0);
__decorate([
    index_esm.dt()
], InfoTile.prototype, "filters", void 0);
__decorate([
    index_esm.Le()
], InfoTile.prototype, "limetypes", void 0);
InfoTile.style = infoTileCss();

exports.limebb_info_tile = InfoTile;
