import sys
from collections.abc import Sequence
from typing import Literal

import frappe
from frappe import _
from frappe.utils import strip_html_tags
from frappe.utils.data import safe_decode


def msgprint(
	msg: str | Sequence[str] | Sequence[Sequence[str]],
	title: str | None = None,
	raise_exception: bool | type[Exception] | Exception = False,
	as_table: bool = False,
	as_list: bool = False,
	indicator: Literal["blue", "green", "orange", "red", "yellow"] | None = None,
	alert: bool = False,
	primary_action: dict | None = None,
	is_minimizable: bool = False,
	wide: bool = False,
	*,
	realtime=False,
	allow_dangerous_html=False,
) -> None:
	"""Print a message to the user (via HTTP response).
	Messages are sent in the `__server_messages` property in the
	response JSON and shown in a pop-up / modal.

	:param msg: Message.
	:param title: [optional] Message title. Default: "Message".
	:param raise_exception: [optional] Raise given exception and show message.
	:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
	:param as_list: [optional] If `msg` is a list, render as un-ordered list.
	:param primary_action: [optional] Bind a primary server/client side action.
	:param is_minimizable: [optional] Allow users to minimize the modal
	:param wide: [optional] Show wide modal
	:param realtime: Publish message immediately using websocket.
	:param allow_dangerous_html: Allow arbitrary HTML in message.
	"""
	import inspect

	from frappe.utils.html_utils import clean_html

	msg = safe_decode(msg)
	out = frappe._dict(message=msg)

	def _raise_exception():
		if raise_exception:
			if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
				exc = raise_exception(msg)
			elif isinstance(raise_exception, Exception):
				exc = raise_exception
				exc.args = (msg,)
			else:
				exc = frappe.ValidationError(msg)
			if out.__frappe_exc_id:
				exc.__frappe_exc_id = out.__frappe_exc_id
			raise exc

	if frappe.flags.mute_messages:
		_raise_exception()
		return

	if isinstance(msg, Sequence):
		if as_list:
			out.as_list = as_list
		else:
			out.as_table = as_table

	if not allow_dangerous_html:
		if out.as_list:
			out.message = [clean_html(cell) for cell in msg]
		elif out.as_table:
			out.message = [[clean_html(cell) for cell in row] for row in msg]
		else:
			out.message = clean_html(msg)

	if sys.stdin and sys.stdin.isatty():
		if out.as_list:
			msg = [strip_html_tags(cell) for cell in msg]
		elif out.as_table:
			msg = [[strip_html_tags(cell) for cell in row] for row in msg]
		else:
			msg = strip_html_tags(msg)

	if frappe.flags.print_messages and out.message:
		print(f"Message: {msg}")

	out.title = title or _("Message", context="Default title of the message dialog")

	if not indicator and raise_exception:
		indicator = "red"

	if indicator:
		out.indicator = indicator

	if is_minimizable:
		out.is_minimizable = is_minimizable

	if alert:
		out.alert = 1

	if raise_exception:
		out.raise_exception = 1
		out.__frappe_exc_id = frappe.generate_hash()

	if primary_action:
		out.primary_action = primary_action

	if wide:
		out.wide = wide

	if realtime:
		frappe.publish_realtime(event="msgprint", message=out)
	else:
		frappe.message_log.append(out)
	_raise_exception()


def toast(message: str, indicator: Literal["blue", "green", "orange", "red", "yellow"] | None = None):
	msgprint(message, indicator=indicator, alert=True)


def clear_messages():
	frappe.local.message_log = []


def get_message_log() -> list[dict]:
	return [msg_out for msg_out in frappe.local.message_log]


def clear_last_message():
	if len(frappe.local.message_log) > 0:
		frappe.local.message_log = frappe.local.message_log[:-1]


def throw(
	msg: str | Sequence[str],
	exc: type[Exception] | Exception = frappe.ValidationError,
	title: str | None = None,
	is_minimizable: bool = False,
	wide: bool = False,
	as_list: bool = False,
	primary_action=None,
	*,
	allow_dangerous_html=False,
) -> None:
	"""Throw execption and show message (`msgprint`).

	:param msg: Message.
	:param exc: Exception class. Default `frappe.ValidationError`
	:param title: [optional] Message title. Default: "Message".
	:param is_minimizable: [optional] Allow users to minimize the modal
	:param wide: [optional] Show wide modal
	:param as_list: [optional] If `msg` is a list, render as un-ordered list.
	:param primary_action: [optional] Bind a primary server/client side action.
	"""
	msgprint(
		msg,
		raise_exception=exc,
		title=title,
		indicator="red",
		is_minimizable=is_minimizable,
		wide=wide,
		as_list=as_list,
		primary_action=primary_action,
		allow_dangerous_html=allow_dangerous_html,
	)


def throw_permission_error():
	throw(_("Not permitted"), frappe.PermissionError)


__all__ = [
	"clear_last_message",
	"clear_messages",
	"get_message_log",
	"msgprint",
	"throw",
	"throw_permission_error",
	"toast",
]
