
import { Observable, Subject } from 'rxjs';
import { TextDocument as TextDocumentOriginal } from 'cdecode/client';
import { ITextDocumentItem, WebExtHostDocumentsShape, IModelChangedEvent, Connection } from '@workbench-stack/extension-api';
import { URI, UriComponents } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import * as TypeConverters from '@workbench-stack/extension-api/lib/core/converters/ext-host-type-converters';
import { MainThreadDocumentsAndEditors } from '../client/client-document-and-editors';

type TextDocument = Pick<TextDocumentOriginal, 'languageId'> & { text: string };
export class ExtHostDocuments implements WebExtHostDocumentsShape {
    public onDidChangeDocument = new Subject();
    private documents = new Map<string, ITextDocumentItem>();

    constructor(proxy: any, private _documentsAndEditors: any) { }

    /**
     * Returns the known document with the given URI.
     *
     * @internal
     */
    public get(resource: string | UriComponents): TextDocument {
        let doc;
        if (typeof resource === 'string') {
            doc = this.documents.get(resource);
        } else {
            const uri = URI.revive(resource);
            doc = this.documents.get(uri.toString());
        }
        if (!doc) {
            throw new Error(`document not found: ${resource}`);
        }
        return doc;
    }

    /**
     * If needed, perform a sync with the client to ensure that its pending sends have been received before
     * retrieving this document.
     *
     * @todo This is necessary because hovers can be sent before the document is loaded, and it will cause a
     * "document not found" error.
     */
    public async getSync(resource: string | UriComponents): Promise<TextDocument> {
        let doc;
        if (typeof resource === 'string') {
            doc = this._documentsAndEditors.getDocument(resource);
        } else {
            const uri = URI.revive(resource);
            doc = this._documentsAndEditors.getDocument(uri.toString());
        }
        if (doc) {
            return doc;
        }
        // await this.sync();
        return null;
    }

    /**
     * Returns all known documents.
     *
     * @internal
     */
    public getAll(): TextDocument[] {
        return Array.from(this.documents.values());
    }

    private textDocumentAdds = new Subject<TextDocument>();
    public readonly onDidOpenTextDocument: Observable<TextDocument> = this.textDocumentAdds;

    public $acceptModelChanged(uriComponents: UriComponents, events: IModelChangedEvent, isDirty: boolean): void {
        const uri = URI.revive(uriComponents);
        const data = this._documentsAndEditors.getDocument(uri.toString());
        if (!data) {
            throw new Error('unknown document');
        }
        data._acceptIsDirty(isDirty);
            data.onEvents(events);

        const changeEvent = {
            document: data.document,
            contentChanges: events.changes.map((change) => ({
                text: change.text,
                rangeOffset: change.rangeOffset,
                rangeLength: change.rangeLength,
                range: TypeConverters.Range.to(change.range),
            })),
        };

        this.onDidChangeDocument.next(changeEvent);
    }

    public $acceptDocumentData(docs: ITextDocumentItem[] | null): void {
        if (!docs) {
            // We don't ever (yet) communicate to the extension when docs are closed.
            return;
        }
        for (const doc of docs) {
            const uri = URI.revive(doc.uri);
            const isNew = !this.documents.has(uri.toString());
            this.documents.set(uri.toString(), doc);
            if (isNew) {
                this.textDocumentAdds.next(doc);
            }
        }
    }
}
