import { h, Host } from "@stencil/core";
import { PlatformServiceName, } from "@limetech/lime-web-components";
import { getPercentageClass } from "../../util/percentage-class";
import { normalizeValue } from "../../util/normalize-value";
const DEFAULT_MULTIPLIER_FACTOR = 1;
const DEFAULT_RANGE_MAX = 100;
const DEFAULT_RANGE_MIN = 0;
const PERCENTAGE = 100;
const DECIMALS = 10;
const EPSILON = 0.0001;
/**
 * This component generates a visualization that displays a percentage value
 * alongside a horizontal bar showing the percentage visually.
 *
 * It can be used to display completion rates, usage metrics, or any other
 * value that can be represented as a percentage.
 *
 * The component is designed to be used in read-only contexts, such as displaying
 * field values in a CRM object card.
 *
 * :::note
 * The percentage visualizer displays only one digit after the decimal point!
 * :::
 *
 * @exampleComponent limebb-percentage-visualizer-basic
 * @exampleComponent limebb-percentage-visualizer-multiplier
 * @exampleComponent limebb-percentage-visualizer-range
 * @exampleComponent limebb-percentage-visualizer-reduce-presence
 * @exampleComponent limebb-percentage-visualizer-colors
 * @exampleComponent limebb-percentage-visualizer-locale
 * @exampleComponent limebb-percentage-visualizer-composite
 * @beta
 */
export class PercentageVisualizer {
    constructor() {
        /**
         * The maximum value of the visualization range.
         * This defines the upper bound of the scale used for visualization.
         * Must be greater than rangeMin.
         */
        this.rangeMax = DEFAULT_RANGE_MAX;
        /**
         * The minimum value of the visualization range.
         * This defines the lower bound of the scale used for visualization.
         * Must be less than rangeMax.
         */
        this.rangeMin = DEFAULT_RANGE_MIN;
        /**
         * A multiplier factor to convert input values to percentages.
         * For example, if your input is 0.75 and you want to display it as 75%, use a multiplier of 100.
         */
        this.multiplier = DEFAULT_MULTIPLIER_FACTOR;
        /**
         * When set to `false`, if the `value` equals `0` or there is no value
         * (_undefined_, _null_ or empty string), the component will
         * render the visualization. See the examples for more details.
         */
        this.reducePresence = true;
        /**
         * When set to `true`, makes the filled section showing the percentage colorful.
         * Colors change follow Lime CRM's conventions:
         * - with intervals of 10%, from 0% to 100% (when the range doesn't start with a negative value).
         * - with intervals of 20%, from -200% to +200% (when the range starts with a negative value).
         */
        this.displayPercentageColors = false;
        this.locale = 'en';
        this.labelId = 'label-' + crypto.randomUUID();
        this.helperTextId = 'helper-text-' + crypto.randomUUID();
        this.zeroPointId = 'zero-point-' + crypto.randomUUID();
        this.renderHelperLine = () => {
            if (!this.hasHelperText()) {
                return;
            }
            return (h("limel-helper-line", { helperTextId: this.helperTextId, helperText: this.helperText, invalid: this.invalid }));
        };
        this.hasHelperText = () => {
            return this.helperText !== null && this.helperText !== undefined;
        };
    }
    valueChanged(newValue) {
        this.numValue = this.parseValue(newValue);
    }
    componentWillLoad() {
        try {
            this.locale = this.platform
                .get(PlatformServiceName.UserPreferencesRepository)
                .get('locale');
            this.numberFormat = new Intl.NumberFormat(this.locale);
        }
        catch (_a) {
            console.warn(`Could not get user's locale (${this.locale}), falling back to 'en'.`);
            this.locale = 'en';
            this.numberFormat = new Intl.NumberFormat('en');
        }
        this.numValue = this.parseValue(this.value);
    }
    componentDidLoad() {
        this.triggerRangeWarning();
    }
    componentWillUpdate() {
        this.triggerRangeWarning();
    }
    triggerRangeWarning() {
        if (this.rangeMin >= this.rangeMax) {
            console.warn(`[limebb-percentage-visualizer] Invalid range: rangeMin (${this.rangeMin}) must be less than rangeMax (${this.rangeMax}). Visualization may not appear as expected.`);
        }
    }
    render() {
        const normalizedValue = this.getNormalizedValue();
        const zeroPoint = this.getZeroPointPosition();
        return (h(Host, { key: 'c57fbde9785115a91c28db17ccebee42a335a80f', class: this.getContainerClassList(), style: {
                '--limebb-percentage-visualizer-zero-point-position': `${zeroPoint}%`,
                '--limebb-percentage-visualizer-width': `${Math.abs(normalizedValue)}%`,
                '--limebb-percentage-visualizer-rotate': `${normalizedValue >= 0 ? '0deg' : '180deg'}`,
            } }, h("limel-notched-outline", { key: '27d6dada52041ce9ebec9d987bae303afacfff89', tabIndex: 0, role: "meter", "aria-valuemin": this.rangeMin, "aria-valuemax": this.rangeMax, "aria-valuenow": this.numValue, "aria-labelledby": this.labelId, "aria-describedby": this.helperTextId, label: this.label, labelId: this.labelId, invalid: this.invalid, required: this.required, hasValue: true, hasFloatingLabel: true, "aria-controls": this.helperTextId }, h("div", { key: '2afd6d0ff95eace6fac444fd3e2732d22ab97728', slot: "content" }, this.renderVisualization())), this.renderHelperLine()));
    }
    renderVisualization() {
        if (this.reducePresence && (this.numValue === 0 || !this.hasValue())) {
            return;
        }
        if (!this.hasValue()) {
            return h("span", { class: "no-value" }, "\u2013");
        }
        return [
            h("div", { class: "percentage-bar" }, h("div", { class: "percentage-fill" }), this.renderZeroPoint()),
            h("span", { class: "percentage-text" }, this.formatValue(this.numValue), h("span", { class: "percent-sign" }, "%")),
        ];
    }
    renderZeroPoint() {
        if (this.rangeMin >= 0 || this.rangeMax <= 0) {
            return;
        }
        return [
            h("div", { id: this.zeroPointId, class: "zero-point" }),
            h("limel-tooltip", { elementId: this.zeroPointId, openDirection: "top", label: "0" }),
        ];
    }
    hasValue() {
        return (this.value !== undefined && this.value !== null && this.value !== '');
    }
    getContainerClassList() {
        if (!this.hasValue()) {
            return;
        }
        const processedValue = this.roundValue(this.multiplyByMultiplier(this.numValue));
        const percentageClass = getPercentageClass(processedValue);
        return Object.assign({}, (percentageClass && { [percentageClass]: true }));
    }
    getZeroPointPosition() {
        const totalRange = this.rangeMax - this.rangeMin;
        if (totalRange <= 0) {
            // Handle invalid range by providing a safe default
            return 0; // Align zero point at the start as a consistent fallback
        }
        return ((0 - this.rangeMin) / totalRange) * PERCENTAGE;
    }
    getNormalizedValue() {
        const processedValue = this.roundValue(this.multiplyByMultiplier(this.numValue));
        const totalRange = this.rangeMax - this.rangeMin;
        if (totalRange <= 0) {
            return 0;
        }
        const zeroRelativePosition = ((0 - this.rangeMin) / totalRange) * PERCENTAGE;
        // Calculate the position relative to the zero point
        // Using the EPSILON value serves several purposes:
        // 1. Prevents Division by Zero:
        // If this.maxValue or this.minValue is exactly 0,
        // we'd get an infinity or NaN result without this protection.
        // 2. Handles Edge Cases:
        // Even though we validate that minValue < maxValue,
        // there could still be cases where one of them is very close to zero,
        // which could cause unstable calculations.
        // 3. Defensive Programming:
        // It ensures that the component degrades gracefully
        // even in unexpected scenarios (like when a consumer sets maxValue to 0).
        // .4 Numerical Stability:
        // In floating-point math, using a small epsilon value
        // is a best practice to avoid precision issues.
        if (processedValue >= 0) {
            return ((processedValue / Math.max(this.rangeMax, EPSILON)) *
                (PERCENTAGE - zeroRelativePosition));
        }
        else {
            return ((processedValue / Math.max(Math.abs(this.rangeMin), EPSILON)) *
                zeroRelativePosition);
        }
    }
    multiplyByMultiplier(value) {
        return value * this.multiplier;
    }
    roundValue(value) {
        // Round to 1 decimal place
        return Math.round(value * DECIMALS) / DECIMALS;
    }
    unifyMinusSign(input) {
        // Replace Unicode minus sign (−) with ASCII hyphen-minus (-)
        return input.replaceAll('−', '-');
    }
    convertToUnicodeMinus(input) {
        // Replace ASCII hyphen-minus (-) with Unicode minus sign (−)
        return input.replaceAll('-', '−');
    }
    parseValue(value) {
        if (typeof value === 'string') {
            return Number.parseFloat(normalizeValue(this.unifyMinusSign(value), this.locale));
        }
        return value;
    }
    formatValue(value) {
        const processedValue = this.roundValue(this.multiplyByMultiplier(value));
        const constructedValue = this.numberFormat.format(processedValue);
        if (processedValue < 0) {
            return this.convertToUnicodeMinus(constructedValue);
        }
        return constructedValue;
    }
    static get is() { return "limebb-percentage-visualizer"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["percentage-visualizer.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["percentage-visualizer.css"]
        };
    }
    static get properties() {
        return {
            "platform": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentPlatform",
                    "resolved": "LimeWebComponentPlatform",
                    "references": {
                        "LimeWebComponentPlatform": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentPlatform",
                            "referenceLocation": "LimeWebComponentPlatform"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inherit",
                            "text": undefined
                        }],
                    "text": "Reference to the platform service container.\n\nUse this to access all platform services like repositories, HTTP client,\ncommand bus, navigator, and more. The platform instance is shared across\nall components in the same application context."
                },
                "getter": false,
                "setter": false
            },
            "context": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentContext",
                    "resolved": "LimeWebComponentContext",
                    "references": {
                        "LimeWebComponentContext": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentContext",
                            "referenceLocation": "LimeWebComponentContext"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inherit",
                            "text": undefined
                        }],
                    "text": "The context describing where this component is running.\n\nThe context provides information about the current limetype and record ID\n(if viewing/editing a specific record). Components can use this to load\nrelevant data and understand their operating environment."
                },
                "getter": false,
                "setter": false
            },
            "value": {
                "type": "any",
                "mutable": false,
                "complexType": {
                    "original": "number | string",
                    "resolved": "number | string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Preferably a number (or a string that can be converted to a number\nby the component) that represents the percentage value."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "value"
            },
            "rangeMax": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The maximum value of the visualization range.\nThis defines the upper bound of the scale used for visualization.\nMust be greater than rangeMin."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "range-max",
                "defaultValue": "DEFAULT_RANGE_MAX"
            },
            "rangeMin": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The minimum value of the visualization range.\nThis defines the lower bound of the scale used for visualization.\nMust be less than rangeMax."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "range-min",
                "defaultValue": "DEFAULT_RANGE_MIN"
            },
            "multiplier": {
                "type": "number",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "A multiplier factor to convert input values to percentages.\nFor example, if your input is 0.75 and you want to display it as 75%, use a multiplier of 100."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "multiplier",
                "defaultValue": "DEFAULT_MULTIPLIER_FACTOR"
            },
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The label of the field.\nWill be displayed as a floating label on top,\nmimicking other form elements, such as input fields."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "label"
            },
            "invalid": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to indicate that the current value is invalid."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "invalid"
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to render an asterisk beside the label,\nonly visually indicating that the field is required.\nUseful when the component is used as a from field on cards."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "required"
            },
            "helperText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Optional helper text to display below the field when it is focused.\nThe helper text also appears when the component is `invalid`."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "helper-text"
            },
            "reducePresence": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean | undefined",
                    "resolved": "boolean | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "When set to `false`, if the `value` equals `0` or there is no value\n(_undefined_, _null_ or empty string), the component will\nrender the visualization. See the examples for more details."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "reduce-presence",
                "defaultValue": "true"
            },
            "displayPercentageColors": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean | undefined",
                    "resolved": "boolean | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "When set to `true`, makes the filled section showing the percentage colorful.\nColors change follow Lime CRM's conventions:\n- with intervals of 10%, from 0% to 100% (when the range doesn't start with a negative value).\n- with intervals of 20%, from -200% to +200% (when the range starts with a negative value)."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "display-percentage-colors",
                "defaultValue": "false"
            }
        };
    }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "valueChanged"
            }];
    }
}
