from typing import Any, Callable, Iterable, Optional, Union
from cotation.acoustic.display.io_widget.output import Output
from PySide6.QtGui import QGuiApplication, QPalette
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
	QFrame,
	QLabel,
	QScrollArea,
	QVBoxLayout,
	QWidget,
)

from cotation.acoustic.display.io_widget.ui_line_with_input import UILineWithInput


class InfoBox(QFrame):
	"""
	A customizable information box widget for displaying a title, optional paragraph, and dynamic content in a Qt application.
	The InfoBox class extends QFrame and provides a structured layout for presenting information, such as a bold title, an optional descriptive paragraph, and a list of dynamic content widgets (e.g., Output or UILineWithInput instances). It supports resize callbacks for interactive input elements and offers methods to check if all input fields are filled.
	Attributes:
		title (str): The title text displayed at the top of the info box.
		paragraph (Optional[str]): An optional paragraph providing additional information.
		dynamic_content (Optional[list[Union[Output, UILineWithInput]]]): A list of dynamic widgets to be displayed below the paragraph.
		_resize_callback (Optional[Callable[[int], Any]]): An optional callback function triggered on resize events from input widgets.
	"""

	title: str
	paragraph: Optional[str]
	dynamic_content: Optional[list[Union[Output, UILineWithInput]]]
	_resize_callback: Optional[Callable[[int], Any]]

	def __init__(
		self,
		title: str,
		paragraph: Optional[str] = None,
		dynamic_content: Optional[list[Union[Output, UILineWithInput]]] = None,
	):
		super().__init__()

		self.title = title
		self.paragraph = paragraph
		self._resize_callback = None

		if dynamic_content is None:
			dynamic_content = []

		self.dynamic_content = dynamic_content.copy()

		# Connect resize callbacks for inputs
		for i in filter(lambda i: isinstance(i, UILineWithInput), self.dynamic_content):
			assert isinstance(i, UILineWithInput)
			i.set_resize_callback(self._handle_resize)

		self.init_ui()

	def set_resize_callback(self, callback: Callable[[int], Any]) -> None:
		"""
		Sets the callback function to be invoked when a resize event occurs.

		Args:
			callback (Callable[[int], Any]): A function that takes an integer (typically representing the new size)
				and performs an action in response to the resize event.

		Returns:
			None
		"""
		self._resize_callback = callback

	def _handle_resize(self, value: int) -> None:
		"""
		Handles the resize event by invoking the registered resize callback with the given value.

		Args:
			value (int): The new size value to be passed to the resize callback.

		Returns:
			None
		"""
		if self._resize_callback:
			self._resize_callback(value)

	def init_ui(self):
		"""
		Initializes and sets up the user interface for the widget.
		This method configures the widget's style, creates a vertical layout,
		and adds UI components such as the title and an optional paragraph.
		It also handles the addition of dynamic content to the layout and
		sets the final layout for the widget.
		Returns:
			None
		"""
		self.set_style()
		layout = QVBoxLayout()

		layout.addWidget(self.create_title())

		if self.paragraph is not None:
			layout.addWidget(self.create_paragraph())

		self.handle_dynamic_content(layout)

		self.setLayout(layout)

	def create_title(self) -> QLabel:
		"""
		Creates and returns a QLabel widget displaying the title with bold styling and word wrapping enabled.
		Returns:
			QLabel: A label widget containing the title text, styled in bold and set to wrap words.
		"""
		title_label = QLabel(self.title)
		title_label.setStyleSheet("font-weight: bold;")
		title_label.setWordWrap(True)

		return title_label

	def create_paragraph(self) -> QLabel:
		"""
		Creates a QLabel widget containing the paragraph text with word wrapping enabled and a maximum width.
		Returns:
			QLabel: A QLabel widget displaying the paragraph text, word-wrapped and constrained to a maximum width of 300 pixels.
		"""
		paragraph_label = QLabel(self.paragraph)
		paragraph_label.setWordWrap(True)
		paragraph_label.setMaximumWidth(300)

		return paragraph_label

	def set_style(self):
		"""
		Configures the visual appearance of the widget.
		This method sets the frame style to a styled panel with a plain shadow,
		specifies the line width, and enables automatic background filling.
		It also customizes the widget's palette by setting the window color to match
		the application's base color, ensuring visual consistency with the overall UI.
		Returns:
			None
		"""
		self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Plain)
		self.setLineWidth(1)

		self.setAutoFillBackground(True)

		palette = QPalette()
		palette.setColor(
			QPalette.ColorRole.Window,
			QGuiApplication.palette().color(QPalette.ColorRole.Base),
		)

		self.setPalette(palette)

	def handle_dynamic_content(self, layout: QVBoxLayout):
		"""
		Adds dynamic content widgets to the provided QVBoxLayout.
		This method iterates over the `self.dynamic_content` attribute, which is expected to be
		an iterable of QWidget instances, and adds each widget to the specified layout. If
		`self.dynamic_content` is None, the method returns without making any changes.
		Args:
			layout (QVBoxLayout): The layout to which the dynamic content widgets will be added.
		Returns:
			None
		"""
		if self.dynamic_content is None:
			return

		for content in self.dynamic_content:
			layout.addWidget(content)

	def get_all_filled(self) -> bool:
		"""
		Checks whether all input fields in the dynamic content are filled.
		Returns:
			bool: True if all instances of UILineWithInput in self.dynamic_content are filled
			(as determined by their get_is_filled() method), or if dynamic_content is None.
			Returns False if any input is not filled.
		"""
		if self.dynamic_content is None:
			return True

		inputs: list[UILineWithInput] = [
			i for i in self.dynamic_content if isinstance(i, UILineWithInput)
		]
		filled: Iterable[bool] = map(lambda i: i.get_is_filled(), inputs)

		return all(filled)


class Info(QWidget):
	boxes: list[InfoBox]
	_resize_callback: Optional[Callable[[int], Any]]

	scroll_layout: QVBoxLayout

	def __init__(self, parent: Optional[QWidget] = None):
		super().__init__(parent)
		self.scroll_area = QScrollArea()
		self._resize_callback = None
		self.init_ui()
		self.boxes = []

	def set_resize_callback(self, callback: Callable[[int], Any]) -> None:
		"""
		Sets a callback function to be invoked when a resize event occurs.

		Args:
			callback (Callable[[int], Any]): A function that takes an integer (typically representing the new size)
				as its argument and performs an action in response to a resize event.

		Returns:
			None
		"""
		self._resize_callback = callback

	def _handle_resize(self, value: int) -> None:
		"""
		Handles the resize event by invoking the registered resize callback with the given value.

		Args:
			value (int): The new size value to be passed to the resize callback.

		Returns:
			None
		"""
		if self._resize_callback:
			self._resize_callback(value)

	def init_ui(self):
		"""
		Initializes the user interface for the widget.
		This method sets up a vertical layout containing a scrollable area.
		The scroll area is configured to be resizable and contains a widget
		with a vertical box layout aligned to the top. The scroll area is
		then added to the main layout of the widget.
		"""
		layout = QVBoxLayout()
		self.scroll_area.setWidgetResizable(True)

		scroll_content = QWidget()
		self.scroll_layout = QVBoxLayout(scroll_content)
		self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)

		self.scroll_area.setWidget(scroll_content)
		layout.addWidget(self.scroll_area)

		self.setLayout(layout)

	def add_infobox(self, box: InfoBox):
		"""
		Adds an InfoBox widget to the current widget's layout and internal list.

		This method sets a resize callback on the provided InfoBox, appends it to the internal
		list of boxes, and adds it to the scrollable layout for display.

		Args:
			box (InfoBox): The InfoBox instance to be added to the widget.

		"""
		box.set_resize_callback(self._handle_resize)
		self.boxes.append(box)
		self.scroll_layout.addWidget(box)

	def get_all_filled(self) -> bool:
		"""
		Checks whether all contained boxes are filled.

		Returns:
			bool: True if all boxes in self.boxes return True for their get_all_filled() method, indicating they are all filled; False otherwise.
		"""
		is_filled: Iterable[bool] = map(lambda b: b.get_all_filled(), self.boxes)
		return all(is_filled)
