import { __esDecorate, __runInitializers } from "tslib";
import { html, nothing } from 'lit';
import { repeat } from 'lit/directives/repeat.js';
import { SemanticType, } from '@api-client/core/browser.js';
import { ApiEditor } from './ApiEditor.js';
import { property, state } from 'lit/decorators.js';
import { isValidAccessRuleType, isValidAccessTarget, isValidOperationName } from '../lib/validators.js';
import { renderHelp } from '../../help/render.js';
import { createEmptyAccessRule } from '../lib/access_helpers.js';
let ApiModelingAccessConfiguration = (() => {
    let _classSuper = ApiEditor;
    let _operation_decorators;
    let _operation_initializers = [];
    let _operation_extraInitializers = [];
    let _target_decorators;
    let _target_initializers = [];
    let _target_extraInitializers = [];
    let _rules_decorators;
    let _rules_initializers = [];
    let _rules_extraInitializers = [];
    let _userRoleProperty_decorators;
    let _userRoleProperty_initializers = [];
    let _userRoleProperty_extraInitializers = [];
    let _resourceOwnerAssociation_decorators;
    let _resourceOwnerAssociation_initializers = [];
    let _resourceOwnerAssociation_extraInitializers = [];
    return class ApiModelingAccessConfiguration extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _operation_decorators = [property({ type: String })];
            _target_decorators = [property({ type: String })];
            _rules_decorators = [state()];
            _userRoleProperty_decorators = [state()];
            _resourceOwnerAssociation_decorators = [state()];
            __esDecorate(this, null, _operation_decorators, { kind: "accessor", name: "operation", static: false, private: false, access: { has: obj => "operation" in obj, get: obj => obj.operation, set: (obj, value) => { obj.operation = value; } }, metadata: _metadata }, _operation_initializers, _operation_extraInitializers);
            __esDecorate(this, null, _target_decorators, { kind: "accessor", name: "target", static: false, private: false, access: { has: obj => "target" in obj, get: obj => obj.target, set: (obj, value) => { obj.target = value; } }, metadata: _metadata }, _target_initializers, _target_extraInitializers);
            __esDecorate(this, null, _rules_decorators, { kind: "accessor", name: "rules", static: false, private: false, access: { has: obj => "rules" in obj, get: obj => obj.rules, set: (obj, value) => { obj.rules = value; } }, metadata: _metadata }, _rules_initializers, _rules_extraInitializers);
            __esDecorate(this, null, _userRoleProperty_decorators, { kind: "accessor", name: "userRoleProperty", static: false, private: false, access: { has: obj => "userRoleProperty" in obj, get: obj => obj.userRoleProperty, set: (obj, value) => { obj.userRoleProperty = value; } }, metadata: _metadata }, _userRoleProperty_initializers, _userRoleProperty_extraInitializers);
            __esDecorate(this, null, _resourceOwnerAssociation_decorators, { kind: "accessor", name: "resourceOwnerAssociation", static: false, private: false, access: { has: obj => "resourceOwnerAssociation" in obj, get: obj => obj.resourceOwnerAssociation, set: (obj, value) => { obj.resourceOwnerAssociation = value; } }, metadata: _metadata }, _resourceOwnerAssociation_initializers, _resourceOwnerAssociation_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #operation_accessor_storage = __runInitializers(this, _operation_initializers, void 0);
        /**
         * The operation of the access configuration.
         * Can be one of: `list`, `read`, `create`, `update`, `delete`, or `search`.
         *
         * This property is only relevant when the `target` property is set to `action`.
         * If the `target` property is set to `api` or `endpoint`, this property is ignored.
         *
         * If the value is not set, the editor will not render anything.
         * @attribute
         */
        get operation() { return this.#operation_accessor_storage; }
        set operation(value) { this.#operation_accessor_storage = value; }
        #target_accessor_storage = (__runInitializers(this, _operation_extraInitializers), __runInitializers(this, _target_initializers, void 0));
        /**
         * The target of the access configuration.
         * Can be one of: `api`, `action`, or `endpoint`.
         *
         * When the value is set to `api`, the `operation` and `domainKey` properties are ignored.
         * When the value is set to `action`, the `operation` property must be set and the `domainKey` property
         * must point to an exposed entity.
         * When the value is set to `endpoint`, the `operation` property is ignored and the `domainKey` property
         * must point to an exposed entity.
         *
         * If the value is not set, the editor will not render anything.
         * @attribute
         */
        get target() { return this.#target_accessor_storage; }
        set target(value) { this.#target_accessor_storage = value; }
        #rules_accessor_storage = (__runInitializers(this, _target_extraInitializers), __runInitializers(this, _rules_initializers, void 0));
        /**
         * The access rules being edited.
         * This property is set when the `model`, `domainKey`, `operation`, or `target` properties change.
         * The editor should use this property to render the access rules and allow editing them.
         *
         * Note, it is a direct reference to the underlying model. Any changes to this array or its items
         * will be reflected in the model.
         */
        get rules() { return this.#rules_accessor_storage; }
        set rules(value) { this.#rules_accessor_storage = value; }
        #userRoleProperty_accessor_storage = (__runInitializers(this, _rules_extraInitializers), __runInitializers(this, _userRoleProperty_initializers, null
        /**
         * An association of the exposed entity that has the ResourceOwner semantic.
         */
        ));
        /**
         * The domain property of the user object in the domain model that has the UserRole semantic.
         */
        get userRoleProperty() { return this.#userRoleProperty_accessor_storage; }
        set userRoleProperty(value) { this.#userRoleProperty_accessor_storage = value; }
        #resourceOwnerAssociation_accessor_storage = (__runInitializers(this, _userRoleProperty_extraInitializers), __runInitializers(this, _resourceOwnerAssociation_initializers, null));
        /**
         * An association of the exposed entity that has the ResourceOwner semantic.
         */
        get resourceOwnerAssociation() { return this.#resourceOwnerAssociation_accessor_storage; }
        set resourceOwnerAssociation(value) { this.#resourceOwnerAssociation_accessor_storage = value; }
        constructor() {
            super();
            __runInitializers(this, _resourceOwnerAssociation_extraInitializers);
            this.operation = undefined;
            this.target = undefined;
            this.rules = [];
        }
        willUpdate(cp) {
            if (cp.has('operation') || cp.has('target')) {
                this.setupModel(this.model, this.domainKey);
            }
            else {
                // We don't need to call `super.willUpdate(cp)` if we are calling `setupModel`
                super.willUpdate(cp);
            }
        }
        resetModel() {
            this.rules = [];
            this.userRoleProperty = null;
            this.resourceOwnerAssociation = null;
        }
        setupModel(model, domainKey) {
            const { target } = this;
            this.resetModel();
            if (!model || !isValidAccessTarget(target)) {
                return;
            }
            this.setupRules(model, target, domainKey);
            this.setupUserRoleProperty(model);
            this.setupResourceOwnerAssociation(model, target, domainKey);
        }
        setupRules(model, target, domainKey) {
            const { operation } = this;
            if (target === 'api') {
                this.rules = model.accessRule ?? [];
                return;
            }
            // Everything else requires an `domainKey`
            if (!domainKey) {
                return;
            }
            const entity = model.exposes.find((e) => e.key === domainKey);
            if (!entity) {
                return;
            }
            if (target === 'endpoint') {
                this.rules = entity.accessRule ?? [];
                return;
            }
            // Everything else requires an operation
            if (!isValidOperationName(operation)) {
                return;
            }
            // At this point we know that target === 'action' and operation is set
            // So we need to find the action in the entity and get its access rules
            const action = entity.actions.find((a) => a.kind === operation);
            this.rules = action?.accessRule ?? [];
        }
        setupUserRoleProperty(model) {
            const domain = model.domain;
            if (!domain) {
                this.userRoleProperty = null;
                return;
            }
            // check domain properties first
            for (const entity of domain.listEntities()) {
                if (entity.hasSemantic(SemanticType.User)) {
                    this.analyzeUserEntity(entity);
                    return;
                }
            }
            // check foreign entities next
            for (const entity of domain.listAllForeignEntities()) {
                if (entity.hasSemantic(SemanticType.User)) {
                    this.analyzeUserEntity(entity);
                    return;
                }
            }
        }
        analyzeUserEntity(entity) {
            let roleProp = null;
            for (const property of entity.listProperties()) {
                if (property.hasSemantic(SemanticType.UserRole)) {
                    roleProp = property;
                }
                if (roleProp) {
                    break;
                }
            }
            this.userRoleProperty = roleProp;
        }
        /**
         * Finds and sets the resource owner association for the exposed entity.
         * @param model The API model
         * @param target The target of the editor
         * @param domainKey The exposed entity id.
         */
        setupResourceOwnerAssociation(model, target, domainKey) {
            if (target === 'api' || !domainKey) {
                return;
            }
            const domain = model.domain;
            if (!domain) {
                return;
            }
            const exposed = model.exposes.find((e) => e.key === domainKey);
            if (!exposed) {
                return;
            }
            const entity = domain.findEntity(exposed.entity.key, exposed.entity.domain);
            if (!entity) {
                return;
            }
            for (const assoc of entity.listAssociations()) {
                if (assoc.hasSemantic(SemanticType.ResourceOwnerIdentifier)) {
                    this.resourceOwnerAssociation = assoc;
                    return;
                }
            }
        }
        handleAddModeSelect(e) {
            const target = e.detail.item;
            if (!target) {
                return;
            }
            const { item } = target.dataset;
            if (!isValidAccessRuleType(item)) {
                return;
            }
            this.addEmptyRule(item);
        }
        /**
         * Adds an empty access rule of the given type to the model.
         * @param rule The rule to add
         */
        addEmptyRule(rule) {
            const src = this.rules;
            const info = createEmptyAccessRule(rule);
            if (rule === 'resourceOwner' && this.resourceOwnerAssociation) {
                // Set the property to the resource owner association property
                ;
                info.property = this.resourceOwnerAssociation.key;
            }
            src.push(info);
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        synchronizeModel() {
            const { operation, target, model } = this;
            if (!model || !isValidAccessTarget(target)) {
                return;
            }
            const src = this.rules ?? [];
            if (target === 'api') {
                model.accessRule = src.length > 0 ? src : undefined;
                return;
            }
            // Everything else requires an `domainKey`
            const { domainKey: domainKey } = this;
            if (!domainKey) {
                return;
            }
            const entity = model.exposes.find((e) => e.key === domainKey);
            if (!entity) {
                return;
            }
            if (target === 'endpoint') {
                entity.accessRule = src.length > 0 ? src : undefined;
                return;
            }
            // Everything else requires an operation
            if (!isValidOperationName(operation)) {
                return;
            }
            // At this point we know that target === 'action' and operation is set
            // So we need to find the action in the entity and set its access rules
            const action = entity.actions.find((a) => a.kind === operation);
            if (!action) {
                return;
            }
            action.accessRule = src.length > 0 ? src : undefined;
        }
        handleRuleDelete(e) {
            const button = e.currentTarget;
            const index = Number(button.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            this.rules.splice(index, 1);
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleAddDomain(e) {
            const button = e.currentTarget;
            const ruleIndex = Number(button.dataset.ruleIndex);
            if (Number.isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= this.rules.length) {
                return;
            }
            const rule = this.rules[ruleIndex];
            if (rule.type !== 'matchEmailDomain') {
                return;
            }
            if (!Array.isArray(rule.domains)) {
                rule.domains = [];
            }
            rule.domains.push(''); // Add an empty domain
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleDomainDelete(e) {
            const button = e.currentTarget;
            const index = Number(button.dataset.index);
            const ruleIndex = Number(button.dataset.ruleIndex);
            if (Number.isNaN(index) ||
                index < 0 ||
                Number.isNaN(ruleIndex) ||
                ruleIndex < 0 ||
                ruleIndex >= this.rules.length) {
                return;
            }
            const rule = this.rules[ruleIndex];
            if (rule.type !== 'matchEmailDomain' || !Array.isArray(rule.domains) || index >= rule.domains.length) {
                return;
            }
            rule.domains.splice(index, 1);
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleDomainChange(e) {
            const input = e.currentTarget;
            const value = input.value.trim();
            const index = Number(input.dataset.index);
            const ruleIndex = Number(input.dataset.ruleIndex);
            if (Number.isNaN(index) ||
                index < 0 ||
                Number.isNaN(ruleIndex) ||
                ruleIndex < 0 ||
                ruleIndex >= this.rules.length) {
                return;
            }
            const rule = this.rules[ruleIndex];
            if (rule.type !== 'matchEmailDomain' || !Array.isArray(rule.domains) || index >= rule.domains.length) {
                return;
            }
            rule.domains[index] = value;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleUserRoleChange(e) {
            const input = e.currentTarget;
            const role = input.value;
            const ruleIndex = Number(input.dataset.ruleIndex);
            if (Number.isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= this.rules.length) {
                return;
            }
            const rule = this.rules[ruleIndex];
            if (rule.type !== 'matchUserRole') {
                return;
            }
            if (!Array.isArray(rule.role)) {
                rule.role = [];
            }
            if (input.checked) {
                // Add the role
                if (!rule.role.includes(role)) {
                    rule.role.push(role);
                }
            }
            else {
                // Remove the role
                const roleIndex = rule.role.indexOf(role);
                if (roleIndex >= 0) {
                    rule.role.splice(roleIndex, 1);
                }
            }
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        render() {
            if (!isValidAccessTarget(this.target)) {
                return nothing;
            }
            return html ` ${this.renderIntro()} ${this.renderAddButton()} ${this.renderRules()} `;
        }
        renderIntro() {
            return html `<div class="intro">
      <div class="header">
        <span class="title-large">Access mode</span>
        ${renderHelp('modeling', 'api:access-configuration')}
      </div>
      <span class="inline-help body-medium"
        >Add an access mode that controls who can perform an API action on your data. All conditions have to be true to
        allow access.</span
      >
    </div>`;
        }
        renderAddButton() {
            const options = [
                html `<ui-list-item role="menuitem" data-item="public">Public</ui-list-item>`,
                html `<ui-list-item role="menuitem" data-item="authenticated">Authenticated</ui-list-item>`,
                html `<ui-list-item role="menuitem" data-item="matchUserRole">User role</ui-list-item>`,
                html `<ui-list-item role="menuitem" data-item="matchEmailDomain">Email match</ui-list-item>`,
            ];
            const { target } = this;
            if (target === 'action' || target === 'endpoint') {
                options.push(html `<ui-list-item role="menuitem" data-item="resourceOwner">Resource owner</ui-list-item>`);
            }
            return html `<ui-dropdown-list @select="${this.handleAddModeSelect}" closeOnTab closeOnOutsideClick noOverlap>
      <ui-button color="outlined">
        <ui-icon slot="icon" icon="add" aria-hidden="true"></ui-icon>
        Add mode
      </ui-button>
      <ui-list slot="dropdown" role="menu">${options}</ui-list>
    </ui-dropdown-list>`;
        }
        renderRules() {
            const { rules } = this;
            if (rules.length === 0) {
                return nothing;
            }
            const result = [];
            const len = rules.length;
            rules.forEach((rule, index) => {
                result.push(this.renderRule(rule, index));
                if (index < len - 1) {
                    result.push(html `<ui-divider class="rule-divider"></ui-divider>`);
                }
            });
            return html `<div class="rules">${result}</div>`;
        }
        renderRule(rule, index) {
            if (rule.type === 'public') {
                return this.renderPublicRule(index);
            }
            if (rule.type === 'authenticated') {
                return this.renderAuthenticatedRule(index);
            }
            if (rule.type === 'resourceOwner') {
                return this.renderResourceOwnerRule(index);
            }
            if (rule.type === 'matchUserRole') {
                return this.renderMatchUserRoleRule(rule, index);
            }
            if (rule.type === 'matchEmailDomain') {
                return this.renderMatchEmailDomainRule(rule, index);
            }
            return html `<div class="rule">
      <span class="rule-title title-medium">Unknown rule</span>
      <span class="rule-description body-medium">This rule type is not supported.</span>
    </div>`;
        }
        renderRuleHeader(title, helpId, index) {
            return html `<div class="rule-header">
      <span class="rule-title title-medium">${title}</span>
      <div class="actions">
        ${renderHelp('modeling', helpId)}
        <ui-icon-button type="button" data-index="${index}" @click="${this.handleRuleDelete}">
          <ui-icon>delete_outline</ui-icon>
        </ui-icon-button>
      </div>
    </div>`;
        }
        renderRuleDescription(description) {
            return html `<div class="rule-explainer">
      <ui-icon>info</ui-icon>
      <span class="body-medium">${description}</span>
    </div>`;
        }
        renderPublicRule(index) {
            return html `<div class="rule">
      ${this.renderRuleHeader('Public', 'api:access-configuration-public', index)}
      ${this.renderRuleDescription('Everyone can perform this API action.')}
    </div>`;
        }
        renderAuthenticatedRule(index) {
            return html `<div class="rule">
      ${this.renderRuleHeader('Authenticated user', 'api:access-configuration-authenticated', index)}
      ${this.renderRuleDescription('Any authenticated user of the API can perform this API action.')}
    </div>`;
        }
        renderResourceOwnerRule(index) {
            let desc = '';
            if (this.target === 'api') {
                desc = 'This access mode cannot be used on the API level.';
            }
            else {
                desc = 'Only the resource owner can perform this API action. ';
                desc += 'The data entity must define the Resource Owner semantic on one of the properties.';
            }
            return html `<div class="rule">
      ${this.renderRuleHeader('Resource owner', 'api:access-configuration-resource-owner', index)}
      ${this.renderRuleDescription(desc)} ${this.renderResourceOwnerRuleDetails()}
    </div>`;
        }
        renderResourceOwnerRuleDetails() {
            let content;
            const { resourceOwnerAssociation } = this;
            if (!resourceOwnerAssociation) {
                content = html `<div class="error-message">
        <ui-icon aria-hidden="true">error_outline</ui-icon>
        The owner field is not defined in the domain model.
      </div>`;
            }
            else {
                content = html `<span>Owner field: <strong>${resourceOwnerAssociation.info.getLabel()}</strong></span>`;
            }
            return html `<div class="resource-owner-details body-medium">${content}</div>`;
        }
        renderMatchUserRoleRule(rule, index) {
            let desc = 'Only users with matched roles can perform this API action. ';
            desc += 'The data entity must define the Role semantic on one of the properties.';
            // const roles = rule.role ?? []
            return html `<div class="rule">
      ${this.renderRuleHeader('User role', 'api:access-configuration-match-user-role', index)}
      ${this.renderRuleDescription(desc)} ${this.renderUserRoles(rule, index)}
    </div>`;
        }
        renderUserRoles(rule, ruleIndex) {
            const { userRoleProperty } = this;
            if (!userRoleProperty) {
                return html `<span class="body-medium"
        >No property found in the domain model that has the UserRole semantic.</span
      >`;
            }
            const enums = userRoleProperty.schema?.enum;
            if (!Array.isArray(enums) || enums.length === 0) {
                return html `<span class="body-medium">The UserRole property does not define any roles.</span>`;
            }
            const roles = rule.role ?? [];
            return html `<div class="user-roles">
      <div class="rule-header">
        <span class="rule-title title-small">User roles</span>
        <div class="actions">${renderHelp('modeling', 'api:access-configuration-match-user-role')}</div>
      </div>

      ${repeat(enums, (item) => this.renderRoleInput(roles, item, ruleIndex))}
    </div>`;
        }
        renderRoleInput(roles, role, ruleIndex) {
            const checked = roles.includes(role);
            return html `<div class="role-input">
      <label class="label-large">
        <ui-checkbox
          ?checked=${checked}
          name="userRole"
          value="${role}"
          data-rule-index="${ruleIndex}"
          @change="${this.handleUserRoleChange}"
        ></ui-checkbox>
        ${role}
      </label>
    </div>`;
        }
        renderMatchEmailDomainRule(rule, index) {
            const desc = 'Only users with matched email domains can perform this API action.';
            // const domains = rule.domains ?? []
            return html `<div class="rule">
      ${this.renderRuleHeader('Email match', 'api:access-configuration-match-email', index)}
      ${this.renderRuleDescription(desc)} ${this.renderEmailDomains(rule.domains ?? [], index)}
    </div>`;
        }
        renderEmailDomains(domains, ruleIndex) {
            return html `<div class="email-domains">
      <div class="rule-header">
        <span class="rule-title title-small">Email domains</span>
        <div class="actions">${renderHelp('modeling', 'api:access-configuration-match-email-domain')}</div>
      </div>

      ${repeat(domains, (item, index) => this.renderDomainInput(item, index, ruleIndex))}

      <ui-button color="outlined" data-rule-index="${ruleIndex}" @click="${this.handleAddDomain}">Add domain</ui-button>
    </div>`;
        }
        renderDomainInput(domain, index, ruleIndex) {
            return html `<div class="domain-input">
      <ui-outlined-text-field
        label="Domain"
        name="domain-${index}"
        .value=${domain}
        @change="${this.handleDomainChange}"
        data-index="${index}"
        data-rule-index="${ruleIndex}"
        class="domain-field"
      ></ui-outlined-text-field>
      <ui-icon-button
        type="button"
        data-index="${index}"
        data-rule-index="${ruleIndex}"
        @click="${this.handleDomainDelete}"
      >
        <ui-icon>delete_outline</ui-icon>
      </ui-icon-button>
    </div>`;
        }
    };
})();
/**
 * An editor for configuring access settings of the API.
 * Access settings can be added either to the API itself, on an endpoint (exposed entity), or to individual operations.
 *
 * This editor doesn't support the dirty state. Any changes made in the editor are immediately reflected in the model.
 */
export default ApiModelingAccessConfiguration;
//# sourceMappingURL=ApiModelingAccessConfiguration.js.map