import { IconProps, VariantType } from '@adminjs/design-system';
import AdminJS from '../../adminjs.js';
import { CurrentAdmin } from '../../current-admin.interface.js';
import ViewHelpers from '../utils/view-helpers/view-helpers.js';
import BaseRecord from '../adapters/record/base-record.js';
import BaseResource from '../adapters/resource/base-resource.js';
import ActionDecorator from '../decorators/action/action-decorator.js';
import { LayoutElement, LayoutElementFunction } from '../utils/layout-element-parser/index.js';
import { RecordJSON } from '../../frontend/interfaces/index.js';
import { type NoticeMessage } from '../../frontend/interfaces/noticeMessage.interface.js';
export type ActionQueryParameters = {
    sortBy?: string;
    direction?: 'asc' | 'desc';
    filters?: Record<string, unknown>;
    perPage?: number;
    page?: number;
};
export type ActionType = 'resource' | 'record' | 'bulk';
/**
 * Execution context for an action. It is passed to the {@link Action#handler},
 * {@link Action#before} and {@link Action#after} functions.
 *
 * @memberof Action
 * @alias ActionContext
 */
export type ActionContext = {
    /**
     * current instance of AdminJS. You may use it to fetch other Resources by their names:
     */
    _admin: AdminJS;
    /**
     * Resource on which action has been invoked. Null for dashboard handler.
     */
    resource: BaseResource;
    /**
     * Record on which action has been invoked (only for {@link actionType} === 'record')
     */
    record?: BaseRecord;
    /**
     * Records on which action has been invoked (only for {@link actionType} === 'bulk')
     */
    records?: Array<BaseRecord>;
    /**
     * view helpers
     */
    h: ViewHelpers;
    /**
     * Object of currently invoked function. Not present for dashboard action
     */
    action: ActionDecorator;
    /**
     * Currently logged in admin
     */
    currentAdmin?: CurrentAdmin;
    /**
     * Any custom property which you can add to context
     */
    [key: string]: any;
};
/**
 * Context object passed to a PageHandler
 *
 * @alias PageContext
 * @memberof AdminJSOptions
 */
export type PageContext = {
    /**
     * current instance of AdminJS. You may use it to fetch other Resources by their names:
     */
    _admin: AdminJS;
    /**
   * Currently logged in admin
   */
    currentAdmin?: CurrentAdmin;
    /**
   * view helpers
   */
    h: ViewHelpers;
};
/**
 * ActionRequest
 * @memberof Action
 * @alias ActionRequest
 */
export type ActionRequest = {
    /**
     * parameters passed in an URL
     */
    params: {
        /**
         * Id of current resource
         */
        resourceId: string;
        /**
         * Id of current record (in case of record action)
         */
        recordId?: string;
        /**
         * Id of selected records (in case of bulk action) divided by commas
         */
        recordIds?: string;
        /**
         * Name of an action
         */
        action: string;
        /**
         * an optional search query string (for `search` resource action)
         */
        query?: string;
        [key: string]: any;
    };
    /**
     * POST data passed to the backend
     */
    payload?: Record<string, any>;
    /**
     * Elements of query string
     */
    query?: Record<string, any>;
    /**
     * HTTP method
     */
    method: 'post' | 'get';
};
/**
 * Base response for all actions
 * @memberof Action
 * @alias ActionResponse
 */
export type ActionResponse = {
    /**
     * Notice message which should be presented to the end user after showing the action
     */
    notice?: NoticeMessage;
    /**
     * redirect path
     */
    redirectUrl?: string;
    /**
     * Any other custom parameter
     */
    [key: string]: any;
};
/**
 * @description
 * Defines the type of {@link Action#isAccessible} and {@link Action#isVisible} functions
 * @alias IsFunction
 * @memberof Action
 */
export type IsFunction = (context: ActionContext) => boolean;
/**
 * Required response of a Record action. Extends {@link ActionResponse}
 *
 * @memberof Action
 * @alias RecordActionResponse
 */
export type RecordActionResponse = ActionResponse & {
    /**
     * Record object.
     */
    record: RecordJSON;
};
/**
 * Required response of a Record action. Extends {@link ActionResponse}
 *
 * @memberof Action
 * @alias RecordActionResponse
 */
export type BulkActionResponse = ActionResponse & {
    /**
     * Array of RecordJSON objects.
     */
    records: Array<RecordJSON>;
};
/**
 * Type of a handler function. It has to return response compatible
 * with {@link ActionResponse}, {@link BulkActionResponse} or {@link RecordActionResponse}
 *
 * @alias ActionHandler
 * @async
 * @memberof Action
 * @returns {T | Promise<T>}
 */
export type ActionHandler<T> = (request: ActionRequest, response: any, context: ActionContext) => T | Promise<T>;
/**
 * Before action hook. When it is given - it is performed before the {@link ActionHandler}
 * method.
 * @alias Before
 * @returns {ActionRequest | Promise<ActionRequest>}
 * @memberof Action
 * @async
 */
export type Before = (
/**
 * Request object
 */
request: ActionRequest, 
/**
* Invocation context
*/
context: ActionContext) => ActionRequest | Promise<ActionRequest>;
/**
 * Type of an after hook action.
 *
 * @memberof Action
 * @alias After
 * @async
 */
export type After<T> = (
/**
 * Response returned by the default ActionHandler
 */
response: T, 
/**
 * Original request which has been sent to ActionHandler
 */
request: ActionRequest, 
/**
 * Invocation context
 */
context: ActionContext) => T | Promise<T>;
export type BuildInActions = 'show' | 'edit' | 'list' | 'delete' | 'bulkDelete' | 'new' | 'search';
/**
 * @classdesc
 * Interface representing an Action in AdminJS.
 * Look at {@tutorial actions} to see where you can use this interface.
 *
 * #### Example Action
 *
 * ```
 * const action = {
 *   actionType: 'record',
 *   icon: 'View',
 *   isVisible: true,
 *   handler: async () => {...},
 *   component: 'MyAction',
 * }
 * ```
 *
 * There are 3 kinds of actions:
 *
 * 1. Resource action, which is performed for an entire resource.
 * 2. Record action, invoked for an record in a resource
 * 3. Bulk action, invoked for an set of records in a resource
 *
 * ...and there are 7 actions predefined in AdminJS
 *
 * 1. {@link module:NewAction new} (resource action) - create new records in a resource
 * 2. {@link module:ListAction list} (resource action) - list all records within a resource
 * 3. {@link module:SearchAction search} (resource action) - search by query string
 * 4. {@link module:EditAction edit} (record action) - update records in a resource
 * 5. {@link module:ShowAction show} (record action) - show details of given record
 * 6. {@link module:DeleteAction delete} (record action) - delete given record
 * 7. {@link module:BulkDeleteAction bulkDelete} (bulk action) - delete given records
 *
 * Users can also create their own actions or override those already existing by using
 * {@link ResourceOptions}
 *
 * ```javascript
 * const AdminJSOptions = {
 *   resources: [{
 *     resource: User,
 *     options: {
 *       actions: {
 *         // example of overriding existing 'new' action for
 *         // User resource.
 *         new: {
 *           icon: 'Add'
 *         },
 *         // Example of creating a new 'myNewAction' which will be
 *         // a resource action available for User model
 *         myNewAction: {
 *           actionType: 'resource',
 *           handler: async (request, response, context) => {...}
 *         }
 *       }
 *     }
 *   }]
 * }
 *
 * const { ACTIONS } = require('adminjs')
 * // example of adding after filter for 'show' action for all resources
 * ACTIONS.show.after = async () => {...}
 * ```
 */
export interface Action<T extends ActionResponse> {
    /**
     * Name of an action which is its uniq key.
     * If you use one of _list_, _search_, _edit_, _new_, _show_, _delete_ or
     * _bulkDelete_ you override existing actions.
     * For all other keys you create a new action.
     */
    name: BuildInActions | string;
    /**
     * indicates if action should be visible for given invocation context.
     * It also can be a simple boolean value.
     * `True` by default.
     * The most common example of usage is to hide resources from the UI.
     * So let say we have 2 resources __User__ and __Cars__:
     *
     * ```javascript
     * const User = mongoose.model('User', mongoose.Schema({
     *   email: String,
     *   encryptedPassword: String,
     * }))
     * const Car = mongoose.model('Car', mongoose.Schema({
     *   name: String,
     *   ownerId: { type: mongoose.Types.ObjectId, ref: 'User' },
     * })
     * ```
     *
     * so if we want to hide Users collection, but allow people to pick user when
     * creating cars. We can do this like this:
     *
     * ```javascript
     * new AdminJS({ resources: [{
     *   resource: User,
     *   options: { actions: { list: { isVisible: false } } }
     * }]})
     * ```
     * In contrast - when we use {@link Action#isAccessible} instead - user wont be able to
     * pick car owner.
     *
     * @see {@link ActionContext}   parameter passed to isAccessible
     * @see {@link IsFunction}      exact type of the function
     */
    isVisible?: boolean | IsFunction;
    /**
     * Indicates if the action can be invoked for given invocation context.
     * You can pass a boolean or function of type {@link IsFunction}, which
     * takes {@link ActionContext} as an argument.
     *
     * You can use it as a carrier between the hooks.
     *
     * Example for isVisible function which allows the user to edit cars which belongs only
     * to her:
     *
     * ```javascript
     * const canEditCars = ({ currentAdmin, record }) => {
     *   return currentAdmin && (
     *     currentAdmin.role === 'admin'
     *     || currentAdmin._id === record.param('ownerId')
     *   )
     * }
     *
     * new AdminJS({ resources: [{
     *   resource: Car,
     *   options: { actions: { edit: { isAccessible: canEditCars } } }
     * }]})
     * ```
     *
     * @see {@link ActionContext}   parameter passed to isAccessible
     * @see {@link IsFunction}      exact type of the function
     */
    isAccessible?: boolean | IsFunction;
    /**
     * If filter should be visible on the sidebar. Only for _resource_ actions
     *
     * Example of creating new resource action with filter
     *
     * ```javascript
     * new AdminJS({ resources: [{
     *   resource: Car,
     *   options: { actions: {
     *     newAction: {
     *       type: 'resource',
     *       showFilter: true,
     *     }
     *   }}
     * }]})
     * ```
     */
    showFilter?: boolean;
    /**
     * If action should have resource actions buttons displayed above action header.
     *
     * Defaults to `true`
     *
     * @new in version v5.8.1
     */
    showResourceActions?: boolean;
    /**
     * Type of an action - could be either _resource_, _record_ or _bulk_.
     *
     * <img src="./images/actions.png">
     *
     * When you define a new action - it is required.
     */
    actionType: ActionType;
    /**
     * icon name for the action. Take a look {@link Icon} component,
     * because what you put here is passed down to it.
     *
     * ```javascript
     * new AdminJS({ resources: [{
     *   resource: Car,
     *   options: { actions: { edit: { icon: 'Add' } } },
     * }]})
     * ```
     */
    icon?: IconProps['icon'];
    /**
     * guard message - user will have to confirm it before executing an action.
     *
     * ```javascript
     * new AdminJS({ resources: [{
     *   resource: Car,
     *   options: { actions: {
     *     delete: {
     *       guard: 'doYouReallyWantToDoThis',
     *     }
     *   }}
     * }]})
     * ```
     *
     * What you enter there goes to a translate function,
     * so in order to define the actual message you will have to specify its
     * translation in {@link AdminJSOptions.Locale}
     */
    guard?: string;
    /**
     * Component which will be used to render the action. To pass the component
     * use {@link ComponentLoader.add} or {@link ComponentLoader.override} method.
     *
     * Action components accepts {@link ActionProps} and are rendered by the
     * {@link BaseActionComponent}
     *
     * When component is set to `false` then action doesn't have it's own view.
     * Instead after clicking button it is immediately performed. Example of
     * an action without a view is {@link module:DeleteAction}.
     */
    component?: string | false;
    /**
     * handler function which will be invoked by either:
     * - {@link ApiController#resourceAction}
     * - {@link ApiController#recordAction}
     * - or {@link ApiController#bulkAction}
     * when user visits clicks action link.
     *
     * If you are defining this action for a record it has to return:
     * - {@link ActionResponse} for resource action
     * - {@link RecordActionResponse} for record action
     * - {@link BulkActionResponse} for bulk action
     *
     * ```javascript
     * // Handler of a 'record' action
     * handler: async (request, response, context) {
     *   const user = context.record
     *   const Cars = context._admin.findResource('Car')
     *   const userCar = Car.findOne(context.record.param('carId'))
     *   return {
     *     record: user.toJSON(context.currentAdmin),
     *   }
     * }
     * ```
     *
     * Required for new actions. For modifying already defined actions
     * like new and edit we suggest using {@link Action#before} and {@link Action#after} hooks.
     */
    handler: ActionHandler<T> | Array<ActionHandler<T>> | null;
    /**
     * Before action hook. When it is given - it is performed before the {@link Action#handler}
     * method.
     *
     * Example of hashing password before creating it:
     *
     * ```javascript
     * actions: {
     *   new: {
     *     before: async (request) => {
     *       if(request.payload.password) {
     *         request.payload = {
     *           ...request.payload,
     *           encryptedPassword: await bcrypt.hash(request.payload.password, 10),
     *           password: undefined,
     *         }
     *       }
     *       return request
     *     },
     *   }
     * }
     * ```
     */
    before?: Before | Array<Before>;
    /**
     * After action hook. When it is given - it is performed on the returned,
     * by {@link Action#handler handler} function response.
     *
     * You can use it to (just an idea)
     * - create log of changes done in the app
     * - prefetch additional data after original {@link Handler} is being performed
     *
     * Creating a changelog example:
     *
     * ```javascript
     * // example mongoose model
     * const ChangeLog = mongoose.model('ChangeLog', mongoose.Schema({
     *   // what action
     *   action: { type: String },
     *   // who
     *   userId: { type: mongoose.Types.ObjectId, ref: 'User' },
     *   // on which resource
     *   resource: { type: String },
     *   // was record involved (resource and recordId creates to polymorphic relation)
     *   recordId: { type: mongoose.Types.ObjectId },
     * }, { timestamps: true }))
     *
     * // actual after function
     * const createLog = async (originalResponse, request, context) => {
     *   // checking if object doesn't have any errors or is a delete action
     *   if ((request.method === 'post'
     *        && originalResponse.record
     *        && !Object.keys(originalResponse.record.errors).length)
     *        || context.action.name === 'delete') {
     *     await ChangeLog.create({
     *       action: context.action.name,
     *       // assuming in the session we store _id of the current admin
     *       userId: context.currentAdmin && context.currentAdmin._id,
     *       resource: context.resource.id(),
     *       recordId: context.record && context.record.id(),
     *     })
     *   }
     *   return originalResponse
     * }
     *
     * // and attaching this function to actions for all resources
     * const { ACTIONS } = require('adminjs')
     *
     * ACTIONS.edit.after = [createLog]
     * ACTIONS.delete.after = [createLog]
     * ACTIONS.new.after = [createLog]
     * ```
     *
     */
    after?: After<T> | Array<After<T>>;
    /**
     * Indicates if given action should be seen in a drawer or in a full screen. Default to false
     */
    showInDrawer?: boolean;
    /**
     * Indicates if Action Header should be hidden.
     * Action header consist of:
     * - breadcrumbs
     * - action buttons
     * - action title
     */
    hideActionHeader?: boolean;
    /**
     * The max width of action HTML container.
     * You can put here an actual size in px or an array of widths, where different values
     * will be responsible for different breakpoints.
     * It is directly passed to action's wrapping {@link Box} component, to its `width` property.
     *
     * Examples
     * ```javascript
     *
     * // passing regular string
     * containerWidth: '800px'
     *
     * // passing number for 100% width
     * containerWidth: 1
     *
     * // passing values for different {@link breakpoints}
     * containerWidth: [1, 1/2, 1/3]
     * ```
     */
    containerWidth?: string | number | Array<string | number>;
    /**
     * Definition for the layout. Works with the edit and show actions.
     *
     * With the help of {@link LayoutElement} you can put all the properties to whatever
     * layout you like, without knowing React.
     *
     * This is an example of defining a layout
     *
     * ```
     * const layout = [{ width: 1 / 2 }, [
     *     ['@H3', { children: 'Company data' }],
     *     'companyName',
     *     'companySize',
     *   ]],
     *   [
     *     ['@H3', { children: 'Contact Info' }],
     *     [{ flexDirection: 'row', flex: true }, [
     *       ['email', { pr: 'default', flexGrow: 1 }],
     *       ['address', { flexGrow: 1 }],
     *     ]],
     *   ],
     * ]
     * ```
     *
     * Alternatively you can pass a {@link LayoutElementFunction function} taking
     * {@link CurrentAdmin} as an argument. This will allow you to show/hide
     * given property for restricted users.
     *
     * To see entire documentation and more examples visit {@link LayoutElement}
     *
     * @see LayoutElement
     * @see LayoutElementFunction
     */
    layout?: LayoutElementFunction | Array<LayoutElement>;
    /**
     * Defines the variant of the action. based on that it will receive given color.
     * @new in version v3.3
     */
    variant?: VariantType;
    /**
     * Action can be nested. If you give here another action name - it will be nested under it.
     * If parent action doesn't exists - it will be nested under name in the parent.
     * @new in version v3.3
     */
    parent?: string;
    /**
     * Any custom properties you want to pass down to {@link ActionJSON}. They have to
     * be stringified.
     * @new in version v3.3
     */
    custom?: Record<string, any>;
}
