import { Json } from '@api-client/json';
import { Thing } from '../../models/Thing.js';
import { nanoid } from 'nanoid';
import { AmfShapeGenerator } from '../../amf/AmfShapeGenerator.js';
import { DataPropertyKind } from '../../models/kinds.js';
import { BinaryFormats, DataPropertyAttributes, DataPropertyList, DataPropertyTypes, NumberFormats, } from '../DataFormat.js';
/**
 * @deprecated
 */
export class DataProperty {
    root;
    kind = DataPropertyKind;
    key = '';
    /**
     * The description of the data namespace.
     */
    info = Thing.fromName('');
    /**
     * Wether the data property is required.
     */
    required;
    /**
     * Whether the data property allows multiple items.
     */
    multiple;
    /**
     * Whether this property describes a primary key of the entity.
     */
    primary;
    /**
     * Whether this property describes an indexed property of the entity.
     */
    index;
    /**
     * Whether the property is read only in the schema.
     */
    readOnly;
    /**
     * Whether the property is write only in the schema.
     */
    writeOnly;
    /**
     * Whether this property is deprecated.
     */
    deprecated;
    /**
     * Optional general purpose tags for the UI.
     *
     * Note to implementations, use the `addTag()` method as it propagates the "tag" value in the namespace.
     */
    tags = [];
    /**
     * Reserved for future use.
     *
     * The keys of the taxonomy items associated with the property.
     */
    taxonomy = [];
    /**
     * The data type for this property.
     * Note, not all schemas support the same type. For example, defining `sint32`
     * works for protocol buffers but does not for JSON. In such case we use default
     * translation `sint32` -> `number`. Another way it to create
     * a derivative entity for specific schema to describe specific schema case.
     */
    type = 'string';
    /**
     * The general schema definition of this property.
     * This is propagated to all bindings (when they support these properties).
     */
    schema;
    /**
     * The list of bindings for this property.
     *
     * A binding defines a translation from a data model to a specific format.
     * For example allows to define properties required to generate AMF shape and therefore RAML/OAS shapes for web APIs
     * or a protocol buffer schema.
     */
    bindings = [];
    /**
     * @deprecated Use the direct import of types.
     */
    static get supportedTypes() {
        return [...DataPropertyTypes];
    }
    static fromName(root, name) {
        const property = new DataProperty(root);
        property.info = Thing.fromName(name);
        return property;
    }
    static fromType(root, type) {
        const result = new DataProperty(root);
        result.type = type;
        return result;
    }
    /**
     * @param input The data property definition to restore.
     */
    constructor(root, input) {
        this.root = root;
        let init;
        if (typeof input === 'string') {
            init = JSON.parse(input);
        }
        else if (typeof input === 'object') {
            init = input;
        }
        else {
            init = {
                kind: DataPropertyKind,
                key: nanoid(),
                info: Thing.fromName('').toJSON(),
                type: 'string',
            };
        }
        this.new(init);
    }
    new(init) {
        if (!DataProperty.isDataProperty(init)) {
            throw new Error(`Not a data property.`);
        }
        const { info, key = nanoid(), kind = DataPropertyKind, multiple, required, type = DataPropertyList.string, index, primary, readOnly, writeOnly, tags, taxonomy, deprecated, schema, bindings, } = init;
        this.kind = kind;
        this.key = key;
        this.type = type;
        if (info) {
            this.info = new Thing(info);
        }
        else {
            this.info = Thing.fromName('');
        }
        if (typeof multiple === 'boolean') {
            this.multiple = multiple;
        }
        else {
            this.multiple = undefined;
        }
        if (typeof required === 'boolean') {
            this.required = required;
        }
        else {
            this.required = undefined;
        }
        if (typeof index === 'boolean') {
            this.index = index;
        }
        else {
            this.index = undefined;
        }
        if (typeof deprecated === 'boolean') {
            this.deprecated = deprecated;
        }
        else {
            this.deprecated = undefined;
        }
        if (typeof primary === 'boolean') {
            this.primary = primary;
        }
        else {
            this.primary = undefined;
        }
        if (typeof readOnly === 'boolean') {
            this.readOnly = readOnly;
        }
        else {
            this.readOnly = undefined;
        }
        if (typeof writeOnly === 'boolean') {
            this.writeOnly = writeOnly;
        }
        else {
            this.writeOnly = undefined;
        }
        if (Array.isArray(tags)) {
            this.tags = [...tags];
        }
        else {
            this.tags = [];
        }
        if (Array.isArray(taxonomy)) {
            this.taxonomy = [...taxonomy];
        }
        else {
            this.taxonomy = [];
        }
        if (schema) {
            this.schema = Json.clone(schema);
        }
        else {
            this.schema = undefined;
        }
        if (Array.isArray(bindings)) {
            this.bindings = bindings.map((i) => Json.clone(i));
        }
        else {
            this.bindings = [];
        }
    }
    static isDataProperty(input) {
        const typed = input;
        if (!input || typed.kind !== DataPropertyKind) {
            return false;
        }
        return true;
    }
    toJSON() {
        const result = {
            kind: DataPropertyKind,
            info: this.info.toJSON(),
            key: this.key,
            type: this.type || DataPropertyList.string,
        };
        if (typeof this.index === 'boolean') {
            result.index = this.index;
        }
        if (typeof this.deprecated === 'boolean') {
            result.deprecated = this.deprecated;
        }
        if (typeof this.primary === 'boolean') {
            result.primary = this.primary;
        }
        if (typeof this.multiple === 'boolean') {
            result.multiple = this.multiple;
        }
        if (typeof this.required === 'boolean') {
            result.required = this.required;
        }
        if (typeof this.readOnly === 'boolean') {
            result.readOnly = this.readOnly;
        }
        if (typeof this.writeOnly === 'boolean') {
            result.writeOnly = this.writeOnly;
        }
        if (Array.isArray(this.tags) && this.tags.length) {
            result.tags = [...this.tags];
        }
        if (Array.isArray(this.taxonomy) && this.taxonomy.length) {
            result.taxonomy = [...this.taxonomy];
        }
        if (this.schema) {
            result.schema = Json.clone(this.schema);
        }
        if (Array.isArray(this.bindings) && this.bindings.length) {
            result.bindings = this.bindings.map((i) => Json.clone(i));
        }
        return result;
    }
    /**
     * Removes self from the parent entity and the namespace definition.
     */
    remove() {
        const { root } = this;
        const entity = root.definitions.entities.find((i) => i.properties.some((j) => j.key === this.key));
        if (entity) {
            const assocIndex = entity.properties.findIndex((i) => i.key === this.key);
            entity.properties.splice(assocIndex, 1);
        }
        const defIndex = this.root.definitions.properties.findIndex((i) => i.key === this.key);
        if (defIndex >= 0) {
            this.root.definitions.properties.splice(defIndex, 1);
        }
    }
    /**
     * Adds a new tag to the property. It also populates the root namespace's tags when tag is new.
     *
     * Note, it does nothing when the tag is already defined.
     *
     * @param tag The tag to add.
     */
    addTag(tag) {
        if (!tag) {
            return;
        }
        const lower = tag.toLowerCase();
        const { tags } = this;
        if (tags.some((t) => t.toLowerCase() === lower)) {
            return;
        }
        tags.push(tag);
        const { definitions } = this.root;
        if (!definitions.tags.some((t) => t.toLowerCase() === lower)) {
            definitions.tags.push(tag);
        }
    }
    /**
     * Removes a tag from the property. Unlike the `addTag()` this won't remove a `tag` from the root namespace.
     *
     * @param tag The tag to remove.
     */
    removeTag(tag) {
        if (!tag) {
            return;
        }
        const lower = tag.toLowerCase();
        const { tags } = this;
        const index = tags.findIndex((t) => t.toLowerCase() === lower);
        if (index >= 0) {
            tags.splice(index, 1);
        }
    }
    /**
     * Creates a Property Shape of AMF.
     * The property itself is auto-generated. If the `schema` is defined then it is used
     * as the `range` of the property. Otherwise basic shape is generated for the range.
     *
     * This is a preferred way of reading the AMF shape as this synchronizes changed
     * data properties with the shape definition.
     *
     * @returns AMF property shape definition.
     */
    toApiShape() {
        const serializer = new AmfShapeGenerator();
        return serializer.property(this);
    }
    /**
     * Checks whether the passed value is one of the supported data types.
     * @param value The value to test
     * @returns True when the passed value is one of the supported data types.
     */
    static isValidType(value) {
        if (typeof value !== 'string') {
            return false;
        }
        return DataPropertyTypes.includes(value);
    }
    /**
     * Checks whether the passed value is one of the supported data property attributes.
     * @param value The value to test
     * @returns True when the passed value is one of the supported data property attributes.
     */
    static isValidAttribute(value) {
        if (typeof value !== 'string') {
            return false;
        }
        return DataPropertyAttributes.includes(value);
    }
    /**
     * Checks whether the passed value of one of the supported `format` values.
     *
     * @param value The value to test
     * @returns `true` when the value is one of the supported `format` values.
     */
    static isValidFormat(value) {
        if (value === undefined) {
            // It is the valid value.
            return true;
        }
        if (typeof value !== 'string') {
            return false;
        }
        if (NumberFormats.includes(value)) {
            return true;
        }
        if (BinaryFormats.includes(value)) {
            return true;
        }
        return false;
    }
    /**
     * Creates the `schema` object if missing and returns it.
     * @returns The schema object.
     */
    ensureSchema() {
        if (!this.schema) {
            this.schema = {};
        }
        return this.schema;
    }
    /**
     * Creates if not existing and returns web bindings definition.
     * @returns The web binding definition
     */
    getWebBinding() {
        let object = this.bindings.find((i) => i.type === 'web');
        if (!object) {
            object = { type: 'web', schema: {} };
            this.bindings.push(object);
        }
        return object.schema;
    }
    /**
     * Returns the schema value of the binding, if any was created.
     * @param type The type of the binding to read.
     * @returns The binding schema, if any
     */
    readBinding(type) {
        const item = this.bindings.find((i) => i.type === type);
        return item?.schema;
    }
    /**
     * Returns a parent data entity where this data property exist.
     */
    getParentInstance() {
        return this.root.definitions.entities.find((e) => e.properties.some((e) => e.key === this.key));
    }
    /**
     * Checks whether the property is a child of the given namespace, data model, or an entity.
     * The relationship doesn't have to be direct, as long as the property is in the hierarchy it will return true.
     *
     * @param key The key of the parent to check.
     * @returns True if this data property is a child of the given namespace, data model, or an entity
     */
    isChildOf(key) {
        if (this.key === key) {
            return false;
        }
        const parent = this.getParentInstance();
        if (!parent) {
            return false;
        }
        if (parent.key === key) {
            return true;
        }
        return parent.isChildOf(key);
    }
}
//# sourceMappingURL=DataProperty.js.map