import { __esDecorate, __runInitializers } from "tslib";
import { html, LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import { CsvImporter } from '@api-client/core/modeling/importers/CsvImporter.js';
import { FilteringJsonSchemaImporter } from '@api-client/core/modeling/importers/FilteringJsonSchemaImporter.js';
import { FileDropMixin } from '../../mixins/FileDropMixin.js';
import { fromError } from '../../exceptions/builder.js';
const Base = FileDropMixin(LitElement);
var ImportType;
(function (ImportType) {
    ImportType[ImportType["unknown"] = 0] = "unknown";
    ImportType[ImportType["CSV"] = 1] = "CSV";
    ImportType[ImportType["JSON"] = 2] = "JSON";
})(ImportType || (ImportType = {}));
var ProcessState;
(function (ProcessState) {
    ProcessState[ProcessState["idle"] = 0] = "idle";
    ProcessState[ProcessState["reading"] = 1] = "reading";
    ProcessState[ProcessState["previewing"] = 2] = "previewing";
})(ProcessState || (ProcessState = {}));
let DomainFileImport = (() => {
    let _classSuper = Base;
    let _root_decorators;
    let _root_initializers = [];
    let _root_extraInitializers = [];
    let _importType_decorators;
    let _importType_initializers = [];
    let _importType_extraInitializers = [];
    let _processState_decorators;
    let _processState_initializers = [];
    let _processState_extraInitializers = [];
    let _importFiles_decorators;
    let _importFiles_initializers = [];
    let _importFiles_extraInitializers = [];
    let _selectedFileIndex_decorators;
    let _selectedFileIndex_initializers = [];
    let _selectedFileIndex_extraInitializers = [];
    return class DomainFileImport extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _root_decorators = [property({ type: Object })];
            _importType_decorators = [state()];
            _processState_decorators = [state()];
            _importFiles_decorators = [state()];
            _selectedFileIndex_decorators = [state()];
            __esDecorate(this, null, _root_decorators, { kind: "accessor", name: "root", static: false, private: false, access: { has: obj => "root" in obj, get: obj => obj.root, set: (obj, value) => { obj.root = value; } }, metadata: _metadata }, _root_initializers, _root_extraInitializers);
            __esDecorate(this, null, _importType_decorators, { kind: "accessor", name: "importType", static: false, private: false, access: { has: obj => "importType" in obj, get: obj => obj.importType, set: (obj, value) => { obj.importType = value; } }, metadata: _metadata }, _importType_initializers, _importType_extraInitializers);
            __esDecorate(this, null, _processState_decorators, { kind: "accessor", name: "processState", static: false, private: false, access: { has: obj => "processState" in obj, get: obj => obj.processState, set: (obj, value) => { obj.processState = value; } }, metadata: _metadata }, _processState_initializers, _processState_extraInitializers);
            __esDecorate(this, null, _importFiles_decorators, { kind: "accessor", name: "importFiles", static: false, private: false, access: { has: obj => "importFiles" in obj, get: obj => obj.importFiles, set: (obj, value) => { obj.importFiles = value; } }, metadata: _metadata }, _importFiles_initializers, _importFiles_extraInitializers);
            __esDecorate(this, null, _selectedFileIndex_decorators, { kind: "accessor", name: "selectedFileIndex", static: false, private: false, access: { has: obj => "selectedFileIndex" in obj, get: obj => obj.selectedFileIndex, set: (obj, value) => { obj.selectedFileIndex = value; } }, metadata: _metadata }, _selectedFileIndex_initializers, _selectedFileIndex_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #root_accessor_storage = __runInitializers(this, _root_initializers, void 0);
        /**
         * The instance of the root DataNamespace.
         */
        get root() { return this.#root_accessor_storage; }
        set root(value) { this.#root_accessor_storage = value; }
        #importType_accessor_storage = (__runInitializers(this, _root_extraInitializers), __runInitializers(this, _importType_initializers, ImportType.unknown
        /**
         * The state of the file import process.
         */
        ));
        /**
         * The type of the files to be imported.
         */
        get importType() { return this.#importType_accessor_storage; }
        set importType(value) { this.#importType_accessor_storage = value; }
        #processState_accessor_storage = (__runInitializers(this, _importType_extraInitializers), __runInitializers(this, _processState_initializers, ProcessState.idle
        /**
         * The files that are currently being processed.
         */
        ));
        /**
         * The state of the file import process.
         */
        get processState() { return this.#processState_accessor_storage; }
        set processState(value) { this.#processState_accessor_storage = value; }
        #importFiles_accessor_storage = (__runInitializers(this, _processState_extraInitializers), __runInitializers(this, _importFiles_initializers, []
        /**
         * The index of the currently selected file in the importFiles array.
         * This is used to keep track of which file preview is being rendered.
         */
        ));
        /**
         * The files that are currently being processed.
         */
        get importFiles() { return this.#importFiles_accessor_storage; }
        set importFiles(value) { this.#importFiles_accessor_storage = value; }
        #selectedFileIndex_accessor_storage = (__runInitializers(this, _importFiles_extraInitializers), __runInitializers(this, _selectedFileIndex_initializers, 0
        /**
         * The user options for the CSV parser.
         * This is used to configure the CSV parser.
         */
        ));
        /**
         * The index of the currently selected file in the importFiles array.
         * This is used to keep track of which file preview is being rendered.
         */
        get selectedFileIndex() { return this.#selectedFileIndex_accessor_storage; }
        set selectedFileIndex(value) { this.#selectedFileIndex_accessor_storage = value; }
        /**
         * The user options for the CSV parser.
         * This is used to configure the CSV parser.
         */
        cvsOptions = __runInitializers(this, _selectedFileIndex_extraInitializers);
        constructor() {
            super();
            this.dropTarget = this;
        }
        /**
         * Reports an error to the element.
         * It will dispatch an 'error' event with the Exception object as the detail.
         * It unifies the error handling across the application.
         * @param error The error to report.
         */
        reportError(error) {
            const e = fromError(error);
            this.dispatchEvent(new CustomEvent('error', {
                bubbles: true,
                composed: true,
                detail: e,
            }));
        }
        handleFileChange(e) {
            const inputElement = e.target;
            const fileList = inputElement.files;
            if (fileList && fileList.length > 0) {
                this.processDroppedFiles(fileList);
            }
        }
        handleTriggerFile() {
            const input = this.shadowRoot?.querySelector('input[type="file"]');
            input?.click();
        }
        async processDroppedFiles(files) {
            if (!files.length) {
                return;
            }
            this.processState = ProcessState.reading;
            this.importFiles = [];
            this.selectedFileIndex = 0;
            const fileData = [];
            let detectedType = ImportType.unknown;
            // 1. Analyze files and determine types
            for (const file of Array.from(files)) {
                const fileType = this.detectFileType(file);
                if (fileType === ImportType.unknown) {
                    continue;
                }
                // Ensure all files are of the same type
                if (detectedType === ImportType.unknown) {
                    detectedType = fileType;
                }
                else if (detectedType !== fileType) {
                    // eslint-disable-next-line no-console
                    console.error('Mixed file types are not supported');
                    this.processState = ProcessState.idle;
                    return;
                }
                fileData.push({
                    file,
                    type: fileType,
                    processed: false,
                });
            }
            this.importType = detectedType;
            this.importFiles = fileData;
            try {
                if (detectedType === ImportType.CSV) {
                    await this.handleMultipleCsvFiles();
                }
                else if (detectedType === ImportType.JSON) {
                    await this.handleMultipleJsonFiles();
                }
            }
            catch (e) {
                this.reportError(e);
                this.processState = ProcessState.idle;
            }
        }
        /**
         * Detects the file type based on file extension and MIME type.
         */
        detectFileType(file) {
            const fileName = file.name.toLowerCase();
            const mimeType = file.type;
            if (fileName.endsWith('.csv') || mimeType === 'text/csv') {
                return ImportType.CSV;
            }
            if (fileName.endsWith('.json') || mimeType === 'application/json') {
                return ImportType.JSON;
            }
            return ImportType.unknown;
        }
        /**
         * Handles multiple CSV files by parsing the first one for preview.
         */
        async handleMultipleCsvFiles() {
            if (!this.importFiles.length || !this.root) {
                return;
            }
            for (const fileData of this.importFiles) {
                if (fileData.processed) {
                    continue;
                }
                try {
                    await this.processCsvFileData(fileData);
                    fileData.processed = true;
                }
                catch (e) {
                    // eslint-disable-next-line no-console
                    console.error(e);
                    throw new Error(`Error processing CSV file ${fileData.file.name}: ${e.message}`);
                }
            }
            this.processState = ProcessState.previewing;
        }
        /**
         * Handles multiple JSON schema files by processing them directly.
         */
        async handleMultipleJsonFiles() {
            if (!this.importFiles.length || !this.root) {
                return;
            }
            // @TODO: we should do this in batches to avoid memory issues with large files,
            // but also API payload size limits. To do it, we should split it into several phases:
            // 1. Import entities only.
            // 2. Import properties only.
            // 3. Import associations only.
            // 4. Import parents metadata.
            const importer = new FilteringJsonSchemaImporter(this.root, {
                excludeConceptualMarkers: true,
            });
            const input = [];
            for (const fileData of this.importFiles) {
                try {
                    const text = await fileData.file.text();
                    const contents = JSON.parse(text);
                    input.push({ contents, path: fileData.file.name });
                    fileData.processed = true;
                }
                catch (e) {
                    // eslint-disable-next-line no-console
                    console.error(e);
                    throw new Error(`Error processing JSON file ${fileData.file.name}: ${e.message}`);
                }
            }
            const report = await importer.import(input, 'JSON Schema Import');
            this.dispatchEvent(new CustomEvent('openmodel', {
                bubbles: true,
                composed: true,
                detail: { key: report.model.key, kind: report.model.kind, domain: this.root.key },
            }));
            this.root.notifyChange();
            this.processState = ProcessState.idle;
        }
        /**
         * Processes a single CSV file data with the current settings.
         */
        async processCsvFileData(fileData) {
            if (!this.root) {
                return;
            }
            const opts = this.cvsOptions || {};
            if (!opts.maxRows) {
                opts.maxRows = 10;
            }
            const parser = new CsvImporter(this.root, opts);
            fileData.data = await parser.parse(fileData.file);
        }
        /**
         * Reprocesses all CSV files with current options.
         */
        async reprocessCsvFiles() {
            if (!this.root) {
                return;
            }
            this.processState = ProcessState.reading;
            for (const fileData of this.importFiles) {
                if (fileData.type === ImportType.CSV) {
                    await this.processCsvFileData(fileData);
                }
            }
            this.processState = ProcessState.previewing;
        }
        handleCsvOptionChange(e) {
            const node = e.currentTarget;
            const { value, name } = node;
            if (!value) {
                return;
            }
            if (name !== 'delimiter' && name !== 'quote') {
                return;
            }
            if (!this.cvsOptions) {
                this.cvsOptions = {};
            }
            this.cvsOptions[name] = value;
            this.reprocessCsvFiles();
        }
        async handleCsvSave() {
            const { root } = this;
            if (!root) {
                return;
            }
            const parser = new CsvImporter(root);
            const files = this.importFiles.map((info) => info.data).filter(Boolean);
            const result = await parser.importMany(files, 'CSV Import');
            this.dispatchEvent(new CustomEvent('openmodel', {
                bubbles: true,
                composed: true,
                detail: { key: result.model.key, kind: result.model.kind, domain: root.key },
            }));
            root.notifyChange();
            this.processState = ProcessState.idle;
        }
        handlePreviewTabChange(e) {
            const { detail } = e;
            this.selectedFileIndex = detail.index;
            this.requestUpdate();
        }
        render() {
            const { processState } = this;
            switch (processState) {
                case ProcessState.reading:
                    return this.renderLoadingState();
                case ProcessState.previewing:
                    return this.renderDataPreview();
                default:
                    return this.renderDropTarget();
            }
        }
        renderHeader() {
            let message = '';
            if (this.processState === ProcessState.previewing) {
                if (this.importType === ImportType.CSV) {
                    const fileCount = this.importFiles.length || 1;
                    message = `Configure CSV file import (${fileCount} file${fileCount > 1 ? 's' : ''})`;
                }
            }
            else {
                message = 'You can upload files that describe your data. Supported formats: CSV and JSON schema files.';
            }
            return html `
      <div class="header">
        <h2 class="title-large">Import schema</h2>
        <p class="body-medium">${message}</p>
      </div>
    `;
        }
        renderDropTarget() {
            return html `
      ${this.renderHeader()}
      <div class="file-import drop-info">
        <button class="trigger-button" type="button" @click=${this.handleTriggerFile}>
          <ui-icon class="trigger-icon">description</ui-icon>
        </button>
        <p class="body-large">Drop files here.</p>
        <p class="body-medium">Supported: *.csv, *.json (JSON Schema) files.</p>
        <input hidden type="file" @change=${this.handleFileChange} accept=".csv,.json" multiple />
      </div>
    `;
        }
        renderLoadingState() {
            return html `
      <div class="import-loader">
        <p class="body-large">Processing files...</p>
      </div>
    `;
        }
        renderDataPreview() {
            switch (this.importType) {
                case ImportType.CSV:
                    return this.renderCsvPreview();
                default:
                    return html `<p>Unsupported file type</p>`;
            }
        }
        renderCsvPreview() {
            const previewData = this.importFiles[this.selectedFileIndex]?.data;
            const typed = previewData;
            if (!typed) {
                return html `<p>No data available</p>`;
            }
            const fileCount = this.importFiles.length || 1;
            return html `
      ${this.renderHeader()} ${this.renderCsvImportOptions()} ${this.renderPreviewOrTabs()}
      <div class="table-wrapper">
        <table class="data-table">
          <thead class="label-large">
            <tr>
              ${typed.format.map((row) => html ` <th scope="col">${row.name}</th> `)}
            </tr>
          </thead>
          <tbody class="body-medium">
            ${typed.values.map((row) => html `
                <tr>
                  ${row.map((item) => html ` <td>${item}</td> `)}
                </tr>
              `)}
          </tbody>
        </table>
      </div>
      <div class="actions">
        <ui-button color="filled" class="action-button" @click="${this.handleCsvSave}">
          Import ${fileCount} file${fileCount > 1 ? 's' : ''}
        </ui-button>
      </div>
    `;
        }
        renderPreviewOrTabs() {
            if (this.importFiles.length <= 1) {
                return html `<h3 class="title-medium">Preview</h3>`;
            }
            return html `
      <ui-tabs @change="${this.handlePreviewTabChange}">
        ${this.importFiles.map((fileData, index) => html `
            <ui-tab aria-controls="preview${index}" data-index="${index}" ?selected=${this.selectedFileIndex === index}
              >${fileData.file.name}</ui-tab
            >
          `)}
      </ui-tabs>
    `;
        }
        renderCsvImportOptions() {
            const { cvsOptions = {} } = this;
            const { quote = '"', delimiter = ',' } = cvsOptions;
            const delimiters = [',', ';', '|'];
            const quotes = ['"', "'", '`'];
            return html `<div class="csv-options">
      <h3 class="title-medium">Import options</h3>
      <div class="options">
        <ui-select
          label="Separator character"
          class="input"
          name="delimiter"
          .value="${delimiter}"
          @change="${this.handleCsvOptionChange}"
        >
          ${delimiters.map((item) => html `
              <ui-option value="${item}">
                <div>${item}</div>
              </ui-option>
            `)}
        </ui-select>
        <ui-select
          label="Quote character"
          class="input"
          name="quote"
          .value="${quote}"
          @change="${this.handleCsvOptionChange}"
        >
          ${quotes.map((item) => html `
              <ui-option value="${item}">
                <div>${item}</div>
              </ui-option>
            `)}
        </ui-select>
      </div>
    </div>`;
        }
    };
})();
/**
 * A component that allows the user to import files that describe data schemas.
 * Supports CSV files (with preview) and JSON schema files.
 *
 * @fires openentity - Fired when an entity is selected.
 * @fires openmodel - Fired when a model is selected.
 */
export default DomainFileImport;
//# sourceMappingURL=DomainFileImport.js.map