import type { Field, FieldList, FieldListItems, FieldType, FieldPath } from './model-fields';

export type UpdateOperation =
    | UpdateOperationSet
    | UpdateOperationUnset
    | UpdateOperationInsert
    | UpdateOperationRemove
    | UpdateOperationReorder;

export interface UpdateOperationBase {
    opType: string;
    fieldPath: FieldPath;
    locale?: string;
}

/**
 * The "set" operation is used to set (or replace) field values.
 *
 * The "modelField" contains the model field of the field being set.
 * The "field" contains the new field value in the form of {@link UpdateOperationField}.
 * The "fieldPath" contains the path to the field from the document root.
 *
 * When setting lists, the "fieldPath" is set to the path of the list field,
 * the "modelField" is set to the model field describing the list
 * (field.type === 'list'), and the "field" contain the new list in the form of
 * {@link UpdateOperationListField}.
 *
 * ```
 * {
 *   opType: "set",
 *   fieldPath: ["seo", "keywords"],
 *   modelField: {
 *     type: "list",
 *     name: "keywords",
 *     items: { type: "string" }
 *   },
 *   field: {
 *     type: "list",
 *     items: [
 *       { type: "string", value: "hello" },
 *       { type: "string", value: "world" }
 *     ]
 *   }
 * }
 * ```
 *
 * When replacing a list item, the "fieldPath" is set to the path of the list
 * followed by the item index that need to be replaced, the "modelField" is the
 * model of the list item (field.items.type), and the "field" contains the new
 * item to be inserted in the form of {@link UpdateOperationField}.
 *
 * ```
 * {
 *   opType: "set",
 *   fieldPath: ["seo", "keywords", 0],
 *   modelField: { type: "string" },
 *   field: { type: "string", value: "hi" }
 * }
 * ```
 *
 */
export interface UpdateOperationSet extends UpdateOperationBase {
    opType: 'set';
    modelField: Field | FieldListItems;
    field: UpdateOperationField;
}

export interface UpdateOperationUnset extends UpdateOperationBase {
    opType: 'unset';
    modelField: Field | FieldListItems;
}

export interface UpdateOperationInsert extends UpdateOperationBase {
    opType: 'insert';
    modelField: FieldList;
    index?: number;
    item: UpdateOperationListFieldItem;
}

export interface UpdateOperationRemove extends UpdateOperationBase {
    opType: 'remove';
    modelField: FieldList;
    index: number;
}

export interface UpdateOperationReorder extends UpdateOperationBase {
    opType: 'reorder';
    modelField: FieldList;
    order: number[];
}

export type UpdateOperationField =
    | UpdateOperationValueField
    | UpdateOperationObjectField
    | UpdateOperationModelField
    | UpdateOperationReferenceField
    | UpdateOperationCrossReferenceField
    | UpdateOperationListField;

export type UpdateOperationValueFieldType = Exclude<
    FieldType,
    'object' | 'model' | 'reference' | 'cross-reference' | 'list'
>;

export type UpdateOperationValueField = DistributeUpdateOperationValueField<UpdateOperationValueFieldType>;

export type DistributeUpdateOperationValueField<Type extends UpdateOperationValueFieldType> =
    Type extends UpdateOperationValueFieldType ? UpdateOperationValueFieldForType<Type> : never;

export type UpdateOperationValueFieldForType<Type extends UpdateOperationValueFieldType> = {
    type: Type;
    value: any;
};

export type UpdateOperationObjectField = {
    type: 'object';
    fields: Record<string, UpdateOperationField>;
};

export type UpdateOperationModelField = {
    type: 'model';
    modelName: string;
    fields: Record<string, UpdateOperationField>;
};

export type UpdateOperationReferenceField = {
    type: 'reference';
    refType: 'document' | 'asset';
    refId: string;
};

export type UpdateOperationCrossReferenceField = {
    type: 'cross-reference';
    value: {
        refSrcType: string;
        refProjectId: string;
        refId: string;
    };
};

export type UpdateOperationListField = {
    type: 'list';
    items: UpdateOperationListFieldItem[];
};

export type UpdateOperationListFieldItem = Exclude<UpdateOperationField, UpdateOperationListField>;
