import type { ConfigDelegate } from './config-delegate';
import type { DocumentWithSource } from './content-source-document';
import type { DocumentField, DocumentObjectField, DocumentModelField } from './content-source-document-fields';
import type { ModelWithSource, ObjectModel } from './models';
import type { ClientField, FieldPath } from './model-fields';
import type { User } from './content-source';
import type { DocumentHookBaseOptions } from './config';
import type {
    Field,
    FieldBoolean,
    FieldColor,
    FieldDate,
    FieldDatetime,
    FieldEnum,
    FieldHtml,
    FieldMarkdown,
    FieldModel,
    FieldNumber,
    FieldObject,
    FieldReference,
    FieldSlug,
    FieldString,
    FieldText,
    FieldUrl
} from './model-fields';
import { ICONS } from './consts';
import { ContentSourceActions } from './config';
import { DistributiveOmit } from './utility-types';

export interface CustomActionCommonParams {
    name: string;
    label?: string;
    icon?: (typeof ICONS)[number];
    hidden?: boolean;
    preferredStyle?: 'button-primary' | 'button-secondary';
    inputFields?: CustomActionInputField[];
}

export interface CustomActionGlobal extends CustomActionCommonParams {
    type: 'global';
    state?: CustomActionStateFunction;
    run: CustomActionRunFunction;
    permissions?: CustomActionPermissionsFunction;
}

export interface CustomActionBulk extends CustomActionCommonParams {
    type: 'bulk';
    state?: CustomActionStateFunction;
    run: CustomActionRunFunction<{ documents: DocumentWithSource[] }>;
    permissions?: CustomActionPermissionsFunction;
}

export interface CustomActionModel extends CustomActionCommonParams {
    type: 'model';
    run: CustomActionRunFunction<CustomActionModelRunOptions>;
    permissions?: CustomActionPermissionsFunction;
    /**
     * Lists model names for which the action is shown in the visual editor.
     * Applicable only for actions defined in the stackbit config's root action array.
     */
    models?: string[];
    locations?: ('new-card' | 'preset-card')[];
}

export interface CustomActionModelRunOptions {
    /** The selected model for the action. */
    actionModel: ModelWithSource;
    /** The {@link ContentSourceActions} for the content source of the given `actionModel` */
    contentSourceActions: ContentSourceActions;
    /**
     * The location in UI where the action was triggered.
     * The 'preset-card' will be set when the user triggers the action from a
     * preset. In this case the `inputData.presetData` will contain the data of
     * the selected preset.
     * The 'new-card' will be set when the user triggers the action not from a preset.
     */
    location: 'new-card' | 'preset-card';
}

export interface CustomActionDocument extends CustomActionCommonParams {
    type?: 'document';
    state?: CustomActionStateFunction<CustomActionDocumentStateOptions>;
    run: CustomActionRunFunction<CustomActionDocumentRunOptions>;
    permissions?: CustomActionPermissionsFunction<CustomActionDocumentStateOptions>;
}

export interface CustomActionDocumentStateOptions {
    document: DocumentWithSource;
    model: ModelWithSource;
}

export interface CustomActionDocumentRunOptions extends CustomActionDocumentStateOptions {
    contentSourceActions: ContentSourceActions;
}

/**
 * Custom action that runs in the context of an "object" model, within an existing document.
 * This action can be used to create new nested objects within an existing document.
 */
export interface CustomActionModelObject extends CustomActionCommonParams {
    type: 'model';
    run: CustomActionRunFunction<CustomActionModelObjectRunOptions>;
    permissions?: CustomActionPermissionsFunction;
    locations?: ('new-card' | 'preset-card')[];
}

export interface CustomActionModelObjectRunOptions {
    parentDocument: DocumentWithSource;
    parentModel: ModelWithSource;
    modelField: FieldModel;
    fieldPath: FieldPath;
    objectModel: ModelWithSource<ObjectModel>;
    location: 'new-card' | 'preset-card';
    contentSourceActions: ContentSourceActions;
}

/**
 * Custom action that runs in the context of a nested object represented by a model of type "object".
 * This action can be used to update an existing nested object within an existing document.
 */
export interface CustomActionObjectModel extends CustomActionCommonParams {
    type?: 'object';
    state?: CustomActionStateFunction<CustomActionObjectModelStateParams>;
    run: CustomActionRunFunction<CustomActionObjectModelRunParams>;
    permissions?: CustomActionPermissionsFunction<CustomActionObjectModelStateParams>;
}

export interface CustomActionObjectModelStateParams {
    parentDocument: DocumentWithSource;
    parentModel: ModelWithSource;
    documentField: DocumentModelField;
    modelField: FieldModel;
    fieldPath: FieldPath;
    objectModel: ModelWithSource<ObjectModel>;
}

export interface CustomActionObjectModelRunParams extends CustomActionObjectModelStateParams {
    contentSourceActions: ContentSourceActions;
}

export interface CustomActionObjectField extends CustomActionCommonParams {
    type: 'object';
    state?: CustomActionStateFunction<CustomActionObjectFieldStateParams>;
    run: CustomActionRunFunction<CustomActionObjectFieldRunParams>;
    permissions?: CustomActionPermissionsFunction<CustomActionObjectFieldStateParams>;
}

export interface CustomActionObjectFieldStateParams {
    parentDocument: DocumentWithSource;
    parentModel: ModelWithSource;
    documentField: DocumentObjectField;
    modelField: FieldObject;
    fieldPath: FieldPath;
}

export interface CustomActionObjectFieldRunParams extends CustomActionObjectFieldStateParams {
    contentSourceActions: ContentSourceActions;
}

export interface CustomActionField extends CustomActionCommonParams {
    type?: 'field';
    state?: CustomActionStateFunction<CustomActionFieldStateParams>;
    run: CustomActionRunFunction<CustomActionFieldRunParams>;
    permissions?: CustomActionPermissionsFunction<CustomActionFieldStateParams>;
}

export interface CustomActionFieldStateParams {
    parentDocument: DocumentWithSource;
    parentModel: ModelWithSource;
    documentField?: DocumentField;
    modelField: Field;
    fieldPath: FieldPath;
}

export interface CustomActionFieldRunParams extends CustomActionFieldStateParams {
    contentSourceActions: ContentSourceActions;
}

export type CustomActionState = 'enabled' | 'running' | 'disabled';

export type CustomActionStateFunction<T = unknown> = (
    options: CustomActionStateCommonOptions & T
) => Promise<CustomActionState>;

export interface CustomActionStateCommonOptions extends ConfigDelegate {
    actionId: string;
    currentLocale?: string;
    currentUser?: User;
    currentPageUrl?: string;
    currentPageDocument?: DocumentWithSource;
}

/**
 * The `run` function runs the action that was triggered in the visual editor.
 * This function should return a promise.
 *
 * If this function resolves with `undefined` value, the action is considered to
 * have completed successfully. This function may also resolve with the
 * {@link CustomActionResult} object to show "success" or "fail" messages in the
 * visual editor.
 */
export type CustomActionRunFunction<T = unknown> = (
    options: CustomActionRunCommonOptions & T
) => Promise<CustomActionResult | void>;

export interface CustomActionRunCommonOptions
    extends ConfigDelegate,
        Pick<DocumentHookBaseOptions, 'getContentSourceActionsForSource' | 'getUserContextForContentSourceType'> {
    actionId: string;
    inputData?: Record<string, any>;
    progress: CustomActionProgressFunction;
    currentLocale?: string;
    currentUser?: User;
    currentPageUrl?: string;
    currentPageDocument?: DocumentWithSource;
}

export type CustomActionProgressFunction = (options: { message?: string; percent?: number }) => void;

export interface CustomActionResult {
    /** @deprecated */
    state?: CustomActionState;
    /**
     * If the result object contains the `success` property, the action is
     * considered to have completed successfully, and the value of the `success`
     * property will be shown in the visual editor as a success message.
     */
    success?: string;
    /**
     * If the result object contains the `error` property, the action is
     * considered to have failed, and the value of the `error` property will be
     * shown in the visual editor as an error message.
     */
    error?: string;
    /**
     * When the action is triggered programmatically from within a custom field,
     * it may return a `result` value. This value will be returned from the
     * {@link CustomControlWindow.stackbit.runAction} function called from the
     * custom field.
     *
     * @see FieldCustomControlTypeProps
     * @see CustomControlWindow
     */
    result?: any;
}

export type CustomActionPermissionsFunction<T = unknown> = (
    options: CustomActionPermissionsCommonOptions & T
) => CustomActionPermissions | undefined;

export type CustomActionPermissions = {
    canExecute?: boolean;
};

export interface CustomActionPermissionsCommonOptions extends ConfigDelegate {
    actionId: string;
    currentLocale?: string;
    userContext: User;
    currentPageUrl?: string;
    currentPageDocument?: DocumentWithSource;
}

export type CustomActionClientModel<
    T extends
        | CustomActionModel
        | CustomActionDocument
        | CustomActionModelObject
        | CustomActionObjectModel
        | CustomActionField
> = Omit<T, 'permissions' | 'run' | 'state' | 'inputFields'> & {
    inputFields?: CustomActionInputClientField[];
};

export type CustomActionInputField = DistributiveOmit<
    | FieldString
    | FieldUrl
    | FieldSlug
    | FieldText
    | FieldMarkdown
    | FieldHtml
    | FieldNumber
    | FieldBoolean
    | FieldDate
    | FieldDatetime
    | FieldColor
    | FieldEnum
    | FieldReference,
    'actions'
>;

export type CustomActionInputClientField = ClientField<CustomActionInputField>;
