import {DataSyncOptions} from '../../../../js/core/src/aiContext/options/dataSyncOptions';
import {ContextAdapter} from '../adapters/context/contextAdapter';
import {ContextAdapterBuilder} from '../adapters/context/contextAdapterBuilder';
import {CallbackArgType, CallbackFunction} from '../callbackFunction';
import {ContextItemHandler, ContextTaskHandler} from './contextObservers';
import {
    ContextActionResult,
    DestroyContextResult,
    FlushContextResult,
    InitializeContextResult,
    RunTaskResult,
} from './contextResults';
import {ContextItemDataType, ContextItems} from './data';

/**
 * The current status of the context instance.
 * The default status is 'idle' and it changes to 'initializing' when the context is being initialized.
 * A context that is ready to be used has the status 'syncing' (i.e. the being synchronized with the backend).
 * If the context fails to initialize or synchronize, the status changes to 'error'.
 * The final status of a context is 'destroyed' after it has been destroyed.
 */
export type AiContextStatus = 'idle' | 'initializing' | 'syncing' | 'error' | 'destroyed';

/**
 * The status of a specific item in the context.
 * This is used to ensure that no race conditions occur when updating the context.
 */
export type AiContextItemStatus = 'set' | 'updating' | 'deleted';

/**
 * The AiContext is responsible for managing data and tasks in the context of a user session.
 * This data and tasks can be used to provide a more personalized and relevant experience to the AI chatbot user.
 */
export interface AiContext {
    /**
     * The unique identifier of the context instance.
     * This identifier is generated by the backend and assigned to the context instance when it is initialized.
     * This is only set once the context has been initialized. It will be null if the context has not been initialized.
     * When the context is destroyed, the context id will be set to null.
     *
     * @returns {string | null}
     */
    get contextId(): string | null;

    /**
     * Destroys the context instance and clears all data and tasks associated with it.
     * The context instance cannot be used after it has been destroyed.
     * If the destroy operation fails, the context instance is left in an error state.
     *
     * @returns {Promise<DestroyContextResult>}
     */
    destroy(): Promise<DestroyContextResult>;

    /**
     * Flushes the data synchronization queue.
     * This method will make an API call to the adapter to flush the data synchronization queue.
     * This can be done automatically by the data sync service (the frequency depends on the data sync strategy set),
     * but in situations where the data needs to be flushed immediately, this method can be called.
     *
     * @returns {Promise<FlushContextResult>}
     */
    flush(): Promise<FlushContextResult>;

    /**
     * Checks if the context is observing a state item with the provided itemId.
     *
     * @param {string} itemId
     * @returns {boolean}
     */
    hasItem(itemId: string): boolean;

    /**
     * Checks if the context has a task with the provided taskId that can be run.
     * Runnable tasks are tasks that have been registered with the backend and can be executed in the context.
     * When a registration fails, the task is not runnable.
     *
     * @param {string} taskId
     * @returns {boolean}
     */
    hasRunnableTask(taskId: string): boolean;

    /**
     * Checks if the context is observing a task with the provided taskId.
     *
     * @param {string} taskId
     * @returns {boolean}
     */
    hasTask(taskId: string): boolean;

    /**
     * Initializes the context instance and sets the initial data.
     * The context instance cannot be used before it has been initialized.
     * If the initialize operation fails, the context instance is left in an error state.
     * Initialization can only be done once for a context instance, when it's in the 'idle' state.
     *
     * @param {Object} data The optional initial data to set in the context instance.
     * @returns {Promise<InitializeContextResult>}
     */
    initialize(data?: ContextItems): Promise<InitializeContextResult>;

    /**
     * Observes a state item and updates the context when the state item's value changes.
     * This method returns a ContextItemHandler that can be used to update the state item's value.
     * If the context instance is destroyed or not properly initialized, the method will return undefined and will
     * log a warning. Users can use the status property to check the status of the context instance.
     *
     * The state item's description passed as a second argument is used by AI to determine how and it what context the
     * state item is used. When the user queries the AI about a specific data in the page, the description will be used
     * to determine which context state items are relevant to the query, and thus it should always be detailed and
     * accurate.
     *
     * @param {string} itemId The unique identifier of the state item to observe.
     * @param {string} description The description of the state item. The description is used to instruct the LLM on
     *      how the state item is used and its role in the context. e.g. When the state item is 'logged-in-user', the
     *      description could be 'The logged-in user in the marketplace app. It can be used to provide a more
     *      personalized experience to the user.'
     *
     * @param {ContextItemDataType} initialData The initial data to set in the state item.
     *
     * @returns {ContextItemHandler | undefined} The state item handler that can be used to update the state item's
     *     value. If the context instance is destroyed or an item with the same itemId is already being observed, the
     *     method will return undefined and a warning will be logged.
     */
    observeState(
        itemId: string,
        description: string,
        initialData?: ContextItemDataType,
    ): ContextItemHandler | undefined;

    /**
     * Registers a task in the context instance.
     * A task is a function that can be executed in the context of the user session.
     *
     * @param {string} taskId The unique identifier of the task. If a task with the same identifier already exists,
     *    the registerTask method will return an error.
     * @param {string} description The description of the task. The description is used to instruct the LLM on the
     *  usefulness and role of the task. e.g. When the task is 'get-user-age', the description could be 'Get the age
     *  of the logged-in user from the user profile.'
     * @param {Function} callback The function to execute when the task runs.
     * @param {string[]} paramDescriptions The descriptions of the parameters that the task function expects. The
     *   parameter descriptions are very important because they instruct the LLM on how to retrieve the parameters' data
     *   from the context and how to use them in the task function. The parameter descriptions should be accurate and
     *   explicit. They should describe the role of the parameters in the callback function and how they can be
     *     retrieved. e.g. When the task function expects a parameter that represents the user's preferred language,
     *     the parameter description could be 'The preferred language of the logged-in user. It can be retrieved from
     *     the user profile.'
     *
     * @returns {ContextActionResult}
     */
    registerTask(
        taskId: string,
        description: string,
        callback: CallbackFunction,
        paramDescriptions?: string[],
    ): ContextTaskHandler | undefined;

    /**
     * Resets the context instance and sets the provided data. The contextId will not change after the reset operation
     * but all the context data and tasks will be cleared.
     * If the reset operation fails, the context instance is left in an error state.
     *
     * The reset operation clears all data and tasks associated with the context instance and sets the provided data.
     * It will also unregister all observers and tasks. You will need to re-register the tasks and re-observe the
     * elements and state items after the reset operation.
     *
     * @param {Object} data The optional data to set in the context instance.
     * @returns {Promise<ContextActionResult>}
     */
    reset(data?: ContextItems): Promise<ContextActionResult>;

    /**
     * Runs a task in the context instance.
     *
     * The task runner will attempt to retrieve the parameters from the context and execute the task function.
     * If the task function succeeds, the task runner will return the result of the task function under the 'result'
     * property of the returned object. If the task function fails, the task runner will return an error under the
     * 'error' property of the returned object.
     *
     * The status of the context instance will not change after running a task, regardless of the result of the task
     * function.
     *
     * @param {string} taskId The unique identifier of the task to run.
     * @param {Array<any>} parameters The parameters to pass to the task function.
     * @returns {Promise<RunTaskResult>}
     */
    runTask(taskId: string, parameters?: Array<CallbackArgType>): Promise<RunTaskResult>;

    /**
     * Get the status of the context.
     * The status will change as the context is being initialized, destroyed, or if an error occurs.
     *
     * - The initial status is 'idle'.
     * - The status will change to 'initializing' once the context is being initialized.
     * - The status will change to 'syncing' once the context has been initialized and is being synced.
     * - The status will change to 'error' if an error occurs.
     * - The status will change to 'destroyed' once the context has been destroyed.
     *
     * @returns {AiContextStatus}
     */
    get status(): AiContextStatus;

    /**
     * Sets the adapter to use for the context instance.
     * The adapter is responsible for synchronizing the context instance with the backend.
     * This method should be called before the context instance is initialized.
     * If the adapter is not set, the initialize method will fail.
     *
     * @param {ContextAdapterBuilder | ContextAdapter} adapter
     * @returns {AiContext}
     */
    withAdapter(adapter: ContextAdapterBuilder | ContextAdapter): AiContext;

    /**
     * Sets the options to use for data synchronization.
     * The options are used to configure the behavior of the data synchronization process.
     * This method should be called before the context instance is initialized.
     *
     * @param {DataSyncOptions} options
     * @returns {AiContext}
     */
    withDataSyncOptions(options: DataSyncOptions): AiContext;
}
