/* tslint:disable */

import { toErrorMessage } from '@vscode/monaco-editor/esm/vs/base/common/errorMessage';
import { IDisposable, IReference, dispose } from '@vscode/monaco-editor/esm/vs/base/common/lifecycle';
import { Schemas } from '@vscode/monaco-editor/esm/vs/base/common/network';
import { URI, UriComponents } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import { MainThreadDocumentsShape, IExtHostContext, MainContext } from '@workbench-stack/extension-api/lib/protocol/server.protocol';
import { Observable, Subscription } from 'rxjs';
import { SubscriptionMap } from '../common';
import { WebExtHostDocumentsShape, ITextDocumentItem, Connection, createProxyAndHandleRequests } from '@workbench-stack/extension-api';
import { MainThreadDocumentsAndEditors } from './client-document-and-editors';
import { IFileStat } from '@files-stack/client-state';
export class BoundModelReferenceCollection {

    private _data = new Array<{ length: number, dispose(): void }>();
    private _length = 0;

    constructor(
        private _maxAge: number = 1000 * 60 * 3,
        private _maxLength: number = 1024 * 1024 * 80
    ) {
        //
    }

    dispose(): void {
        this._data = dispose(this._data);
    }

    add(ref: IReference<any>): void {
        let length = ref.object.textEditorModel.getValueLength();
        let handle: any;
        let entry: { length: number, dispose(): void };
        const dispose = () => {
            let idx = this._data.indexOf(entry);
            if (idx >= 0) {
                this._length -= length;
                ref.dispose();
                clearTimeout(handle);
                this._data.splice(idx, 1);
            }
        };
        handle = setTimeout(dispose, this._maxAge);
        entry = { length, dispose };

        this._data.push(entry);
        this._length += length;
        this._cleanup();
    }

    private _cleanup(): void {
        while (this._length > this._maxLength) {
            this._data[0].dispose();
        }
    }
}

export class MainThreadDocuments implements MainThreadDocumentsShape {

    private subscriptions = new Subscription();
    private registrations = new SubscriptionMap();
    private proxy: WebExtHostDocumentsShape;

    private _toDispose: IDisposable[];
	private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
	private readonly _modelIsSynced: { [modelId: string]: boolean; };
	private _modelReferenceCollection = new BoundModelReferenceCollection();

    constructor(
        documentsAndEditors: MainThreadDocumentsAndEditors,
        connection: Connection, 
        modelTextDocuments: Observable<ITextDocumentItem[] | null>,
        private textFileService: any,
        private fileService: any,
        private untitledEditorService: any,
        private textModelResolverService: any,
        ) {
        this.proxy = createProxyAndHandleRequests('documents', connection, this);

        this.subscriptions.add(
            modelTextDocuments.subscribe(docs => {
                this.proxy.$acceptDocumentData(docs || []);
            }),
        );

        this.subscriptions.add(this.registrations);


        this._toDispose = [];

    }

    public dispose(): void {
    }

    // --- from extension host process

    $trySaveDocument(uri: UriComponents): Promise<boolean> | any {
        const resource = URI.revive(uri).toString();
        return this.textFileService.save(resource);
    }

    $tryOpenDocument(_uri: UriComponents): Promise<any>| any {
        const uri = URI.revive(_uri);
        if (!uri.scheme || !(uri.fsPath || uri.authority)) {
			return Promise.reject(new Error(`Invalid uri. Scheme and authority or path must be set.`));
        }

        let promise: Promise<boolean>;
        switch(uri.scheme) {
            case Schemas.untitled:
                promise = this._handleUntitledScheme(uri);
                break;
            case Schemas.file:
            default:
                promise = this._handleAsResourceInput(uri);
                break;
        }

        return promise.then(success => {
            if(!success) {
                return Promise.reject(new Error('cannot open ' + uri.toString()));
            } else if (!this._modelIsSynced[uri.toString()]) {
                return Promise.reject(new Error('cannot open ' + uri.toString()));
            } else {
                return undefined;
            }
        }, err => {
            return Promise.reject(new Error('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err)));
        })
        

    }

    $tryCreateDocument(options?: { language?: string, content?: string }): Promise<URI>| any {
        return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined);
    }

    private _handleAsResourceInput(uri: URI): Promise<boolean> {
        // return this._textModel.createModelReference(uri).then(ref => {
            
        // })
        return this.textModelResolverService.createModelReference(uri)
        
    }
    private _handleUntitledScheme(uri: URI): Promise<boolean> {
		const asFileUri = uri.with({ scheme: Schemas.file });
		return this.fileService.resolveFile(asFileUri).then(stats => {
			// don't create a new file ontop of an existing file
			return Promise.reject(new Error('file already exists on disk'));
		}, err => {
			return this._doCreateUntitled(uri).then(resource => !!resource);
		});
    }
    
    private _doCreateUntitled(resource?: URI, modeId?: string, initialValue?: string): Promise<URI> {
		const untitledFile = this.untitledEditorService.loadOrCreate({
			resource,
			modeId,
			initialValue,
			useResourcePath: Boolean(resource && resource.path)
		}).then((model) => {
			const resource = model.resoruce;

			if (!this._modelIsSynced[resource.toString()]) {
				throw new Error(`expected URI ${resource.toString()} to have come to LIFE`);
			}

			this.proxy.$acceptDirtyStateChanged(resource, true); // mark as dirty

			return resource;
		});
	}
}
