import Log from "sap/base/Log";
import { defineBuildingBlock } from "sap/fe/core/buildingBlocks/BuildingBlockSupport";
import RuntimeBuildingBlock from "sap/fe/core/buildingBlocks/RuntimeBuildingBlock";
import * as MetaModelConverter from "sap/fe/core/converters/MetaModelConverter";
import type { PropertiesOf } from "sap/fe/core/helpers/ClassSupport";
import { defineReference } from "sap/fe/core/helpers/ClassSupport";
import { getResourceModel } from "sap/fe/core/helpers/ResourceModelHelper";
import type { Ref } from "sap/fe/core/jsx-runtime/jsx";
import Button from "sap/m/Button";
import CustomListItem from "sap/m/CustomListItem";
import Dialog from "sap/m/Dialog";
import FlexItemData from "sap/m/FlexItemData";
import HBox from "sap/m/HBox";
import List from "sap/m/List";
import MessageStrip from "sap/m/MessageStrip";
import Text from "sap/m/Text";
import Toolbar from "sap/m/Toolbar";
import ToolbarSpacer from "sap/m/ToolbarSpacer";
import type UI5Element from "sap/ui/core/Element";
import { ValueState } from "sap/ui/core/library";
import type View from "sap/ui/core/mvc/View";
import Sorter from "sap/ui/model/Sorter";
import JSONModel from "sap/ui/model/json/JSONModel";
import type { default as ODataV4Context } from "sap/ui/model/odata/v4/Context";
import type ODataMetaModel from "sap/ui/model/odata/v4/ODataMetaModel";
import type { EventHandler } from "types/extension_types";
import type PageController from "../../PageController";
import type ResourceModel from "../../ResourceModel";
import type { AcceptAllParams } from "../../controllerextensions/Recommendations";
import valueFormatters from "../../formatters/ValueFormatter";
import { standardRecommendationHelper, type StandardRecommendationAdditionalValues } from "../../helpers/StandardRecommendationHelper";
export enum RecommendationDialogDecision {
	Accept = "Accept_Recommendations",
	Ignore = "Ignore_Recommendations",
	Continue = "Continue_Editing",
	Skipped = "Skipped"
}

@defineBuildingBlock({
	name: "ConfirmRecommendationDialog",
	namespace: "sap.fe.core.controllerextensions"
})
export class ConfirmRecommendationDialogBlock extends RuntimeBuildingBlock {
	constructor(props: PropertiesOf<ConfirmRecommendationDialogBlock>) {
		super(props);
		this.view = props.view as View;
		this.confirmRecommendationDialogResourceModel = getResourceModel(this.view);
	}

	@defineReference()
	confirmRecommendationDialog!: Ref<Dialog>;

	public view!: View;

	private confirmRecommendationDialogResourceModel!: ResourceModel;

	protected key!: string;

	private isSave!: boolean;

	private acceptAllParams!: AcceptAllParams;

	/**
	 * Resolves the promise with the selected dialog list option
	 */
	private promiseResolve!: Function;

	/**
	 * Rejects the promise of open dialog
	 */
	private promiseReject!: Function;

	/**
	 * Opens the confirm recommendations dialog.
	 *
	 * @param isSave Boolean flag which would be set to true if we are saving the document and would be false if we do apply changes
	 * @returns Promise which would resolve with RecommendationDialogDecision (Accept, Ignore, Continue, Skipped)
	 */
	public async open(isSave: boolean): Promise<RecommendationDialogDecision> {
		this.acceptAllParams = await (this.view?.getController() as PageController).recommendations.fetchAcceptAllParams();
		const acceptModel = this.getAcceptAllModel();
		this.view.setModel(acceptModel, "_acceptDialogModel");
		if (!acceptModel.getData().items?.length) {
			return RecommendationDialogDecision.Skipped;
		}
		this.isSave = isSave;
		const dialog = this.getContent();
		dialog?.setEscapeHandler(this.onContinueEditing.bind(this));
		this.view.addDependent(dialog as UI5Element);
		dialog?.open();
		return new Promise((resolve, reject) => {
			this.promiseResolve = resolve;
			this.promiseReject = reject;
		});
	}

	/**
	 * Handler to close the confirmRecommendation dialog.
	 *
	 */
	public close(): void {
		this.confirmRecommendationDialog.current?.close();
		this.confirmRecommendationDialog.current?.destroy();
	}

	/**
	 * Handler for Accept and Save button.
	 */
	private async onAcceptAndSave(): Promise<void> {
		try {
			const isAccepted = await (this.view?.getController() as PageController).recommendations.acceptRecommendations(
				this.acceptAllParams
			);
			if (!isAccepted) {
				this.promiseReject("Accept Failed");
			}
			this.promiseResolve(RecommendationDialogDecision.Accept);
		} catch {
			Log.error("Accept Recommendations Failed");
			this.promiseReject("Accept Failed");
		} finally {
			this.close();
		}
	}

	/**
	 * Handler for Ignore and Save button.
	 */
	private onIgnoreAndSave(): void {
		(this.view?.getController() as PageController).recommendations.clearRecommendationForContexts();
		this.promiseResolve(RecommendationDialogDecision.Ignore);
		this.close();
	}

	/**
	 * Handler for Continue Editing button.
	 */
	private onContinueEditing(): void {
		this.promiseResolve(RecommendationDialogDecision.Continue);
		this.close();
	}

	/**
	 * Gets the label or name of the Field based on its property path.
	 *
	 * @param targetPath
	 * @returns Returns the label of the Field.
	 */
	private getFieldName(targetPath: string): string {
		const involvedDataModelObject = MetaModelConverter.getInvolvedDataModelObjectsForTargetPath(
			targetPath,
			this.view?.getBindingContext()?.getModel()?.getMetaModel() as ODataMetaModel
		);
		return involvedDataModelObject?.targetObject?.annotations?.Common?.Label || targetPath.split("/")[targetPath.split("/").length - 1];
	}
	/**
	 * Fetches text for recommendation based on display mode.
	 *
	 * @param recommendation
	 * @param displayMode
	 * @returns Text for a recommendation
	 */
	private getText(recommendation: StandardRecommendationAdditionalValues, displayMode: string): string {
		if (recommendation.text && recommendation.value) {
			switch (displayMode) {
				case "Description":
					return recommendation.text;
				case "DescriptionValue":
					return valueFormatters.formatWithBrackets(recommendation.text, recommendation.value);
				case "ValueDescription":
					return valueFormatters.formatWithBrackets(recommendation.value, recommendation.text);
				case "Value":
					return recommendation.value;
			}
		}
		return recommendation.value || "";
	}

	/**
	 * Returns Button with given text, type and pressHandler.
	 *
	 * @param text Text for the button
	 * @param type Type of the button
	 * @param pressHandler Press Handler for the button
	 * @returns Button with the given settings
	 */
	private getButton(text: string, type: string, pressHandler: EventHandler<object>): Button {
		return (
			<Button text={text} type={type} width={"auto"} press={pressHandler}>
				{{
					layoutData: (
						<>
							<FlexItemData minWidth={"100%"}></FlexItemData>
						</>
					)
				}}
			</Button>
		);
	}

	/**
	 * Returns Footer with Buttons.
	 *
	 * @returns Footer
	 */

	private getFooter(): Toolbar {
		return (
			<Toolbar>
				{{
					content: (
						<>
							<ToolbarSpacer />
							{this.getButton(
								this.isSave
									? this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_ACCEPT_AND_SAVE")
									: this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_ACCEPT"),
								"Emphasized",
								this.onAcceptAndSave.bind(this)
							)}
							{this.getButton(
								this.isSave
									? this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_IGNORE_AND_SAVE")
									: this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_IGNORE"),
								"Ghost",
								this.onIgnoreAndSave.bind(this)
							)}
							{this.getButton(
								this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_CONTINUE_EDITING"),
								"Ghost",
								this.onContinueEditing.bind(this)
							)}
						</>
					)
				}}
			</Toolbar>
		);
	}

	/**
	 * This method created a JSON Model for the accept all dialog data.
	 * @returns The JSON Model for accept all dialog.
	 */
	getAcceptAllModel(): JSONModel {
		const acceptModel = new JSONModel();
		const items = [];
		for (const recommendationData of this.acceptAllParams?.recommendationData || []) {
			const entityName = standardRecommendationHelper.getEntityName(recommendationData.context as ODataV4Context) || "";
			const identifierTexts =
				recommendationData.contextIdentifierText && recommendationData.contextIdentifierText.length > 0
					? entityName + " (" + recommendationData.contextIdentifierText + " )"
					: entityName;

			if (recommendationData.value || recommendationData.text) {
				const targetPath = recommendationData.context?.getPath() + "/" + recommendationData.propertyPath;
				const displayMode = standardRecommendationHelper.getDisplayModeForTargetPath(
					targetPath,
					this.view?.getBindingContext()?.getModel()?.getMetaModel() as ODataMetaModel
				);
				const listData = {
					fieldName: this.getFieldName(targetPath).valueOf(),
					fieldValue: this.getText(recommendationData, displayMode),
					identifierTexts: identifierTexts
				};
				items.push(listData);
			}
		}
		acceptModel.setData({ items: items });
		return acceptModel;
	}

	/**
	 * This method groups the contexts according to the identifier texts.
	 * @param context The context of the item
	 * @returns The text of the group
	 */
	getGroup(context: ODataV4Context): string {
		return context.getProperty("identifierTexts");
	}

	/**
	 * This function returns the message strip to be shown in the accept dialog.
	 * @returns A message strip with the desired text.
	 */
	getMessageStrip(): MessageStrip {
		const acceptAllData = (this.view.getModel("_acceptDialogModel") as JSONModel)?.getData()?.items;
		const messageStripText =
			acceptAllData.length > 1
				? this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_TEXT", [acceptAllData.length])
				: this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_TEXT_SINGULAR");
		return <MessageStrip text={messageStripText} />;
	}

	/**
	 * This method creates the content for the dialog.
	 * @returns A list as content of the dialog.
	 */
	getDialogContent(): List {
		return (
			<List items={{ model: "_acceptDialogModel", path: "/items", sorter: new Sorter("identifierTexts", false, this.getGroup) }}>
				<CustomListItem>
					<HBox class="sapUiSmallMarginBegin sapUiTinyMargin">
						<Text text="{_acceptDialogModel>fieldName}: {_acceptDialogModel>fieldValue}" />
					</HBox>
				</CustomListItem>
			</List>
		);
	}

	/**
	 * The building block render function.
	 *
	 * @returns An XML-based string
	 */
	getContent(): Dialog {
		return (
			<Dialog
				title={this.confirmRecommendationDialogResourceModel.getText("C_RECOMMENDATION_DIALOG_TITLE")}
				state={ValueState.Information}
				type={"Message"}
				ref={this.confirmRecommendationDialog}
				resizable={"true"}
				contentWidth={"35rem"}
			>
				{{
					content: (
						<>
							{this.getMessageStrip()}
							{this.getDialogContent()}
						</>
					),
					footer: <>{this.getFooter()}</>
				}}
			</Dialog>
		) as Dialog;
	}
}
