import { isNullOrUndefined } from '@coveo/bueno';
import { isErrorResponse, isSuccessResponse, } from '../../api/search/search-api-client.js';
import { applyDidYouMeanCorrection } from '../did-you-mean/did-you-mean-actions.js';
import { didYouMeanAutomatic } from '../did-you-mean/did-you-mean-analytics-actions.js';
import { snapshot } from '../history/history-actions.js';
import { extractHistory } from '../history/history-state.js';
import { updateQuery } from '../query/query-actions.js';
import { applyQueryTriggerModification, updateIgnoreQueryTrigger, } from '../triggers/triggers-actions.js';
import { logQueryError } from './search-analytics-actions.js';
import { mapSearchResponse, } from './search-mappings.js';
import { buildSearchRequest } from './search-request.js';
export class AsyncSearchThunkProcessor {
    config;
    onUpdateQueryForCorrection;
    constructor(config, onUpdateQueryForCorrection = (modification) => {
        this.dispatch(updateQuery({ q: modification }));
    }) {
        this.config = config;
        this.onUpdateQueryForCorrection = onUpdateQueryForCorrection;
    }
    async fetchFromAPI({ mappings, request }, options) {
        const startedAt = Date.now();
        const response = mapSearchResponse(await this.extra.apiClient.search(request, options), mappings);
        const duration = Date.now() - startedAt;
        const queryExecuted = this.getState().query?.q || '';
        return { response, duration, queryExecuted, requestExecuted: request };
    }
    async process(fetched) {
        return (this.processQueryErrorOrContinue(fetched) ??
            (await this.processQueryCorrectionsOrContinue(fetched)) ??
            (await this.processQueryTriggersOrContinue(fetched)) ??
            this.processSuccessResponse(fetched));
    }
    processQueryErrorOrContinue(fetched) {
        if (isErrorResponse(fetched.response)) {
            this.dispatch(logQueryError(fetched.response.error));
            return this.rejectWithValue(fetched.response.error);
        }
        return null;
    }
    async processQueryCorrectionsOrContinue(fetched) {
        const state = this.getState();
        const successResponse = this.getSuccessResponse(fetched);
        if (!successResponse || !state.didYouMean) {
            return null;
        }
        const { enableDidYouMean, automaticallyCorrectQuery } = state.didYouMean;
        const { results, queryCorrections, queryCorrection } = successResponse;
        if (!enableDidYouMean) {
            return null;
        }
        // When the server applied a correction (via pipeline override) but the
        // client did not request auto-correction, we still need to acknowledge
        // the corrected query so subsequent requests (e.g., facet Show More)
        // use the right query.
        if (!automaticallyCorrectQuery) {
            if (!isNullOrUndefined(queryCorrection) &&
                !isNullOrUndefined(queryCorrection.correctedQuery)) {
                return this.processModernDidYouMeanAutoCorrection(fetched);
            }
            return null;
        }
        const shouldExecuteClassicDidYouMeanAutoCorrection = results.length === 0 && queryCorrections && queryCorrections.length !== 0;
        const shouldExecuteModernDidYouMeanAutoCorrection = !isNullOrUndefined(queryCorrection) &&
            !isNullOrUndefined(queryCorrection.correctedQuery);
        const shouldExitWithNoAutoCorrection = !shouldExecuteClassicDidYouMeanAutoCorrection &&
            !shouldExecuteModernDidYouMeanAutoCorrection;
        if (shouldExitWithNoAutoCorrection) {
            return null;
        }
        const ret = shouldExecuteClassicDidYouMeanAutoCorrection
            ? await this.processLegacyDidYouMeanAutoCorrection(fetched)
            : this.processModernDidYouMeanAutoCorrection(fetched);
        this.dispatch(snapshot(extractHistory(this.getState())));
        return ret;
    }
    async processLegacyDidYouMeanAutoCorrection(originalFetchedResponse) {
        const originalQuery = this.getCurrentQuery();
        const originalSearchSuccessResponse = this.getSuccessResponse(originalFetchedResponse);
        if (!originalSearchSuccessResponse.queryCorrections) {
            return null;
        }
        const { correctedQuery } = originalSearchSuccessResponse.queryCorrections[0];
        const retried = await this.automaticallyRetryQueryWithCorrection(correctedQuery);
        if (isErrorResponse(retried.response)) {
            this.dispatch(logQueryError(retried.response.error));
            return this.rejectWithValue(retried.response.error);
        }
        this.dispatch(snapshot(extractHistory(this.getState())));
        return {
            ...retried,
            response: {
                ...retried.response.success,
                queryCorrections: originalSearchSuccessResponse.queryCorrections,
            },
            automaticallyCorrected: true,
            originalQuery,
        };
    }
    processModernDidYouMeanAutoCorrection(originalFetchedResponse) {
        const successResponse = this.getSuccessResponse(originalFetchedResponse);
        const { correctedQuery, originalQuery } = successResponse.queryCorrection;
        this.onUpdateQueryForCorrection(correctedQuery);
        return {
            ...originalFetchedResponse,
            response: {
                ...successResponse,
            },
            queryExecuted: correctedQuery,
            automaticallyCorrected: true,
            originalQuery,
        };
    }
    async processQueryTriggersOrContinue(fetched) {
        const successResponse = this.getSuccessResponse(fetched);
        if (!successResponse) {
            return null;
        }
        const correctedQuery = successResponse.triggers.find((trigger) => trigger.type === 'query')
            ?.content || '';
        if (!correctedQuery) {
            return null;
        }
        const ignored = this.getState().triggers?.queryModification.queryToIgnore;
        if (ignored === correctedQuery) {
            this.dispatch(updateIgnoreQueryTrigger(''));
            return null;
        }
        const originalQuery = this.getCurrentQuery();
        const retried = await this.automaticallyRetryQueryWithTriggerModification(correctedQuery);
        if (isErrorResponse(retried.response)) {
            this.dispatch(logQueryError(retried.response.error));
            return this.rejectWithValue(retried.response.error);
        }
        this.dispatch(snapshot(extractHistory(this.getState())));
        return {
            ...retried,
            response: {
                ...retried.response.success,
            },
            automaticallyCorrected: false,
            originalQuery,
        };
    }
    processSuccessResponse(fetched) {
        this.dispatch(snapshot(extractHistory(this.getState())));
        return {
            ...fetched,
            response: this.getSuccessResponse(fetched),
            automaticallyCorrected: false,
            originalQuery: this.getCurrentQuery(),
        };
    }
    getSuccessResponse(fetched) {
        if (isSuccessResponse(fetched.response)) {
            return fetched.response.success;
        }
        return null;
    }
    async automaticallyRetryQueryWithCorrection(correction) {
        this.onUpdateQueryForCorrection(correction);
        const state = this.getState();
        const { actionCause } = didYouMeanAutomatic();
        const fetched = await this.fetchFromAPI(await buildSearchRequest(state, this.extra.navigatorContext, {
            actionCause,
        }), { origin: 'mainSearch' });
        this.dispatch(applyDidYouMeanCorrection(correction));
        return fetched;
    }
    async automaticallyRetryQueryWithTriggerModification(modified) {
        this.dispatch(applyQueryTriggerModification({
            newQuery: modified,
            originalQuery: this.getCurrentQuery(),
        }));
        this.onUpdateQueryForCorrection(modified);
        const fetched = await this.fetchFromAPI(await buildSearchRequest(this.getState(), this.extra.navigatorContext), { origin: 'mainSearch' });
        return fetched;
    }
    getCurrentQuery() {
        const state = this.getState();
        return state.query?.q !== undefined ? state.query.q : '';
    }
    get extra() {
        return this.config.extra;
    }
    getState() {
        return this.config.getState();
    }
    get dispatch() {
        return this.config.dispatch;
    }
    get rejectWithValue() {
        return this.config.rejectWithValue;
    }
}
