import { selectAnswerApiQueryParams } from '../../features/generated-answer/answer-api-selectors.js';
import { setAnswerContentFormat, setAnswerId, setCannotAnswer, updateCitations, updateMessage, } from '../../features/generated-answer/generated-answer-actions.js';
import { logGeneratedAnswerResponseLinked, logGeneratedAnswerStreamEnd, } from '../../features/generated-answer/generated-answer-analytics-actions.js';
import { fetchEventSource } from '../../utils/fetch-event-source/fetch.js';
import { getOrganizationEndpoint } from '../platform-client.js';
import { answerSlice } from './answer-slice.js';
const handleHeaderMessage = (draft, payload) => {
    const { contentFormat } = payload;
    draft.contentFormat = contentFormat;
    draft.isStreaming = true;
    draft.isLoading = false;
};
const handleMessage = (draft, payload) => {
    if (draft.answer === undefined) {
        draft.answer = payload.textDelta;
    }
    else if (typeof payload.textDelta === 'string') {
        draft.answer = draft.answer.concat(payload.textDelta);
    }
};
const handleCitations = (draft, payload) => {
    draft.citations = payload.citations;
};
const handleEndOfStream = (draft, payload) => {
    draft.generated = payload.answerGenerated;
    draft.isStreaming = false;
};
const handleError = (draft, message) => {
    const errorMessage = message.errorMessage || 'Unknown error occurred';
    draft.error = {
        message: errorMessage,
        code: message.code,
    };
    draft.isStreaming = false;
    draft.isLoading = false;
    // Throwing an error here breaks the client and prevents the error from reaching the state.
    console.error(`Generated answer error: ${errorMessage} (code: ${message.code})`);
};
export const updateCacheWithEvent = (event, draft, dispatch) => {
    const message = JSON.parse(event.data);
    if (message.finishReason === 'ERROR' && message.errorMessage) {
        handleError(draft, message);
    }
    const parsedPayload = message.payload.length
        ? JSON.parse(message.payload)
        : {};
    switch (message.payloadType) {
        case 'genqa.headerMessageType':
            if (parsedPayload.contentFormat) {
                handleHeaderMessage(draft, parsedPayload);
                dispatch(setAnswerContentFormat(parsedPayload.contentFormat));
            }
            break;
        case 'genqa.messageType':
            if (parsedPayload.textDelta) {
                handleMessage(draft, parsedPayload);
                dispatch(updateMessage({ textDelta: parsedPayload.textDelta }));
            }
            break;
        case 'genqa.citationsType':
            if (parsedPayload.citations) {
                handleCitations(draft, parsedPayload);
                dispatch(updateCitations({ citations: parsedPayload.citations }));
            }
            break;
        case 'genqa.endOfStreamType':
            handleEndOfStream(draft, parsedPayload);
            dispatch(logGeneratedAnswerStreamEnd(parsedPayload.answerGenerated ?? false));
            dispatch(logGeneratedAnswerResponseLinked());
            break;
    }
};
export const buildAnswerEndpoint = (platformEndpoint, organizationId, answerConfigurationId, insightId) => {
    if (!platformEndpoint || !organizationId || !answerConfigurationId) {
        throw new Error('Missing required parameters for answer endpoint');
    }
    const basePath = `/rest/organizations/${organizationId}`;
    const prefix = insightId
        ? `insight/v1/configs/${insightId}/answer`
        : `answer/v1/configs`;
    return `${platformEndpoint}${basePath}/${prefix}/${answerConfigurationId}/generate`;
};
export const answerApi = answerSlice.injectEndpoints({
    overrideExisting: true,
    endpoints: (builder) => ({
        getAnswer: builder.query({
            queryFn: () => ({
                data: {
                    contentFormat: undefined,
                    answer: undefined,
                    citations: undefined,
                    error: undefined,
                    generated: false,
                    isStreaming: true,
                    isLoading: true,
                },
            }),
            serializeQueryArgs: ({ endpointName, queryArgs }) => {
                // RTK Query serialize our endpoints and they're serialized state arguments as the key in the store.
                // Keys must match, because if anything in the query changes, it's not the same query anymore.
                // Analytics data is excluded entirely as it contains volatile fields that change during streaming.
                const { analytics: _analytics, ...queryArgsWithoutAnalytics } = queryArgs;
                // Standard RTK key, with analytics excluded
                return `${endpointName}(${JSON.stringify(queryArgsWithoutAnalytics)})`;
            },
            async onCacheEntryAdded(args, { getState, cacheDataLoaded, updateCachedData, dispatch }) {
                await cacheDataLoaded;
                /**
                 * createApi has to be called prior to creating the redux store and is used as part of the store setup sequence.
                 * It cannot use the inferred state used by Redux, thus the casting.
                 * https://redux-toolkit.js.org/rtk-query/usage-with-typescript#typing-dispatch-and-getstate
                 */
                const { configuration, generatedAnswer, insightConfiguration } = getState();
                const { organizationId, environment, accessToken } = configuration;
                const platformEndpoint = getOrganizationEndpoint(organizationId, environment);
                const answerEndpoint = buildAnswerEndpoint(platformEndpoint, organizationId, generatedAnswer.answerConfigurationId, insightConfiguration?.insightId);
                await fetchEventSource(answerEndpoint, {
                    method: 'POST',
                    body: JSON.stringify(args),
                    headers: {
                        Authorization: `Bearer ${accessToken}`,
                        Accept: 'application/json',
                        'Content-Type': 'application/json',
                        'Accept-Encoding': '*',
                    },
                    fetch,
                    onopen: async (res) => {
                        const answerId = res.headers.get('x-answer-id');
                        if (answerId) {
                            updateCachedData((draft) => {
                                draft.answerId = answerId;
                                dispatch(setAnswerId(answerId));
                            });
                        }
                    },
                    onmessage: (event) => {
                        updateCachedData((draft) => {
                            updateCacheWithEvent(event, draft, dispatch);
                        });
                    },
                    onerror: (error) => {
                        throw error;
                    },
                    onclose: () => {
                        updateCachedData((draft) => {
                            dispatch(setCannotAnswer(!draft.generated));
                        });
                    },
                });
            },
        }),
    }),
});
export const fetchAnswer = (fetchAnswerParams) => {
    return answerApi.endpoints.getAnswer.initiate(fetchAnswerParams);
};
// Select answer state from RTK endpoint state
export const selectAnswer = (state) => {
    const params = selectAnswerApiQueryParams(state);
    return answerApi.endpoints.getAnswer.select(params)(state);
};
