import { __decorate } from "tslib";
/* eslint-disable no-param-reassign */
import { html } from "lit";
import { Events as CoreEvents, Property, HeadersArray, HttpDefinitions } from '@api-client/core/build/browser.js';
import { property, state } from "lit/decorators.js";
import { live } from "lit/directives/live.js";
import ApiElement from "../ApiElement.js";
import { EventTypes } from "../../events/EventTypes.js";
import { SnackNotifications } from "../../ui/notification/SnackNotifications.js";
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-icon-button.js';
import '../../define/ui/ui-dropdown-list.js';
import '../../define/ui/ui-list.js';
import '../../define/ui/ui-list-item.js';
import '../../define/ui/ui-switch.js';
import '../../define/ui/ui-divider.js';
import '../../define/ui/ui-icon.js';
const nameSuggestions = HttpDefinitions.queryRequestHeaders().map(item => item.key);
/**
 * An editor that creates HTTP headers as a data model that can be translated into a headers string.
 */
export default class HeadersForm extends ApiElement {
    constructor() {
        super();
        /**
         * When set the editor is in read only mode.
         * @attribute
         */
        this.readOnly = false;
        /**
         * Whether the text editor is rendered instead of the form.
         * @attribute
         */
        this.text = false;
        /**
         * The headers model.
         */
        this.model = [];
        this.internalModel = new HeadersArray();
        this.autocompleteMap = {};
        this.handleGlobalContentType = this.handleGlobalContentType.bind(this);
    }
    connectedCallback() {
        super.connectedCallback();
        this.eventsTarget.addEventListener(EventTypes.Http.Request.State.contentTypeChange, this.handleGlobalContentType);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.eventsTarget.removeEventListener(EventTypes.Http.Request.State.contentTypeChange, this.handleGlobalContentType);
    }
    update(cp) {
        if (cp.has('model')) {
            if (this.model) {
                this.internalModel = HeadersArray.from(this.model);
                this.model.forEach((item, index) => this.setupValueAutocomplete(item.name, index));
            }
            else {
                this.internalModel = new HeadersArray();
            }
        }
        if (cp.has('text')) {
            if (this.text) {
                this.textValue = this.internalModel.toRawString();
            }
            else {
                this.textValue = undefined;
            }
        }
        super.update(cp);
    }
    setupHeaders(headers) {
        this.autocompleteMap = {};
        if (headers) {
            this.internalModel = HeadersArray.fromHeaders(headers);
            this.model = this.internalModel.toJSON();
        }
        else {
            this.internalModel = new HeadersArray();
            this.model = [];
        }
        this.requestUpdate();
    }
    /**
     * Updates header value. It the header does not exists at the position it adds a new header.
     * @param nameOrIndex Header name or index
     * @param value Header value
     */
    updateHeaderValue(nameOrIndex, value) {
        const { internalModel } = this;
        let index;
        if (typeof nameOrIndex === 'number') {
            index = nameOrIndex;
        }
        else {
            const lowerName = nameOrIndex.toLowerCase();
            index = internalModel.findIndex((item) => (item.name || '').toLocaleLowerCase() === lowerName);
        }
        if (index === -1) {
            if (typeof nameOrIndex === 'string') {
                this.addHeader(nameOrIndex, value);
            }
            else {
                this.addHeader('', value);
            }
            this.propagateModelChange();
            return;
        }
        internalModel[index].value = value;
        this.model[index].value = value;
        if (this.text) {
            this.textValue = internalModel.toRawString();
        }
        this.propagateModelChange();
        this.requestUpdate();
    }
    /**
     * Updates header name. It the header does not exists at the position it adds a new header.
     * @param nameOrIndex The header name or index
     * @param value The header name
     */
    updateHeaderName(nameOrIndex, value) {
        const { internalModel } = this;
        let index;
        if (typeof nameOrIndex === 'number') {
            index = nameOrIndex;
        }
        else {
            const lowerName = nameOrIndex.toLowerCase();
            index = internalModel.findIndex((item) => (item.name || '').toLocaleLowerCase() === lowerName);
        }
        if (index === -1) {
            this.addHeader(value);
            return;
        }
        internalModel[index].name = value;
        this.model[index].name = value;
        this.setupValueAutocomplete(value, index);
        if (this.text) {
            this.textValue = internalModel.toRawString();
        }
        this.propagateModelChange();
        this.requestUpdate();
    }
    setupValueAutocomplete(name, index) {
        const query = name.toLowerCase();
        const items = HttpDefinitions.queryRequestHeaders(query);
        const item = items.find(i => i.key.toLowerCase() === query);
        if (item && item.autocomplete) {
            this.autocompleteMap[`header-value-${index}`] = item.autocomplete;
        }
        else {
            delete this.autocompleteMap[`header-value-${index}`];
        }
    }
    /**
     * Removes a header from the list.
     * @param nameOrIndex Header name or its index.
     */
    removeHeader(nameOrIndex) {
        const { internalModel } = this;
        let index;
        if (typeof nameOrIndex === 'number') {
            index = nameOrIndex;
        }
        else {
            const lowerName = nameOrIndex.toLowerCase();
            index = internalModel.findIndex((item) => (item.name || '').toLocaleLowerCase() === lowerName);
        }
        if (index < 0) {
            return;
        }
        internalModel.splice(index, 1);
        this.model.splice(index, 1);
        delete this.autocompleteMap[`header-value-${index}`];
        if (this.text) {
            this.textValue = internalModel.toRawString();
        }
        this.propagateModelChange();
        this.requestUpdate();
    }
    /**
     * Adds a header to the list of headers
     */
    addHeader(name = '', value = '') {
        const prop = Property.String(name, value);
        this.internalModel.push(prop);
        this.model.push(prop.toJSON());
        // the value hasn't actually changed here so no events
        this.requestUpdate();
    }
    /**
     * Removes all parameters.
     */
    clear() {
        this.model.splice(0);
        this.internalModel.splice(0);
        this.propagateModelChange();
        this.autocompleteMap = {};
        if (this.text) {
            this.textValue = '';
        }
        this.requestUpdate();
    }
    /**
     * Toggles enabled state of a header.
     */
    toggleHeader(index, enabled) {
        this.model[index].enabled = enabled;
        this.internalModel[index].enabled = enabled;
        if (this.text) {
            this.textValue = this.internalModel.toRawString();
        }
        this.propagateModelChange();
    }
    /**
     * Toggles the state between form and text editors.
     */
    toggle() {
        this.text = !this.text;
    }
    async copyToClipboard() {
        if (!navigator.clipboard) {
            throw new Error(`Clipboard API not supported.`);
        }
        await navigator.clipboard.writeText(this.internalModel.toString());
        SnackNotifications.notify('Headers copied to clipboard.', 2000);
        CoreEvents.Telemetry.event({
            category: 'Usage',
            action: 'Click',
            label: 'Headers editor clipboard copy',
        }, this);
    }
    handleGlobalContentType(e) {
        const { value } = e.detail;
        this.updateHeaderValue('content-type', value);
    }
    /**
     * Updates the `value` from the current model and dispatches the value change event
     */
    propagateModelChange() {
        this.notifyChange();
    }
    async focusLast() {
        await this.updateComplete;
        const row = this.shadowRoot?.querySelector('.param-row:last-of-type');
        if (!row) {
            return;
        }
        try {
            const node = row.querySelector('.name-input input');
            if (node) {
                node.focus();
            }
        }
        catch (e) {
            // ...
        }
    }
    handleAdd() {
        this.addHeader();
    }
    handleOption(e) {
        const element = e.detail.item;
        const { option } = element.dataset;
        if (!option) {
            return;
        }
        if (option === 'clear') {
            this.clear();
        }
        else if (option === 'text-editor') {
            this.toggle();
        }
    }
    handleNameChange(e) {
        const input = e.target;
        const index = Number(input.dataset.index);
        if (Number.isNaN(index)) {
            return;
        }
        this.updateHeaderName(index, input.value);
        input.checkValidity();
    }
    handleValueChange(e) {
        const input = e.target;
        const index = Number(input.dataset.index);
        if (Number.isNaN(index)) {
            return;
        }
        this.updateHeaderValue(index, input.value);
        input.checkValidity();
    }
    handleRemove(e) {
        const node = e.currentTarget;
        const index = Number(node.dataset.index);
        if (Number.isNaN(index)) {
            return;
        }
        this.removeHeader(index);
    }
    handleToggle(e) {
        const node = e.currentTarget;
        const index = Number(node.dataset.index);
        if (Number.isNaN(index)) {
            return;
        }
        this.toggleHeader(index, node.checked);
    }
    handleText(e) {
        const input = e.target;
        const { value } = input;
        this.setupHeaders(value);
        this.textValue = value;
        this.notifyChange();
    }
    handleCopy() {
        this.copyToClipboard();
    }
    render() {
        return html `
    ${this.renderActionButtons()}
    ${this.renderEditor()}
    ${this.renderNamesList()}
    ${this.renderAutocompleteLists()}
    `;
    }
    renderActionButtons() {
        return html `
    <div class="editor-actions">
      <ui-button type="tonal" @click="${this.handleAdd}">Add</ui-button>
      <ui-button type="tonal" @click="${this.handleCopy}">Copy</ui-button>
      <ui-dropdown-list @select="${this.handleOption}" closeOnOutsideClick class="dropdown">
        <ui-icon-button aria-label="Additional options"><ui-icon icon="moreVert" role="presentation"></ui-icon></ui-icon-button>
        <ui-list slot="dropdown" role="menu">
          <ui-list-item role="menuitem" data-option="text-editor" image="icon" title="Toggles between a text editor and a form">
            <ui-icon role="presentation" slot="start" icon="${this.text ? 'checkBox' : 'checkBoxBlank'}"></ui-icon>
            Text editor
          </ui-list-item>
          <ui-divider type="inset"></ui-divider>
          <ui-list-item role="menuitem" data-option="clear" image="icon" title="Removes all parameters from the editor">
            <ui-icon role="presentation" slot="start" icon="deleteOutline"></ui-icon>
            Clear form
          </ui-list-item>
        </ui-list>
      </ui-dropdown-list>
    </div>
    `;
    }
    renderEditor() {
        if (this.text) {
            return this.renderText();
        }
        return this.renderList();
    }
    renderText() {
        const { textValue = '' } = this;
        return html `
    <div class="text-input">
      <div class="error" role="presentation">Invalid character</div>
      <div class="state"></div>
      <textarea 
        .value="${live(textValue)}"
        data-enable-grammarly="false"
        @change="${this.handleText}"
      ></textarea>
    </div>
    `;
    }
    renderList() {
        const items = this.internalModel;
        if (!Array.isArray(items) || !items.length) {
            return this.renderEmptyMessage();
        }
        return this.renderParams(items);
    }
    renderEmptyMessage() {
        return html `
    <div class="empty-message">No parameters added</div>
    `;
    }
    renderParams(items) {
        return html `
    ${items.map((item, index) => this.renderParam(item, index))}
    `;
    }
    renderParam(item, index) {
        return html `
    <div class="param-row">
      ${this.renderToggleButton(item, index)}
      ${this.renderNameField(item, index)}
      ${this.renderValueField(item, index)}
      ${this.renderDeleteParameter(index)}
    </div>
    `;
    }
    renderToggleButton(item, index) {
        return html `
    <ui-switch 
      .checked="${!!item.enabled}" 
      @change="${this.handleToggle}" 
      aria-label="Toggle this parameter"
      data-index="${index}"
    ></ui-switch>
    `;
    }
    renderDeleteParameter(index) {
        return html `
    <ui-icon-button aria-label="Delete this parameter" data-index="${index}" @click="${this.handleRemove}">
      <ui-icon icon="deleteOutline"></ui-icon>
    </ui-icon-button>
    `;
    }
    renderNameField(item, index) {
        return html `
    <div class="input-wrapper name-input">
      <div class="error" role="presentation">Invalid character</div>
      <div class="state"></div>
      <input
        type="text"
        name="name"
        autocomplete="off"
        .value="${live(item.name)}" 
        data-index="${index}" 
        ?readOnly="${this.readOnly}" 
        pattern="\\S*" 
        aria-label="Parameter name value"
        @change="${this.handleNameChange}"
        list="names-list"
      />
    </div>
    `;
    }
    renderValueField(item, index) {
        return html `
    <div class="input-wrapper value-input">
      <div class="error" role="presentation">Invalid character</div>
      <div class="state"></div>
      <input
        type="text"
        name="value"
        .value="${live(item.value)}" 
        data-index="${index}" 
        ?readOnly="${this.readOnly}" 
        aria-label="Parameter value"
        @change="${this.handleValueChange}"
        list="header-value-${index}"
      />
    </div>
    `;
    }
    renderNamesList() {
        return html `
    <datalist id="names-list">
    ${nameSuggestions.map(i => html `<option value="${i}">`)}
    </datalist>
    `;
    }
    renderAutocompleteLists() {
        const { autocompleteMap } = this;
        return Object.keys(autocompleteMap).map(key => html `
    <datalist id="${key}">
    ${autocompleteMap[key].map(i => html `<option value="${i}">`)}
    </datalist>
    `);
    }
}
__decorate([
    property({ type: Boolean, reflect: true })
], HeadersForm.prototype, "readOnly", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], HeadersForm.prototype, "text", void 0);
__decorate([
    property({ type: Array })
], HeadersForm.prototype, "model", void 0);
__decorate([
    state()
], HeadersForm.prototype, "internalModel", void 0);
__decorate([
    state()
], HeadersForm.prototype, "textValue", void 0);
//# sourceMappingURL=HeadersForm.js.map