import onScan from "onscan.js";

erpnext.PointOfSale.ItemSelector = class {
	// eslint-disable-next-line no-unused-vars
	constructor({ frm, wrapper, events, pos_profile, settings }) {
		this.wrapper = wrapper;
		this.events = events;
		this.pos_profile = pos_profile;
		this.hide_images = settings.hide_images;
		this.item_display_class = this.hide_images ? "hide-item-image" : "show-item-image";
		this.auto_add_item = settings.auto_add_item_to_cart;

		this.item_ready_group = this.get_parent_item_group();
		this.inti_component();
	}

	inti_component() {
		this.prepare_dom();
		this.make_search_bar();
		this.load_items_data();
		this.bind_events();
		this.attach_shortcuts();
	}

	prepare_dom() {
		this.wrapper.append(
			`<section class="items-selector">
				<div class="filter-section">
					<div class="label">${__("All Items")}</div>
					<div class="search-field"></div>
					<div class="item-group-field"></div>
				</div>
				<div class="items-container"></div>
			</section>`
		);

		this.$component = this.wrapper.find(".items-selector");
		this.$items_container = this.$component.find(".items-container");

		this.$items_container.addClass(this.item_display_class);
	}

	async get_parent_item_group() {
		const r = await frappe.call({
			method: "erpnext.selling.page.point_of_sale.point_of_sale.get_parent_item_group",
			args: {
				pos_profile: this.pos_profile,
			},
		});
		if (r.message) this.item_group = this.parent_item_group = r.message;
	}

	async load_items_data() {
		await this.item_ready_group;

		this.start_item_loading_animation();

		if (!this.price_list) {
			const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");
			this.price_list = res.message.selling_price_list;
		}

		this.get_items({})
			.then(({ message }) => {
				this.render_item_list(message.items);
			})
			.always(() => {
				this.stop_item_loading_animation();
			});
	}

	get_items({ start = 0, page_length = 40, search_term = "" }) {
		const doc = this.events.get_frm().doc;
		const price_list = (doc && doc.selling_price_list) || this.price_list;
		let { item_group, pos_profile } = this;

		return frappe.call({
			method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
			freeze: true,
			args: { start, page_length, price_list, item_group, search_term, pos_profile },
		});
	}

	render_item_list(items) {
		this.$items_container.html("");

		if (!items?.length) {
			this.set_items_not_found_banner();
			return;
		}

		if (this.$items_container.hasClass("items-not-found")) {
			this.$items_container.removeClass("items-not-found");
			this.$items_container.addClass(this.item_display_class);
		}

		if (this.hide_images) {
			this.$items_container.append(this.render_item_list_column_header());
		}

		items?.forEach((item) => {
			const item_html = this.get_item_html(item);
			this.$items_container.append(item_html);
		});
	}

	set_items_not_found_banner() {
		this.$items_container.removeClass(this.item_display_class);
		this.$items_container.addClass("items-not-found");
		this.$items_container.html(__("Items not found."));
	}

	render_item_list_column_header() {
		return `<div class="list-column">
			<div class="column-name">${__("Name")}</div>
			<div class="column-price">${__("Price")}</div>
			<div class="column-uom">${__("UOM")}</div>
			<div class="column-qty-available">${__("Quantity Available")}</div>
		</div>`;
	}

	get_item_html(item) {
		const me = this;
		// eslint-disable-next-line no-unused-vars
		function sanitize_item_data(item) {
			return Object.fromEntries(
				Object.entries(item).map(([key, value]) => [
					key,
					typeof value === "string" ? frappe.utils.escape_html(value) : value,
				])
			);
		}
		const sanitize_item = sanitize_item_data(item);
		const {
			item_code,
			stock_uom,
			item_name,
			item_image,
			serial_no,
			batch_no,
			barcode,
			actual_qty,
			uom,
			price_list_rate,
		} = sanitize_item;
		const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
		let indicator_color;
		let qty_to_display = actual_qty;

		if (item.is_stock_item) {
			indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";

			if (Math.round(qty_to_display) > 999) {
				qty_to_display = Math.round(qty_to_display) / 1000;
				qty_to_display = qty_to_display.toFixed(1) + "K";
			}
		} else {
			indicator_color = "";
			qty_to_display = "";
		}

		function get_item_image_html() {
			if (me.hide_images) return "";
			if (item_image) {
				return `<div class="item-qty-pill">
							<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
						</div>
						<div class="item-display">
							<img
								onerror="cur_pos.item_selector.handle_broken_image(this)"
								class="item-img" src="${item_image}"
								alt="${item_name}"
							>
						</div>`;
			} else {
				return `<div class="item-qty-pill">
							<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
						</div>
						<div class="item-display abbr">${frappe.get_abbr(item_name)}</div>`;
			}
		}

		return `<div class="item-wrapper"
				data-item-code="${item_code}" data-serial-no="${serial_no}"
				data-batch-no="${batch_no}" data-uom="${uom}"
				data-rate="${price_list_rate || 0}"
				data-stock-uom="${stock_uom}"
				title="${item_name}">

				${get_item_image_html()}

				<div class="item-detail">
					<div class="item-name">
						${!me.hide_images ? frappe.ellipsis(item_name, 18) : item_name}
					</div>
					${
						!me.hide_images
							? `<div class="item-rate">
								${frappe.utils.escape_html(format_currency(price_list_rate, item.currency, precision)) || 0} / ${uom}
							</div>`
							: `
							<div class="item-price">${
								frappe.utils.escape_html(
									format_currency(price_list_rate, item.currency, precision)
								) || 0
							}</div>
							<div class="item-uom">${uom}</div>
							<div class="item-qty-available">${qty_to_display || "Non stock item"}</div>
							`
					}
				</div>
			</div>`;
	}

	handle_broken_image($img) {
		const item_abbr = frappe.utils.escape_html($($img).attr("alt"));
		$($img).parent().replaceWith(`<div class="item-display abbr">${item_abbr}</div>`);
	}

	make_search_bar() {
		const me = this;
		this.$component.find(".search-field").html("");
		this.$component.find(".item-group-field").html("");

		this.search_field = frappe.ui.form.make_control({
			df: {
				label: __("Search"),
				fieldtype: "Data",
				placeholder: __("Search by item code, serial number or barcode"),
			},
			parent: this.$component.find(".search-field"),
			render_input: true,
		});
		this.item_group_field = frappe.ui.form.make_control({
			df: {
				label: __("Item Group"),
				fieldtype: "Link",
				options: "Item Group",
				placeholder: __("Select item group"),
				only_select: true,
				onchange: function () {
					me.item_group = this.value;
					!me.item_group && (me.item_group = me.parent_item_group);
					me.filter_items();
					me.set_item_selector_filter_label(this.value);
				},
				get_query: function () {
					return {
						query: "erpnext.selling.page.point_of_sale.point_of_sale.item_group_query",
						filters: {
							pos_profile: me.pos_profile,
						},
					};
				},
			},
			parent: this.$component.find(".item-group-field"),
			render_input: true,
		});
		this.search_field.toggle_label(false);
		this.item_group_field.toggle_label(false);

		$(this.item_group_field.awesomplete.ul).css("min-width", "unset");

		this.hide_open_link_btn();
		this.attach_clear_btn();
	}

	set_item_selector_filter_label(value) {
		const $filter_label = this.$component.find(".label");

		$filter_label.html(value ? frappe.utils.escape_html(__(value)) : __("All Items"));
	}

	hide_open_link_btn() {
		$(this.item_group_field.$wrapper.find(".btn-open")).css("display", "none");
	}

	attach_clear_btn() {
		this.search_field.$wrapper.find(".control-input").append(
			`<span class="link-btn">
				<a class="btn-open no-decoration" title="${__("Clear")}">
					${frappe.utils.icon("close", "sm")}
				</a>
			</span>`
		);

		this.item_group_field.$wrapper.find(".link-btn").append(
			`<a class="btn-clear" tabindex="-1" style="display: inline-block;" title="${__("Clear Link")}">
				${frappe.utils.icon("close", "xs", "es-icon")}
			</a>`
		);

		this.$clear_search_btn = this.search_field.$wrapper.find(".link-btn");
		this.$clear_item_group_btn = this.item_group_field.$wrapper.find(".btn-clear");

		this.$clear_search_btn.on("click", "a", () => {
			this.set_search_value("");
			this.search_field.set_focus();
		});

		this.$clear_item_group_btn.on("click", () => {
			$(this.item_group_field.$input[0]).val("").trigger("input");
			this.item_group_field.set_focus();
		});
	}

	set_search_value(value) {
		$(this.search_field.$input[0]).val(value).trigger("input");
	}

	bind_events() {
		const me = this;
		window.onScan = onScan;

		onScan.decodeKeyEvent = function (oEvent) {
			var iCode = this._getNormalizedKeyNum(oEvent);
			switch (true) {
				case iCode >= 48 && iCode <= 90: // numbers and letters
				case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.)
				case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
				case iCode >= 186 && iCode <= 194: // (; = , - . / `)
				case iCode >= 219 && iCode <= 222: // ([ \ ] ')
				case iCode == 32: // spacebar
					if (oEvent.key !== undefined && oEvent.key !== "") {
						return oEvent.key;
					}

					var sDecoded = String.fromCharCode(iCode);
					switch (oEvent.shiftKey) {
						case false:
							sDecoded = sDecoded.toLowerCase();
							break;
						case true:
							sDecoded = sDecoded.toUpperCase();
							break;
					}
					return sDecoded;
				case iCode >= 96 && iCode <= 105: // numbers on numeric keypad
					return 0 + (iCode - 96);
			}
			return "";
		};

		onScan.attachTo(document, {
			onScan: (sScancode) => {
				if (this.search_field && this.$component.is(":visible")) {
					this.search_field.set_focus();
					this.set_search_value(sScancode);
					this.barcode_scanned = true;
				}
			},
		});

		this.$component.on("click", ".item-wrapper", function () {
			const $item = $(this);
			const item_code = $item.attr("data-item-code");
			let batch_no = $item.attr("data-batch-no");
			let serial_no = $item.attr("data-serial-no");
			let uom = $item.attr("data-uom");
			let rate = $item.attr("data-rate");
			let stock_uom = $item.attr("data-stock-uom");

			// escape(undefined) returns "undefined" then unescape returns "undefined"
			batch_no = batch_no === "undefined" ? undefined : batch_no;
			serial_no = serial_no === "undefined" ? undefined : serial_no;
			uom = uom === "undefined" ? undefined : uom;
			rate = rate === "undefined" ? undefined : rate;
			stock_uom = stock_uom === "undefined" ? undefined : stock_uom;

			me.events.item_selected({
				field: "qty",
				value: "+1",
				item: { item_code, batch_no, serial_no, uom, rate, stock_uom },
			});
		});

		this.search_field.$input.on("input", (e) => {
			clearTimeout(this.last_search);
			this.last_search = setTimeout(() => {
				const search_term = e.target.value;
				this.filter_items({ search_term });
			}, 300);

			this.$clear_search_btn.toggle(Boolean(this.search_field.$input.val()));
		});

		this.search_field.$input.on("focus", () => {
			this.$clear_search_btn.toggle(Boolean(this.search_field.$input.val()));
		});
	}

	attach_shortcuts() {
		const ctrl_label = frappe.utils.is_mac() ? "⌘" : "Ctrl";
		this.search_field.parent.attr("title", `${ctrl_label}+I`);
		frappe.ui.keys.add_shortcut({
			shortcut: "ctrl+i",
			action: () => this.search_field.set_focus(),
			condition: () => this.$component.is(":visible"),
			description: __("Focus on search input"),
			ignore_inputs: true,
			page: cur_page.page.page,
		});
		this.item_group_field.parent.attr("title", `${ctrl_label}+G`);
		frappe.ui.keys.add_shortcut({
			shortcut: "ctrl+g",
			action: () => this.item_group_field.set_focus(),
			condition: () => this.$component.is(":visible"),
			description: __("Focus on Item Group filter"),
			ignore_inputs: true,
			page: cur_page.page.page,
		});

		// for selecting the last filtered item on search
		frappe.ui.keys.on("enter", () => {
			const selector_is_visible = this.$component.is(":visible");
			if (!selector_is_visible || this.search_field.get_value() === "") return;

			if (this.items.length == 1) {
				this.$items_container.find(".item-wrapper").click();
				frappe.utils.play_sound("submit");
				this.set_search_value("");
			} else if (this.items.length == 0 && this.barcode_scanned) {
				// only show alert of barcode is scanned and enter is pressed
				frappe.show_alert({
					message: __("No items found. Scan barcode again."),
					indicator: "orange",
				});
				frappe.utils.play_sound("error");
				this.barcode_scanned = false;
				this.set_search_value("");
			}
		});
	}

	filter_items({ search_term = "" } = {}) {
		this.start_item_loading_animation();

		const selling_price_list = this.events.get_frm().doc.selling_price_list;

		if (search_term) {
			search_term = search_term.toLowerCase();

			// memoize
			this.search_index = this.search_index || {};
			this.search_index[selling_price_list] = this.search_index[selling_price_list] || {};
			if (this.search_index[selling_price_list][search_term]) {
				const items = this.search_index[selling_price_list][search_term];
				this.items = items;
				this.render_item_list(items);
				this.auto_add_item &&
					this.search_field.$input[0].value &&
					this.items.length == 1 &&
					this.add_filtered_item_to_cart();
				return;
			}
		}

		this.get_items({ search_term })
			.then(({ message }) => {
				// eslint-disable-next-line no-unused-vars
				const { items, serial_no, batch_no, barcode } = message;
				if (search_term && !barcode) {
					this.search_index[selling_price_list][search_term] = items;
				}
				this.items = items;
				this.render_item_list(items);
				this.auto_add_item &&
					this.search_field.$input[0].value &&
					this.items.length == 1 &&
					this.add_filtered_item_to_cart();
			})
			.always(() => {
				this.stop_item_loading_animation();
			});
	}

	start_item_loading_animation() {
		this.$items_container.addClass("is-loading");
	}

	stop_item_loading_animation() {
		this.$items_container.removeClass("is-loading");
	}

	add_filtered_item_to_cart() {
		this.$items_container.find(".item-wrapper").click();
		this.set_search_value("");
	}

	toggle_component(show) {
		this.set_search_value("");
		this.$component.css("display", show ? "flex" : "none");
	}
};
