import { __esDecorate, __runInitializers } from "tslib";
import { nothing, html } from 'lit';
import { state } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import { DomainAssociation } from '@api-client/core/modeling/DomainAssociation.js';
import { DomainEntityKind } from '@api-client/core/models/kinds.js';
import { cancelEvent } from '@api-client/core/lib/events/Utils.js';
import { ModelingElement } from './ModelingElement.js';
import { Events } from '../../events/Events.js';
import { renderHelp } from '../../help/render.js';
import { getSemanticsByCategory, SemanticScope, SemanticType, } from '@api-client/core/modeling/Semantics.js';
import { TagsTaxonomy } from '../dialogs/TagsTaxonomy.js';
import { CategoryTaxonomy } from '../dialogs/CategoryTaxonomy.js';
let DomainAssociationEditor = (() => {
    let _classSuper = ModelingElement;
    let _domainElement_decorators;
    let _domainElement_initializers = [];
    let _domainElement_extraInitializers = [];
    return class DomainAssociationEditor extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _domainElement_decorators = [state()];
            __esDecorate(this, null, _domainElement_decorators, { kind: "accessor", name: "domainElement", static: false, private: false, access: { has: obj => "domainElement" in obj, get: obj => obj.domainElement, set: (obj, value) => { obj.domainElement = value; } }, metadata: _metadata }, _domainElement_initializers, _domainElement_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #domainElement_accessor_storage = __runInitializers(this, _domainElement_initializers, void 0);
        /**
         * The `DomainAssociation` object being edited.
         * This is updated when the `selected` property changes.
         */
        get domainElement() { return this.#domainElement_accessor_storage; }
        set domainElement(value) { this.#domainElement_accessor_storage = value; }
        willUpdate(cp) {
            if (cp.has('selected') || cp.has('root') || cp.has('namespace')) {
                this.computeAssociation();
            }
            super.willUpdate(cp);
        }
        async computeAssociation() {
            const { root, selected, namespace } = this;
            this.domainElement = undefined;
            if (!root || !selected) {
                this.domainElement = undefined;
                return;
            }
            // MD components have issues updating values
            // so we need to wait for the updateComplete to remove them.
            await this.updateComplete;
            this.domainElement = root.findAssociation(selected, namespace);
        }
        /**
         * Sets the value of one of the "attribute" fields.
         * These are the checkboxes located in the "Attributes" section.
         *
         * @param name The name of the attribute to set.
         * @param value The boolean option to set as the value.
         * @throws {TypeError} When the passed name is not a valid attribute name.
         * @throws {TypeError} When the passed value is not a boolean value.
         * @fires change - Dispatched when an attribute value is updated.
         */
        setModelAttribute(name, value) {
            if (!DomainAssociation.isValidAttribute(name)) {
                throw new TypeError(`Invalid attribute name: ${name}.`);
            }
            if (typeof value !== 'boolean') {
                throw new TypeError(`Invalid attribute value: ${value}.`);
            }
            const { domainElement } = this;
            if (!domainElement) {
                return;
            }
            domainElement[name] = value;
            this.notifyChange();
            this.requestUpdate();
        }
        /**
         * Sets the value of the association's `schema.linked` property.
         * @param value The value to set the `linked` property to.
         */
        setSchemaLinked(value) {
            const { domainElement } = this;
            if (!domainElement) {
                return;
            }
            const schema = domainElement.ensureSchema();
            schema.linked = value;
            this.notifyChange();
            this.requestUpdate();
        }
        /**
         * A handler for the one of the metadata inputs value change.
         *
         * It supports the following fields:
         *
         * - `name`
         * - `displayName`
         * - `description`
         */
        handleMetaChange(e) {
            const { domainElement } = this;
            if (!domainElement) {
                return;
            }
            const node = e.target;
            const { name, value } = node;
            if (value && !node.reportValidity()) {
                return;
            }
            if (!['name', 'displayName', 'description'].includes(name)) {
                return;
            }
            if (name === 'name' && !value) {
                return;
            }
            domainElement.info[name] = value;
            this.notifyChange();
        }
        /**
         * A handler for any of the checkboxes in the `attributes` section.
         * It calls the `setModelAttribute()` method.
         */
        handleAttributeOptionChange(e) {
            const checkbox = e.target;
            const { name = '', checked } = checkbox;
            this.setModelAttribute(name, !!checked);
        }
        handleLinkedChange(e) {
            const checkbox = e.target;
            const { name, checked } = checkbox;
            if (name !== 'linked') {
                return;
            }
            this.setSchemaLinked(!!checked);
        }
        async handleSelectTarget() {
            const domain = this.domainElement?.domain;
            if (!domain) {
                throw new Error('Domain is not defined for the domain association.');
            }
            const data = {
                reason: 'pick',
                domain,
                allowed: [DomainEntityKind],
                withRecent: true,
            };
            const result = await Events.Domain.Browse.browse(data, this);
            if (!result) {
                return;
            }
            if (result.result === 'canceled') {
                return;
            }
            if (result.kind !== DomainEntityKind) {
                return;
            }
            this.addTarget(result.selected, result.domain);
        }
        handleDeleteTarget(e) {
            const button = e.target;
            const { key, namespace } = button.dataset;
            if (!key || !namespace) {
                return;
            }
            const { domainElement, root } = this;
            if (!domainElement || !root) {
                return;
            }
            let entity;
            if (root.key === namespace) {
                entity = root.findEntity(key);
            }
            else {
                // const foreign = root.foreign.find((item) => item.key === namespace)
                // if (!foreign) {
                //   throw new Error(`Namespace ${namespace} not found.`)
                // }
                // entity = foreign.findEntity(key)
                throw new Error(`Foreign namespaces are not supported yet.`);
            }
            if (entity) {
                domainElement.removeTarget(entity);
                this.notifyChange();
                this.requestUpdate();
            }
        }
        handleNavigateTarget(e) {
            const item = e.currentTarget;
            const { key } = item.dataset;
            if (!key) {
                return;
            }
            const detail = {
                key,
                kind: DomainEntityKind,
                domain: this.root?.key || '',
            };
            this.dispatchEvent(new CustomEvent('openentity', { detail }));
        }
        handleUnionTypeChange(e) {
            const select = e.target;
            const { value } = select;
            const { domainElement } = this;
            if (!domainElement) {
                return;
            }
            const schema = domainElement.ensureSchema();
            schema.unionType = value;
            this.notifyChange();
            this.requestUpdate();
        }
        handleTargetDragEnter(e) {
            const dt = e.dataTransfer;
            if (!dt) {
                return;
            }
            const hasJsonData = dt.types.includes('application/json');
            if (!hasJsonData) {
                return;
            }
            const zone = e.currentTarget;
            zone.classList.add('drop-zone');
            dt.dropEffect = 'link';
            dt.effectAllowed = 'link';
        }
        handleTargetDragOver(e) {
            const dt = e.dataTransfer;
            if (!dt) {
                return;
            }
            const hasJsonData = dt.types.includes('application/json');
            if (!hasJsonData) {
                return;
            }
            e.preventDefault();
        }
        handleTargetDragLeave(e) {
            const dt = e.dataTransfer;
            if (!dt) {
                return;
            }
            const zone = e.currentTarget;
            zone.classList.remove('drop-zone');
        }
        handleTargetDrop(e) {
            const dt = e.dataTransfer;
            if (!dt) {
                return;
            }
            const zone = e.currentTarget;
            zone.classList.remove('drop-zone');
            const raw = dt.getData('application/json');
            const data = JSON.parse(raw);
            const entities = data.items.filter((item) => item.kind === DomainEntityKind);
            if (entities.length === 0 || !this.domainElement || !this.root) {
                return;
            }
            for (const info of entities) {
                const entity = this.root.findEntity(info.key, info.domain);
                if (!entity) {
                    throw new Error(`Entity with key ${info.key} not found in namespace ${info.domain}.`);
                }
                this.domainElement.addTarget(entity);
            }
            this.notifyChange();
            this.requestUpdate();
        }
        /**
         * Adds a target entity to the association.
         *
         * @param key The key of the target entity to add.
         * @param nsKey The namespace key of the target entity, if it is in a foreign namespace.
         */
        addTarget(key, nsKey) {
            const { domainElement, root } = this;
            if (!domainElement || !root) {
                return;
            }
            const entity = root.findEntity(key, nsKey);
            if (entity) {
                domainElement.addTarget(entity);
            }
            else {
                throw new Error(`Entity with key ${key} not found in namespace ${nsKey || root.key}.`);
            }
            this.notifyChange();
            this.requestUpdate();
        }
        handleDelete() {
            const { domainElement } = this;
            if (!domainElement) {
                return;
            }
            const { root } = this;
            if (!root) {
                return;
            }
            root.removeAssociation(domainElement.key);
            this.dispatchEvent(new CustomEvent('delete', { detail: { key: domainElement.key, kind: domainElement.kind } }));
        }
        handleDeleteTaxonomy(e) {
            const button = e.currentTarget;
            const { id } = button.dataset;
            if (!id) {
                return;
            }
            this.domainElement?.removeSemantic(id);
        }
        handleTaxonomySelect(e) {
            const { value } = e.detail.item.dataset;
            if (value) {
                this.domainElement?.addSemantic({
                    id: value,
                });
                this.requestUpdate();
            }
        }
        handleConfigureTaxonomy(e) {
            const button = e.currentTarget;
            const { id } = button.dataset;
            if (!id) {
                return;
            }
            const element = this.domainElement;
            if (!element) {
                return;
            }
            let dialog;
            if (id === SemanticType.Tags) {
                dialog = new TagsTaxonomy(element);
            }
            else if (id === SemanticType.Categories) {
                dialog = new CategoryTaxonomy(element);
            }
            else {
                throw new TypeError(`Unsupported taxonomy type: ${id}.`);
            }
            dialog.run();
        }
        render() {
            const { domainElement } = this;
            if (!domainElement) {
                return nothing;
            }
            return this.renderForm(domainElement);
        }
        renderForm(model) {
            return html `
      <form name="association" @submit="${cancelEvent}" data-key="${model.key}" class="modeling-form">
        ${this.renderMetaInputs(model)} ${this.renderAttributes(model)} ${this.renderTargetEntitySection(model)}
        ${this.renderSchema(model)} ${this.renderDataGovernanceSection(model)}
      </form>
    `;
        }
        renderMetaInputs(model) {
            const disabled = !this.canEdit;
            const formatError = 'Name must start with a letter or underscore and can only contain letters, numbers, and underscores.';
            return html `
      <div class="editor-section">
        <div class="row input-row">
          <ui-outlined-text-field
            class="input"
            name="name"
            .value="${live(model.info.name || '')}"
            label="Name"
            required
            minLength="2"
            maxLength="59"
            pattern="[a-zA-Z_][a-zA-Z0-9_]+"
            .invalidText="${formatError}"
            ?disabled="${disabled}"
            @change="${this.handleMetaChange}"
          ></ui-outlined-text-field>
          ${renderHelp('association', 'name-input')}
        </div>
        <div class="row input-row">
          <ui-outlined-text-field
            class="input"
            name="displayName"
            .value="${live(model.info.displayName || '')}"
            label="Display name (optional)"
            @change="${this.handleMetaChange}"
            ?disabled="${disabled}"
          ></ui-outlined-text-field>
          ${renderHelp('association', 'displayName-input')}
        </div>
        <div class="row input-row">
          <ui-outlined-text-field
            class="input"
            name="description"
            .value="${live(model.info.description || '')}"
            label="Description (optional)"
            @change="${this.handleMetaChange}"
            ?disabled="${disabled}"
          ></ui-outlined-text-field>
          ${renderHelp('association', 'description-input')}
        </div>
      </div>
    `;
        }
        renderAttributes(model) {
            return html `<div class="editor-section">
      <div class="section-title title-medium">Attributes</div>
      ${this.renderAttributeOptions(model)}
    </div>`;
        }
        renderAttributeOptions(model) {
            const { required = false, multiple = false } = model;
            const disabled = !this.canEdit;
            return html `<div class="checkbox-group">
      <div class="row">
        <div class="input checkbox">
          <ui-checkbox
            id="att-required"
            .checked="${required}"
            name="required"
            ?disabled="${disabled}"
            @change="${this.handleAttributeOptionChange}"
          ></ui-checkbox>
          <div class="label">
            <label for="att-required" class="body-medium">Required</label>
          </div>
        </div>
        ${renderHelp('association', 'required-checkbox')}
      </div>
      <div class="row">
        <div class="input checkbox metadata">
          <ui-checkbox
            id="att-multiple"
            .checked="${multiple}"
            name="multiple"
            ?disabled="${disabled}"
            @change="${this.handleAttributeOptionChange}"
          ></ui-checkbox>
          <div class="label">
            <label for="att-multiple" class="body-medium">Multiple values</label>
            <span class="body-small meta">Creates an array of values.</span>
          </div>
        </div>
        ${renderHelp('association', 'multiple-checkbox')}
      </div>
    </div>`;
        }
        renderSchema(model) {
            const { linked = false } = model.schema || {};
            const disabled = !this.canEdit;
            return html `<div class="editor-section">
      <div class="section-title title-medium">Schema</div>
      <div class="row">
        <div class="input">
          <ui-switch
            .checked="${linked}"
            name="linked"
            id="linked"
            @change="${this.handleLinkedChange}"
            ?disabled="${disabled}"
          ></ui-switch>
          <label for="linked" class="body-medium">Use the reference key to access schema.</label>
        </div>
        ${renderHelp('association', 'linked-switch')}
      </div>
    </div>`;
        }
        renderTargetEntitySection(model) {
            return html `<div
      class="editor-section"
      @dragenter="${this.handleTargetDragEnter}"
      @dragover="${this.handleTargetDragOver}"
      @dragleave="${this.handleTargetDragLeave}"
      @drop="${this.handleTargetDrop}"
    >
      <div class="section-title title-medium">Target entity</div>
      ${this.renderChooseTargetButton()} ${this.renderTargetEntities(model)}
    </div>`;
        }
        /**
         * Renders the section with the current target entity.
         *
         * @todo: The UI only allows to select one target entity.
         *        We need to add a way to select multiple target entities.
         */
        renderTargetEntities(model) {
            const targets = [...model.listTargets()];
            if (!targets.length) {
                return nothing;
            }
            return html `${this.renderTargetItems(targets)} ${targets.length > 1 ? this.renderUnionType(model) : nothing}`;
        }
        renderChooseTargetButton() {
            const disabled = !this.canEdit;
            return html `<div class="row">
      <ui-button color="tonal" type="button" @click="${this.handleSelectTarget}" ?disabled="${disabled}"
        >Choose target entity</ui-button
      >
      ${renderHelp('association', 'target-selector')}
    </div>`;
        }
        renderTargetItems(targets) {
            if (!targets.length) {
                return nothing;
            }
            const disabled = !this.canEdit;
            return html `<ui-chip-set>
      ${targets.map((item) => {
                const label = item.info.getLabel();
                return html `<ui-chip
          type="input"
          class="target-chip"
          data-key="${item.key}"
          data-namespace="${item.domain.key}"
          removable
          title="Open the association target"
          aria-label="Open the association target ${label}"
          ?disabled="${disabled}"
          @remove="${this.handleDeleteTarget}"
          @click="${this.handleNavigateTarget}"
          >${label}</ui-chip
        >`;
            })}
    </ui-chip-set> `;
        }
        renderUnionType(model) {
            const { schema } = model;
            const unionType = schema?.unionType || 'anyOf';
            const supportingText = 'Used with multiple entities.';
            const disabled = !this.canEdit;
            return html `<div class="row input-row">
      <ui-select
        label="Union type"
        class="input"
        name="unionType"
        .value="${unionType}"
        .supportingText="${supportingText}"
        ?disabled="${disabled}"
        @change="${this.handleUnionTypeChange}"
      >
        <ui-option value="allOf">
          <div>All of</div>
          <div slot="supporting-text">Combines all fields into a single schema.</div>
        </ui-option>
        <ui-option value="anyOf">
          <div>Any of (default)</div>
          <div slot="supporting-text">Any of the schemas are valid (separately).</div>
        </ui-option>
        <ui-option value="oneOf">
          <div>One of</div>
          <div slot="supporting-text">Same as Any of, but different validation rules.</div>
        </ui-option>
      </ui-select>
      ${renderHelp('association', 'unionType-select')}
    </div>`;
        }
        renderDataGovernanceSection(model) {
            // TODO: Add classification UI (data tagging)
            const tax = this.renderTaxonomyInput(model);
            if (tax === nothing) {
                return nothing;
            }
            return html `<div class="editor-section">
      <div class="section-title title-medium">Data Governance</div>
      ${tax}
    </div>`;
        }
        renderTaxonomyInput(model) {
            const allCategories = getSemanticsByCategory(SemanticScope.Association);
            const selectedSemantics = [];
            const filteredCategories = {};
            // Filter categories and semantics, excluding already selected ones
            for (const category of Object.keys(allCategories)) {
                const semantics = allCategories[category].filter((s) => {
                    // Check if this semantic is already selected
                    if (model.semantics.some((ms) => ms.id === s.id)) {
                        selectedSemantics.push(s);
                        return false;
                    }
                    return true;
                });
                if (semantics.length > 0) {
                    filteredCategories[category] = semantics;
                }
            }
            if (Object.keys(filteredCategories).length === 0 && selectedSemantics.length === 0) {
                return nothing;
            }
            return html `<div class="taxonomy">
        <ui-button
          id="taxonomy-menu-anchor"
          popoverTarget="taxonomy-menu"
          color="outlined"
          aria-label="Add association taxonomy"
          ?disabled="${!this.canEdit}"
        >
          <ui-icon slot="icon">add</ui-icon>Add taxonomy
        </ui-button>
        <ui-menu popover="auto" id="taxonomy-menu" class="taxonomy-menu" @select="${this.handleTaxonomySelect}">
          ${this.renderTaxonomyCategories(filteredCategories)}
        </ui-menu>
        ${renderHelp('association', 'taxonomy-select')}
      </div>
      ${this.renderSelectedSemantics(selectedSemantics)}`;
        }
        renderSelectedSemantics(selected) {
            if (selected.length === 0) {
                return nothing;
            }
            const items = selected.map((semantic) => this.renderTaxonomyItem(semantic));
            return html `<div class="enabled-taxonomy">${items}</div>`;
        }
        renderTaxonomyItem(semantic) {
            const canEdit = this.canEdit;
            return html `<div class="taxonomy-item">
      <span class="label body-large">${semantic.displayName}</span>
      ${semantic.hasConfig
                ? html `<ui-button
            color="text"
            data-id="${semantic.id}"
            @click="${this.handleConfigureTaxonomy}"
            ?disabled="${!canEdit}"
            >Configure</ui-button
          >`
                : nothing}
      ${canEdit
                ? html `<ui-icon-button
            type="button"
            data-id="${semantic.id}"
            @click="${this.handleDeleteTaxonomy}"
            ?disabled="${!canEdit}"
            ><ui-icon icon="deleteOutline"></ui-icon
          ></ui-icon-button>`
                : nothing}
    </div>`;
        }
        renderTaxonomyCategories(categories) {
            const items = [];
            const entries = Object.entries(categories);
            const cLen = entries.length;
            entries.forEach(([categoryName, semantics], categoryIndex) => {
                // Add category title as a non-selectable menu item
                items.push(html ` <div class="menu-category-title title-small">${categoryName}</div> `);
                // Add semantics from this category
                for (const semantic of semantics) {
                    items.push(html `
          <ui-menu-item data-value="${semantic.id}" lines="two">
            <div>${semantic.displayName}</div>
            <div slot="supporting-text">${semantic.description}</div>
          </ui-menu-item>
        `);
                }
                // Add divider after each category except the last one
                if (categoryIndex < cLen - 1) {
                    items.push(html `<ui-divider></ui-divider>`);
                }
            });
            return items;
        }
        constructor() {
            super(...arguments);
            __runInitializers(this, _domainElement_extraInitializers);
        }
    };
})();
/**
 * Dispatched when the user wants to navigate to a parent entity.
 *
 * @event DomainAssociationEditor#openentity
 * @type {CustomEvent}
 * @property {object} detail - The event details.
 * @property {string} detail.key - The key of the parent entity to navigate to.
 * @property {string} detail.kind - The kind of the parent entity. It will always be `DomainEntityKind`.
 */
/**
 * Dispatched when the user deletes a property.
 *
 * @event DomainAssociationEditor#delete
 * @type {CustomEvent}
 * @property {object} detail - The event details.
 * @property {string} detail.key - The key of the parent entity to navigate to.
 * @property {string} detail.kind - The kind of the parent entity. It will always be `DomainEntityKind`.
 */
/**
 * An element that specializes in editing properties of a domain association.
 *
 * @fires domainbrowserbrowse - Dispatched when the user clicks on the "pick" button. The detail object
 *                              has the full intent configuration.
 * @fires change - Dispatched when the user changes the value of one of the inputs.
 * @fires DomainAssociationEditor#openentity - A custom event with the `key` and `type` when a property was selected.
 * @fires DomainAssociationEditor#delete - A custom event with the `key` and `type` when a property was selected.
 */
export default DomainAssociationEditor;
//# sourceMappingURL=DomainAssociationEditor.js.map