import { __esDecorate, __runInitializers } from "tslib";
import { html, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';
import { ApiEditor } from './ApiEditor.js';
import { isValidAccessTarget, isValidOperationName } from '../lib/validators.js';
import { renderHelp } from '../../help/render.js';
let ApiModelingRateLimiting = (() => {
    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 = [];
    return class ApiModelingRateLimiting 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()];
            __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);
            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 name when editing action-level limits.
         * Ignored when target is 'api' or 'endpoint'.
         * @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 scope where limits are applied: api | endpoint | action.
         * @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, []));
        /**
         * The editable list of rules. This is a direct reference when available.
         */
        get rules() { return this.#rules_accessor_storage; }
        set rules(value) { this.#rules_accessor_storage = value; }
        constructor() {
            super();
            __runInitializers(this, _rules_extraInitializers);
            this.operation = undefined;
            this.target = undefined;
        }
        willUpdate(cp) {
            if (cp.has('operation') || cp.has('target')) {
                this.setupModel(this.model, this.domainKey);
            }
            else {
                super.willUpdate(cp);
            }
        }
        resetModel() {
            this.rules = [];
        }
        setupModel(model, domainKey) {
            this.resetModel();
            if (!model || !isValidAccessTarget(this.target)) {
                return;
            }
            const rules = this.readRules(model, this.target, domainKey, this.operation);
            this.rules = rules ?? [];
        }
        /**
         * Reads rules from the model for the given scope.
         */
        readRules(model, target, domainKey, operation) {
            if (target === 'api') {
                return model.rateLimiting?.rules;
            }
            if (!domainKey) {
                return undefined;
            }
            const entity = model.exposes.find((e) => e.key === domainKey);
            if (!entity) {
                return undefined;
            }
            if (target === 'endpoint') {
                return entity.rateLimiting?.rules;
            }
            if (!isValidOperationName(operation)) {
                return undefined;
            }
            const action = entity.actions.find((a) => a.kind === operation);
            return action?.rateLimiting?.rules;
        }
        /**
         * Synchronizes the current rules array into the model at the proper scope.
         */
        synchronizeModel() {
            const { model, target, operation } = this;
            if (!model || !isValidAccessTarget(target)) {
                return;
            }
            const src = this.rules ?? [];
            if (target === 'api') {
                model.rateLimiting = src.length > 0 ? { rules: src } : undefined;
                return;
            }
            const { domainKey } = this;
            if (!domainKey) {
                return;
            }
            const entity = model.exposes.find((e) => e.key === domainKey);
            if (!entity) {
                return;
            }
            if (target === 'endpoint') {
                entity.rateLimiting = src.length > 0 ? { rules: src } : undefined;
                return;
            }
            if (!isValidOperationName(operation)) {
                return;
            }
            const action = entity.actions.find((a) => a.kind === operation);
            if (!action) {
                return;
            }
            action.rateLimiting = src.length > 0 ? { rules: src } : undefined;
        }
        /**
         * Adds a new rule with sensible defaults.
         */
        addRule() {
            const rule = {
                description: '',
                key: { type: 'ip' },
                rate: 60,
                interval: 'minute',
                burst: 60,
            };
            this.rules.push(rule);
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        deleteRule(index) {
            if (index < 0 || index >= this.rules.length) {
                return;
            }
            this.rules.splice(index, 1);
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleAddRuleClick() {
            this.addRule();
        }
        handleDeleteRuleClick(e) {
            const btn = e.currentTarget;
            const index = Number(btn.dataset.index);
            if (!Number.isNaN(index)) {
                this.deleteRule(index);
            }
        }
        handleDescriptionChange(e) {
            const input = e.currentTarget;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            this.rules[index].description = input.value;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleKeyTypeChange(e) {
            const select = e.currentTarget;
            const index = Number(select.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            const v = e.detail.value;
            if (v !== 'ip' && v !== 'userId' && v !== 'role') {
                return;
            }
            const rule = this.rules[index];
            if (v === 'role') {
                rule.key = { type: 'role', value: '' };
            }
            else {
                rule.key = { type: v };
            }
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleRoleValueChange(e) {
            const input = e.currentTarget;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            const rule = this.rules[index];
            if (rule.key.type !== 'role') {
                return;
            }
            rule.key.value = input.value;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleRateChange(e) {
            const input = e.currentTarget;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            const value = input.valueAsNumber;
            if (!Number.isFinite(value) || value <= 0) {
                return;
            }
            this.rules[index].rate = value;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleIntervalChange(e) {
            const select = e.currentTarget;
            const index = Number(select.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            const v = e.detail.value;
            if (v !== 'second' && v !== 'minute' && v !== 'hour' && v !== 'day') {
                return;
            }
            this.rules[index].interval = v;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        handleBurstChange(e) {
            const input = e.currentTarget;
            const index = Number(input.dataset.index);
            if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
                return;
            }
            const value = input.valueAsNumber;
            if (!Number.isFinite(value) || value <= 0) {
                return;
            }
            this.rules[index].burst = value;
            this.synchronizeModel();
            this.notifyChange();
            this.requestUpdate();
        }
        render() {
            if (!isValidAccessTarget(this.target)) {
                return nothing;
            }
            return html `
      <div class="intro">
        <div class="header">
          <span class="title-large">Rate limiting</span>
          ${renderHelp('modeling', 'api:rate-limiting')}
        </div>
        <span class="inline-help body-medium">
          Define throttling rules to protect the API or specific endpoints/actions from overuse.
        </span>
      </div>

      <ui-button color="outlined" @click="${this.handleAddRuleClick}">
        <ui-icon slot="icon" icon="add" aria-hidden="true"></ui-icon>
        Add rule
      </ui-button>

      ${this.renderRules()}
    `;
        }
        renderRules() {
            const { rules } = this;
            if (!rules || rules.length === 0) {
                return nothing;
            }
            const items = [];
            const len = rules.length;
            rules.forEach((rule, index) => {
                items.push(this.renderRule(rule, index));
                if (index < len - 1) {
                    items.push(html `<ui-divider class="rule-divider"></ui-divider>`); // separator
                }
            });
            return html `<div class="rules">${items}</div>`;
        }
        renderRule(rule, index) {
            return html `
      <div class="rule">
        <div class="rule-header">
          <span class="rule-title title-medium">Rule #${index + 1}</span>
          <div class="actions">
            ${renderHelp('modeling', 'api:rate-limiting:rule')}
            <ui-icon-button type="button" data-index="${index}" @click="${this.handleDeleteRuleClick}">
              <ui-icon>delete_outline</ui-icon>
            </ui-icon-button>
          </div>
        </div>

        <div class="rule-body">
          <div class="row input-row">
            <ui-outlined-text-field
              class="input"
              label="Description (optional)"
              .value=${rule.description ?? ''}
              data-index="${index}"
              @change=${this.handleDescriptionChange}
            ></ui-outlined-text-field>
          </div>

          <div class="row input-row">
            <div class="input select">
              <ui-select
                class="form-field"
                .value="${rule.key.type}"
                data-index="${index}"
                label="Limit target"
                required
                @change=${this.handleKeyTypeChange}
              >
                <ui-option value="ip">Client IP</ui-option>
                <ui-option value="userId">User ID</ui-option>
                <ui-option value="role">User role</ui-option>
              </ui-select>
            </div>
            ${rule.key.type === 'role'
                ? html `<ui-outlined-text-field
                  class="input"
                  label="Role value"
                  .value=${'value' in rule.key ? rule.key.value : ''}
                  data-index="${index}"
                  @change=${this.handleRoleValueChange}
                ></ui-outlined-text-field>`
                : nothing}
          </div>

          <div class="row input-row">
            <ui-outlined-text-field
              class="input"
              type="number"
              label="Rate"
              min="1"
              required
              .value=${String(rule.rate)}
              data-index="${index}"
              @change=${this.handleRateChange}
            ></ui-outlined-text-field>
            <ui-select
              class="input"
              .value="${rule.interval}"
              data-index="${index}"
              label="Interval"
              required
              @change=${this.handleIntervalChange}
            >
              <ui-option value="second">Second</ui-option>
              <ui-option value="minute">Minute</ui-option>
              <ui-option value="hour">Hour</ui-option>
              <ui-option value="day">Day</ui-option>
            </ui-select>
            <ui-outlined-text-field
              class="input"
              type="number"
              label="Burst"
              min="1"
              required
              .value=${String(rule.burst)}
              data-index="${index}"
              @change=${this.handleBurstChange}
            ></ui-outlined-text-field>
          </div>
        </div>
      </div>
    `;
        }
    };
})();
/**
 * An editor for configuring rate limiting settings at the API, endpoint, or action level.
 *
 * This editor writes directly into the provided model (no separate dirty buffer).
 */
export default ApiModelingRateLimiting;
//# sourceMappingURL=ApiModelingRateLimiting.js.map