import { __esDecorate, __runInitializers } from "tslib";
import { html, nothing } from 'lit';
import { SemanticType, } from '@api-client/core/browser.js';
import { nanoid } from 'nanoid';
import { live } from 'lit/directives/live.js';
import { repeat } from 'lit/directives/repeat.js';
import { state } from 'lit/decorators.js';
import { ApiModelingScreen } from './ApiModelingScreen.js';
import { renderHelp } from '../../help/render.js';
import { getFieldDataTypeLabel, getFieldIcon } from '../lib/ui_mapping.js';
let ApiModelingSessionScreen = (() => {
    let _classSuper = ApiModelingScreen;
    let _entities_decorators;
    let _entities_initializers = [];
    let _entities_extraInitializers = [];
    let _userModel_decorators;
    let _userModel_initializers = [];
    let _userModel_extraInitializers = [];
    let _sessionProperties_decorators;
    let _sessionProperties_initializers = [];
    let _sessionProperties_extraInitializers = [];
    let _userObjectProperties_decorators;
    let _userObjectProperties_initializers = [];
    let _userObjectProperties_extraInitializers = [];
    return class ApiModelingSessionScreen extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _entities_decorators = [state()];
            _userModel_decorators = [state()];
            _sessionProperties_decorators = [state()];
            _userObjectProperties_decorators = [state()];
            __esDecorate(this, null, _entities_decorators, { kind: "accessor", name: "entities", static: false, private: false, access: { has: obj => "entities" in obj, get: obj => obj.entities, set: (obj, value) => { obj.entities = value; } }, metadata: _metadata }, _entities_initializers, _entities_extraInitializers);
            __esDecorate(this, null, _userModel_decorators, { kind: "accessor", name: "userModel", static: false, private: false, access: { has: obj => "userModel" in obj, get: obj => obj.userModel, set: (obj, value) => { obj.userModel = value; } }, metadata: _metadata }, _userModel_initializers, _userModel_extraInitializers);
            __esDecorate(this, null, _sessionProperties_decorators, { kind: "accessor", name: "sessionProperties", static: false, private: false, access: { has: obj => "sessionProperties" in obj, get: obj => obj.sessionProperties, set: (obj, value) => { obj.sessionProperties = value; } }, metadata: _metadata }, _sessionProperties_initializers, _sessionProperties_extraInitializers);
            __esDecorate(this, null, _userObjectProperties_decorators, { kind: "accessor", name: "userObjectProperties", static: false, private: false, access: { has: obj => "userObjectProperties" in obj, get: obj => obj.userObjectProperties, set: (obj, value) => { obj.userObjectProperties = value; } }, metadata: _metadata }, _userObjectProperties_initializers, _userObjectProperties_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #entities_accessor_storage = __runInitializers(this, _entities_initializers, []
        /**
         * The data model for the user object.
         */
        );
        /**
         * All available entities to list the user object from.
         */
        get entities() { return this.#entities_accessor_storage; }
        set entities(value) { this.#entities_accessor_storage = value; }
        #userModel_accessor_storage = (__runInitializers(this, _entities_extraInitializers), __runInitializers(this, _userModel_initializers, void 0));
        /**
         * The data model for the user object.
         */
        get userModel() { return this.#userModel_accessor_storage; }
        set userModel(value) { this.#userModel_accessor_storage = value; }
        #sessionProperties_accessor_storage = (__runInitializers(this, _userModel_extraInitializers), __runInitializers(this, _sessionProperties_initializers, []
        /**
         * The list of user object properties.
         */
        ));
        /**
         * The list of session properties that are used to identify the user.
         * This is computed during the data setup and when properties list change.
         */
        get sessionProperties() { return this.#sessionProperties_accessor_storage; }
        set sessionProperties(value) { this.#sessionProperties_accessor_storage = value; }
        #userObjectProperties_accessor_storage = (__runInitializers(this, _sessionProperties_extraInitializers), __runInitializers(this, _userObjectProperties_initializers, []));
        /**
         * The list of user object properties.
         */
        get userObjectProperties() { return this.#userObjectProperties_accessor_storage; }
        set userObjectProperties(value) { this.#userObjectProperties_accessor_storage = value; }
        handleModelChange(model) {
            this.entities = [];
            if (!model || !model.domain) {
                return;
            }
            let changed = false;
            let semanticUserEntity;
            for (const entity of model.domain.listEntities()) {
                this.entities.push(entity);
                if (!semanticUserEntity && entity.hasSemantic(SemanticType.User)) {
                    semanticUserEntity = { key: entity.key };
                }
            }
            for (const entity of model.domain.listAllForeignEntities()) {
                this.entities.push(entity);
                if (!semanticUserEntity && entity.hasSemantic(SemanticType.User)) {
                    semanticUserEntity = { key: entity.key, domain: entity.namespace };
                }
            }
            if (!model.user && semanticUserEntity) {
                model.user = semanticUserEntity;
                changed = true;
            }
            if (model.user) {
                this.userModel = model.domain.findEntity(model.user.key, model.user.domain);
                const session = this.ensureSession(model);
                if (!session.properties || session.properties.length === 0) {
                    session.properties = this.findDefaultSessionProperties(this.userModel);
                    changed = true;
                }
            }
            this.handleUserObjectChange();
            if (changed) {
                model.notifyChange();
            }
        }
        findDefaultSessionProperties(user) {
            const result = [];
            if (!user) {
                return result;
            }
            for (const prop of user.properties) {
                if (prop.primary) {
                    result.push(prop.key);
                }
                else if (prop.hasSemantic(SemanticType.UserRole)) {
                    result.push(prop.key);
                }
                else if (prop.hasSemantic(SemanticType.DeletedFlag)) {
                    result.push(prop.key);
                }
                else if (prop.hasSemantic(SemanticType.DeletedTimestamp)) {
                    result.push(prop.key);
                }
            }
            return result;
        }
        updateUserObjectProperties() {
            this.userObjectProperties = [];
            if (this.userModel) {
                for (const prop of this.userModel.properties) {
                    this.userObjectProperties.push(prop);
                }
            }
        }
        /**
         * Updates the session properties based on the current model.
         * This is called when the user object changes or when the model is updated.
         */
        updateSessionProperties() {
            this.sessionProperties = [];
            const model = this.model;
            if (!model || !model.domain || !this.userModel) {
                return;
            }
            if (model.session && model.session.properties) {
                for (const prop of model.session.properties) {
                    // We use fields here to save on some computations.
                    // The Entity#properties is a generator that look up for a property definition in the domain.
                    // We can save on that as we need to check whether the property exists in the user entity
                    // and only then we lookup for the full property definition.
                    const info = this.userModel.fields.find((f) => f.key === prop);
                    const property = info ? model.domain.findProperty(prop, this.userModel.namespace) : undefined;
                    if (property) {
                        this.sessionProperties.push(property);
                    }
                    else {
                        this.sessionProperties.push({
                            key: prop,
                            namespace: this.userModel.namespace,
                            missing: true,
                        });
                    }
                }
            }
        }
        handleUserObjectChange() {
            this.updateUserObjectProperties();
            this.updateSessionProperties();
        }
        ensureSession(model) {
            if (!model.session) {
                model.session = { properties: [], secret: nanoid(10) };
            }
            return model.session;
        }
        ensureCookieConfiguration(session) {
            if (!session.cookie) {
                session.cookie = {
                    kind: 'cookie',
                    name: 'session',
                    enabled: true,
                    lifetime: '7d',
                    httpOnly: true,
                    secure: true,
                    sameSite: 'none',
                };
            }
            return session.cookie;
        }
        ensureJwtConfiguration(session) {
            if (!session.jwt) {
                session.jwt = {
                    kind: 'jwt',
                    enabled: true,
                    lifetime: '7d',
                };
            }
            return session.jwt;
        }
        handleUserSelectionChange(e) {
            const model = this.model;
            if (!model || !model.domain) {
                return;
            }
            const { item, value } = e.detail;
            if (!item) {
                return;
            }
            const domain = item.dataset.domain;
            if (!value) {
                model.user = undefined;
                this.handleUserObjectChange();
                this.requestUpdate();
                return;
            }
            const entity = model.domain.findEntity(value, domain);
            if (!entity) {
                throw new Error(`Entity with key ${value} not found in domain ${domain}`);
            }
            // Should it be enforced? The user entity should be marked with User semantic.
            // if (!entity.hasSemantic(SemanticType.User)) {
            //   throw new Error(`Entity with key ${value} is not a User object`)
            // }
            model.user = { key: entity.key, domain: entity.namespace };
            this.userModel = entity;
            this.handleUserObjectChange();
            this.requestUpdate();
            this.model?.notifyChange();
        }
        handleSessionPropertyMenuSelect(e) {
            const { item } = e.detail;
            const value = item.value;
            if (!value) {
                return;
            }
            this.addSessionProperty(value);
        }
        addSessionProperty(propertyKey) {
            const model = this.model;
            if (!model || !model.domain) {
                return;
            }
            const session = this.ensureSession(model);
            if (session.properties.includes(propertyKey)) {
                // Property already exists, no need to add it again.
                return;
            }
            session.properties.push(propertyKey);
            this.updateSessionProperties();
            this.model?.notifyChange();
        }
        removeSessionProperty(propertyKey) {
            const model = this.model;
            if (!model || !model.domain) {
                return;
            }
            if (!model.session || !model.session.properties) {
                return;
            }
            const index = model.session.properties.indexOf(propertyKey);
            if (index >= 0) {
                model.session.properties.splice(index, 1);
                this.updateSessionProperties();
                this.model?.notifyChange();
            }
        }
        handleSessionSecretChange(e) {
            const input = e.target;
            if (!input.checkValidity()) {
                input.reportValidity();
                return;
            }
            const model = this.model;
            if (!model) {
                return;
            }
            if (!model.session) {
                model.session = { properties: [], secret: '' };
            }
            model.session.secret = input.value;
            this.model?.notifyChange();
        }
        handleGenerateSecret() {
            const model = this.model;
            if (!model) {
                return;
            }
            if (!model.session) {
                model.session = { properties: [], secret: '' };
            }
            model.session.secret = nanoid(10);
            this.model?.notifyChange();
            this.requestUpdate();
        }
        handleCookieToggle(e) {
            const model = this.model;
            if (!model) {
                return;
            }
            const checkbox = e.target;
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            cookie.enabled = !!checkbox.checked;
            this.model?.notifyChange();
            this.requestUpdate();
        }
        handleJwtToggle(e) {
            const checkbox = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const jwt = this.ensureJwtConfiguration(session);
            jwt.enabled = !!checkbox.checked;
            this.model?.notifyChange();
            this.requestUpdate();
        }
        handleCookieLifetimeChange(e) {
            const input = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            cookie.lifetime = input.value;
            this.model?.notifyChange();
        }
        handleCookieNameChange(e) {
            const input = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            cookie.name = input.value;
            this.model?.notifyChange();
        }
        handleCookieHttpOnlyChange(e) {
            const checkbox = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            cookie.httpOnly = !!checkbox.checked;
            this.model?.notifyChange();
        }
        handleCookieSecureChange(e) {
            const checkbox = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            cookie.secure = !!checkbox.checked;
            this.model?.notifyChange();
        }
        handleCookieSameSiteChange(e) {
            const select = e.target;
            const model = this.model;
            if (!model || !select.value) {
                return;
            }
            const session = this.ensureSession(model);
            const cookie = this.ensureCookieConfiguration(session);
            const value = select.value.toLowerCase();
            cookie.sameSite = value;
            this.model?.notifyChange();
        }
        handleJwtLifetimeChange(e) {
            const input = e.target;
            const model = this.model;
            if (!model) {
                return;
            }
            const session = this.ensureSession(model);
            const jwt = this.ensureJwtConfiguration(session);
            jwt.lifetime = input.value;
            this.model?.notifyChange();
        }
        render() {
            const model = this.model;
            if (!model) {
                return nothing;
            }
            return html `
      <div class="screen-container">
        <div class="section-header">
          <h2 class="headline-medium">Session Configuration</h2>
          <span class="body-medium description"
            >Configure session properties, that is, how a user is recognized and stored.</span
          >
        </div>
        ${this.renderUserObjectSection(model)}${this.renderSessionPropertiesSection()}
        <ui-divider type="full"></ui-divider>
        ${this.renderSessionSecuritySection()}
        <ui-divider type="full"></ui-divider>
        ${this.renderSessionTransportSection()}
      </div>
    `;
        }
        renderUserObjectSection(model) {
            const info = model.user;
            const id = info?.key || '';
            return html `
      <div class="config-section">
        <div class="section-header">
          <h3 class="title-large">User object</h3>
          <span class="body-medium description">
            Select the User object which represent an authenticated user in your API. We will use this information to
            build the authentication logic.
          </span>
        </div>
        <div class="row">
          <ui-select
            label="User object"
            name="userObject"
            .value="${live(id)}"
            @change="${this.handleUserSelectionChange}"
          >
            <ui-option value="">
              <div></div>
            </ui-option>
            ${repeat(this.entities, (entity) => entity.key, (entity) => html `
                <ui-option value="${entity.key}" data-domain="${entity.namespace}">
                  <div>${entity.info.getLabel()}</div>
                  <div slot="supporting-text">${entity.info.description}</div>
                </ui-option>
              `)}
          </ui-select>
          ${renderHelp('api', 'session:user-object-select')}
        </div>
      </div>
    `;
        }
        renderSessionPropertiesSection() {
            const hasModel = !!this.userModel;
            return html `
      <div class="config-section">
        <div class="section-header">
          <h3 class="title-large">Session Properties</h3>
          <span class="body-medium description">
            ${this.userModel
                ? `Select the ${this.userModel.info.getLabel()} object properties that will be included in the session for authentication and authorization.`
                : 'Select the User object to configure session properties.'}
          </span>
        </div>
        <div class="row">
          <ui-button ?disabled="${!hasModel}" color="tonal" type="button" popovertarget="add-property-menu">
            <ui-icon role="presentation">add</ui-icon>
            Add property
          </ui-button>
          <ui-menu id="add-property-menu" popover="manual" @select="${this.handleSessionPropertyMenuSelect}">
            ${repeat(this.userObjectProperties, (prop) => prop.key, (prop) => html `
                <ui-menu-item value="${prop.key}" ?disabled="${this.sessionProperties.some((p) => p.key === prop.key)}">
                  <span>${prop.info.getLabel()}</span>
                  <span slot="supporting-text">${prop.info.description}</span>
                </ui-menu-item>
              `)}
          </ui-menu>
        </div>
        ${this.renderSessionPropertiesList()}
      </div>
    `;
        }
        renderSessionPropertiesList() {
            const items = this.sessionProperties;
            if (!items || items.length === 0) {
                return nothing;
            }
            return html `<ui-list class="fields-list"
      >${repeat(items, (prop) => prop.key, (prop) => this.renderProperty(prop))}</ui-list
    >`;
        }
        renderProperty(item) {
            let label;
            let description;
            let dataType;
            let icon;
            if (this.isMissingProperty(item)) {
                label = 'Missing property';
                description = 'This property is missing in the user object. Please add it to the user object.';
                icon = 'warning';
            }
            else {
                label = item.info.getLabel();
                description = item.info.description || '';
                dataType = getFieldDataTypeLabel(item);
                icon = getFieldIcon(item);
            }
            return html `<ui-list-item class="property-item" data-key="${item.key}" lines="two">
      <span slot="start"><ui-icon class="icon" icon="${icon}"></ui-icon></span>
      <span>${label}</span>
      <span slot="supporting-text">${description}</span>
      <span slot="end-text">${dataType}</span>
      <ui-icon-button slot="end" @click="${() => this.removeSessionProperty(item.key)}">
        <ui-icon>delete</ui-icon>
      </ui-icon-button>
    </ui-list-item>`;
        }
        isMissingProperty(item) {
            return item.missing === true;
        }
        renderSessionSecuritySection() {
            const value = this.model?.session?.secret || '';
            return html `
      <div class="config-section">
        <h3 class="title-large">Session Security</h3>
        <span class="body-medium description">
          Configure the session encryption that applies to all data used to exchange user information between the API
          and the client.
        </span>
        <div class="row">
          <ui-outlined-text-field
            label="Encryption secret"
            name="secret"
            .value="${live(value)}"
            @change="${this.handleSessionSecretChange}"
            required
            type="password"
            minLength="8"
            maxLength="36"
            pattern="[a-zA-Z0-9_\\-]*"
          ></ui-outlined-text-field>
          <span class="generate-button">
            <ui-button @click="${this.handleGenerateSecret}">Regenerate</ui-button>
          </span>
        </div>
        <span class="body-medium description">
          The encryption secret is used to encode and decode cookies and JWT. Once changed, all already issued tokens
          and cookies will be invalid.
        </span>
      </div>
    `;
        }
        renderSessionTransportSection() {
            return html `
      <div class="config-section">
        <div class="section-header">
          <h3 class="title-large">Session transport</h3>
          <span class="body-medium description"
            >Choose how the session is stored and transported. At least one transport is required.</span
          >
        </div>
        ${this.renderCookieSection()}${this.renderJwtSection()}
      </div>
    `;
        }
        renderCookieSection() {
            const enabled = !!this.model?.session?.cookie?.enabled;
            return html `
      <div class="session-transport-settings">
        <div class="row checkbox">
          <div class="input enum">
            <ui-switch .checked="${enabled}" id="cookiesEnabled" @change="${this.handleCookieToggle}"></ui-switch>
            <label for="cookiesEnabled" class="body-medium">Cookies</label>
          </div>
          ${renderHelp('api', 'session:cookies-toggle')}
        </div>
        ${this.renderCookieSettings(enabled)}
      </div>
    `;
        }
        renderCookieSettings(enabled) {
            if (!enabled) {
                return nothing;
            }
            const cookie = this.model?.session?.cookie;
            return html `
      <div class="transport-settings-item separate">
        <span class="body-medium"
          >We created commonly used and recommended settings for you. Feel free to adjust as needed.</span
        >
      </div>
      <div class="transport-settings-item separate">
        <ui-outlined-text-field
          label="Cookie lifetime"
          name="lifetime"
          .value="${live(cookie?.lifetime || '')}"
          @change="${this.handleCookieLifetimeChange}"
          required
          type="text"
          supportingText="The lifetime of the cookie. Use a valid time format, e.g., 7d, 1h, 30m."
        ></ui-outlined-text-field>
      </div>
      <div class="transport-settings-item separate">
        <ui-outlined-text-field
          label="Cookie name"
          name="cookie-name"
          .value="${live(cookie?.name || '')}"
          @change="${this.handleCookieNameChange}"
          required
          type="text"
          pattern="^[a-zA-Z0-9_\\-]+$"
          supportingText="The name of the cookie."
        ></ui-outlined-text-field>
      </div>
      <div class="transport-settings-group">
        <div class="transport-settings-item">
          <div class="row checkbox">
            <div class="input checkbox">
              <ui-checkbox
                id="http-only-flag"
                .checked="${cookie?.httpOnly || false}"
                name="http-only-flag"
                @change="${this.handleCookieHttpOnlyChange}"
              ></ui-checkbox>
              <div class="label">
                <label for="http-only-flag" class="body-medium">HTTP only flag</label>
              </div>
            </div>
            ${renderHelp('api', 'session:http-only-flag')}
          </div>
        </div>
        <div class="transport-settings-item">
          <div class="row checkbox">
            <div class="input checkbox">
              <ui-checkbox
                id="secure-flag"
                .checked="${cookie?.secure || false}"
                name="secure-flag"
                @change="${this.handleCookieSecureChange}"
              ></ui-checkbox>
              <div class="label">
                <label for="secure-flag" class="body-medium">Secure flag</label>
              </div>
            </div>
            ${renderHelp('api', 'session:secure-flag')}
          </div>
        </div>
      </div>
      <div class="transport-settings-item separate">
        <div class="row">
          <ui-select
            label="Cookie same site"
            name="sameSite"
            .value="${live(cookie?.sameSite || '')}"
            @change="${this.handleCookieSameSiteChange}"
          >
            <ui-option value="none">None</ui-option>
            <ui-option value="lax">Lax</ui-option>
          </ui-select>
          ${renderHelp('api', 'session:same-site-cookie-select')}
        </div>
      </div>
    `;
        }
        renderJwtSection() {
            const enabled = !!this.model?.session?.jwt?.enabled;
            return html `
      <div class="session-transport-settings">
        <div class="row checkbox">
          <div class="input enum">
            <ui-switch .checked="${enabled}" id="jwtEnabled" @change="${this.handleJwtToggle}"></ui-switch>
            <label for="jwtEnabled" class="body-medium">JSON Web Token (JWT)</label>
          </div>
          ${renderHelp('api', 'session:jwt-toggle')}
        </div>
        ${this.renderJwtSettings(enabled)}
      </div>
    `;
        }
        renderJwtSettings(enabled) {
            if (!enabled) {
                return nothing;
            }
            const jwt = this.model?.session?.jwt;
            return html `
      <div class="transport-settings-item separate">
        <span class="body-medium"
          >We created a commonly used and recommended settings for you. Feel free to adjust as needed.</span
        >
      </div>
      <div class="transport-settings-item separate">
        <ui-outlined-text-field
          label="Token lifetime"
          name="lifetime"
          .value="${live(jwt?.lifetime || '')}"
          @change="${this.handleJwtLifetimeChange}"
          required
          type="text"
          supportingText="The lifetime of the token. Use a valid time format, e.g., 7d, 1h, 30m."
        ></ui-outlined-text-field>
      </div>
    `;
        }
        constructor() {
            super(...arguments);
            __runInitializers(this, _userObjectProperties_extraInitializers);
        }
    };
})();
/**
 * An API Modeling screen for configuring session management.
 * It allows to:
 * - Select the User entity.
 * - Add session properties from the User entities that are available during the request lifecycle.
 * - Generate session tokens and configure their settings.
 * - Select session transport method.
 */
export default ApiModelingSessionScreen;
//# sourceMappingURL=ApiModelingSessionScreen.js.map