import { deepClone, pascalCase } from '@takeshape/util';
import forEach from 'lodash/forEach.js';
import pick from 'lodash/pick.js';
import { arraySchemaKeys, getShapeQueriesAndMutations, multipleRelationshipSchemaKeys, objectSchemaKeys, scalarSchemaKeys, schemaMetadataKeys } from "../../schema-util.js";
import { formatDate } from "../utils.js";
const annotationMap = {
    l10n: '@l10n',
    sensitive: '@sensitive',
    syncLocaleStructure: '@syncLocaleStructure'
};
function isColor(schema) {
    const { type, properties } = schema;
    return Boolean(type === 'object' && properties?.hsl && properties.hsv && properties.rgb && properties.hex);
}
function getModelType(contentType) {
    if (contentType.single) {
        return 'single';
    }
    if (contentType.taxonomy) {
        return 'taxonomy';
    }
    return 'multiple';
}
function getRelationshipSchema(relationship) {
    const itemSchema = {
        $ref: '#/shapes/TSRelationship/schema'
    };
    if (relationship.type === 'multiple') {
        return {
            type: 'array',
            items: itemSchema,
            '@relationship': relationship
        };
    }
    return {
        ...itemSchema,
        '@relationship': relationship
    };
}
function migrateToContentSchemaV3(params) {
    const { schema, service, name, key, setSource, omitAllSources } = params;
    const { type } = schema;
    const mapping = !omitAllSources && key && setSource !== false && name !== key ? `${service}.${key}` : undefined;
    forEach(schema, (value, key) => {
        if (annotationMap[key]) {
            const newAnnotation = annotationMap[key];
            schema[newAnnotation] = value;
            delete schema[key];
        }
    });
    if (type === 'string' || type === 'boolean' || type === 'number' || type === 'integer') {
        return {
            schema: { ...pick(schema, scalarSchemaKeys), '@mapping': mapping }
        };
    }
    const { relationship } = schema;
    if (relationship) {
        const { contentTypeIds, type: relationshipType, relatedName } = relationship;
        return {
            schema: {
                ...pick(schema, relationshipType === 'multiple' ? multipleRelationshipSchemaKeys : schemaMetadataKeys),
                '@mapping': mapping,
                ...getRelationshipSchema({
                    shapeIds: contentTypeIds,
                    type: relationshipType ?? 'single', // Type is optional on assets
                    relatedName
                })
            }
        };
    }
    if (type === 'object' && schema.draftjs) {
        return {
            schema: {
                ...pick(schema, scalarSchemaKeys),
                '@mapping': mapping,
                '@tag': 'draftjs'
            }
        };
    }
    if (isColor(schema)) {
        return {
            schema: {
                ...pick(schema, scalarSchemaKeys),
                $ref: '#/shapes/TSColor/schema',
                '@mapping': mapping
            }
        };
    }
    if (type === 'object' && schema.properties) {
        const properties = {};
        const shapes = {};
        const shapeTitle = [params.shapeTitle, pascalCase(schema.title ?? params.name)].filter(Boolean).join(' ');
        const shapeName = `${params.shapeName}${pascalCase(params.name)}`;
        const contentSourceId = `takeshape:local:${shapeName}`;
        forEach(schema.properties, (propSchema, name) => {
            const { schema: migratedPropSchema, hoistedShapes } = migrateToContentSchemaV3({
                ...params,
                shapeName,
                shapeTitle,
                depth: params.depth + 1,
                schema: propSchema,
                parentSchema: schema,
                service: contentSourceId,
                key: propSchema.key ?? name,
                name,
                setSource: true
            });
            properties[name] = migratedPropSchema;
            Object.assign(shapes, hoistedShapes);
        });
        if (params.depth > 0) {
            const { title, description, ...rest } = schema;
            return {
                schema: {
                    title,
                    description,
                    '@mapping': mapping,
                    $ref: `#/shapes/${shapeName}/schema`
                },
                hoistedShapes: {
                    ...shapes,
                    [shapeName]: {
                        id: name === key ? shapeName : key,
                        name: shapeName,
                        title: shapeTitle,
                        schema: {
                            ...pick(rest, objectSchemaKeys),
                            type: 'object',
                            properties
                        }
                    }
                }
            };
        }
        return {
            schema: {
                ...pick(schema, objectSchemaKeys),
                properties,
                '@mapping': mapping
            },
            hoistedShapes: shapes
        };
    }
    if (schema.type === 'array' && schema.items) {
        const { schema: itemSchema, hoistedShapes } = migrateToContentSchemaV3({
            ...params,
            schema: schema.items,
            parentSchema: schema,
            service: 'parent',
            setSource: false
        });
        return {
            schema: {
                ...pick(schema, arraySchemaKeys),
                items: itemSchema,
                '@mapping': mapping
            },
            hoistedShapes
        };
    }
    throw new Error(`Unknown schema type "${type}"`);
}
function contentTypeToShape(contentType, contentTypeId) {
    const title = contentType.title ?? contentType.name;
    const name = pascalCase(contentType.name);
    const contentSourceId = `takeshape:local:${name}`;
    const { schema, hoistedShapes } = migrateToContentSchemaV3({
        shapeName: name,
        shapeTitle: title,
        schema: contentType.schema,
        service: contentSourceId,
        name: '',
        key: '',
        depth: 0
    });
    return {
        [name]: {
            id: contentTypeId,
            name,
            title,
            model: {
                type: getModelType(contentType)
            },
            workflow: contentType.workflow,
            schema: schema
        },
        ...hoistedShapes
    };
}
const migrate = async (_, projectSchema) => {
    const { contentTypes, created, updated, ...rest } = projectSchema;
    const shapes = {};
    const forms = {};
    for (const contentTypeId of Object.keys(contentTypes)) {
        const contentType = contentTypes[contentTypeId];
        Object.assign(shapes, contentTypeToShape(contentType, contentTypeId));
        forms[pascalCase(contentType.name)] = contentType.forms;
    }
    // Use deepClone to drop undefined values, this improves compatibility with validation and testing
    // Schemas are typically saved in Dynamo or JSON files which also remove undefined values
    // Dropping them here is done for consistency's sake
    return deepClone({
        ...rest,
        ...getShapeQueriesAndMutations(Object.values(shapes)),
        schemaVersion: '3',
        created: formatDate(created),
        updated: formatDate(updated),
        shapes,
        forms
    });
};
export default migrate;
