import { combineLatest, Subscription, of, Subject } from 'rxjs';
import {
    ExtHostContext, ExtHostDocumentsAndEditorsShape, Connection, IModelAddedData, ITextEditorAddData, createProxyAndHandleRequests,
    IDocumentsAndEditorsDelta, EditorViewColumn,
} from '@workbench-stack/extension-api';
import { ITextModel } from '@vscode/monaco-editor/esm/vs/editor/common/model';
import { ITextFileService, ICodeEditorService, IModelService, ICommonEditor } from '@workbench-stack/core';
import { first, map, take, tap, distinctUntilChanged, pairwise, startWith, switchMap } from 'rxjs/operators';
import { DocumentAndEditorState, TextEditorSnapshot, DocumentAndEditorStateDelta } from './client-document-and-editors-helpers';
import { URI } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import { MainThreadTextEditor } from './client-editor';
import { ClientDocuments } from './client-documents';
import { isArray } from 'util';

const getFromUpdates = fetch => map(fetch);

export class MainThreadDocumentsAndEditors {
    private serverProxy: any;
    private _deltaInited = false;
    private _currentDelta = null;
    private _updates = new Subject();
    private _documents: ClientDocuments;
    private subscriptions = new Subscription();
    private proxy: ExtHostDocumentsAndEditorsShape;
    private _textEditors = <{ [id: string]: MainThreadTextEditor }>Object.create({});

    public onTextEditorAdd = this._updates.pipe(getFromUpdates(e => e.addedEditors));
    public onTextEditorRemove = this._updates.pipe(getFromUpdates(e => e.removedEditors));

    public onDocumentAdd = this._updates.pipe(getFromUpdates(e => e.addedDocuments));
    public onDocumentRemove = this._updates.pipe(getFromUpdates(e => e.removedDocuments));

    constructor(
        connection: Connection,
        private modelService: IModelService,
        codeEditorService: ICodeEditorService,
        private textFileService: ITextFileService,
        ) {

        this._documents = new ClientDocuments(connection, textFileService, this);

        this.proxy = createProxyAndHandleRequests('documentsAndEditors', connection, this);
        this.serverProxy = createProxyAndHandleRequests(ExtHostContext.ExtHostDocumentsAndEditors.sid, connection, this);

        // console.log("[check>>>]: editorService ", editorService, documents);
        const delta = combineLatest(
            of(codeEditorService.listCodeEditors()),
            modelService.onModelAdded.pipe(startWith(modelService.getModels())),
        )
            .pipe(
                map(([editors, models]) => {
                    if(!isArray(models)) {
                        models = [models];
                    }
                    console.log('[check>>>] MainThreadDocumentsAndEditors', { editors, models });
                    let documents: Set<ITextModel> = new Set();
                    models.map(m => documents.add(m));
                    let textEditors: Map<string, TextEditorSnapshot> = new Map();
                    editors.map(ed => textEditors.set(ed.getId(), new TextEditorSnapshot(ed)));
                    const activeEditor = codeEditorService.getActiveCodeEditor() ? codeEditorService.getActiveCodeEditor().getId() : null;
                    const newState = new DocumentAndEditorState(documents, textEditors, activeEditor);
                    return newState;
                }),
                map((newDelta) => DocumentAndEditorState.compute(this._currentDelta, newDelta)),
                map(this._onDelta),
        )
            .subscribe(() => {}, (e) => console.log('[check>>>]: SUBSCRIPTION_ERROR:', e));

        this.subscriptions.add(delta);
    }

    private _onDelta = (delta: DocumentAndEditorStateDelta): void => {
        console.log('[check>>>]: _onDelta:>>', delta, this._textEditors);
        this._currentDelta = delta;

        let removedDocuments: URI[];
        const removedEditors: string[] = [];
        const addedEditors: MainThreadTextEditor[] = [];

        // removed models
        removedDocuments = delta.removedDocuments.map(m => m.uri);

        // added editors
        for (const apiEditor of delta.addedEditors) {
            const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.editor.getModel(),
                apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this.modelService);

            this._textEditors[apiEditor.id] = mainThreadEditor;
            addedEditors.push(mainThreadEditor);
        }

        // removed editors
        for (const { id } of delta.removedEditors) {
            const mainThreadEditor = this._textEditors[id];
            if (mainThreadEditor) {
                mainThreadEditor.dispose();
                delete this._textEditors[id];
                removedEditors.push(id);
            }
        }

        const extHostDelta: IDocumentsAndEditorsDelta = Object.create(null);
        let empty = true;
        if (delta.newActiveEditor !== undefined) {
            empty = false;
            extHostDelta.newActiveEditor = delta.newActiveEditor;
        }
        if (removedDocuments.length > 0) {
            empty = false;
            extHostDelta.removedDocuments = removedDocuments;
        }
        if (removedEditors.length > 0) {
            empty = false;
            extHostDelta.removedEditors = removedEditors;
        }
        if (delta.addedDocuments.length > 0) {
            empty = false;
            extHostDelta.addedDocuments = delta.addedDocuments.map(m => this._toModelAddData(m));
        }
        if (delta.addedEditors.length > 0) {
            empty = false;
            extHostDelta.addedEditors = addedEditors.map(e => this._toTextEditorAddData(e));
        }
        if (!empty) {
            if (!this._deltaInited) {
                this._deltaInited = true;
                extHostDelta.isInitial = true;
            }

            this.proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
            this.serverProxy.$acceptDocumentsAndEditorsDelta(extHostDelta)
                .catch(e => console.error(`[check>>>] ERROR>>>>>>>>>>>>`, e));

            this._updates.next({
                addedEditors,
                removedEditors,
                removedDocuments,
                addedDocuments: delta.addedDocuments,
            });
        }
    }

    private _toModelAddData(model: ITextModel): IModelAddedData {
        return {
            uri: model.uri,
            versionId: model.getVersionId(),
            lines: model.getLinesContent(),
            EOL: model.getEOL(),
            modeId: model.getLanguageIdentifier().language,
            isDirty: this.textFileService.isDirty(model.uri),
        };
    }

    private _toTextEditorAddData(textEditor: MainThreadTextEditor): ITextEditorAddData {
        const props = textEditor.getProperties();
        return {
            id: textEditor.getId(),
            documentUri: textEditor.getModel().uri,
            options: props.options,
            selections: props.selections,
            visibleRanges: props.visibleRanges,
            editorPosition: this._findEditorPosition(textEditor),
        };
    }

    private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn | undefined {
        // @sri implemented in own way
        // for (const workbenchEditor of this._editorService.visibleControls) {
        //     if (editor.matches(workbenchEditor)) {
        //         return editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group);
        //     }
        // }
        // return undefined;
        return 0;
    }

    public findTextEditorIdFor(editor: ICommonEditor): string | undefined {
        for (const id in this._textEditors) {
            if (this._textEditors[id].matches(editor)) {
                return id;
            }
        }
        return undefined;
    }

    public getEditor(id: string): MainThreadTextEditor {
        return this._textEditors[id];
    }

    public unsubscribe(): void {
        this.subscriptions.unsubscribe();
    }
}
