"""Whitelisted-method overrides that filter desk data server-side.

Wired in hooks.py via override_whitelisted_methods. Each wrapper calls the
original Frappe implementation, then strips workspaces / cards / links that the
desk layout config marks hidden or blocked — before the payload reaches the
browser, so there is no flash of soon-to-be-hidden items.

Importing the originals by module path bypasses the override map and always
yields Frappe's real implementation.

NOTE: resolve the originals defensively with getattr — `get_workspace_sidebar_items`
does not exist in every Frappe version (e.g. v14). A hard `from ... import` would
raise ImportError and break the WHOLE module, taking the get_desktop_page override
down with it.
"""

import json

import frappe
from frappe import _

from frappe.desk import desktop as _desktop

from jey_theme import desk_layout

_orig_get_desktop_page = getattr(_desktop, "get_desktop_page", None)
_orig_get_workspace_sidebar_items = getattr(_desktop, "get_workspace_sidebar_items", None)


def _log(message):
	try:
		frappe.log_error(message, "Jey Desk Overrides")
	except Exception:
		pass


@frappe.whitelist()
def get_workspace_sidebar_items():
	if _orig_get_workspace_sidebar_items is None:
		return {"pages": []}  # method absent on this Frappe version; dead path
	result = _orig_get_workspace_sidebar_items()
	# Filtering must never break the desk: on any error, return the original.
	try:
		if not desk_layout.is_enforcing():
			return result
		hidden = desk_layout.hidden_or_blocked_workspaces()
		if not hidden:
			return result
		pages = result.get("pages")
		if isinstance(pages, list):
			result["pages"] = [p for p in pages if p.get("name") not in hidden]
	except Exception:
		_log("get_workspace_sidebar_items filter failed")
	return result


def _workspace_name_from_page(page) -> str | None:
	if isinstance(page, str):
		try:
			page = json.loads(page)
		except Exception:
			return None
	if isinstance(page, dict):
		return page.get("name") or page.get("title")
	return None


def _link_key(link) -> str:
	return f'{link.get("link_type")}::{link.get("link_to")}'


def _cards_by_translated_label(items) -> dict:
	m = {}
	for c in items:
		m.setdefault(c.get("label"), c)
	return m


def _added_allowed(link_type, link_to) -> bool:
	"""Don't surface an injected link to a user who can't access it."""
	try:
		if link_type == "DocType":
			return frappe.has_permission(link_to, "read")
		if link_type == "Report":
			return frappe.has_permission("Report", doc=link_to)
		return True  # Page (own role checks) and anything else
	except Exception:
		return True


def _build_link_item(link_type, link_to, label) -> dict:
	"""Construct a link item shaped like frappe's get_links output so the
	client LinksWidget renders it natively (route is computed client-side)."""
	item = {
		"type": "Link",
		"link_type": link_type,
		"link_to": link_to,
		"label": label or link_to,
		"dependencies": "",
		"onboard": 0,
		"is_query_report": 0,
	}
	if link_type == "Report":
		try:
			vals = frappe.db.get_value("Report", link_to, ["report_type", "ref_doctype"])
			if vals:
				report_type, ref_doctype = vals
				if report_type in ("Query Report", "Script Report", "Custom Report"):
					item["is_query_report"] = 1
				else:
					# Report-builder report: client uses `dependencies` as the doctype.
					item["dependencies"] = ref_doctype or ""
		except Exception:
			pass
	return item


def _inject_added(ws_name, items):
	by_label = _cards_by_translated_label(items)
	for a in desk_layout.added_links(ws_name):
		card = by_label.get(_(a["card"]))
		if not card:
			continue  # target card not present (empty/custom) — v1 limitation
		links = card.get("links")
		if not isinstance(links, list):
			links = card["links"] = []
		key = f'{a["link_type"]}::{a["link_to"]}'
		if any(_link_key(l) == key for l in links):
			continue
		if not _added_allowed(a["link_type"], a["link_to"]):
			continue
		item = _build_link_item(a["link_type"], a["link_to"], a.get("label"))
		item["label"] = _(item["label"])  # output labels are translated
		links.append(item)


def _reorder(ws_name, items):
	by_label = _cards_by_translated_label(items)
	for label, keys in desk_layout.order_map(ws_name).items():
		card = by_label.get(_(label))
		if card and isinstance(card.get("links"), list):
			card["links"] = desk_layout.apply_order(card["links"], keys, _link_key)


def _filter_hide_block(ws_name, items) -> list:
	# Output card labels are translated; forward-translate config labels to match.
	hidden_card_set = {_(label) for label in desk_layout.hidden_card_labels(ws_name)}
	new_items = []
	for card in items:
		if card.get("label") in hidden_card_set:
			continue
		links = card.get("links")
		if isinstance(links, list):
			card["links"] = [
				link
				for link in links
				if not desk_layout.should_hide_link(ws_name, link.get("link_type"), link.get("link_to"))
			]
			if not card["links"]:
				continue  # drop a card emptied purely by hiding
		new_items.append(card)
	return new_items


@frappe.whitelist()
def get_desktop_page(page):
	if _orig_get_desktop_page is None:
		return {}  # method absent on this Frappe version; dead path
	result = _orig_get_desktop_page(page)
	# Transforms must never break the desk: on any error, return the original.
	try:
		ws_name = _workspace_name_from_page(page)
		cards = result.get("cards") if isinstance(result, dict) else None
		if ws_name and isinstance(cards, dict) and isinstance(cards.get("items"), list):
			items = cards["items"]
			_inject_added(ws_name, items)  # always (structural)
			_reorder(ws_name, items)  # always (structural)
			if desk_layout.is_enforcing():
				cards["items"] = _filter_hide_block(ws_name, items)
	except Exception:
		_log("get_desktop_page transform failed")
	return result
