import { IMAGE_EXTENSIONS } from "../../../constants/virtualDrive.const";
import { WindowsManager } from "../../windows/windowsManager";
import { VirtualBase, VirtualBaseJson } from "../virtualBase";

export interface VirtualFileJson extends VirtualBaseJson {	
	ext?: string;
	cnt?: string;
	src?: string;
}

type OptionalStringProperty = string | null | undefined;

/**
 * A virtual file that can be stored inside a folder
 */
export class VirtualFile extends VirtualBase {
	extension: OptionalStringProperty;
	source: OptionalStringProperty;
	content: OptionalStringProperty;

	static NON_TEXT_EXTENSIONS = [
		"png"
	];

	static EVENT_NAMES = {
		CONTENT_CHANGE: "contentchange"
	};

	constructor(name: string, extension?: string | undefined) {
		super(name);
		this.extension = extension;
	}

	setAlias(alias: string): this {
		return super.setAlias(alias);
	}

	/**
	 * Sets the source of this file and removes the content
	 */
	setSource(source: string): this {
		if (this.source === source || !this.canBeEdited)
			return this;

		this.source = source;
		this.content = null;

		this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);

		this.confirmChanges();
		return this;
	}

	/**
	 * Sets the content of this file and removes the source
	 */
	setContent(content: string | string[]): this {
		if (this.content === content || !this.canBeEdited)
			return this;

		this.content = typeof content === "string" ? content : content.join("\n");
		this.source = null;

		this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);

		this.confirmChanges();
		return this;
	}

	get id() {
		if (this.extension == null || this.extension.trim() === "")
			return this.name;

		return `${this.name}.${this.extension}`;
	}

	static splitId(id: string): {
		name: string;
		extension: OptionalStringProperty;
	} {
		if (!id.includes("."))
			return { name: id, extension: "" };

		const sections = id.split(".");
		const extension = sections.pop();
		const name = sections.join(".");
		return { name, extension };
	}

	/**
	 * Opens this file in an app associated with its extension
	 */
	open(windowsManager: WindowsManager): object | null {
		return windowsManager.openFile(this);
	}

	async read(): Promise<OptionalStringProperty | undefined> {
		if (this.content != null) return this.content;
		if (this.source == null) return null;

		const isText = this.extension == null || !VirtualFile.NON_TEXT_EXTENSIONS.includes(this.extension);

		if (!isText) return this.source;

		return await fetch(this.source).then((response) =>
			response.text()
		).catch((error) => {
			console.error(`Error while reading file with ID: ${this.id}\n`, error);
			return null;
		}) as string;
	}

	isFile(): boolean {
		return true;
	}

	getIconUrl(): string {
		if (this.iconUrl != null)
			return this.iconUrl;

		let iconUrl = null;

		if (this.source != null && this.extension != null && IMAGE_EXTENSIONS.includes(this.extension))
			return this.source;

		const { skin } = this.getRoot().systemManager;

		switch (this.extension) {
			case "txt":
			case "md":
				iconUrl = skin.fileIcons.text ?? skin.fileIcons.generic;
				break;
			case "xml":
			case "js":
			case "json":
			case "jsx":
			case "ts":
			case "tsx":
			case "css":
			case "html":
			case "yml":
				iconUrl = skin.fileIcons.code ?? skin.fileIcons.generic;
				break;
			default:
				iconUrl = skin.fileIcons.generic;
				break;
		}

		return iconUrl;
	}

	getType(): string {
		let type = "";

		if (this.extension == null) return "Unknown file";

		switch (this.extension) {
			case "png":
				type = "PNG Image";
				break;
			case "txt":
				type = "Text";
				break;
			case "md":
				type = "Markdown source";
				break;
			case "xml":
				type = "XML source";
				break;
		}

		return `${type} file (.${this.extension.toLowerCase()})`.trim();
	}

	toJSON(): VirtualFileJson | null {
		// Don't return file if it can't or hasn't been edited
		if (!this.canBeEdited || (this.editedByUser == null || !this.editedByUser))
			return null;

		const object = super.toJSON() as VirtualFileJson;

		if (object == null)
			return null;
		
		if (this.extension != null) {
			object.ext = this.extension;
		}

		if (this.content != null) {
			object.cnt = this.content;
		} else if (this.source != null) {
			object.src = this.source;
		}

		return object;
	}
}