import Log from "sap/base/Log";
import type { FEView } from "sap/fe/core/BaseController";
import type ResourceModel from "sap/fe/core/ResourceModel";
import type { BackendUser, User } from "sap/fe/core/controllerextensions/collaboration/CollaborationCommon";
import { CollaborationUtils, UserEditingState, UserStatus } from "sap/fe/core/controllerextensions/collaboration/CollaborationCommon";
import type { InternalModelContext } from "sap/fe/core/helpers/ModelHelper";
import ResourceModelHelper from "sap/fe/core/helpers/ResourceModelHelper";
import CommonHelper from "sap/fe/macros/CommonHelper";
import Avatar from "sap/m/Avatar";
import Button from "sap/m/Button";
import Dialog from "sap/m/Dialog";
import HBox from "sap/m/HBox";
import ObjectIdentifier from "sap/m/ObjectIdentifier";
import ObjectStatus from "sap/m/ObjectStatus";
import Text from "sap/m/Text";
import VBox from "sap/m/VBox";
import Core from "sap/ui/core/Core";
import { ValueState } from "sap/ui/core/library";
import type Context from "sap/ui/model/Context";
import Filter from "sap/ui/model/Filter";
import type GridTableColumn from "sap/ui/table/Column";
import type GridTable from "sap/ui/table/Table";

export default class CollaborationDiscard {
	public id?: string;

	private promiseResolve!: Function;

	public discardResourceModel!: ResourceModel;

	public containingView!: FEView;

	public manageDialog!: Dialog;

	private actionButton!: Button;

	private topText!: string;

	private bottomText!: string;

	private actionIsSave!: boolean;

	private static GridTableControl: typeof GridTable;

	private static GridTableColumnControl: typeof GridTableColumn;

	constructor(view: FEView, isSave: boolean) {
		this.actionIsSave = isSave;
		this.containingView = view;
		this.discardResourceModel = ResourceModelHelper.getResourceModel(view);
		if (isSave) {
			this.actionButton = this.getSaveButton();
			this.topText = this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_EDITING_DRAFT");
			this.bottomText = this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_SAVE_WARNING");
		} else {
			this.actionButton = this.getDiscardButton();
			this.topText = this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_CHANGES_DRAFT");
			this.bottomText = this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_DISCARD_WARNING");
		}
	}

	static async load(): Promise<typeof CollaborationDiscard> {
		if (CollaborationDiscard.GridTableControl === undefined) {
			// Required before usage to ensure the library is loaded and not each file individually
			try {
				await Core.loadLibrary("sap.ui.table", { async: true });
			} catch (e) {
				const errorMessage = `Couldn't load building block sap.ui.table please make sure the following libraries are available sap.ui.table}`;
				Log.error(errorMessage);
				throw new Error(errorMessage);
			}
			const { default: GridTableControl } = await import("sap/ui/table/Table");
			CollaborationDiscard.GridTableControl = GridTableControl;
			const { default: GridTableColumnControl } = await import("sap/ui/table/Column");
			CollaborationDiscard.GridTableColumnControl = GridTableColumnControl;
		}
		return this;
	}

	/**
	 * Returns the manage dialog used to invite further users.
	 *
	 * @returns The control tree
	 */
	getManageDialog(): Dialog {
		this.manageDialog = (
			<Dialog
				title={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_TITLE")}
				state={ValueState.Warning}
				contentWidth="42rem"
			>
				{{
					buttons: (
						<>
							keepDraftButton = {this.getKeepDraftButton()}
							confirmActionButton = {this.actionButton}
							cancelButton = {this.getCancelButton()}
						</>
					),
					content: (
						<VBox class="sapUiSmallMargin">
							<ObjectIdentifier class="sapUiSmallMarginBottom" text={this.topText}></ObjectIdentifier>

							{this.getManageDialogUserTable()}

							<Text class="sapUiSmallMarginTop" text={this.bottomText}></Text>
							<Text
								class="sapUiSmallMarginTop"
								text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_QUESTION")}
							></Text>
						</VBox>
					)
				}}
			</Dialog>
		);
		this.containingView.addDependent(this.manageDialog);
		this.manageDialog.bindElement({
			model: "internal",
			path: "collaboration"
		});
		return this.manageDialog;
	}

	/**
	 * Returns the table columns of invited users.
	 *
	 * @returns The control tree
	 */
	getManageDialogUserTableColumns(): GridTableColumn[] {
		return (
			<>
				<CollaborationDiscard.GridTableColumnControl width="3em">
					{{
						template: (
							<HBox alignItems="Center" justifyContent="SpaceBetween" width="100%">
								<Avatar displaySize="XS" backgroundColor="{internal>color}" initials="{internal>initials}" />
							</HBox>
						)
					}}
				</CollaborationDiscard.GridTableColumnControl>
				<CollaborationDiscard.GridTableColumnControl width="10rem">
					{{
						label: <Text text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_INVITATION_TABLE_USER_COLUMN")} />,
						template: <Text text="{internal>name}" />
					}}
				</CollaborationDiscard.GridTableColumnControl>
				<CollaborationDiscard.GridTableColumnControl width="14em">
					{{
						label: (
							<Text text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_INVITATION_TABLE_USER_STATUS_COLUMN")} />
						),
						template: (
							<ObjectStatus
								state={{ path: "internal>status", formatter: this.formatUserStatusColor }}
								text={{ path: "internal>status", formatter: this.formatUserStatus }}
							/>
						)
					}}
				</CollaborationDiscard.GridTableColumnControl>
			</>
		);
	}

	/**
	 * Returns the table with the list of invited users.
	 *
	 * @returns The control tree
	 */
	getManageDialogUserTable(): GridTable {
		const viewInternalModelContext = this.containingView.getBindingContext("internal") as InternalModelContext;
		const editingUsers = viewInternalModelContext.getProperty("collaboration/currentlyEditingUsers");
		let tableRowCount = 5;
		if (CommonHelper.isDesktop()) {
			tableRowCount = editingUsers.length < 5 ? editingUsers.length : 5;
		} else {
			tableRowCount = editingUsers.length < 3 ? editingUsers.length : 3;
		}

		return (
			<CollaborationDiscard.GridTableControl
				width="100%"
				rows={{ path: "internal>currentlyEditingUsers" }}
				visibleRowCount={tableRowCount}
				visibleRowCountMode="Fixed"
				selectionMode="None"
			>
				{{
					columns: this.getManageDialogUserTableColumns()
				}}
			</CollaborationDiscard.GridTableControl>
		);
	}

	/**
	 * Formatter to set the user color depending on the editing status.
	 *
	 * @param userStatus The editing status of the user
	 * @returns The user status color
	 */
	formatUserStatusColor(userStatus: UserStatus): ValueState {
		switch (userStatus) {
			case UserStatus.CurrentlyEditing:
				return ValueState.Success;
			case UserStatus.ChangesMade:
				return ValueState.Warning;
			case UserStatus.NoChangesMade:
			case UserStatus.NotYetInvited:
			default:
				return ValueState.Information;
		}
	}

	/**
	 * Formatter to set the user status depending on the editing status.
	 *
	 * @param userStatus The editing status of the user
	 * @returns The user status
	 */
	formatUserStatus = (userStatus: UserStatus): string => {
		switch (userStatus) {
			case UserStatus.CurrentlyEditing:
				return this.discardResourceModel.getText("C_COLLABORATIONDRAFT_USER_CURRENTLY_EDITING");
			case UserStatus.ChangesMade:
				return this.discardResourceModel.getText("C_COLLABORATIONDRAFT_USER_CHANGES_MADE");
			case UserStatus.NoChangesMade:
				return this.discardResourceModel.getText("C_COLLABORATIONDRAFT_USER_NO_CHANGES_MADE");
			case UserStatus.NotYetInvited:
			default:
				return this.discardResourceModel.getText("C_COLLABORATIONDRAFT_USER_NOT_YET_INVITED");
		}
	};

	/**
	 * Reads the currently invited user and store it in the internal model.
	 *
	 * @param view The current view
	 * @returns Promise that is resolved once the users are read.
	 */
	async readInvitedUsers(): Promise<void> {
		const view = this.containingView;
		const model = view.getModel();
		const parameters = {
			$select: "UserID,UserDescription,UserEditingState"
		};
		const invitedUserList = model.bindList(
			"DraftAdministrativeData/DraftAdministrativeUser",
			view.getBindingContext() as Context,
			[],
			[],
			parameters
		);
		const me = CollaborationUtils.getMe(view);
		const internalModelContext = view.getBindingContext("internal") as InternalModelContext;
		if (me) {
			invitedUserList.filter(
				new Filter({
					path: "UserID",
					operator: "NE",
					value1: me.id
				})
			);
		}

		// for now we set a limit to 100. there shouldn't be more than a few
		return invitedUserList.requestContexts(0, 100).then(function (contexts) {
			const editingUsers: User[] = [];
			const activeUsers = view.getModel("internal").getProperty("/collaboration/activeUsers") || [];
			let userStatus: UserStatus;
			if (contexts?.length > 0) {
				contexts.forEach(function (oContext) {
					const userData = oContext.getObject() as BackendUser;
					const isActive = activeUsers.find((u: User) => u.id === userData.UserID);
					const userDescription = userData.UserDescription || userData.UserID;
					const initials = CollaborationUtils.formatInitials(userDescription);
					if (isActive) {
						userStatus = UserStatus.CurrentlyEditing;
					} else if (userData.UserEditingState === UserEditingState.InProgress) {
						userStatus = UserStatus.ChangesMade;
					} else {
						userStatus = UserStatus.NoChangesMade;
					}

					const user: User = {
						id: userData.UserID,
						name: userDescription,
						status: userStatus,
						color: CollaborationUtils.getUserColor(userData.UserID, activeUsers, editingUsers),
						initials: initials
					};
					editingUsers.push(user);
				});
			}
			internalModelContext.setProperty("collaboration/currentlyEditingUsers", editingUsers);
			return;
		});
	}

	/**
	 * Returns the Save button.
	 *
	 * @returns A button
	 */
	private getSaveButton(): Button {
		return <Button text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_SAVE")} press={this.saveManageDialog} />;
	}

	/**
	 * Event handler for the Save action of the manage dialog.
	 *
	 */
	saveManageDialog = (): void => {
		this.promiseResolve("save");
		this.manageDialog.close();
		this.manageDialog.destroy();
	};

	/**
	 * Returns the Discard button.
	 *
	 * @returns A button
	 */
	private getDiscardButton(): Button {
		return <Button text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_DISCARD")} press={this.discardManageDialog} />;
	}

	/**
	 * Event handler for the Discard action of the manage dialog.
	 *
	 */
	private discardManageDialog = (): void => {
		this.promiseResolve("discardConfirmed");
		this.manageDialog.close();
		this.manageDialog.destroy();
	};

	/**
	 * Returns the Cancel button.
	 *
	 * @returns A button
	 */
	private getCancelButton(): Button {
		return <Button text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_CANCEL")} press={this.cancelManageDialog} />;
	}

	/**
	 * Event handler for the Cancel action of the manage dialog.
	 *
	 */
	private cancelManageDialog = (): void => {
		this.promiseResolve("cancel");
		this.manageDialog.close();
		this.manageDialog.destroy();
	};

	/**
	 * Returns the Save button.
	 *
	 * @returns A button
	 */
	private getKeepDraftButton(): Button {
		return (
			<Button
				text={this.discardResourceModel.getText("C_COLLABORATIONDRAFT_DISCARD_KEEP_DRAFT")}
				press={this.keepDraftManageDialog}
				type="Emphasized"
			/>
		);
	}

	/**
	 * Event handler for the Keep Draft action of the manage dialog.
	 *
	 */
	private keepDraftManageDialog = (): void => {
		this.promiseResolve("keepDraft");
		this.manageDialog.close();
		this.manageDialog.destroy();
	};

	/**
	 * Reads the users, and opens the dialog to get the user input.
	 *
	 * @returns A string of the action selected by the user
	 */
	async getUserAction(): Promise<string> {
		await this.readInvitedUsers();
		return this.open();
	}

	/**
	 * Opens the discard draft from Discard/Cancel action.
	 *
	 * @returns A string of the action selected by the user
	 */
	public async open(): Promise<string> {
		const internalModelContext = this.containingView.getBindingContext("internal") as InternalModelContext;
		const editingUsers = internalModelContext.getProperty("collaboration/currentlyEditingUsers");
		if (editingUsers.length === 0) {
			return this.actionIsSave ? "save" : "discard";
		}
		// We create the dialog after reading the users
		this.manageDialog = this.getManageDialog();
		// We set up the binding context of the Dialog
		(this.manageDialog.getBindingContext("internal") as InternalModelContext).setProperty("currentlyEditingUsers", editingUsers);
		this.manageDialog.open();

		return new Promise((resolve) => {
			this.promiseResolve = resolve;
		});
	}
}
