import { IAmfExampleTypes, modelTypeToAmfDataType } from './AmfTypes.js';
import { AmfNamespace } from './definitions/Namespace.js';
import { anyShape, arrayShape, fileShape, nodeShape, propertyShape, recursiveShape, scalarShape, unionShape, } from './definitions/Shapes.js';
import { AmfDataNode } from './models/AmfDataNode.js';
import { nanoid } from 'nanoid';
import { DataEntityKind, DataPropertyKind } from '../models/kinds.js';
import { DataValueGenerator } from './DataValueGenerator.js';
/**
 * Serializes Data shapes (DataEntity, DataProperty, DataAssociation) to AMF-related shapes.
 *
 * This is used when defining entities (according to the `DataEntity` schema)
 *  and then we want to translate this into AMF shapes.
 *
 * @deprecated This class refers to the deprecated old data domain model.
 * Use the `ShapeGenerator` with the new domain model.
 */
export class AmfShapeGenerator {
    #valueGenerator;
    /**
     * Used with data domain value generation for entity properties.
     * Lazily loaded class only when functions are used.
     * This needs to be a class instances because some functions are incremental
     */
    get valueGenerator() {
        if (!this.#valueGenerator) {
            this.#valueGenerator = new DataValueGenerator();
        }
        return this.#valueGenerator;
    }
    /**
     * Serializes the Entity to the AMF node shape.
     *
     * @param input The Property to serialize.
     * @param generatedEntities The list keys of already generated entities. This prohibits recursive shape generation.
     */
    entity(input, generatedEntities = []) {
        if (generatedEntities.includes(input.key)) {
            // create a recursive shape.
            return this._recursiveShape(input);
        }
        generatedEntities.push(input.key);
        const result = nodeShape(input.key);
        result.id = input.key;
        this._updateBaseProperties(input, result);
        result.properties = [];
        input.properties.forEach((item) => {
            // we check whether the property is hidden.
            // This is not happening when calling the `property()` because this method
            // always returns the AMF shape.
            const wb = item.readBinding('web');
            if (wb && wb.hidden) {
                return;
            }
            const shape = this.property(item);
            result.properties.push(shape);
        });
        input.associations.forEach((assoc) => {
            const prop = this.associationProperty(assoc, generatedEntities);
            result.properties.push(prop);
        });
        input.getComputedParents().forEach((parent) => {
            const shape = this.entity(parent, generatedEntities);
            result.inherits.push(shape);
        });
        return result;
    }
    /**
     * Serializes an Entity Property to the AMF property shape.
     *
     * @param input The Property to serialize.
     */
    property(input) {
        const { required, key } = input;
        const result = propertyShape(key);
        result.path = `${AmfNamespace.aml.vocabularies.data.key}${input.info.name}`;
        if (required) {
            result.minCount = 1;
        }
        result.range = this._readPropertyRange(input);
        // for example, Example generator needs to know the name of the property
        // as it does not look into the "range" object.
        this._updateBaseProperties(input, result);
        // sync the name of the property shape with the range, in case it was changed by the bindings
        if (result.range.name) {
            result.name = result.range.name;
        }
        return result;
    }
    /**
     * Serializes an Entity property to the AMF property shape with association
     * targets as defined in the schema configuration.
     *
     * @param input The Property to serialize.
     */
    associationProperty(input, generatedEntities = []) {
        const { required, key } = input;
        const result = propertyShape(key);
        result.path = `${AmfNamespace.aml.vocabularies.data.key}${input.info.name}`;
        if (required) {
            result.minCount = 1;
        }
        result.range = this.associationShape(input, generatedEntities);
        this._updateBaseProperties(input, result);
        return result;
    }
    /**
     * Generates a shape for an association. Most likely you want to use the `associationProperty()` method instead.
     *
     * @param input The data association instance.
     * @returns The range value for the PropertyShape.
     */
    associationShape(input, generatedEntities = []) {
        const schema = input.schema;
        if (schema && schema.linked) {
            // This is a link to the schema. In an API that would be the id
            // of a resource to request the data from.
            const range = scalarShape(input.key);
            range.id = `link-${input.key}`;
            range.dataType = modelTypeToAmfDataType('string');
            return range;
        }
        const items = this.associationUnion(input, generatedEntities);
        if (!items) {
            return;
        }
        const unionType = (schema && schema.unionType) || 'anyOf';
        if (Array.isArray(items)) {
            const range = unionShape(input.key);
            this._updateBaseProperties(input, range);
            range.anyOf = [];
            if (unionType === 'anyOf') {
                range.anyOf = items;
            }
            else if (unionType === 'allOf') {
                range.and = items;
            }
            else if (unionType === 'oneOf') {
                range.xone = items;
            }
            else {
                // not.
                range.not = items[0]; // ?
            }
            if (input.multiple) {
                return this.refactorShapeToArray(input.key, range);
            }
            return range;
        }
        if (unionType === 'not') {
            const wrapper = anyShape(input.key);
            wrapper.id = `not-shape-${input.key}`;
            wrapper.not = items;
            return wrapper;
        }
        if (input.multiple) {
            return this.refactorShapeToArray(input.key, items);
        }
        return items;
    }
    /**
     * Generates a shape list for an union. Most likely you want to use the `associationProperty()` method instead.
     *
     * @param input The data association instance.
     * @returns The range value for the PropertyShape.
     */
    associationUnion(input, generatedEntities = []) {
        const targets = input.getTargets().map((i) => this.entity(i, generatedEntities));
        if (!targets.length) {
            return undefined;
        }
        if (targets.length > 1) {
            return targets;
        }
        return targets[0];
    }
    /**
     * The DataProperty may have both the `schema` and the `bindings`. For AMF shape we read `schema` for
     * default value, examples, and enum values. We also look for the `web`
     * bindings for more detailed definition of a shape.
     *
     * @param input
     * @returns
     */
    _readPropertyRange(input) {
        const bindings = input.readBinding('web');
        let schema;
        if (input.schema) {
            schema = input.schema;
        }
        return this._createAmfSchema(input, schema, bindings);
    }
    _createAmfSchema(input, schema, bindings) {
        const { multiple, type } = input;
        if (multiple) {
            return this._generateArrayShape(input, schema, bindings);
        }
        if (type === 'binary') {
            return this._generateFileShape(input, schema, bindings);
        }
        return this._generateScalarShape(input, schema, bindings);
    }
    /**
     * Normally this would be part of generating a scalar schema but the the property is an array this
     * is generated on the array and not on the range.
     *
     * @param result The scalar or array shape.
     * @param schema The property schema
     * @param type The data type of the parent property as set on the `range`
     * @param isArray Whether the DataProperty is multiple
     */
    _setShapeSchema(input, result, schema, type, isArray) {
        if (schema.defaultValue) {
            const { type: dfFormat } = schema.defaultValue;
            const { value } = schema.defaultValue;
            if (dfFormat === 'function') {
                const tmp = this.valueGenerator.generate(value, input.type);
                if (tmp !== DataValueGenerator.noValue) {
                    const dt = AmfDataNode.scalar(tmp, dfFormat);
                    result.defaultValue = dt.toJSON();
                }
            }
            else {
                const dt = AmfDataNode.scalar(value, type);
                result.defaultValue = dt.toJSON();
            }
        }
        if (Array.isArray(schema.enum)) {
            result.values = schema.enum.map((i) => AmfDataNode.scalar(i, type).toJSON());
        }
        if (Array.isArray(schema.examples)) {
            if (isArray) {
                result.examples = this._generateArrayExamples(schema.examples, type);
            }
            else {
                result.examples = this._generateExamples(schema.examples, type);
            }
        }
    }
    _generateArrayShape(input, schema, bindings) {
        const result = arrayShape(input.key);
        const { type } = input;
        if (type === 'binary') {
            // we do not pass schema to the range generator as we set schema's properties on the array shape.
            result.items = this._generateFileShape(input, undefined, bindings);
        }
        else {
            result.items = this._generateScalarShape(input, undefined, bindings);
        }
        if (schema) {
            const type = result.items.dataType;
            this._setShapeSchema(input, result, schema, type, input.multiple);
        }
        return result;
    }
    _generateScalarShape(input, schema, bindings) {
        const result = scalarShape(input.key);
        this._updateBaseProperties(input, result);
        this._fillScalarShapeCommonProperties(result, input, bindings);
        if (!result.dataType) {
            result.dataType = modelTypeToAmfDataType(input.type, bindings);
        }
        if (schema) {
            this._setShapeSchema(input, result, schema, result.dataType, input.multiple);
        }
        return result;
    }
    _generateExamples(examples, type) {
        return examples.map((current) => {
            const value = AmfDataNode.scalar(current, type).toJSON();
            const item = {
                id: nanoid(),
                customDomainProperties: [],
                strict: true,
                types: IAmfExampleTypes,
                structuredValue: value,
            };
            return item;
        });
    }
    _generateArrayExamples(examples, type) {
        const item = {
            id: nanoid(),
            customDomainProperties: [],
            strict: true,
            types: IAmfExampleTypes,
        };
        const value = new AmfDataNode('array');
        examples.forEach((item) => {
            const member = AmfDataNode.scalar(item, type);
            value.addMember(member);
        });
        item.structuredValue = value.toJSON();
        return [item];
    }
    _generateFileShape(input, schema, bindings) {
        // if (bindings && bindings.format === 'base64') {
        //   // this is a binary format of a string shape
        // }
        const result = fileShape(input.key);
        this._updateBaseProperties(input, result);
        if (bindings) {
            if (Array.isArray(bindings.fileTypes)) {
                result.fileTypes = bindings.fileTypes;
            }
            this._fillScalarShapeCommonProperties(result, input, bindings);
            if (bindings.format === 'base64') {
                result.format = AmfNamespace.w3.xmlSchema.base64Binary;
            }
        }
        return result;
    }
    _fillScalarShapeCommonProperties(result, input, bindings) {
        if (bindings?.name) {
            result.name = bindings.name;
        }
        if (bindings?.xml) {
            result.xmlSerialization = bindings.xml;
        }
        if (bindings?.pattern) {
            result.pattern = bindings.pattern;
        }
        const { schema, type } = input;
        if (schema) {
            if (typeof schema.multipleOf === 'number') {
                result.multipleOf = schema.multipleOf;
            }
            if (typeof schema.minimum === 'number') {
                if (type === 'string') {
                    result.minLength = schema.minimum;
                }
                else {
                    result.minimum = schema.minimum;
                }
            }
            if (typeof schema.maximum === 'number') {
                if (type === 'string') {
                    result.maxLength = schema.maximum;
                }
                else {
                    result.maximum = schema.maximum;
                }
            }
            if (typeof schema.exclusiveMinimum === 'boolean') {
                result.exclusiveMinimum = schema.exclusiveMinimum;
            }
            if (typeof schema.exclusiveMaximum === 'boolean') {
                result.exclusiveMaximum = schema.exclusiveMaximum;
            }
        }
        if (bindings?.format) {
            switch (bindings.format) {
                case 'base64':
                    result.format = AmfNamespace.w3.xmlSchema.base64Binary;
                    break;
                case 'double':
                    result.format = AmfNamespace.w3.xmlSchema.double;
                    break;
                case 'float':
                    result.format = AmfNamespace.w3.xmlSchema.float;
                    break;
                case 'int32':
                    result.format = AmfNamespace.w3.xmlSchema.integer;
                    break;
                case 'int64':
                    result.format = AmfNamespace.w3.xmlSchema.integer;
                    break;
            }
            // result.format = bindings.format
        }
        if (typeof input.readOnly === 'boolean') {
            result.readOnly = input.readOnly;
        }
        if (typeof input.writeOnly === 'boolean') {
            result.writeOnly = input.writeOnly;
        }
    }
    _updateBaseProperties(input, target) {
        // const bindings = input.readBinding('web') as PropertyWebBindings | undefined
        target.name = input.info.name;
        target.displayName = input.info.displayName;
        target.description = input.info.description;
        if (input.kind === DataPropertyKind) {
            target.deprecated = input.deprecated;
        }
        else if (input.kind === DataEntityKind) {
            target.deprecated = input.deprecated;
        }
    }
    _recursiveShape(input) {
        return recursiveShape(input.key, input.key);
    }
    /**
     * Translates generated schema from an array shape to the shape defined in the `items` of the array.
     *
     * @param array The source array.
     * @returns The definition of the `items` of the array.
     */
    refactorArrayToShape(array) {
        const { items } = array;
        return items;
    }
    /**
     * Translates the shape to an array shape. This happens when data model property
     * is changed from 'multiple' to not-multiple and back.
     *
     * @param id The key of the parameter or an association
     * @param shape The shape to wrap as an array.
     * @returns Array shape.
     */
    refactorShapeToArray(id, shape) {
        const result = arrayShape(id);
        result.items = shape;
        return result;
    }
}
//# sourceMappingURL=AmfShapeGenerator.js.map