import { from, Observable, of, throwError } from 'rxjs';
import {
    catchError,
    distinctUntilChanged,
    filter,
    map,
    publishReplay,
    refCount,
    startWith,
    tap,
    switchMap,
} from 'rxjs/operators';
import { gql, graphQLContent } from '../graphql/graphql';
import { ExtensionsByLimitDocument } from '../components/generated-model';
import { createAggregateError } from '../util/errors';
import { PlatformContext } from '@workbench-stack/extension-api';
import {
    IRegistryExtension, isErrorLike, ErrorLike, SettingsCascadeOrError, asError,
    IConfiguredExtension, IConfiguredRegistryExtension,
} from '@workbench-stack/extension-api';
import { parseExtensionManifestOrError } from './extension-manifest';
import { IConfiguredSettingsCascade, IGeneratedSettings as ISettings } from '@workbench-stack/core';
const LOADING: 'loading' = 'loading';


/**
 * @returns An observable that emits the list of extensions configured in the viewer's final settings upon
 * subscription and each time it changes.
 */
// export function viewerConfiguredExtensions({
//     settings,
//     queryGraphQL,
// }) {
//     return from();
// }


/**
 * Query the GraphQL API for registry metadata about the extensions given in {@link extensionIDs}.
 *
 * @return An observable that emits once with the results.
 */
export function queryConfiguredRegistryExtensions(
    { queryGraphQL }: Pick<PlatformContext, 'queryGraphQL'>,
    extensionIDs: string[],
): Observable<IConfiguredRegistryExtension[]> {
    if (extensionIDs.length === 0) {
        return of([]);
    }
    return queryGraphQL<{ extensionRegistry: { extensions: { nodes: any } } }>(
        ExtensionsByLimitDocument as any,
        {
            first: extensionIDs.length,
            prioritizeExtensionIDs: extensionIDs,
        },
        // },
    ).pipe(
        tap(({ data }) => console.log("[TAP.DATA]:", data)),
        map(({ data, errors }) => {
            if (
                !data ||
                !data.extensionRegistry ||
                !data.extensionRegistry.extensions ||
                !data.extensionRegistry.extensions.nodes
            ) {
                throw createAggregateError(errors as any);
            }
            return data.extensionRegistry.extensions.nodes.map(
                ({ id, extensionID, url, manifest, viewerCanAdminister }) => ({
                    id,
                    extensionID,
                    url,
                    manifest: manifest ? { raw: manifest.raw } : null,
                    viewerCanAdminister,
                }),
            );
        }),
        map(registryExtensions => {
            const configuredExtensions: IConfiguredRegistryExtension[] = [];
            for (const extensionID of extensionIDs) {
                const registryExtension = registryExtensions.find(x => x.extensionID === extensionID);
                configuredExtensions.push(
                    registryExtension
                        ? toConfiguredRegistryExtension(registryExtension)
                        : { id: extensionID, manifest: null, rawManifest: null, registryExtension: undefined },
                );
            }
            return configuredExtensions;
        }),
    );
}

type MinimalRegistryExtension = Pick<IRegistryExtension, 'extensionID' | 'id' | 'url' | 'viewerCanAdminister'> & {
    manifest: { raw: string } | null,
};

/**
 * Converts to a {@link IConfiguredRegistryExtension} value.
 *
 * @template X the extension type
 */
export function toConfiguredRegistryExtension<X extends MinimalRegistryExtension>(extension: X): IConfiguredRegistryExtension<X> {
    return {
        id: extension.extensionID,
        manifest: extension.manifest ? parseExtensionManifestOrError(extension.manifest.raw) : null,
        rawManifest: (extension && extension.manifest && extension.manifest.raw) || null,
        registryExtension: extension,
    };
}

/** Reports whether the given extension is enabled in the settings. */
export function isExtensionEnabled(settings: ISettings | ErrorLike | null, extensionID: string): boolean {
    return !!settings && !isErrorLike(settings) && !!(settings as ISettings).extensions && !!(settings as ISettings).extensions[extensionID];
}

/**
 * Returns the extension's script URL from its manifest.
 *
 * @param extension The extension whose script URL to get.
 * @throws If the script URL can't be determined.
 * @returns The extension's script URL.
 */
export function getScriptURLFromExtensionManifest(extension: IConfiguredExtension): string {
    if (!extension.manifest) {
        throw new Error(`extension ${JSON.stringify(extension.id)}: no manifest found`);
    }
    if (isErrorLike(extension.manifest)) {
        throw new Error(`extension ${JSON.stringify(extension.id)}: invalid manifest: ${extension.manifest.message}`);
    }
    if (!extension.manifest.bundleURL) {
        throw new Error(`extension ${JSON.stringify(extension.id)}: no "bundleURL" property in manifest`);
    }
    return extension.manifest.bundleURL;
}

/**
 * @throws An error if the final settings has an error.
 * @returns An array of extension IDs configured in the settings.
 */
export function extensionIDsFromSettings(settings: SettingsCascadeOrError): string[] {
    if (isErrorLike(settings.final)) {
        throw asError(settings.final);
    }
    if (!settings.final || !(settings as IConfiguredSettingsCascade).final.extensions) {
        return [];
    }
    return Object.keys((settings as IConfiguredSettingsCascade).final.extensions);
}
