import { call, put, takeEvery, select, spawn, take, delay, } from "redux-saga/effects";
import { channel } from "redux-saga";
import { axiosInstance } from "../../utils/api";
import { alwaysEndWithSlash, serverDefault } from "../../utils/server-detection";
import { getFetchStream } from "../../utils/netdata-sdk";
import { isMainJs } from "../../utils/env";
import { fillMissingData, transformResults } from "../../utils/fill-missing-data";
import { showCloudInstallationProblemNotification, showCloudConnectionProblemNotification, } from "../../components/notifications";
import { selectGlobalPanAndZoom, selectSnapshot, selectRegistry } from "../global/selectors";
import { stopSnapshotModeAction } from "../dashboard/actions";
import { isPrintMode } from "../dashboard/utils/parse-url";
import { INFO_POLLING_FREQUENCY } from "../global/constants";
import { fetchDataAction, fetchChartAction, fetchDataForSnapshotAction, fetchInfoAction, fetchDataCancelAction, } from "./actions";
const CONCURRENT_CALLS_LIMIT_METRICS = isMainJs ? 30 : 60;
const CONCURRENT_CALLS_LIMIT_PRINT = 2;
const CONCURRENT_CALLS_LIMIT_SNAPSHOTS = 1;
const fetchDataResponseChannel = channel();
export function* watchFetchDataResponseChannel() {
    while (true) {
        const action = (yield take(fetchDataResponseChannel));
        // special case - if requested relative timeRange, and during request the mode has been changed
        // to absolute global-pan-and-zoom, cancel the store update
        // todo do xss check of data
        if (action.type === fetchDataAction.success.toString()) {
            const payload = action.payload;
            const { viewRange } = payload.fetchDataParams;
            const [start, end] = viewRange;
            const globalPanAndZoom = (yield select(selectGlobalPanAndZoom));
            if (globalPanAndZoom
                && (start <= 0 || end <= 0) // check if they are not timestamps
            ) {
                yield put(fetchDataCancelAction({
                    id: payload.id,
                }));
                // eslint-disable-next-line no-continue
                continue;
            }
        }
        yield put(action);
    }
}
// todo construct a new version of key that will be safer to be used in future
// (while keeping old key supported for some time)
// perhaps the key could be passed as attribute to the chart, to avoid matching
const constructCompatibleKey = (dimensions, options) => (
// strange transformations for backwards compatibility. old snapshot keys were encoded this way
// that empty dimensions were actually "null" string
`${dimensions === undefined
    ? "null"
    : encodeURIComponent(dimensions)},${encodeURIComponent(options)}`);
// currently BE always transforms data as if `flip` was there
const IS_FLIP_RESPECTED_IN_COMPOSITE_CHARTS = false;
const [fetchMetrics$] = getFetchStream(isPrintMode ? CONCURRENT_CALLS_LIMIT_PRINT : CONCURRENT_CALLS_LIMIT_METRICS);
function* fetchDataSaga({ payload }) {
    const { 
    // props for api
    host, context, chart, format, points, group, gtime, options, after, before, dimensions, labels, postGroupBy, postAggregationMethod, aggrMethod, dimensionsAggrMethod, nodeIDs, httpMethod, groupBy = "dimension", // group by node, dimension, or label keys
    aggrGroups = [], 
    // props for the store
    fetchDataParams, id, cancelTokenSource, } = payload;
    const snapshot = yield select(selectSnapshot);
    if (snapshot) {
        // if reading snapshot
        const dimensionsWithUrlOptions = constructCompatibleKey(dimensions, options);
        const matchingKey = Object.keys(snapshot.data).find((snapshotKey) => (snapshotKey.startsWith(chart) && snapshotKey.includes(dimensionsWithUrlOptions)));
        if (!matchingKey) {
            // eslint-disable-next-line no-console
            console.warn(`Could not find snapshot key for chart: ${chart} and id ${id}`);
            return;
        }
        const data = snapshot.data[matchingKey];
        yield put(fetchDataAction.success({
            chartData: data,
            fetchDataParams,
            id,
        }));
        return;
    }
    const url = isMainJs
        ? `${alwaysEndWithSlash(host)}api/v1/data`
        : host;
    const agentOptionsOriginal = options.split("|");
    const hasFlip = agentOptionsOriginal.includes("flip");
    const shouldAddFakeFlip = !IS_FLIP_RESPECTED_IN_COMPOSITE_CHARTS && !hasFlip;
    // if flip is not respected in composite-charts, send it always (like dygraph charts normally do)
    const agentOptions = shouldAddFakeFlip
        ? agentOptionsOriginal.concat("flip") : agentOptionsOriginal;
    const groupValues = [
        groupBy === "node" || groupBy === "dimension" ? groupBy : `label=${groupBy}`,
        postGroupBy && `label=${postGroupBy}`,
    ].filter(Boolean);
    const axiosOptions = httpMethod === "POST" ? {
        // used by cloud's room-overview
        data: {
            filter: {
                nodeIDs,
                context,
                dimensions: dimensions ? dimensions.split(/['|]/) : undefined,
                labels,
            },
            after,
            before,
            points,
            group,
            gtime,
            agent_options: agentOptions,
            ...(postAggregationMethod && { post_aggregation_methods: [postAggregationMethod] }),
            aggregations: [groupBy !== "dimension" && {
                    method: dimensionsAggrMethod || "sum",
                    groupBy: ["chart", ...groupValues],
                },
                {
                    method: aggrMethod,
                    groupBy: groupValues,
                    ...(aggrGroups.length && { labels: aggrGroups }),
                }].filter(Boolean),
        },
    } : {
        params: {
            chart,
            _: new Date().valueOf(),
            format,
            points,
            group,
            gtime,
            options,
            after,
            before,
            dimensions,
        },
    };
    const onSuccessCallback = (data) => {
        var _a;
        if (!((_a = data) === null || _a === void 0 ? void 0 : _a.result)) {
            fetchDataResponseChannel.put(fetchDataAction.failure({ id }));
        }
        else {
            const { fillMissingPoints } = fetchDataParams;
            const transformedResults = transformResults(data, format, shouldAddFakeFlip);
            const chartData = {
                ...transformedResults,
                // @ts-ignore
                ...(("post_aggregated_data" in data.result) && {
                    postAggregationMethod,
                    groupBy,
                    postGroupBy,
                    aggrGroups,
                    // @ts-ignore
                    postAggregated: data.result.post_aggregated_data[postAggregationMethod],
                }),
            };
            fetchDataResponseChannel.put(fetchDataAction.success({
                chartData: fillMissingPoints
                    ? fillMissingData(chartData, fillMissingPoints)
                    : chartData,
                fetchDataParams,
                id,
            }));
        }
    };
    const onErrorCallback = (error) => {
        console.warn("fetch chart data failure", error); // eslint-disable-line no-console
        fetchDataResponseChannel.put(fetchDataAction.failure({ id }));
    };
    fetchMetrics$.next({
        ...axiosOptions,
        method: httpMethod || "GET",
        url,
        onErrorCallback,
        onSuccessCallback,
        cancelTokenSource,
    });
}
const [fetchForSnapshot$, resetFetchForSnapshot$] = getFetchStream(CONCURRENT_CALLS_LIMIT_SNAPSHOTS);
function fetchDataForSnapshotSaga({ payload }) {
    const { host, chart, format, points, group, gtime, options, after, before, dimensions, aggrMethod, groupBy, nodeIDs, chartLibrary, id, } = payload;
    // backwards-compatibility, the keys look like this:
    // net_errors.stf0,dygraph,null,ms%7Cflip%7Cjsonwrap%7Cnonzero
    const chartDataUniqueID = `${chart},${chartLibrary},${constructCompatibleKey(dimensions, options)}`;
    const url = `${alwaysEndWithSlash(host)}api/v1/data`;
    const params = {
        chart,
        _: new Date().valueOf(),
        format,
        points,
        group,
        gtime,
        options,
        after,
        before,
        dimensions,
        ...(aggrMethod && { aggr_method: aggrMethod }),
        ...(nodeIDs && { node_ids: nodeIDs.join(",") }),
        ...(groupBy && { groupBy }),
    };
    const onSuccessCallback = (data) => {
        fetchDataResponseChannel.put(fetchDataForSnapshotAction.success({
            snapshotData: data,
            id,
        }));
        // temporarly, until main.js finished rewrite
        // @ts-ignore
        window.chartUpdated({
            chartDataUniqueID,
            data,
        });
    };
    const onErrorCallback = () => {
        fetchDataResponseChannel.put(fetchDataForSnapshotAction.failure({ id }));
        // @ts-ignore
        window.chartUpdated({
            chartDataUniqueID,
            chart,
            data: null,
        });
    };
    fetchForSnapshot$.next({
        url,
        params,
        onErrorCallback,
        onSuccessCallback,
    });
}
function stopSnapshotModeSaga() {
    // any calls in the queue should stop when save-snapshot modal is closed
    resetFetchForSnapshot$.next();
}
function* fetchChartSaga({ payload }) {
    const { chart, id, host } = payload;
    const snapshot = yield select(selectSnapshot);
    if (snapshot) {
        yield put(fetchChartAction.success({
            chartMetadata: snapshot.charts.charts[chart],
            id,
        }));
        return;
    }
    let response;
    const url = isMainJs
        ? `${alwaysEndWithSlash(host)}api/v1/chart`
        : host.replace("/data", "/chart");
    try {
        response = yield call(axiosInstance.get, url, {
            params: {
                chart,
            },
        });
    }
    catch (e) {
        console.warn("fetch chart details failure"); // eslint-disable-line no-console
        yield put(fetchChartAction.failure({ id }));
        return;
    }
    yield put(fetchChartAction.success({
        chartMetadata: response.data,
        id,
    }));
}
function* fetchInfoSaga({ payload }) {
    var _a, _b, _c, _d, _e, _f;
    const { poll } = payload;
    let isCloudEnabled = false;
    let isAgentClaimed = false;
    let isCloudAvailable = false;
    let isACLKAvailable = false;
    try {
        const registry = yield select(selectRegistry);
        const wasCloudAvailable = (_a = registry) === null || _a === void 0 ? void 0 : _a.isCloudAvailable;
        const wasACLKAvailable = (_b = registry) === null || _b === void 0 ? void 0 : _b.isACLKAvailable;
        const { data } = yield call(axiosInstance.get, `${serverDefault}/api/v1/info`);
        isCloudAvailable = ((_c = data) === null || _c === void 0 ? void 0 : _c["cloud-available"]) || false;
        isCloudEnabled = ((_d = data) === null || _d === void 0 ? void 0 : _d["cloud-enabled"]) || false;
        isAgentClaimed = ((_e = data) === null || _e === void 0 ? void 0 : _e["agent-claimed"]) || false;
        isACLKAvailable = ((_f = data) === null || _f === void 0 ? void 0 : _f["aclk-available"]) || false;
        yield put(fetchInfoAction.success({
            isCloudAvailable, isCloudEnabled, isAgentClaimed, isACLKAvailable, fullInfoPayload: data,
        }));
        if (isCloudEnabled && (wasCloudAvailable === null) && !isCloudAvailable) {
            // show only once per session
            showCloudInstallationProblemNotification();
        }
        if (isCloudAvailable && isAgentClaimed && (wasACLKAvailable !== false) && !isACLKAvailable) {
            // show at session-init and if we see a change of isACLKAvailable from true to false
            showCloudConnectionProblemNotification();
        }
        // TODO: No success notification spec`ed?
        // else if (!wasACLKAvailable && isACLKAvailable) {
        //   toast.success("Connected to the Cloud!", {
        //     position: "bottom-right",
        //     type: toast.TYPE.SUCCESS,
        //     autoClose: NOTIFICATIONS_TIMEOUT,
        //   })
        // }
    }
    catch (e) {
        console.warn("fetch agent info failure"); // eslint-disable-line no-console
        yield put(fetchInfoAction.failure());
    }
    if (poll && isCloudEnabled && isAgentClaimed) {
        yield delay(INFO_POLLING_FREQUENCY);
        yield put(fetchInfoAction({ poll: true }));
    }
}
export function* chartSagas() {
    yield takeEvery(fetchDataAction.request, fetchDataSaga);
    yield takeEvery(fetchChartAction.request, fetchChartSaga);
    yield takeEvery(fetchDataForSnapshotAction.request, fetchDataForSnapshotSaga);
    yield takeEvery(stopSnapshotModeAction, stopSnapshotModeSaga);
    yield takeEvery(fetchInfoAction.request, fetchInfoSaga);
    yield spawn(watchFetchDataResponseChannel);
}
//# sourceMappingURL=sagas.js.map