from enum import Enum
from types import TracebackType
from typing import Any, Self, override

from archinstall.lib.menu.helpers import Selection
from archinstall.lib.output import error
from archinstall.lib.translationhandler import tr
from archinstall.tui.types import Chars
from archinstall.tui.ui.components import InstanceRunnable
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.ui.result import ResultType

CONFIG_KEY = '__config__'


class SpecialMenuKey(Enum):
	SAVE = f'{CONFIG_KEY}_save'
	INSTALL = f'{CONFIG_KEY}_install'
	ABORT = f'{CONFIG_KEY}_abort'

	@staticmethod
	def matches(key: str) -> bool:
		return any(key == item.value for item in SpecialMenuKey)


class AbstractMenu[ValueT](InstanceRunnable[ValueT]):
	def __init__(
		self,
		item_group: MenuItemGroup,
		config: Any,
		title: str | None = None,
		auto_cursor: bool = True,
		allow_reset: bool = False,
		reset_warning: str | None = None,
	):
		self._menu_item_group = item_group
		self._config = config
		self.auto_cursor = auto_cursor
		self._allow_reset = allow_reset
		self._reset_warning = reset_warning
		self._title = title

		self.is_context_mgr = False

		self._sync_from_config()

	def __enter__(self, *args: Any, **kwargs: Any) -> Self:
		self.is_context_mgr = True
		return self

	def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
		# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
		# TODO: skip processing when it comes from a planified exit
		if exc_type is not None:
			error(str(exc_value))
			print('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues')

			# Return None to propagate the exception
			return None

		self.sync_all_to_config()

	def _sync_from_config(self) -> None:
		for item in self._menu_item_group._menu_items:
			if item.key is not None and not SpecialMenuKey.matches(item.key):
				config_value = getattr(self._config, item.key)
				if config_value is not None:
					item.value = config_value

	def sync_all_to_config(self) -> None:
		for item in self._menu_item_group._menu_items:
			if item.key:
				setattr(self._config, item.key, item.value)

	def _sync(self, item: MenuItem) -> None:
		if not item.key or SpecialMenuKey.matches(item.key):
			return

		config_value = getattr(self._config, item.key)

		if config_value is not None:
			item.value = config_value
		elif item.value is not None:
			setattr(self._config, item.key, item.value)

	def set_enabled(self, key: str, enabled: bool) -> None:
		# the __config__ is associated with multiple items
		found = False

		is_config_key = key == CONFIG_KEY

		for item in self._menu_item_group.items:
			if item.key:
				if item.key == key or (is_config_key and SpecialMenuKey.matches(item.key)):
					item.enabled = enabled
					found = True

		if not found:
			raise ValueError(f'No selector found: {key}')

	def disable_all(self) -> None:
		for item in self._menu_item_group.items:
			item.enabled = False

	def is_config_valid(self) -> bool:
		return True

	@override
	async def run(self) -> ValueT | None:
		return await self.show()

	async def show(self) -> ValueT | None:
		self._sync_from_config()

		while True:
			result = await Selection[ValueT](
				title=self._title,
				group=self._menu_item_group,
				allow_skip=False,
				allow_reset=self._allow_reset,
				preview_location='right',
			).show()

			match result.type_:
				case ResultType.Selection:
					item: MenuItem = result.item()
					self._menu_item_group.focus_item = item

					if item.action is None:
						if item.key == SpecialMenuKey.INSTALL.value:
							if not self.is_config_valid():
								continue
							break
						elif item.key == SpecialMenuKey.ABORT.value:
							return None
						else:
							break
					else:
						item.value = await item.action(item.value)
				case ResultType.Reset:
					return None
				case _:
					pass

		self.sync_all_to_config()
		return self._config


class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
	def __init__(
		self,
		item_group: MenuItemGroup,
		config: Any,
		auto_cursor: bool = True,
		allow_reset: bool = False,
	):
		back_text = f'{Chars.Right_arrow} ' + tr('Back')
		item_group.add_item(MenuItem(text=back_text))

		super().__init__(
			item_group,
			config=config,
			auto_cursor=auto_cursor,
			allow_reset=allow_reset,
		)
