import { __esDecorate, __runInitializers } from "tslib";
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { PayloadSerializer, } from '@api-client/core/lib/transformers/PayloadSerializer.js';
import { queryRequestHeaders } from '@api-client/core/lib/definitions/HttpDefinitions.js';
import { live } from 'lit/directives/live.js';
import ApiElement from '../ApiElement.js';
import '../../md/icons/ui-icon.js';
import '../../md/switch/ui-switch.js';
import '../../md/icon-button/ui-icon-button.js';
import '../../md/button/ui-button.js';
let hasSupport;
try {
    const fd = new FormData();
    fd.append('test', new Blob(['.'], { type: 'image/jpg' }), 'test.jpg');
    hasSupport = 'entries' in fd;
}
catch {
    hasSupport = false;
}
export const hasFormDataSupport = hasSupport;
function normalizePartValue(item) {
    if (typeof item.value === 'string') {
        const oldValue = item.value;
        item.value = {
            type: 'string',
            data: oldValue,
        };
    }
}
let mimeSuggestions = [];
(() => {
    const items = queryRequestHeaders('content-type');
    const item = items.find((i) => i.key.toLowerCase() === 'content-type');
    if (!item || !item.autocomplete) {
        return;
    }
    mimeSuggestions = item.autocomplete;
})();
let BodyMultipartEditor = (() => {
    let _classSuper = ApiElement;
    let _instanceExtraInitializers = [];
    let _readOnly_decorators;
    let _readOnly_initializers = [];
    let _readOnly_extraInitializers = [];
    let _disabled_decorators;
    let _disabled_initializers = [];
    let _disabled_extraInitializers = [];
    let _allowFormInfo_decorators;
    let _allowFormInfo_initializers = [];
    let _allowFormInfo_extraInitializers = [];
    let _set_value_decorators;
    let _set_model_decorators;
    return class BodyMultipartEditor extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _readOnly_decorators = [property({ type: Boolean, reflect: true })];
            _disabled_decorators = [property({ type: Boolean, reflect: true })];
            _allowFormInfo_decorators = [property({ type: Boolean })];
            _set_value_decorators = [property({ type: Object })];
            _set_model_decorators = [property({ type: Array })];
            __esDecorate(this, null, _readOnly_decorators, { kind: "accessor", name: "readOnly", static: false, private: false, access: { has: obj => "readOnly" in obj, get: obj => obj.readOnly, set: (obj, value) => { obj.readOnly = value; } }, metadata: _metadata }, _readOnly_initializers, _readOnly_extraInitializers);
            __esDecorate(this, null, _disabled_decorators, { kind: "accessor", name: "disabled", static: false, private: false, access: { has: obj => "disabled" in obj, get: obj => obj.disabled, set: (obj, value) => { obj.disabled = value; } }, metadata: _metadata }, _disabled_initializers, _disabled_extraInitializers);
            __esDecorate(this, null, _allowFormInfo_decorators, { kind: "accessor", name: "allowFormInfo", static: false, private: false, access: { has: obj => "allowFormInfo" in obj, get: obj => obj.allowFormInfo, set: (obj, value) => { obj.allowFormInfo = value; } }, metadata: _metadata }, _allowFormInfo_initializers, _allowFormInfo_extraInitializers);
            __esDecorate(this, null, _set_value_decorators, { kind: "setter", name: "value", static: false, private: false, access: { has: obj => "value" in obj, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _set_model_decorators, { kind: "setter", name: "model", static: false, private: false, access: { has: obj => "model" in obj, set: (obj, value) => { obj.model = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #readOnly_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _readOnly_initializers, false
        /**
         * When set all controls are disabled in the form
         * @attribute
         */
        ));
        /**
         * When set the editor is in read only mode.
         * @attribute
         */
        get readOnly() { return this.#readOnly_accessor_storage; }
        set readOnly(value) { this.#readOnly_accessor_storage = value; }
        #disabled_accessor_storage = (__runInitializers(this, _readOnly_extraInitializers), __runInitializers(this, _disabled_initializers, false
        /**
         * @attribute
         */
        ));
        /**
         * When set all controls are disabled in the form
         * @attribute
         */
        get disabled() { return this.#disabled_accessor_storage; }
        set disabled(value) { this.#disabled_accessor_storage = value; }
        #allowFormInfo_accessor_storage = (__runInitializers(this, _disabled_extraInitializers), __runInitializers(this, _allowFormInfo_initializers, void 0));
        /**
         * @attribute
         */
        get allowFormInfo() { return this.#allowFormInfo_accessor_storage; }
        set allowFormInfo(value) { this.#allowFormInfo_accessor_storage = value; }
        /**
         * The value of this form.
         * When reading the `value` it generated the FormData from the model values.
         * When setting this property is regenerates the `model` to the value in the FormData.
         * When setting to anything other than the `FormData` it sets the `model` to an empty array.
         */
        get value() {
            return this.toFormData();
        }
        set value(value) {
            this.updateModel(value);
        }
        /**
         * The model used by the external application
         */
        modelInternal = (__runInitializers(this, _allowFormInfo_extraInitializers), []);
        /**
         * Computed data model for the view.
         * Don't set both `value` and `model`. If the model exists then
         * set only it or otherwise the `value` setter override the model.
         */
        get model() {
            return this.modelInternal;
        }
        set model(value) {
            const old = this.modelInternal;
            if (old === value) {
                return;
            }
            if (Array.isArray(value)) {
                this.modelInternal = value;
            }
            else {
                this.modelInternal = [];
            }
            this.requestUpdate('model', old);
            if (value) {
                this.upgradeLegacy(value);
            }
        }
        /**
         * @returns The FormData from the current model.
         */
        toFormData() {
            const validItems = this.modelInternal.filter((item) => {
                const { name, value } = item;
                if (!name) {
                    return false;
                }
                if (typeof value === 'string') {
                    return true;
                }
                if (value.type === 'string') {
                    return true;
                }
                if (value.type === 'blob' || value.type === 'file') {
                    return !!value.data && !!value.data.length;
                }
                return true;
            });
            return PayloadSerializer.deserializeFormData(validItems);
        }
        /**
         * Makes an attempt to upgrade old definitions of the `IMultipartBody`.
         * If this fails, the form will show error instead of part field.
         * @param value The current model.
         */
        async upgradeLegacy(value) {
            const ps = [];
            for (const part of value) {
                if (part.isFile === undefined) {
                    continue;
                }
                if (part.isFile === false && !part.type) {
                    delete part.isFile;
                    const oldValue = (part.value || '');
                    part.value = {
                        type: 'string',
                        data: oldValue,
                    };
                }
                else if (part.isFile === false && part.type) {
                    ps.push(this.upgradeLegacyBlob(part));
                }
                else {
                    ps.push(this.upgradeLegacyFile(part));
                }
            }
            if (ps.length) {
                await Promise.allSettled(ps);
                this.requestUpdate();
                this.notifyChange();
            }
        }
        async upgradeLegacyBlob(part) {
            if (typeof part.value !== 'string') {
                return;
            }
            const readBlob = PayloadSerializer.deserializeBlobLegacy(part.value);
            if (!readBlob) {
                return;
            }
            const contents = await readBlob.text();
            part.blobText = contents || '';
            const blob = new Blob([part.blobText], { type: part.type });
            part.value = await PayloadSerializer.stringifyBlob(blob);
            delete part.type;
            delete part.isFile;
        }
        async upgradeLegacyFile(part) {
            if (typeof part.value !== 'string') {
                return;
            }
            const readBlob = PayloadSerializer.deserializeBlobLegacy(part.value);
            if (!readBlob) {
                return;
            }
            const fname = part.fileName || 'unknown-file.txt';
            const file = new File([readBlob], fname, { type: readBlob.type });
            part.value = await PayloadSerializer.stringifyFile(file);
            delete part.fileName;
            delete part.isFile;
        }
        async updateModel(value) {
            if (!value || !(value instanceof FormData)) {
                this.model = [];
                setTimeout(() => {
                    this.dispatchEvent(new CustomEvent('modelupdate', { bubbles: false, composed: false }));
                });
                return;
            }
            const payload = await PayloadSerializer.stringifyFormData(value);
            this.model = payload.data;
            this.dispatchEvent(new CustomEvent('modelupdate', { bubbles: false, composed: false }));
        }
        /**
         * Adds an instance of a file to the form data
         */
        async addFile(file) {
            if (this.readOnly || this.disabled) {
                return;
            }
            const { model } = this;
            const entry = await PayloadSerializer.serializeFormDataEntry(file.name, file);
            model.push(entry);
            this.notifyChange();
            this.requestUpdate();
        }
        /**
         * Removes all parameters.
         */
        clear() {
            this.model = [];
            this.notifyChange();
        }
        /**
         * Adds an empty text part item to the form.
         */
        addEmptyText() {
            if (this.readOnly || this.disabled) {
                return;
            }
            const { model } = this;
            const obj = {
                name: '',
                value: {
                    data: '',
                    type: 'string',
                },
                enabled: true,
            };
            model.push(obj);
            this.requestUpdate();
        }
        /**
         * Adds an empty file part item to the form.
         */
        addEmptyFile() {
            if (this.readOnly || this.disabled) {
                return;
            }
            const { model } = this;
            const obj = {
                name: '',
                value: {
                    data: [],
                    type: 'file',
                },
                enabled: true,
            };
            model.push(obj);
            this.requestUpdate();
        }
        /**
         * Removes a parameter from an index.
         */
        removeProperty(index) {
            const items = this.modelInternal;
            items.splice(index, 1);
            this.requestUpdate();
            this.notifyChange();
        }
        /**
         * Toggles enabled state of a parameter.
         */
        toggleProperty(index, enabled) {
            const items = this.modelInternal;
            const item = items[index];
            item.enabled = enabled;
            this.notifyChange();
        }
        updatePartName(index, value) {
            const item = this.modelInternal[index];
            const old = item.name;
            if (old === value) {
                return;
            }
            item.name = value;
            this.notifyChange();
        }
        updateTextPartValue(index, value) {
            const item = this.modelInternal[index];
            normalizePartValue(item);
            const typedValue = item.value;
            if (typedValue.data === value) {
                return;
            }
            typedValue.data = value;
            this.notifyChange();
        }
        async updateBlobPartValue(index, value) {
            const item = this.modelInternal[index];
            normalizePartValue(item);
            const typedValue = item.value;
            if (typedValue.data === value) {
                return;
            }
            item.blobText = value;
            const blob = new Blob([value], { type: typedValue.meta?.mime || 'text/plain' });
            item.value = await PayloadSerializer.stringifyBlob(blob);
            this.notifyChange();
        }
        async updateFileValue(index, file) {
            const item = this.modelInternal[index];
            if (!item || !file) {
                return;
            }
            item.value = await PayloadSerializer.stringifyFile(file);
            this.notifyChange();
            this.requestUpdate();
        }
        async changePartMime(index, value) {
            const item = this.modelInternal[index];
            if (!item) {
                return;
            }
            normalizePartValue(item);
            // change the type from text to blob, if needed
            const typedValue = item.value;
            if (typedValue.type === 'string') {
                item.blobText = typedValue.data;
            }
            else if (!item.blobText && typedValue.data) {
                const restored = PayloadSerializer.deserializeBlob(typedValue);
                item.blobText = await restored.text();
            }
            const blob = new Blob([item.blobText || ''], { type: value });
            item.value = await PayloadSerializer.stringifyBlob(blob);
            this.notifyChange();
        }
        handleRemove(e) {
            const node = e.currentTarget;
            const index = Number(node.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.removeProperty(index);
        }
        handleToggle(e) {
            const node = e.currentTarget;
            const index = Number(node.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.toggleProperty(index, !!node.checked);
        }
        handleFilePick(e) {
            const node = e.target;
            const input = node.nextElementSibling;
            input.click();
        }
        handleFilePartValue(e) {
            e.stopPropagation();
            const input = e.target;
            if (!input.files) {
                return;
            }
            const index = Number(input.dataset.index);
            this.updateFileValue(index, input.files[0]);
        }
        handleNameChange(e) {
            const input = e.target;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.updatePartName(index, input.value);
            input.checkValidity();
        }
        handleValueChange(e) {
            const input = e.target;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.updateTextPartValue(index, input.value);
            input.checkValidity();
        }
        handleBlobValueChange(e) {
            const input = e.target;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.updateBlobPartValue(index, input.value);
            input.checkValidity();
        }
        handleMimeChange(e) {
            const input = e.target;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index)) {
                return;
            }
            this.changePartMime(index, input.value);
            input.checkValidity();
        }
        render() {
            if (!hasSupport) {
                return this.renderNoSupport();
            }
            return html ` ${this.renderList()} ${this.renderMimeList()} ${this.renderFormInfo()} `;
        }
        renderFormInfo() {
            if (!this.allowFormInfo) {
                return nothing;
            }
            return html `
      <p class="form-info">
        Note that the content-type header will be added when the request is sent. We encourage you to use variables to
        insert sensitive data.
      </p>
    `;
        }
        renderList() {
            const items = this.model;
            if (!Array.isArray(items) || !items.length) {
                return this.renderEmptyMessage();
            }
            return this.renderParams(items);
        }
        renderNoSupport() {
            return html ` <div class="empty-message">Your browser does not support this editor</div> `;
        }
        renderEmptyMessage() {
            return html ` <div class="empty-message">No parameters added</div> `;
        }
        renderParams(items) {
            return html ` ${items.map((item, index) => this.renderParam(item, index))} `;
        }
        renderToggleButton(part, index) {
            return html `
      <ui-switch
        .checked="${!!part.enabled}"
        @change="${this.handleToggle}"
        aria-label="Toggle this parameter"
        data-index="${index}"
        ?disabled="${this.readOnly}"
      ></ui-switch>
    `;
        }
        renderDeleteParameter(index) {
            return html `
      <ui-icon-button
        aria-label="Delete this parameter"
        data-index="${index}"
        @click="${this.handleRemove}"
        ?disabled="${this.readOnly}"
        class="delete-button"
      >
        <ui-icon icon="deleteOutline" role="presentation"></ui-icon>
      </ui-icon-button>
    `;
        }
        renderNameField(part, index) {
            return html `
      <div class="input-wrapper name-input">
        <div class="error" role="presentation">Name is required</div>
        <div class="state"></div>
        <input
          type="text"
          name="name"
          required
          autocomplete="off"
          .value="${live(part.name)}"
          data-index="${index}"
          ?readOnly="${this.readOnly}"
          aria-label="Part name value"
          placeholder="Part name"
          @change="${this.handleNameChange}"
        />
      </div>
    `;
        }
        renderValueField(part, index) {
            const value = part.value.data || '';
            return html `
      <div class="input-wrapper value-input">
        <div class="error" role="presentation">Value is required</div>
        <div class="state"></div>
        <input
          type="text"
          name="value"
          required
          .value="${live(value)}"
          data-index="${index}"
          ?readOnly="${this.readOnly}"
          aria-label="Text part value"
          placeholder="Part text value"
          @change="${this.handleValueChange}"
        />
      </div>
    `;
        }
        renderBlobValueField(part, index) {
            const { blobText } = part;
            return html `
      <div class="input-wrapper value-input">
        <div class="error" role="presentation">Value is required</div>
        <div class="state"></div>
        <input
          type="text"
          name="value"
          required
          .value="${live(blobText || '')}"
          data-index="${index}"
          ?readOnly="${this.readOnly}"
          aria-label="Text part value"
          placeholder="Part text value"
          @change="${this.handleBlobValueChange}"
        />
      </div>
    `;
        }
        renderMimeField(index, value) {
            return html `
      <div class="input-wrapper mime-input">
        <div class="error" role="presentation">Invalid character</div>
        <div class="state"></div>
        <input
          type="text"
          name="value"
          .value="${live(value || '')}"
          data-index="${index}"
          ?readOnly="${this.readOnly}"
          aria-label="Text part mime - optional value"
          placeholder="Part mime (optional)"
          @change="${this.handleMimeChange}"
          list="mime-list"
        />
      </div>
    `;
        }
        renderParam(part, index) {
            if (typeof part.isFile === 'boolean') {
                // When this is still set it means that when setting the model something went wrong.
                // This also mean we can't recover from here so the only option is to remove the part.
                return this.renderIncompatiblePart(index);
            }
            normalizePartValue(part);
            const value = part.value;
            if (value.type === 'string') {
                return this.renderTextPart(part, index);
            }
            if (value.type === 'blob') {
                return this.renderBlobPart(part, index);
            }
            return this.renderFilePart(part, index);
        }
        renderTextPart(part, index) {
            return html `
      <div class="param-row">
        ${this.renderToggleButton(part, index)} ${this.renderNameField(part, index)}
        ${this.renderValueField(part, index)} ${this.renderMimeField(index)} ${this.renderDeleteParameter(index)}
      </div>
    `;
        }
        renderBlobPart(part, index) {
            const item = part.value;
            const meta = item && item.meta;
            return html `
      <div class="param-row">
        ${this.renderToggleButton(part, index)} ${this.renderNameField(part, index)}
        ${this.renderBlobValueField(part, index)} ${this.renderMimeField(index, meta && meta.mime)}
        ${this.renderDeleteParameter(index)}
      </div>
    `;
        }
        renderFilePart(part, index) {
            return html `
      <div class="param-row">
        ${this.renderToggleButton(part, index)} ${this.renderNameField(part, index)}
        ${this.renderFileField(part, index)} ${this.renderDeleteParameter(index)}
      </div>
    `;
        }
        renderFileField(part, index) {
            const { readOnly } = this;
            const item = part.value;
            const meta = item && item.meta;
            return html `
      <div class="input-wrapper file-input">
        <ui-button color="text" @click="${this.handleFilePick}" ?disabled="${readOnly}" class="choose-button"
          >Choose file</ui-button
        >
        <input type="file" hidden data-index="${index}" @change="${this.handleFilePartValue}" />
        ${meta ? html `<span class="file-info">${meta.name} (${meta.mime}), ${item.data.length} bytes</span>` : ''}
      </div>
    `;
        }
        renderIncompatiblePart(index) {
            return html `
      <div class="param-row">
        <p class="incompatible-part">This part cannot be rendered. Please, remove this part.</p>
        ${this.renderDeleteParameter(index)}
      </div>
    `;
        }
        renderMimeList() {
            return html `
      <datalist id="mime-list">${mimeSuggestions.map((i) => html `<option value="${i}"></option>`)}</datalist>
    `;
        }
    };
})();
/**
 * Multipart payload editor.
 *
 * The element internally uses a generated from a FormData model. It is API Client's `IMultipartBody` array.
 * The FormData is generated on-fly when accessing the `value` getter. It also regenerates the model when
 * the `value` property change. Because of that you should never set the value on the editor but only manipulate the
 * `model` property.
 */
export default BodyMultipartEditor;
//# sourceMappingURL=BodyMultipartEditor.js.map