import { h, Host, } from "@stencil/core";
import { createRandomString } from "../../util/random-string";
import CodeMirror from "codemirror";
import "codemirror/mode/css/css";
import "codemirror/mode/htmlmixed/htmlmixed";
import "codemirror/mode/javascript/javascript";
import "codemirror/mode/jinja2/jinja2";
import "codemirror/addon/selection/active-line";
import "codemirror/addon/edit/matchbrackets";
import "codemirror/addon/edit/matchtags";
import "codemirror/addon/lint/lint";
import "codemirror/addon/lint/json-lint";
import "codemirror/addon/fold/foldgutter";
import "codemirror/addon/fold/brace-fold";
import "codemirror/addon/fold/xml-fold";
import jslint from "jsonlint-mod";
import translate from "../../global/translations";
/**
 * @exampleComponent limel-example-code-editor-basic
 * @exampleComponent limel-example-code-editor-readonly-with-line-numbers
 * @exampleComponent limel-example-code-editor-fold-lint-wrap
 * @exampleComponent limel-example-code-editor-copy
 * @exampleComponent limel-example-code-editor-composite
 */
export class CodeEditor {
    constructor() {
        /**
         * The code to be rendered
         */
        this.value = '';
        /**
         * Set to `true` to make the editor read-only.
         * Use `readonly` when the editor is only there to present the data it holds,
         * and will not become possible for the current user to edit.
         */
        this.readonly = false;
        /**
         * Set to `true` to disable the editor.
         * Use `disabled` to indicate that the editor can normally be interacted
         * with, but is currently disabled. This tells the user that if certain
         * requirements are met, the editor may become enabled again.
         */
        this.disabled = false;
        /**
         * Set to `true` to indicate that the current value of the input editor is
         * invalid.
         */
        this.invalid = false;
        /**
         * Set to `true` to indicate that the field is required.
         */
        this.required = false;
        /**
         * Displays line numbers in the editor
         */
        this.lineNumbers = false;
        /**
         * Wraps long lines instead of showing horizontal scrollbar
         */
        this.lineWrapping = false;
        /**
         * Allows the user to fold code
         */
        this.fold = false;
        /**
         * Enables linting of JSON content
         */
        this.lint = false;
        /**
         * Select color scheme for the editor
         */
        this.colorScheme = 'auto';
        /**
         * Defines the language for translations.
         * Will translate the translatable strings on the components.
         */
        this.translationLanguage = 'en';
        /**
         * Set to false to hide the copy button
         */
        this.showCopyButton = true;
        this.wasCopied = 'idle';
        this.handleChangeDarkMode = () => {
            if (this.colorScheme !== 'auto') {
                return;
            }
            this.forceRedraw();
        };
        this.handleChange = () => {
            this.change.emit(this.editor.getValue());
        };
        this.handleResize = () => {
            if (!this.editor) {
                return;
            }
            this.editor.refresh();
        };
        this.renderHelperLine = () => {
            if (!this.helperText) {
                return;
            }
            return (h("limel-helper-line", { helperText: this.helperText, helperTextId: this.helperTextId, invalid: this.invalid }));
        };
        this.copyCode = async () => {
            var _a, _b, _c;
            // Prefer the live editor content; fall back to the prop value
            const text = (_c = (_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : this.value) !== null && _c !== void 0 ? _c : '';
            try {
                await navigator.clipboard.writeText(text);
                this.wasCopied = 'success';
                setTimeout(() => {
                    this.wasCopied = 'idle';
                }, 2000);
            }
            catch (error) {
                console.error('Failed to copy to clipboard:', error);
                this.wasCopied = 'failed';
            }
        };
        this.labelId = createRandomString();
        this.helperTextId = createRandomString();
    }
    connectedCallback() {
        this.observer = new ResizeObserver(this.handleResize);
        this.observer.observe(this.host);
        this.darkMode.addEventListener('change', this.handleChangeDarkMode);
    }
    disconnectedCallback() {
        var _a;
        this.observer.unobserve(this.host);
        (_a = this.editor) === null || _a === void 0 ? void 0 : _a.off('change', this.handleChange);
        this.editor = null;
        this.darkMode.removeEventListener('change', this.handleChangeDarkMode);
        const editorElement = this.host.shadowRoot.querySelector('.editor');
        // eslint-disable-next-line no-unsafe-optional-chaining
        for (const child of editorElement === null || editorElement === void 0 ? void 0 : editorElement.childNodes) {
            child.remove();
        }
    }
    componentDidRender() {
        if (this.editor) {
            return;
        }
        this.editor = this.createEditor();
        this.updateInputFieldAccessibilityAttributes();
    }
    watchValue(newValue) {
        if (!this.editor) {
            return;
        }
        const currentValue = this.editor.getValue();
        if (newValue === currentValue) {
            // Circuit breaker for when the change comes from the editor itself
            // The caret position will be reset without this
            return;
        }
        this.editor.getDoc().setValue(newValue || '');
    }
    watchDisabled() {
        this.updateEditorReadOnlyState();
        this.updateInputFieldAccessibilityAttributes();
    }
    watchReadonly() {
        this.updateEditorReadOnlyState();
        this.updateInputFieldAccessibilityAttributes();
    }
    watchInvalid() {
        this.updateInputFieldAccessibilityAttributes();
    }
    watchRequired() {
        this.updateInputFieldAccessibilityAttributes();
    }
    watchHelperText() {
        this.updateInputFieldAccessibilityAttributes();
    }
    createEditor() {
        const options = this.getOptions();
        const editor = CodeMirror(this.host.shadowRoot.querySelector('.editor'), options);
        editor.on('change', this.handleChange);
        // Replace tab with spaces and use the actual indent setting for
        // the space count
        editor.setOption('extraKeys', {
            Tab: (codeMirror) => {
                const spaces = ' '.repeat(codeMirror.getOption('indentUnit'));
                codeMirror.replaceSelection(spaces);
            },
        });
        return editor;
    }
    getOptions() {
        let mode = this.language;
        const TAB_SIZE = 4;
        let theme = 'lime light';
        const gutters = [];
        if (this.isDarkMode()) {
            theme = 'lime dark';
        }
        if (this.language === 'json') {
            mode = {
                name: 'application/json',
                json: true,
            };
            if (this.lint) {
                gutters.push('CodeMirror-lint-markers');
                if (!('jsonlint' in window)) {
                    window['jsonlint'] = jslint;
                }
            }
        }
        else if (this.language === 'typescript') {
            mode = {
                name: 'application/typescript',
                typescript: true,
            };
        }
        else if (this.language === 'html') {
            mode = 'htmlmixed';
        }
        if (this.fold) {
            gutters.push('CodeMirror-foldgutter');
        }
        return {
            mode: mode,
            value: this.value || '',
            theme: theme,
            readOnly: this.getReadOnlyOption(),
            tabSize: TAB_SIZE,
            indentUnit: TAB_SIZE,
            lineNumbers: this.lineNumbers,
            lineWrapping: this.lineWrapping,
            styleActiveLine: true,
            matchBrackets: true,
            matchTags: { bothTags: true },
            lint: this.lint,
            foldGutter: this.fold,
            gutters: gutters,
        };
    }
    isDarkMode() {
        if (this.colorScheme !== 'auto') {
            return this.colorScheme === 'dark';
        }
        return this.darkMode.matches;
    }
    render() {
        const classList = {
            editor: true,
            readonly: this.readonly || this.disabled,
            disabled: this.disabled,
            'is-dark-mode': this.isDarkMode(),
            'is-light-mode': !this.isDarkMode(),
        };
        return (h(Host, { key: '648df79d8ea88a3f87028743197214b45f3708af' }, this.renderCopyButton(), h("limel-notched-outline", { key: '8ec590da7d7cfb0c313ae61ecf517f36e978a756', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid, disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasFloatingLabel: true }, h("div", { key: '3fd86155267f590d9578ed673d56e1e2fe6ee7ce', slot: "content", class: classList })), this.renderHelperLine()));
    }
    forceRedraw() {
        // eslint-disable-next-line sonarjs/pseudo-random
        this.random = Math.random();
    }
    renderCopyButton() {
        var _a;
        const hasContent = !!(((_a = this.editor) === null || _a === void 0 ? void 0 : _a.getValue()) || this.value);
        if (!hasContent || this.disabled || !this.showCopyButton) {
            return;
        }
        return (h("button", { class: "copy-button", onClick: this.copyCode, type: "button", "aria-live": "polite", "aria-atomic": "true", "aria-label": this.getButtonAriaLabel() }, this.getButtonText()));
    }
    getButtonText() {
        if (this.wasCopied === 'success') {
            return translate.get('code-editor.copied', this.translationLanguage);
        }
        if (this.wasCopied === 'failed') {
            return translate.get('code-editor.copy-failed', this.translationLanguage);
        }
        return translate.get('code-editor.copy', this.translationLanguage);
    }
    getButtonAriaLabel() {
        const label = this.label ||
            translate.get('code-editor', this.translationLanguage);
        const translationKeys = {
            success: 'code-editor.copied-aria-label',
            failed: 'code-editor.copy-failed-aria-label',
            idle: 'code-editor.copy-aria-label',
        };
        const key = translationKeys[this.wasCopied];
        return translate.get(key, this.translationLanguage, { label });
    }
    get darkMode() {
        return matchMedia('(prefers-color-scheme: dark)');
    }
    updateEditorReadOnlyState() {
        if (!this.editor) {
            return;
        }
        this.editor.setOption('readOnly', this.getReadOnlyOption());
    }
    getReadOnlyOption() {
        if (this.disabled) {
            return 'nocursor';
        }
        return this.readonly;
    }
    updateInputFieldAccessibilityAttributes() {
        if (!this.editor) {
            return;
        }
        const inputField = this.editor.getInputField();
        if (!inputField) {
            return;
        }
        inputField.id = this.labelId;
        if (this.helperText) {
            inputField.setAttribute('aria-describedby', this.helperTextId);
            inputField.setAttribute('aria-controls', this.helperTextId);
        }
        else {
            inputField.removeAttribute('aria-describedby');
            inputField.removeAttribute('aria-controls');
        }
        if (this.required) {
            inputField.setAttribute('aria-required', 'true');
        }
        else {
            inputField.removeAttribute('aria-required');
        }
        if (this.invalid) {
            inputField.setAttribute('aria-invalid', 'true');
        }
        else {
            inputField.removeAttribute('aria-invalid');
        }
        if (this.disabled) {
            inputField.setAttribute('aria-disabled', 'true');
        }
        else {
            inputField.removeAttribute('aria-disabled');
        }
        if (this.readonly || this.disabled) {
            inputField.setAttribute('aria-readonly', 'true');
        }
        else {
            inputField.removeAttribute('aria-readonly');
        }
    }
    static get is() { return "limel-code-editor"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["code-editor.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["code-editor.css"]
        };
    }
    static get properties() {
        return {
            "value": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The code to be rendered"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "value",
                "defaultValue": "''"
            },
            "language": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "Language",
                    "resolved": "\"css\" | \"html\" | \"javascript\" | \"jinja2\" | \"json\" | \"typescript\"",
                    "references": {
                        "Language": {
                            "location": "import",
                            "path": "./code-editor.types",
                            "id": "src/components/code-editor/code-editor.types.ts::Language",
                            "referenceLocation": "Language"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The language of the code"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "language"
            },
            "readonly": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to make the editor read-only.\nUse `readonly` when the editor is only there to present the data it holds,\nand will not become possible for the current user to edit."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "readonly",
                "defaultValue": "false"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to disable the editor.\nUse `disabled` to indicate that the editor can normally be interacted\nwith, but is currently disabled. This tells the user that if certain\nrequirements are met, the editor may become enabled again."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "disabled",
                "defaultValue": "false"
            },
            "invalid": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to indicate that the current value of the input editor is\ninvalid."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "invalid",
                "defaultValue": "false"
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to indicate that the field is required."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "required",
                "defaultValue": "false"
            },
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "The input label."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "label"
            },
            "helperText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Optional helper text to display below the input field when it has focus"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "helper-text"
            },
            "lineNumbers": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Displays line numbers in the editor"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "line-numbers",
                "defaultValue": "false"
            },
            "lineWrapping": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Wraps long lines instead of showing horizontal scrollbar"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "line-wrapping",
                "defaultValue": "false"
            },
            "fold": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Allows the user to fold code"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "fold",
                "defaultValue": "false"
            },
            "lint": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Enables linting of JSON content"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "lint",
                "defaultValue": "false"
            },
            "colorScheme": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "ColorScheme",
                    "resolved": "\"auto\" | \"dark\" | \"light\"",
                    "references": {
                        "ColorScheme": {
                            "location": "import",
                            "path": "./code-editor.types",
                            "id": "src/components/code-editor/code-editor.types.ts::ColorScheme",
                            "referenceLocation": "ColorScheme"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Select color scheme for the editor"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "color-scheme",
                "defaultValue": "'auto'"
            },
            "translationLanguage": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "Languages",
                    "resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
                    "references": {
                        "Languages": {
                            "location": "import",
                            "path": "../date-picker/date.types",
                            "id": "src/components/date-picker/date.types.ts::Languages",
                            "referenceLocation": "Languages"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Defines the language for translations.\nWill translate the translatable strings on the components."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "translation-language",
                "defaultValue": "'en'"
            },
            "showCopyButton": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to false to hide the copy button"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "show-copy-button",
                "defaultValue": "true"
            }
        };
    }
    static get states() {
        return {
            "random": {},
            "wasCopied": {}
        };
    }
    static get events() {
        return [{
                "method": "change",
                "name": "change",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the code has changed. Will only be emitted when the code\narea has lost focus"
                },
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                }
            }];
    }
    static get elementRef() { return "host"; }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "watchValue"
            }, {
                "propName": "disabled",
                "methodName": "watchDisabled"
            }, {
                "propName": "readonly",
                "methodName": "watchReadonly"
            }, {
                "propName": "invalid",
                "methodName": "watchInvalid"
            }, {
                "propName": "required",
                "methodName": "watchRequired"
            }, {
                "propName": "helperText",
                "methodName": "watchHelperText"
            }];
    }
}
