import { __esDecorate, __runInitializers } from "tslib";
import { nanoid } from '../nanoid.js';
import { ApiModelKind, DataDomainKind } from '../models/kinds.js';
import { Thing } from '../models/Thing.js';
import { DataDomain } from './DataDomain.js';
import { DependentModel } from './DependentModel.js';
import { observed, toRaw } from '../decorators/observed.js';
let ApiModel = (() => {
    let _classSuper = DependentModel;
    let _termsOfService_decorators;
    let _termsOfService_initializers = [];
    let _termsOfService_extraInitializers = [];
    let _contact_decorators;
    let _contact_initializers = [];
    let _contact_extraInitializers = [];
    let _license_decorators;
    let _license_initializers = [];
    let _license_extraInitializers = [];
    return class ApiModel extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _termsOfService_decorators = [observed()];
            _contact_decorators = [observed({ deep: true })];
            _license_decorators = [observed({ deep: true })];
            __esDecorate(this, null, _termsOfService_decorators, { kind: "accessor", name: "termsOfService", static: false, private: false, access: { has: obj => "termsOfService" in obj, get: obj => obj.termsOfService, set: (obj, value) => { obj.termsOfService = value; } }, metadata: _metadata }, _termsOfService_initializers, _termsOfService_extraInitializers);
            __esDecorate(this, null, _contact_decorators, { kind: "accessor", name: "contact", static: false, private: false, access: { has: obj => "contact" in obj, get: obj => obj.contact, set: (obj, value) => { obj.contact = value; } }, metadata: _metadata }, _contact_initializers, _contact_extraInitializers);
            __esDecorate(this, null, _license_decorators, { kind: "accessor", name: "license", static: false, private: false, access: { has: obj => "license" in obj, get: obj => obj.license, set: (obj, value) => { obj.license = value; } }, metadata: _metadata }, _license_initializers, _license_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        /**
         * The data domain kind recognizable by the ecosystem.
         */
        kind;
        /**
         * The unique key of the data domain schema.
         * This is a stable identifier that does not change across versions.
         */
        key;
        /**
         * The description of the domain property.
         */
        info;
        /**
         * The designated Data Entity that represents a "User".
         * This entity must be marked with the "User" semantic in the Data Modeler.
         *
         * This property is required to publish the API.
         */
        user;
        /**
         * Configuration for how users prove their identity.
         * The API model is invalid if this is not set.
         */
        authentication;
        /**
         * Configuration for what authenticated users are allowed to do.
         * The API model is invalid if this is not set.
         */
        authorization;
        /**
         * Configuration for the transport and payload of the user session.
         * The API model is invalid if this is not set.
         */
        session;
        /**
         * The specific subset of Data Entities to be exposed by this API.
         * These are the entities that are included in the data domain schema.
         */
        exposes;
        /**
         * Optional array of access rules that define the access control policies
         * for the API. These rules are used to enforce security and permissions
         * on the exposed entities.
         *
         * These rules apply to all exposed entities and actions. An API action
         * can declare its own access rules, which will override these.
         */
        accessRule;
        /**
         * Optional configuration for API-wide rate limiting and throttling.
         * Defines rules to protect the API from overuse.
         */
        rateLimiting;
        #termsOfService_accessor_storage = __runInitializers(this, _termsOfService_initializers, void 0);
        /**
         * A URL to the Terms of Service for the API.
         */
        get termsOfService() { return this.#termsOfService_accessor_storage; }
        set termsOfService(value) { this.#termsOfService_accessor_storage = value; }
        #contact_accessor_storage = (__runInitializers(this, _termsOfService_extraInitializers), __runInitializers(this, _contact_initializers, void 0));
        /**
         * The contact information for the exposed API.
         */
        get contact() { return this.#contact_accessor_storage; }
        set contact(value) { this.#contact_accessor_storage = value; }
        #license_accessor_storage = (__runInitializers(this, _contact_extraInitializers), __runInitializers(this, _license_initializers, void 0));
        /**
         * The license information for the API.
         */
        get license() { return this.#license_accessor_storage; }
        set license(value) { this.#license_accessor_storage = value; }
        /**
         * When the initializing flag is set to true,
         * the domain is not notified of changes.
         */
        #initializing = (__runInitializers(this, _license_extraInitializers), true);
        /**
         * When the notifying flag is set to true,
         * the domain is pending a notification.
         * No other notifications will be sent until
         * the current notification is sent.
         */
        #notifying = false;
        /**
         * A convenience getter that returns the DataDomain associated with this API model.
         * Since the API model can have only one DataDomain,
         * this getter returns the first dependency in the list.
         *
         * The parent interface `DependentModel` allows for multiple dependencies,
         * to unify the dependency management across different models.
         */
        get domain() {
            if (this.dependencyList.length === 0) {
                return undefined;
            }
            const domain = this.dependencyList[0];
            return this.dependencies.get(domain.key);
        }
        static createSchema(input = {}) {
            const { key = nanoid(), exposes = [] } = input;
            const info = Thing.fromJSON(input.info, { name: 'Unnamed API' }).toJSON();
            const result = {
                kind: ApiModelKind,
                key,
                info,
                exposes,
            };
            if (input.user) {
                result.user = { ...input.user };
            }
            if (input.dependencyList) {
                result.dependencyList = structuredClone(input.dependencyList);
            }
            if (input.authentication) {
                result.authentication = input.authentication;
            }
            if (input.authorization) {
                result.authorization = input.authorization;
            }
            if (input.session) {
                result.session = input.session;
            }
            if (input.accessRule) {
                result.accessRule = input.accessRule;
            }
            if (input.rateLimiting) {
                result.rateLimiting = input.rateLimiting;
            }
            if (input.termsOfService) {
                result.termsOfService = input.termsOfService;
            }
            if (input.contact) {
                result.contact = structuredClone(input.contact);
            }
            if (input.license) {
                result.license = structuredClone(input.license);
            }
            return result;
        }
        constructor(state, domain) {
            const init = ApiModel.createSchema(state);
            const instances = [];
            if (domain instanceof DataDomain) {
                instances.push(domain);
            }
            else if (typeof domain === 'object' && domain.kind === DataDomainKind) {
                instances.push(new DataDomain(domain));
            }
            else if (domain) {
                throw new Error(`Invalid domain provided. Expected a DataDomain instance or schema.`);
            }
            super(init.dependencyList, instances);
            this.kind = init.kind;
            this.key = init.key;
            this.info = new Thing(init.info);
            this.user = init.user;
            if (init.authentication) {
                this.authentication = structuredClone(init.authentication);
            }
            if (init.authorization) {
                this.authorization = structuredClone(init.authorization);
            }
            if (init.session) {
                this.session = structuredClone(init.session);
            }
            if (Array.isArray(init.exposes)) {
                this.exposes = structuredClone(init.exposes);
            }
            else {
                this.exposes = [];
            }
            if (init.accessRule) {
                this.accessRule = structuredClone(init.accessRule);
            }
            if (init.rateLimiting) {
                this.rateLimiting = structuredClone(init.rateLimiting);
            }
            if (init.termsOfService) {
                this.termsOfService = init.termsOfService;
            }
            if (init.contact) {
                this.contact = init.contact;
            }
            if (init.license) {
                this.license = init.license;
            }
            this.#initializing = false;
            this.info.addEventListener('change', () => {
                this.notifyChange();
            });
        }
        toJSON() {
            const result = {
                kind: this.kind,
                key: this.key,
                info: this.info.toJSON(),
                exposes: structuredClone(this.exposes),
            };
            if (this.user) {
                result.user = { ...this.user };
            }
            if (this.dependencyList.length > 0) {
                result.dependencyList = structuredClone(this.dependencyList);
            }
            if (this.authentication) {
                result.authentication = structuredClone(this.authentication);
            }
            if (this.authorization) {
                result.authorization = structuredClone(this.authorization);
            }
            if (this.session) {
                result.session = structuredClone(this.session);
            }
            if (this.accessRule) {
                result.accessRule = structuredClone(this.accessRule);
            }
            if (this.rateLimiting) {
                result.rateLimiting = structuredClone(this.rateLimiting);
            }
            if (this.termsOfService) {
                result.termsOfService = this.termsOfService;
            }
            if (this.contact) {
                result.contact = structuredClone(toRaw(this, this.contact));
            }
            if (this.license) {
                result.license = structuredClone(toRaw(this, this.license));
            }
            return result;
        }
        /**
         * This function is used internally by all domain elements to notify that something has changed.
         * Since we want to notify listeners after the operation commits, we use microtask
         * to ensure that the event is dispatched after the current operation.
         */
        notifyChange() {
            if (this.#notifying || this.#initializing) {
                return;
            }
            this.#notifying = true;
            queueMicrotask(() => {
                this.#notifying = false;
                const event = new Event('change');
                this.dispatchEvent(event);
            });
        }
        /**
         * Exposes a new entity in the API model.
         * If the entity already exists, it returns the existing one.
         * @param entityKey The key of the entity to expose.
         * @returns The exposed entity.
         */
        exposeEntity(entityKey) {
            const existing = this.exposes.find((e) => e.key === entityKey);
            if (existing) {
                return existing;
            }
            const newEntity = {
                key: entityKey,
                actions: [],
            };
            this.exposes.push(newEntity);
            this.notifyChange();
            return newEntity;
        }
        /**
         * Removes an entity from the API model.
         * @param entityKey The key of the entity to remove.
         */
        removeEntity(entityKey) {
            const index = this.exposes.findIndex((e) => e.key === entityKey);
            if (index !== -1) {
                this.exposes.splice(index, 1);
                this.notifyChange();
            }
        }
        /**
         * Returns the exposed entity by its key.
         * @param entityKey The key of the entity to find.
         * @returns The exposed entity or undefined if not found.
         */
        getExposedEntity(entityKey) {
            return this.exposes.find((e) => e.key === entityKey);
        }
        /**
         * Clears the API model for a new entity change.
         * This method resets the dependencies, exposes, user,
         * authentication, authorization, and session properties.
         */
        cleanForEntityChange() {
            this.dependencies.clear();
            this.dependencyList = [];
            this.exposes = [];
            this.user = undefined;
            if (this.session) {
                this.session.properties = [];
            }
            if (this.authentication && this.authentication.strategy === 'UsernamePassword') {
                const typed = this.authentication;
                typed.passwordKey = undefined;
            }
            if (this.authorization && this.authorization.strategy == 'RBAC') {
                const typed = this.authorization;
                typed.roleKey = '';
            }
        }
        /**
         * Attaches a DataDomain to this API model.
         * This method clears any existing dependencies and sets the new domain.
         *
         * @param domain The DataDomain to attach to this API model.
         * @throws Error if the domain does not have a version set in its info.
         */
        attachDataDomain(domain) {
            if (!domain.info.version) {
                throw new Error(`Cannot attach DataDomain without a version. Please set the version in the domain info.`);
            }
            this.cleanForEntityChange();
            this.dependencies.set(domain.key, domain);
            this.dependencyList = [{ key: domain.key, version: domain.info.version }];
            this.notifyChange();
        }
    };
})();
export { ApiModel };
//# sourceMappingURL=ApiModel.js.map