import {
	FileSystemProvider,
	Event,
	Uri,
	FileStat,
	FileType,
	FileChangeEvent,
	EventEmitter,
	workspace,
	FileChangeType,
} from "vscode";
import { Disposable } from "@hediet/std/disposable";
import { BufferImpl } from "../utils/buffer";

export class DrawioFileSystemController {
	public readonly dispose = Disposable.fn();

	private readonly fs = new VirtualFileSystemProvider();
	public readonly scheme = "drawio";

	constructor() {
		this.dispose.track(
			workspace.registerFileSystemProvider(this.scheme, this.fs, {
				isCaseSensitive: true,
				isReadonly: false,
			})
		);
	}

	public getOrCreateFileForUri(uri: Uri): {
		file: File;
		didFileExist: boolean;
	} {
		return this.fs.getOrCreateFile(uri);
	}

	/*public getRandomFile(extensionWithDot: string): File {
		const id1 = new Date().getTime();
		const id2 = id++;
		return this.fs.getOrCreateFile(`/${id1}_${id2}${extensionWithDot}`);
	}*/
}

export class VirtualFileSystemProvider implements FileSystemProvider {
	private fileChangedEmitter = new EventEmitter<FileChangeEvent[]>();
	public readonly onDidChangeFile = this.fileChangedEmitter.event;

	private readonly files = new Map<string, File>();

	public getOrCreateFile(uri: Uri): { file: File; didFileExist: boolean } {
		const key = uri.toString();

		const f = this.files.get(key);
		if (f) {
			return { file: f, didFileExist: true };
		}

		const newFile = new File(uri, Uint8Array.from([]));
		newFile.onDidChangeFile(() =>
			this.fileChangedEmitter.fire([
				{ type: FileChangeType.Changed, uri: newFile.uri },
			])
		);
		this.files.set(key, newFile);
		return { file: newFile, didFileExist: false };
	}

	readFile(uri: Uri): Uint8Array | Thenable<Uint8Array> {
		return this.getOrCreateFile(uri).file.data;
	}

	writeFile(
		uri: Uri,
		content: Uint8Array,
		options: { create: boolean; overwrite: boolean }
	): void | Thenable<void> {
		return this.getOrCreateFile(uri).file.write(content);
	}

	stat(uri: Uri): FileStat {
		const f = this.getOrCreateFile(uri).file;
		return {
			type: FileType.File,
			ctime: 0,
			mtime: 0,
			size: f.data.length,
		};
	}

	watch(
		uri: Uri,
		options: { recursive: boolean; excludes: string[] }
	): Disposable {
		return Disposable.empty;
	}

	readDirectory(
		uri: Uri
	):
		| [string, import("vscode").FileType][]
		| Thenable<[string, import("vscode").FileType][]> {
		throw new Error("Method not implemented.");
	}

	createDirectory(uri: Uri): void | Thenable<void> {
		throw new Error("Method not implemented.");
	}

	delete(uri: Uri, options: { recursive: boolean }): void | Thenable<void> {
		throw new Error("Method not implemented.");
	}

	rename(
		oldUri: Uri,
		newUri: Uri,
		options: { overwrite: boolean }
	): void | Thenable<void> {
		throw new Error("Method not implemented.");
	}
}

export class File {
	private readonly fileChangedEmitter = new EventEmitter();
	public readonly onDidChangeFile = this.fileChangedEmitter.event;

	constructor(public readonly uri: Uri, public data: Uint8Array) {}

	public write(data: Uint8Array): void {
		this.data = data;
		this.fileChangedEmitter.fire(undefined);
	}

	public writeString(str: string): void {
		this.write(Uint8Array.from(BufferImpl.from(str, "utf-8")));
	}

	public readString(): string {
		return BufferImpl.from(this.data).toString("utf-8");
	}
}