import { BehaviorSubject, NEVER, NextObserver, of, from, Subscribable, throwError } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import * as cdecode from 'cdecode/client';
import { PlatformContext, IServices } from '@workbench-stack/extension-api';
import { createConnection, IClientInitData, createMessageTransports, WebExtHostExtensionServiceShape } from '@workbench-stack/extension-api';
import { startExtensionHost } from '../../ext-host';
import { createExtensionHostClient } from '../../../extensions/extension-client';
import { Services } from '../../../services/services';
import { client as apolloClient} from '../../../graphql/__tests__/apollo-client-test-helper';
import configureMockStore from 'redux-mock-store';
import { reduxState$ } from '../../../__tests__/redux-store-mock';
import { URI } from '@vscode/monaco-editor/esm/vs/base/common/uri';

const FIXTURE_MODE = {
    roots: [{ uri: 'file:///' }],
    visibleViewComponents: [
        {
            type: 'textEditor',
            item: {
                uri: URI.parse('file:///f').toJSON(),
                languageId: 'l',
                text: 't',
                getText: () => 'a',
            },
            selections: [],
            isActive: true,
        },
    ],
};

interface TestContext {
    client: WebExtHostExtensionServiceShape;
    extensionHost: typeof cdecode;
}

interface Mocks
    extends Pick<
    PlatformContext,
    | 'settings'
    | 'updateSettings'
    | 'queryGraphQL'
    | 'getScriptURLForExtension'
    | 'clientApplication'
    | 'apolloClient'
    | 'reduxState$'
    // | 'sideloadedExtensionURL'
    > { }


const store = configureMockStore()({ files_stack__openingFiles: []});

const NOOP_MOCKS: Mocks = {
    settings: NEVER,
    updateSettings: () => Promise.reject(new Error('Mocks#updateSettings not implemented')),
    queryGraphQL: () => throwError(new Error('Mocks#queryGraphQL not implemented')),
    getScriptURLForExtension: scriptURL => scriptURL,
    clientApplication: 'cdecode',
    apolloClient: apolloClient,
    reduxState$: reduxState$,
    // sideloadedExtensionURL: new BehaviorSubject<string | null>(null),
};
/**
 * Set up a new client-extension integration test.
 *
 * @internal
 */
export async function integrationTestContext(
    partialMocks: Partial<Mocks> = NOOP_MOCKS,
): Promise<any> {
    const mocks: Mocks = partialMocks ? { ...NOOP_MOCKS, ...partialMocks } : NOOP_MOCKS;

    const [clientTransports, serverTransports] = createMessageTransports();

    const extensionHost = startExtensionHost(serverTransports);

    const services = new Services(mocks as any);
    const client = createExtensionHostClient(
        services,
        of(clientTransports).pipe(
            switchMap(async cliTransports => {
                const connection = createConnection(cliTransports);
                connection.listen();

                const initData: IClientInitData = {
                    cdecodeURL: 'https://example.com',
                    clientApplication: 'cdecode',
                };
                await connection.sendRequest('initialize', [initData]);
                return connection;
            }),
        ),
    );

    services.model.model.next(FIXTURE_MODE);

    await (await extensionHost.__testAPI).internal.sync();
    return {
        client,
        extensionHost: await extensionHost.__testAPI,
        services,
        model: services.model.model,
    };
}

/**
 * Returns a {@link Promise} and a function. The {@link Promise} blocks until the returned function is called.
 *
 * @internal
 */
export function createBarrier(): { wait: Promise<void>; done: () => void } {
    let done!: () => void;
    const wait = new Promise<void>(resolve => (done = resolve));
    return { wait, done };
}

export function collectSubscribableValues<T>(subscribable: cdecode.Subscribable<T>): T[] {
    const values: T[] = [];
    subscribable.subscribe(value => values.push(value));
    return values;
}
