import { ArrayValue, BooleanValue, NumberValue, RecordValue, StringValue, } from '@coveo/bueno';
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchAnswer } from '../../api/knowledge/stream-answer-api.js';
import { nonEmptyStringArray, requiredNonEmptyString, validatePayload, } from '../../utils/validate-payload.js';
import { logGeneratedAnswerResponseLinked, logGeneratedAnswerStreamEnd, } from './generated-answer-analytics-actions.js';
import { buildStreamingRequest, constructAnswerAPIQueryParams, } from './generated-answer-request.js';
import { GENERATION_STEP_NAMES, } from './generated-answer-state.js';
import { generatedContentFormat, } from './generated-response-format.js';
const stringValue = new StringValue({ required: true });
const optionalStringValue = new StringValue();
const booleanValue = new BooleanValue({ required: true });
export const citationSchema = {
    id: stringValue,
    title: stringValue,
    uri: stringValue,
    permanentid: stringValue,
    clickUri: optionalStringValue,
};
export const answerContentFormatSchema = new StringValue({
    required: true,
    constrainTo: generatedContentFormat,
});
export const setIsVisible = createAction('generatedAnswer/setIsVisible', (payload) => validatePayload(payload, booleanValue));
export const setAnswerId = createAction('generatedAnswer/setAnswerId', (payload) => validatePayload(payload, requiredNonEmptyString));
export const setAnswerGenerationMode = createAction('generatedAnswer/setAnswerGenerationMode', (payload) => validatePayload(payload, new StringValue({
    constrainTo: ['automatic', 'manual'],
    required: false,
    default: 'automatic',
})));
export const setIsEnabled = createAction('generatedAnswer/setIsEnabled', (payload) => validatePayload(payload, booleanValue));
export const updateMessage = createAction('generatedAnswer/updateMessage', (payload) => validatePayload(payload, {
    textDelta: stringValue,
}));
export const updateCitations = createAction('generatedAnswer/updateCitations', (payload) => validatePayload(payload, {
    citations: new ArrayValue({
        required: true,
        each: new RecordValue({
            values: citationSchema,
        }),
    }),
}));
export const updateError = createAction('generatedAnswer/updateError', (payload) => validatePayload(payload, {
    message: optionalStringValue,
    code: new NumberValue({ min: 0 }),
}));
export const resetAnswer = createAction('generatedAnswer/resetAnswer');
export const likeGeneratedAnswer = createAction('generatedAnswer/like');
export const dislikeGeneratedAnswer = createAction('generatedAnswer/dislike');
export const openGeneratedAnswerFeedbackModal = createAction('generatedAnswer/feedbackModal/open');
export const expandGeneratedAnswer = createAction('generatedAnswer/expand');
export const collapseGeneratedAnswer = createAction('generatedAnswer/collapse');
export const setId = createAction('generatedAnswer/setId', (payload) => validatePayload(payload, {
    id: new StringValue({
        required: true,
    }),
}));
export const closeGeneratedAnswerFeedbackModal = createAction('generatedAnswer/feedbackModal/close');
export const sendGeneratedAnswerFeedback = createAction('generatedAnswer/sendFeedback');
export const setIsLoading = createAction('generatedAnswer/setIsLoading', (payload) => validatePayload(payload, booleanValue));
export const setIsStreaming = createAction('generatedAnswer/setIsStreaming', (payload) => validatePayload(payload, booleanValue));
export const setAnswerContentFormat = createAction('generatedAnswer/setAnswerContentFormat', (payload) => validatePayload(payload, answerContentFormatSchema));
export const updateResponseFormat = createAction('generatedAnswer/updateResponseFormat', (payload) => validatePayload(payload, {
    contentFormat: new ArrayValue({
        each: answerContentFormatSchema,
        default: ['text/plain'],
    }),
}));
export const updateAnswerConfigurationId = createAction('knowledge/updateAnswerConfigurationId', (payload) => validatePayload(payload, stringValue));
export const registerFieldsToIncludeInCitations = createAction('generatedAnswer/registerFieldsToIncludeInCitations', (payload) => validatePayload(payload, nonEmptyStringArray));
export const setIsAnswerGenerated = createAction('generatedAnswer/setIsAnswerGenerated', (payload) => validatePayload(payload, booleanValue));
export const setCannotAnswer = createAction('generatedAnswer/setCannotAnswer', (payload) => validatePayload(payload, booleanValue));
export const setAnswerApiQueryParams = createAction('generatedAnswer/setAnswerApiQueryParams', (payload) => validatePayload(payload, new RecordValue({})));
export const startStep = createAction('generatedAnswer/startStep', (payload) => validatePayload(payload, {
    name: new StringValue({
        required: true,
        constrainTo: GENERATION_STEP_NAMES,
    }),
    startedAt: new NumberValue({ min: 0, required: true }),
}));
export const finishStep = createAction('generatedAnswer/finishStep', (payload) => validatePayload(payload, {
    name: new StringValue({
        required: true,
        constrainTo: GENERATION_STEP_NAMES,
    }),
    finishedAt: new NumberValue({ min: 0, required: true }),
}));
export const streamAnswer = createAsyncThunk('generatedAnswer/streamAnswer', async (params, config) => {
    const state = config.getState();
    const { dispatch, extra, getState } = config;
    const { search } = getState();
    const { queryExecuted } = search;
    const { setAbortControllerRef } = params;
    const request = await buildStreamingRequest(state);
    const handleStreamPayload = (payloadType, payload) => {
        switch (payloadType) {
            case 'genqa.headerMessageType': {
                const header = JSON.parse(payload);
                dispatch(setAnswerContentFormat(header.contentFormat));
                break;
            }
            case 'genqa.messageType':
                dispatch(updateMessage(JSON.parse(payload)));
                break;
            case 'genqa.citationsType':
                dispatch(updateCitations(JSON.parse(payload)));
                break;
            case 'genqa.endOfStreamType': {
                const isAnswerGenerated = JSON.parse(payload).answerGenerated;
                const cannotAnswer = queryExecuted.length !== 0 && !isAnswerGenerated;
                dispatch(setCannotAnswer(cannotAnswer));
                dispatch(setIsStreaming(false));
                dispatch(setIsAnswerGenerated(isAnswerGenerated));
                dispatch(logGeneratedAnswerStreamEnd(isAnswerGenerated));
                dispatch(logGeneratedAnswerResponseLinked());
                break;
            }
            default:
                if (state.debug) {
                    extra.logger.warn(`Unknown payloadType: "${payloadType}"`);
                }
        }
    };
    dispatch(setIsLoading(true));
    const currentStreamRequestMatchesOriginalStreamRequest = (request) => {
        return (request.streamId ===
            config.getState().search.extendedResults.generativeQuestionAnsweringId);
    };
    const abortController = extra.streamingClient?.streamGeneratedAnswer(request, {
        write: (data) => {
            if (currentStreamRequestMatchesOriginalStreamRequest(request)) {
                dispatch(setIsLoading(false));
                if (data.payload && data.payloadType) {
                    handleStreamPayload(data.payloadType, data.payload);
                }
            }
        },
        abort: (error) => {
            if (currentStreamRequestMatchesOriginalStreamRequest(request)) {
                dispatch(updateError(error));
            }
        },
        close: () => {
            if (currentStreamRequestMatchesOriginalStreamRequest(request)) {
                dispatch(setIsStreaming(false));
            }
        },
        resetAnswer: () => {
            if (currentStreamRequestMatchesOriginalStreamRequest(request)) {
                dispatch(resetAnswer());
            }
        },
    });
    if (abortController) {
        setAbortControllerRef(abortController);
    }
    else {
        dispatch(setIsLoading(false));
    }
});
/**
 * Thunk to handle the sequence of actions required to generate a new answer
 * after a search request.
 *
 * ⚠️ This action only works when an **answer configuration ID** is present
 * in the engine configuration. In that case, the **Answer API** will be used
 * instead of the regular search pipeline.
 *
 * Flow:
 * 1. Reset the current generated answer state.
 * 2. Construct the Answer API query parameters based on the current state.
 * 3. Fetch a new answer from the Answer API using the provided configuration.
 */
export const generateAnswer = createAsyncThunk('generatedAnswer/generateAnswer', async (_, { getState, dispatch, extra: { navigatorContext, logger } }) => {
    dispatch(resetAnswer());
    const state = getState();
    if (state.generatedAnswer.answerConfigurationId) {
        const answerApiQueryParams = constructAnswerAPIQueryParams(state, navigatorContext);
        // TODO: SVCC-5178 Refactor multiple sequential dispatches into single action
        dispatch(setAnswerApiQueryParams(answerApiQueryParams));
        await dispatch(fetchAnswer(answerApiQueryParams));
    }
    else {
        logger.warn('[WARNING] Missing answerConfigurationId in engine configuration. ' +
            'The generateAnswer action requires an answer configuration ID to use CRGA with the Answer API.');
    }
});
