import { ViewerSettingsDocument } from '../components/generated-model';
import { cloneDeep, isFunction } from 'lodash';
import { mergeMap, map } from 'rxjs/operators';
import { Observable, of, Subscribable, from } from 'rxjs';
import { ofType } from 'redux-observable';
import { ApolloClient, ApolloError } from 'apollo-client';
import {
    ISettingsCascade, ISettings, IConfiguredSettingsCascade, IConfiguredSettingsSubject, ISettingsSubject,
    IConfiguredSubject,
} from '@workbench-stack/core';
import { ISettingsService, SettingsCascadeOrError, ErrorLike } from '@workbench-stack/extension-api';
import { SETTINGS_ACTION_TYPES } from '../constants';
import { createAggregateError } from '../util/errors';

/**
 *
 * Get Settings
 * @param action$
 * @param state$
 * @param param2
 */
export function getSettings<S extends ISettings = ISettings>(apolloClient: ApolloClient<any>): Subscribable<SettingsCascadeOrError<S>> {
    return from(apolloClient.watchQuery<ISettingsCascade>({
        query: ViewerSettingsDocument,
    })).pipe(map(({ data }) => {
        const { ...rest } = data;
        console.log('----data--received ...rest', rest);

        return rest as any;
    }));
}

export function updateSettings(apolloClient: ApolloClient<any>, state$: Observable<any>) {
    return async edit => ({
        // const settings = await 
    });
}
// export function getSettingsEpic<S extends ISettings = ISettings>(
//     action$: Observable<any>,
//     state$: Observable<any>,
//     { apolloClient }: { apolloClient: ApolloClient<any> }): Subscribable<SettingsCascadeOrError<S>> {

//     return getSettings(apolloClient, state$);
// }
/**
 *
 * @param action$
 * @param state$
 * @param param2
 */
export const updateSettingsEpic: (action$: Observable<any>, state$: Observable<any>, { apolloClient }: { apolloClient: ApolloClient<any> }) => any
    = (action$, state$, { apolloClient }) =>
        action$
            .pipe(
                ofType(SETTINGS_ACTION_TYPES.UPDATE_SETTINGS),
                mergeMap(() =>
                    apolloClient.query({ query: ViewerSettingsDocument })),
                mergeMap(response =>
                    of({
                        type: 'UPDATED_CONFIGURATION',
                        value: {
                            result: response,
                        },
                    })),
            );



export function settingsService(apolloClient: ApolloClient<any>, reduxStore): ISettingsService {
    return {
        data: getSettings(apolloClient) as any,
        update: edit => reduxStore.dispatch({ type: SETTINGS_ACTION_TYPES.UPDATE_SETTINGS, payload: edit }),
    };
}




/**
 * Converts a GraphQL SettingsCascase value to a SettingsCascadeOrEror value.
 *
 * @param subjects A list of settings subjects in the settings cascade. If empty, an error is thrown.
 */
export function gqlToCascade({
    subjects,
}: {
    subjects: IConfiguredSubject[],
}): IConfiguredSettingsCascade {
    const configuredSubjects: IConfiguredSubject[] = [];
    const allSettings: ISettings[] = [];
    const allSettingsErrors: ErrorLike[] = [];
    // for (const subject of subjects) {
    //     // const settings = subject.latestSettings && parseJSONCOrError<ISettings>(subject.latestSettings.contents);
    //     const lastID = s
    //     const lastID = subject.latestSettings ? subject.latestSettings.id : null;
    //     // configuredSubjects.push({ subject, settings, lastID });
    //     // if (isErrorLink(settings)) {
    //     //     allSettingsError.push(settings);
    //     // } else if (settings !== null) {
    //     //     allSettings.push(settings);
    //     // }
    // }
    return {
        subjects: subjects,
        final:
            allSettingsErrors.length > 0
                ? createAggregateError(allSettingsErrors)
                : mergeSettings<ISettings>(allSettings),
    };
}

/**
 * Deeply merges the settings without modifying any of the input values. The array is ordered from
 * lowest to highest precendence in the merge.
 *
 */
export function mergeSettings<S extends ISettings>(values: S[]): S | null {
    if (values.length === 0) {
        return null;
    }
    const target = cloneDeep(values[0]);
    for (const value of values.slice(1)) {
        merge(target, value);
    }
    return target;
}


/**
 * Shallow merges add into base (modifying base). Only the top-level object is smerged.
 *
 * The merged value for a key path can be customized by providing a
 * function at the same key path in `custom`.
 *
 * Most callers should use mergeSettings, which uses the set of CustomMergeFunctions that are required to properly
 * merge settings.
 */
export function merge(base: any, add: any, custom?: any): void {
    for (const key of Object.keys(add)) {
        if (key in base) {
            const customEntry = custom && custom[key];
            if (customEntry && isFunction(customEntry)) {
                base[key] = customEntry(base[key], add[key]);
            } else {
                base[key] = add[key];
            }
        } else {
            base[key] = add[key];
        }
    }
}
