# Source: led-gauge.js

    /** Define a new zero dependency custom web component ECMA module that can be used as an HTML tag
     *
     * TO USE THIS TEMPLATE: CHANGE ALL INSTANCES OF 'ComponentTemplate' and 'led-gauge'
     * For better formatting of HTML in template strings, use VSCode's "ES6 String HTML" extension
     *
     * Version: See the class code
     *
     */
    /** Copyright (c) 2022-2025 Julian Knight (Totally Information)
     * https://it.knightnet.org.uk, https://github.com/TotallyInformation
     *
     * Licensed under the Apache License, Version 2.0 (the 'License');
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an 'AS IS' BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    import TiBaseComponent from '../libs/ti-base-component'

    /** Only use a template if you want to isolate the code and CSS */
    const template = document.createElement('template')
    template.innerHTML = /*html*/`
        <style>
            :host {
                --value-color: var(--text2, inherit);
                --gauge-background-color: var(--surface2, inherit);
                --hue: 120; /* 0 = red, 120 = green, 240 = blue */
                --on-sat: 100%;
                --off-sat: 20%;
                --on-lum: 45%;
                --off-lum: 25%;
                --label-color: var(--value-color, inherit);

                --segment-count: 10;
                --segment-gap: 0.3rem;
                --gauge-columns: 1fr 1fr;
                --gauge-layout:
                    "label value"
                    "segments segments"
                    "segvals segvals";
                --value-justification: end;

                contain: content; /* performance boost */
                display: grid;
                width: 100%;
                grid-template-columns: var(--gauge-columns);
                grid-template-areas: var(--gauge-layout);
                padding: 1rem;
                background-color: var(--gauge-background-color);
                color: var(--value-color);
                border-radius: var(--border-radius, 5px);
            }

            .segments {
                grid-area: segments;
                display: grid;
                grid-template-columns: repeat(var(--segment-count, 10), 1fr);
                gap: var(--segment-gap);
            }

            .segvals {
                grid-area: segvals;
                display: grid;
                grid-template-columns: 0fr repeat(var(--segment-count, 10), 1fr) 0fr;
                justify-items: start;
                /* margin-left: -.5rem; */
                /* gap: var(--segment-gap); */
            }

            .led {
                background-color: hsl(var(--hue, 0), var(--off-sat, 20%), var(--off-lum, 25%));
                height: 20px;
                border-radius: 3px;
                border: 1px solid var(--gauge-background-color);
                cursor: pointer;
            }

            .led.on {
                background-color: hsl(var(--hue, 0), var(--on-sat, 100%), var(--on-lum, 45%));
                /* box-shadow: 0 0 5px hsl(var(--hue, 0), var(--on-sat, 100%), var(--on-lum, 45%)); */
            }

            slot {
                grid-area: label;
                color: var(--label-color, inherit);
            }
            output {
                grid-area: value;
                justify-self: var(--value-justification, end);
                color: var(--value-color, inherit);
            }
        </style>

        <slot></slot>
        <output class="value"></output>
        <div class="segments" arial-label="Visual LED Gauge"></div>
        <div class="segvals"></div>
    `

    /** Namespace
     * @namespace Alpha
     */

    /**
     * @class
     * @augments TiBaseComponent
     * @description Define a new zero dependency custom web component ECMA module that can be used as an HTML tag
     *
     * @element led-gauge
     * @memberOf Alpha

     * METHODS FROM BASE: (see TiBaseComponent)
     * STANDARD METHODS:
      * @function attributeChangedCallback Called when an attribute is added, removed, updated or replaced
      * @function connectedCallback Called when the element is added to a document
      * @function constructor Construct the component
      * @function disconnectedCallback Called when the element is removed from a document

     * OTHER METHODS:
      * @function valueChanged Process value changed event
      * @function _renderGauge (Re)Create the gauge

     * CUSTOM EVENTS:
      * "led-gauge:connected" - When an instance of the component is attached to the DOM. `evt.details` contains the details of the element.
      * "led-gauge:ready" - Alias for connected. The instance can handle property & attribute changes
      * "led-gauge:disconnected" - When an instance of the component is removed from the DOM. `evt.details` contains the details of the element.
      * "led-gauge:attribChanged" - When a watched attribute changes. `evt.details.data` contains the details of the change.
      *
      * "led-gauge:value-changed" - When the value changes. `evt.details.data` contains the new value.
      * "led-gauge:segment-click" - When a segment is clicked. `evt.details.data` contains the details of the segment & current value.
      * NOTE that listeners can be attached either to the `document` or to the specific element instance.

     * Standard watched attributes (common across all my components):
      * @property {string|boolean} inherit-style - Optional. Load external styles into component (only useful if using template). If present but empty, will default to './index.css'. Optionally give a URL to load.
      * @property {string} name - Optional. HTML name attribute. Included in output _meta prop.

     * Other watched attributes:
      * @property {string|number} value (getter/setter) The current value of the gauge. #value is the private equivalent property
      * @property {string|number} max The maximum value of the gauge
      * @property {string|number} min The minimum value of the gauge
      * @property {string|number} segments (getter/setter) The number of segments in the gauge. #segments is the private equivalent property
      * @property {string} unit The unit of the gauge value
      * @property {string} hide-segment-labels

     * PROPS FROM BASE: (see TiBaseComponent)
     * OTHER STANDARD PROPS:
      * @property {string} componentVersion Static. The component version string (date updated). Also has a getter that returns component and base version strings.

     * Other props:
      * @property {string[]} colors The color of each segment in the gauge
      * @property {boolean} hideSegmentLabels If true, hide the segment labels (hide-segment-labels attribute)
      * @property {HTMLElement} segContainerEl The container for the gauge segments
      * @property {HTMLCollection} segmentElements A collection of the segment div elements
      * @property {HTMLElement} valsContainerEl The container for the segment values
      * @property {HTMLElement} valueEl The container for the gauge value
      * By default, all attributes are also created as properties

     * @slot Container contents

     * See https://github.com/runem/web-component-analyzer?tab=readme-ov-file#-how-to-document-your-components-using-jsdoc
     */
    class LedGauge extends TiBaseComponent {
        /** Component version */
        static componentVersion = '2025-02-25'

        #value = 0
        #segments = 10
        #colors = {}
        // #colors = {
        //     60: 40, // 60%+ is orange
        //     80: 0, // 80%+ is red
        // }
        #min = 0
        #max = 100
        #unit = '%'
        #hideSegmentLabels = false

        /** @type {HTMLElement} */
        segContainerEl
        /** @type {HTMLElement} */
        valsContainerEl
        /** @type {HTMLElement} */
        valueEl
        /** @type {HTMLCollection} */
        segmentElements

        /** Makes HTML attribute change watched
         * @returns {Array<string>} List of all of the html attribs (props) listened to
         */
        static get observedAttributes() {
            return [
                // Standard watched attributes:
                'inherit-style', 'name',
                // Other watched attributes:
                'value', 'min', 'max', 'unit',
                'segments', 'hide-segment-labels',
            ]
        }

        /** Set the number of segments
         * @param {string|number} val Number of segments
         */
        set segments(val) {
            const strSegments = val.toString()
            // Ensure that saved #segments value is numeric
            this.#segments = parseInt(strSegments)
            // Update the CSS variable for the number of segments
            this.style.setProperty('--segment-count', strSegments)
            // (Re)Create and show the gauge
            this._renderGauge()
        }

        /** Get the number of segments
         * @returns {number} Number of segments
         */
        get segments() {
            return this.#segments
        }

        /** Set the value of the gauge
         * @param {string|number} val The value to set
         */
        set value(val) {
            const oldVal = this.#value
            // Ensure that saved #segments value is numeric
            this.#value = parseFloat(val.toString())
            // (Re)Create and show the gauge
            this._renderGauge()
            // Only notify once the component is fully connected
            if (this.connected) {
                // Notify listeners of the change
                this._event('value-change', {
                    value: this.#value,
                    oldValue: oldVal,
                })
                // If using uibuilder, send the new value to Node-RED.
                this.uibSend('value-change', {
                    value: this.#value,
                    oldValue: oldVal,
                })
            }
        }

        /** Get the value of the gauge
         * @returns {number} The current value
         */
        get value() {
            return this.#value
        }

        /** Set the value of the gauge
         * @param {object} val The value to set
         */
        set colors(val) {
            this.#colors = val
            // (Re)Create and show the gauge
            this._renderGauge()
        }

        /** Get the value of the gauge
         * @returns {object} The current value
         */
        get colors() {
            return this.#colors
        }

        /** Set the minimum value of the gauge
         * @param {string|number} val The minimum value
         */
        set min(val) {
            // Ensure that the internal value is numeric
            this.#min = parseFloat(val.toString())
            // (Re)Create and show the gauge
            this._renderGauge()
        }

        /** Get the minimum value of the gauge
         * @returns {number} The minimum value
         */
        get min() {
            return this.#min
        }

        /** Set the maximum value of the gauge
         * @param {string|number} val The maximum value
         */
        set max(val) {
            this.#max = parseFloat(val.toString())
            // (Re)Create and show the gauge
            this._renderGauge()
        }

        /** Get the maximum value of the gauge
         * @returns {number} The maximum value
         */
        get max() {
            return this.#max
        }

        /** Set the unit of the gauge
         * @param {string} val The unit. Default '%'
         * @example '°C'
         */
        set unit(val) {
            this.#unit = val
            this.valueEl.innerText = `${this.value}${this.#unit}`
        }

        /** Get the unit of the gauge
         * @returns {string} The unit
         */
        get unit() {
            return this.#unit
        }

        /** Set whether to hide the segment labels
         * @param {string|boolean} val Whether to hide the segment labels. Default false
         */
        set hideSegmentLabels(val) {
            if (val === '' || val === null || val.toString().toLowerCase() === 'true') val = true
            else val = false
            this.#hideSegmentLabels = val
            this.valsContainerEl.style.display = val ? 'none' : 'grid'
        }

        /** Get whether segment labels are hidden
         * @returns {boolean} Whether segment labels are hidden
         */
        get hideSegmentLabels() {
            return this.#hideSegmentLabels
        }

        /** NB: Attributes not available here - use connectedCallback to reference */
        constructor() {
            super()
            // Only attach the shadow dom if code and style isolation is needed - comment out if shadow dom not required
            if (template && template.content) this._construct(template.content.cloneNode(true))

            // Keep a reference to the segments container
            this.segContainerEl = this.shadowRoot.querySelector('.segments')
            // Keep a reference to the segment values container
            this.valsContainerEl = this.shadowRoot.querySelector('.segvals')
            // Keep a reference to the value container
            this.valueEl = this.shadowRoot.querySelector('.value')
        }

        /** Runs when an instance is added to the DOM */
        connectedCallback() {
            this._connect() // Keep at start.

            // Set up the gauge
            this._renderGauge()

            this._ready() // Keep at end. Let everyone know that a new instance of the component has been connected & is ready
        }

        /** Runs when an instance is removed from the DOM */
        disconnectedCallback() {
            this._disconnect() // Keep at end.
        }

        /** Runs when an observed attribute changes - Note: values are always strings
         * @param {string} attrib Name of watched attribute that has changed
         * @param {string} oldVal The previous attribute value
         * @param {string} newVal The new attribute value
         */
        attributeChangedCallback(attrib, oldVal, newVal) {
            /** Optionally ignore attrib changes until instance is fully connected
             * Otherwise this can fire BEFORE everthing is fully connected.
             */
            // if (!this.connected) return

            if (attrib === 'hide-segment-labels') attrib = 'hideSegmentLabels'

            // Don't bother if the new value same as old
            if ( oldVal === newVal ) return
            // Create a property from the value - WARN: Be careful with name clashes
            this[attrib] = newVal

            // NOTE: value and segments are handled by their own setters.

            // Add other dynamic attribute processing here.
            // If attribute processing doesn't need to be dynamic, process in connectedCallback as that happens earlier in the lifecycle

            // Keep at end. Let everyone know that an attribute has changed for this instance of the component
            this._event('attribChanged', { attribute: attrib, newVal: newVal, oldVal: oldVal, })
        }

        /** Create the gauge */
        _renderGauge() {
            // Calculate the segment step size
            const step = (this.#max - this.#min) / this.#segments

            // Clear out the existing segments
            this.segContainerEl.innerHTML = ''
            this.valsContainerEl.innerHTML = ''

            // Create LED segments
            for (let i = 0; i < this.#segments; i++) {
                // Create the segments
                const segment = document.createElement('div')
                segment.classList.add('led')
                const segmentValue = this.#min + i * step
                segment.title = segmentValue.toString()

                // Create the segment values
                if (i === 0) {
                    const segVal = document.createElement('div')
                    this.valsContainerEl.appendChild(segVal)
                }
                const segVal = document.createElement('div')
                segVal.innerText = Math.round(segmentValue).toString()

                // if colorSegments has a key greater than or equal to the segVal, set the hue to the value of the key
                const hueKey = Object.keys(this.#colors).reverse().find(key => { // eslint-disable-line @stylistic/newline-per-chained-call
                    return Number(key) <= segmentValue + step - 0.01
                })
                if (hueKey !== undefined) segment.style.setProperty('--hue', this.#colors[hueKey])

                // Is the segment on?
                if (this.#value >= segmentValue) segment.classList.add('on')

                // Attach click event listener
                segment.addEventListener('click', () => {
                    const data = {
                        gaugeValue: this.#value,
                        segment: i,
                        segmentValue: Number(segmentValue),
                    }
                    // Fire custom event
                    this._event('segment-click', data)
                    // If using uibuilder, send the new value to Node-RED.
                    this.uibSend('segment-click', data)
                })

                this.segContainerEl.appendChild(segment)
                this.valsContainerEl.appendChild(segVal)
            }

            // Keep a reference to the segment div elements
            this.segmentElements = this.segContainerEl.getElementsByTagName('div')

            const segVal = document.createElement('div')
            this.valsContainerEl.appendChild(segVal)
            segVal.innerText = this.#max.toString()

            // Render current value and unit if applicable
            this.valueEl.innerText = `${this.value}${this.#unit}`
        }
    } // ---- end of Class ---- //

    // Make the class the default export so it can be used elsewhere
    export default LedGauge

    /** Self register the class to global
     * Enables new data lists to be dynamically added via JS
     * and lets the static methods be called
     */
    window['LedGauge'] = LedGauge

    // Self-register the HTML tag
    customElements.define('led-gauge', LedGauge)