import { ArrayValue, BooleanValue, NumberValue, RecordValue, StringValue, } from '@coveo/bueno';
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { nonEmptyStringArray, validatePayload, } from '../../utils/validate-payload.js';
import { logGeneratedAnswerStreamEnd } from './generated-answer-analytics-actions.js';
import { buildStreamingRequest } from './generated-answer-request.js';
import { generatedContentFormat, } from './generated-response-format.js';
const stringValue = new StringValue({ required: true });
const optionalStringValue = new StringValue();
const booleanValue = new BooleanValue({ required: true });
const citationSchema = {
    id: stringValue,
    title: stringValue,
    uri: stringValue,
    permanentid: stringValue,
    clickUri: optionalStringValue,
};
const answerContentFormatSchema = new StringValue({
    required: true,
    constrainTo: generatedContentFormat,
});
export const setIsVisible = createAction('generatedAnswer/setIsVisible', (payload) => validatePayload(payload, booleanValue));
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 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));
                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));
    }
});
