# Source: syntax-highlight.js

    /** Zero dependency web component to show a JavaScript object as a highlighted box in the UI
     *
     * 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.
     */

    /** To Do
     * - Use highlighter from visible-console
     */

    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 {
                display: block;   /* default is inline */
                contain: content; /* performance boost */
                color:white;
                background-color:black;
                margin-top: 0.5rem;
                margin-bottom: 0.5rem;
                padding: 0.5rem;
            }
            pre {
                font-family: Consolas, "ui-monospace", "Lucida Console", monospace;
                white-space: pre;
                margin: 0;
            }
            .key {color:#ffbf35}
            .string {color:#5dff39;}
            .number {color:#70aeff;}
            .boolean {color:#b993ff;}
            .null {color:#93ffe4;}
            .undefined {color:#ff93c9;}
        </style>
        <slot></slot>
        <pre><i>Waiting for data</i></pre>
    `

    /** Namespace
     * @namespace Beta
     */

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

     * @example Set the json property on the element
     *   const showMsg = document.getElementsByTagName('syntax-highlight')[0]
     *   showMsg.json({....})

     * 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 highlight Convert object to HTML

     * CUSTOM EVENTS:
      * "component-template:connected" - When an instance of the component is attached to the DOM. `evt.details` contains the details of the element.
      * "component-template:ready" - Alias for connected. The instance can handle property & attribute changes
      * "component-template:disconnected" - When an instance of the component is removed from the DOM. `evt.details` contains the details of the element.
      * "component-template:attribChanged" - When a watched attribute changes. `evt.details.data` contains the details of the change.
      * 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:
      * None

     * 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 {object|JSON|string} json JSON to convert
      * 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 SyntaxHighlight extends TiBaseComponent {
        /** Component version */
        static componentVersion = '2025-02-25'

        jsonData = {}

        /** 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:
                'auto',
            ]
        }

        /** Element.json = {...} to show the json object
         * @param {object | JSON | string} value JS Object or JSON string to show
         */
        set json(value) {
            if (typeof value === 'string') {
                try {
                    value = JSON.parse(value)
                } catch (e) {
                    console.warn(`[syntax-highlight] Could not parse string "${value}"`)
                }
            }
            this.jsonData = value

            // console.log(value, syntaxHighlight(value))
            this.shadowRoot.removeChild(this.shadowRoot.lastElementChild)
            // @ts-ignore
            this.shadowRoot.appendChild(this.constructor.highlight(value))
        }

        get json() {
            return this.jsonData
        }

        /** NB: Attributes not available here - use connectedCallback to reference */
        constructor() {
            super()
            this.attachShadow({ mode: 'open', delegatesFocus: true, })
                // Only append the template if code and style isolation is needed
                .append(template.content.cloneNode(true))

            // jQuery-like selectors but for the shadow. NB: Returns are STATIC not dynamic lists
            this.createShadowSelectors()  // in base class
        }

        /** Runs when an instance is added to the DOM */
        connectedCallback() {
            // Make sure instance has an ID. Create an id from name or calculation if needed
            this.ensureId()  // in base class
            // Apply parent styles from a stylesheet if required - only required if using an applied template
            this.doInheritStyles()  // in base class

            // this.addEventListener('new-msg', evt => {
            //     this.json = evt.detail
            // })

            // OPTIONAL. Listen for a uibuilder msg that is targetted at this instance of the component
            if (this.uib) document.addEventListener(`uibuilder:msg:_ui:update:${this.id}`, this._uibMsgHandler.bind(this) )

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

        /** Runs when an instance is removed from the DOM */
        disconnectedCallback() {
            // @ts-ignore Remove optional uibuilder event listener
            document.removeEventListener(`uibuilder:msg:_ui:update:${this.id}`, this._uibMsgHandler )

            // Keep at end. Let everyone know that an instance of the component has been disconnected
            this._event('disconnected')
        }

        /** 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) {
            // 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

            // 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, })
        } // --- end of attributeChangedCallback --- //

        /** Return a formatted HTML version of JSON object
         * @param {object|JSON} json JSON object to convert
         * @returns {HTMLElement} Highlighted JSON wrapped in a `<pre>` tag
         */
        static highlight(json) {
            json = JSON.stringify(json, undefined, 4)
            json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') // eslint-disable-line @stylistic/newline-per-chained-call
            json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
                let cls = 'number'
                if ((/^"/).test(match)) {
                    if ((/:$/).test(match)) {
                        cls = 'key'
                    } else {
                        cls = 'string'
                    }
                } else if ((/true|false/).test(match)) {
                    cls = 'boolean'
                } else if ((/null/).test(match)) {
                    cls = 'null'
                }
                return '<span class="' + cls + '">' + match + '</span>'
            })
            const myHtml = document.createElement('pre')
            myHtml.innerHTML = json
            return myHtml
        }
    } // ---- End of SyntaxHighlight class ---- //

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

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

    // Self-register the HTML tag
    customElements.define('syntax-highlight', SyntaxHighlight)