
import { IExtensionsService, IExecutableExtension, IConfiguredExtension, IExtensionManifest } from '@workbench-stack/extension-api';
import { Subscribable, from, ObservableInput, Observable, of, combineLatest } from 'rxjs';
import { map, switchMap, tap, catchError, mergeMap, distinctUntilChanged } from 'rxjs/operators';
import { isErrorLike } from '@workbench-stack/extension-api';
import { ExtensionsDocument } from '../components/generated-model';
import { ApolloClient } from 'apollo-client';
import * as _ from 'lodash';
import { ofType } from 'redux-observable';
import { EXTENSION_ACTION_TYPES } from '../constants';
import { isEqual } from 'lodash';
import { getScriptURLFromExtensionManifest } from '../extensions/extension';
import { logger } from '@cdm-logger/client';
import { flattenMenuObj } from '../util/util';
import { Store } from '@files-stack/client-state';
import { createSelector } from 'reselect';





/**
 * Selector for opened files of a editor group
 */
export const editorOpenedFiles = createSelector(
    state => state,
    (openedFiles) => openedFiles,
);

/**
 * Returns an observable that emits the set of extensions that should be active, based on the previous and
 * current state and each available extension's activationEvents.
 *
 * An extension is activated when one or more of its activationEvents is true. After an extension has been
 * activated, it remains active for the rest of the session (i.e., for as long as the browser tap remains open)
 * as long as it remains enabled. If it is disabeld, it is deactivated. (i.e. "activationEvents" are
 * retrospective/sticky.)
 *
 * @todo Consider whether extensions should be deactivated if none of their activationEvents are true (or that
 * plus a certain period of inactivity).
 *
 * @param extensionActivationFilter A function that returns the set of extensions that should be activated
 * based on the current model only. It does not need to account for remembering which extensions were
 * previously activated in prior states.
 */
export function getActiveExtensions(apolloClient: ApolloClient<any>, store$: Observable<Store.Files>): Subscribable<IExecutableExtension[]> {
    const activatedExtensionsIDs: string[] = [];
    return combineLatest(
        store$,
        getConfiguredExtensions(apolloClient, store$), // need to replace with getActiveExtensions
    ).pipe(
        // TODO: we may need to use reselect and subscribe to it rather than store.
        map(([ state, extensions ]) => ({
            files: [],
            extensions,
            // createSelector(state as any, { editorId: /** activeEditorGroupId(state) */ {} } as any), 
        })),
        tap((e) => console.log('debug', e)),
        distinctUntilChanged((a, b) => isEqual(a, b)),
        // get only active extensions based on the current file
        tap(({ files, extensions }) => {
            logger.info({ childName: 'ExtensionService' }, 'getActiveExtensions to process');
            const activeExtensions = extenionsWithMatchedActivationEvent(files as any, extensions as any);
            logger.info({ childName: 'ExtensionService', activeExtensions }, 'matched activeExtensions');
            for (const x of activeExtensions) {
                if (!activatedExtensionsIDs.includes(x.id)) {
                    activatedExtensionsIDs.push(x.id);
                }
            }
        }),
        map(({ extensions }) => (extensions ? extensions.filter(x => activatedExtensionsIDs.includes(x.id)) : [])),
        distinctUntilChanged((a, b) => isEqual(a, b)),
        map(extensions => extensions.map(x => {
            return {
                id: x.id,
                scriptURL: getScriptURLFromExtensionManifest(x as any),
                manifest: {
                    ...x.manifest,
                    contributes: {
                        ...(x.manifest as IExtensionManifest).contributes,
                        menus: flattenMenuObj((x.manifest as IExtensionManifest).contributes.menus),
                    },
                } as IExtensionManifest,
            } as IExecutableExtension;
        }),
        ),
        tap(extensions => logger.info({ childName: 'ExtensionService', extensions }, 'activeExtensions')),
        map(extensions => {
            return extensions.filter((x): x is IExecutableExtension => {
                return x !== null;
            });
        }) ,
    );
}


// epic wrapper of activateExtension
/**
 *
 * @param action$
 * @param state$
 * @param param2
 */
export const getActiveExtensionsEpic: (action$: Observable<any>, state$: Observable<any>, { apolloClient }: { apolloClient: ApolloClient<any> }) => any
    = (action$, state$, { apolloClient }) =>
        action$
            .pipe(
                ofType(EXTENSION_ACTION_TYPES.FETCH_ACTIVE_EXTENSIONS),
                switchMap(payload => getActiveExtensions(apolloClient, state$)),
                map(extensions => ({ type: EXTENSION_ACTION_TYPES.ACTIVE_EXTENSIONS, payload: extensions })),
            );



/**
 * Returns an observable that emits the set of configured extensions upon subscription and whenever it changes.
 *
 * @param input
 */
export function getConfiguredExtensions(apolloClient: ApolloClient<any>, store$): Subscribable<IExecutableExtension[]> {
    return from(apolloClient.watchQuery<{ extensions: { nodes: any[] } }>({
        query: ExtensionsDocument,
    })).pipe(
        tap(ex => {
            // to make sure tests don't fail
            if (ex.data === undefined) {
                return ex.data = { extensions: { nodes: [] } };
            }
        }),
        map(ex => {
            console.log('ex.data.extensions.nodes!!!!!!!!', typeof ex.data.extensions.nodes);
            return ex.data.extensions.nodes;
        }),
        tap(extensions => logger.info(extensions, 'activeExtensions!!!!!!????')),
    );
}

// wrapper of configuredExtensions
// export const getConfiguredExtensionsEpic: (action$: Observable<any>, state$: Observable<any>, { apolloClient }: { apolloClient: ApolloClient<any> }) => any
//     = (action$, state$, { apolloClient }) =>
//         action$
//             .pipe(
//                 ofType(EXTENSION_ACTION_TYPES.FETCH_CONFIGURED_EXTENSIONS),
//                 switchMap(payload => getConfiguredExtensions(apolloClient, state$)),
//                 map(extensions => of({ type: EXTENSION_ACTION_TYPES.CONFIGURED_EXTENSIONS, payload: extensions })),
//             );


/**
 * Returns an observable that emits the set of enabled extensions upon subscription and whenever it changes.
 *
 * @param input
 */
// export function getEnabledExtensions(apolloClient: ApolloClient<any>, store$): Subscribable<IConfiguredExtension[]>{
//  return combineLatest(
//     getSettings(apolloClient, store$),
//     getConfiguredExtensions(apolloClient, store$),
//  ).pipe(
//      map(([settings, configuredExtensions]) => {
//          const enabled = [...configuredExtensions.filter(x => isExtensionEnabled(settings.final, x.id))];
//          return enabled;
//      }),
//  );
// }

// const memoizedGetScriptURLForExtension = memorizeObservable<string, string | null>(
//     url =>
//         asObservable(this.platformContext.getScriptURLForExtension(url)).pipe(
//             catchError(err => {
//                 console.error(`Error fetchign extension script URL ${url}`, err);
//                 return [null];
//             }),
//         ),
//     url => url,
// );

function asObservable(input: string | ObservableInput<string>): Observable<string> {
    return typeof input === 'string' ? of(input) : from(input);
}


// TODO need to replace x.activationEvents with x.manifest.activationEvents
function extenionsWithMatchedActivationEvent(
    openedFiles: { resource?: string }[] = [],
    enabledExtensions: IConfiguredExtension[],
): IConfiguredExtension[] {
    return enabledExtensions.filter(x => {
        try {
            if (!x.manifest) {
                console.warn(`Extension ${x.id} was not found. Remove it from settings to suppress this warning.`);
                return false;
            } else if (isErrorLike(x.manifest)) {
                console.warn(x.manifest);
                return false;
            } else if (!x.manifest.activationEvents) {
                console.warn(`Extension ${x.id} has no activation events, so it will never be activated.`);
                return false;
            }
            return (x.manifest.activationEvents || []).some(
                e => e === '*' || openedFiles
                    .map(l => (/(?:\.([^.]+))?$/.exec(l.resource)[0] || '').replace('.', ''))
                    .some(l => e === `onLanguage:${l}`),
            );
        } catch (err) {
            console.error(err);
        }
        return false;
    });
}
function getConfiguredExtensionData(apolloClient, store$) {


}
export function extensionsService(apolloClient: ApolloClient<any>, store$): IExtensionsService {

    return {
        configuredExtensions: () => getConfiguredExtensions(apolloClient, store$),
        enabledExtensions: () => null,
        activeExtensions: getActiveExtensions(apolloClient, store$),
    };
}
