from django.utils.text import capfirst

from .config_mixin import resolve_config


class TableMixin:
    """
    Provides methods for table styling and layout.
    """

    def get_table_pixel_height_other_page_elements(self) -> str:
        """Returns the height of other elements on the page that the table is
        displayed on. After subtracting this (in pixels) from the page height,
        the table height will be calculated (in a css style in list.html) as
        {{ get_table_max_height }}% of the remaining viewport height.
        """
        return resolve_config(self).table_pixel_height_px  # px

    def get_table_max_height(self) -> int:
        """Returns the proportion of visible space on the viewport after subtracting
        the height of other elements on the page that the table is displayed on,
        as represented by get_table_pixel_height_other_page_elements().

        The table height is calculated in a css style for max-table-height in list.html.
        """
        return resolve_config(self).table_max_height

    def get_table_max_col_width(self):
        # The max width for the table columns in object_list.html - in characters
        return resolve_config(self).table_max_col_width_css

    def get_table_header_min_wrap_width(self):
        # The max width for the table columns in object_list.html - in characters
        return resolve_config(self).table_header_min_wrap_width_css

    def get_table_classes(self):
        """
        Get the table classes.
        """
        return resolve_config(self).table_classes

    def get_action_button_classes(self):
        """
        Get the action button classes.
        """
        return resolve_config(self).action_button_classes

    def get_extra_button_classes(self):
        """
        Get the extra button classes.
        """
        return resolve_config(self).extra_button_classes

    def get_extra_actions_mode(self):
        """
        Get the rendering mode for row-level extra actions.
        """
        return resolve_config(self).extra_actions_mode

    def get_view_title(self) -> str:
        """
        Return the visible heading for the list page.

        When `view_title` is configured, it overrides the default heading text.
        Otherwise PowerCRUD falls back to the model plural verbose name.
        """
        configured_title = resolve_config(self).view_title
        if configured_title:
            return configured_title

        model = getattr(self, "model", None)
        if model is None or not hasattr(model, "_meta"):
            return ""

        return capfirst(model._meta.verbose_name_plural)

    def get_view_instructions(self) -> str:
        """
        Return the optional plain-text helper copy shown under the list heading.
        """
        configured_instructions = resolve_config(self).view_instructions
        if configured_instructions:
            return configured_instructions
        return ""

    def get_column_help_text(self) -> dict[str, str]:
        """
        Return the optional plain-text help text mapping for rendered list columns.
        """
        configured_help = resolve_config(self).column_help_text
        if isinstance(configured_help, dict):
            return configured_help
        return {}

    def get_inline_edit_always_visible(self) -> bool:
        """
        Return whether inline-editable cells keep a subtle resting highlight.
        """
        return bool(resolve_config(self).inline_edit_always_visible)

    def get_inline_edit_highlight_accent(self) -> str:
        """
        Return the configured inline-edit highlight accent as a hex color.
        """
        return str(resolve_config(self).inline_edit_highlight_accent)

    @staticmethod
    def _parse_inline_edit_highlight_accent(value: str) -> tuple[int, int, int]:
        """
        Convert a hex color string into an RGB tuple.
        """
        normalized = value.strip().lstrip("#")
        if len(normalized) == 3:
            normalized = "".join(ch * 2 for ch in normalized)
        return (
            int(normalized[0:2], 16),
            int(normalized[2:4], 16),
            int(normalized[4:6], 16),
        )

    def _inline_edit_highlight_rgba(self, alpha: float) -> str:
        """
        Return an RGBA string derived from the configured inline-edit accent.
        """
        red, green, blue = self._parse_inline_edit_highlight_accent(
            self.get_inline_edit_highlight_accent()
        )
        return f"rgba({red}, {green}, {blue}, {alpha:.2f})"

    def get_inline_edit_highlight_palette(self) -> dict[str, str]:
        """
        Return the derived inline-edit highlight palette used by the list template.
        """
        return {
            "rest_bg": self._inline_edit_highlight_rgba(0.06),
            "rest_border": self._inline_edit_highlight_rgba(0.18),
            "hover_bg": self._inline_edit_highlight_rgba(0.15),
            "hover_border": self._inline_edit_highlight_rgba(0.35),
            "active_row_outline": self._inline_edit_highlight_rgba(0.85),
            "active_row_bg": self._inline_edit_highlight_rgba(0.12),
            "active_row_overlay": self._inline_edit_highlight_rgba(0.05),
            "active_widget_bg": self._inline_edit_highlight_rgba(0.15),
            "active_widget_border": self._inline_edit_highlight_rgba(0.35),
        }

    # Inline editing helpers -------------------------------------------------
    def get_inline_row_id_prefix(self) -> str:
        """Base DOM id prefix for inline-editable rows."""
        return "pc-row-"

    def get_inline_row_id(self, obj) -> str | None:
        """Return the DOM id for a given object's row."""
        if obj is None:
            return None
        pk = getattr(obj, "pk", None)
        if pk in (None, ""):
            return None
        return f"{self.get_inline_row_id_prefix()}{pk}"

    def get_inline_row_target(self, obj) -> str | None:
        """Return the HTMX target selector for the row container."""
        row_id = self.get_inline_row_id(obj)
        return f"#{row_id}" if row_id else None

    def get_inline_context(self) -> dict:
        """
        Context payload consumed by templates to determine inline editing state.
        """
        enabled = self.get_inline_editing()
        endpoint_name = getattr(self, "get_inline_row_endpoint_name", None)
        dependency_endpoint = getattr(self, "get_inline_dependency_endpoint_name", None)
        dependency_endpoint_name = (
            dependency_endpoint() if callable(dependency_endpoint) else None
        )
        dependency_endpoint_url = self._resolve_inline_endpoint(
            dependency_endpoint_name
        )
        return {
            "enabled": enabled,
            "fields": self.get_inline_edit_fields(),
            "dependencies": self.get_inline_field_dependencies(),
            "row_id_prefix": self.get_inline_row_id_prefix(),
            "row_endpoint_name": endpoint_name() if callable(endpoint_name) else None,
            "dependency_endpoint_name": dependency_endpoint_name,
            "dependency_endpoint_url": dependency_endpoint_url,
        }
