from __future__ import annotations

from dataclasses import dataclass, field

from openpyxl.worksheet.worksheet import Worksheet

from .cell_style import CellStyleSpec, apply_cell_style, merge_cell_styles


@dataclass(frozen=True)
class StyledRegion:
    role: str
    component: str
    r1: int
    c1: int
    r2: int
    c2: int
    is_header: bool | None = None
    column_names: tuple[str | None, ...] | None = None


@dataclass(frozen=True)
class StyleSelector:
    sheet_name: str | None = None
    role: str | None = None
    component: str | None = None
    row: int | None = None
    col: int | None = None
    row_range: tuple[int, int] | None = None
    col_range: tuple[int, int] | None = None
    is_header: bool | None = None
    column_idx: int | None = None
    column_name: str | None = None


@dataclass(frozen=True)
class StyleRule:
    name: str
    selector: StyleSelector
    style: CellStyleSpec
    priority: int = 100
    enabled: bool = True


@dataclass(frozen=True)
class CellContext:
    sheet_name: str
    role: str
    component: str
    row: int
    col: int
    is_header: bool | None
    column_idx: int | None
    column_name: str | None


@dataclass
class StyleRegistry:
    role_styles: dict[str, CellStyleSpec] = field(default_factory=dict)
    rules: list[StyleRule] = field(default_factory=list)

    def set_role_style(self, role: str, style: CellStyleSpec) -> None:
        self.role_styles[role] = style

    def add_rule(self, rule: StyleRule) -> None:
        self.rules.append(rule)

    def list_rules(self) -> list[StyleRule]:
        return sorted(self.rules, key=lambda rule: rule.priority)

    def resolve_style(self, context: CellContext) -> CellStyleSpec | None:
        base_style = self.role_styles.get(context.role)

        matching_styles: list[CellStyleSpec] = []
        for rule in self.list_rules():
            if not rule.enabled:
                continue
            if self._matches(rule.selector, context):
                matching_styles.append(rule.style)

        return merge_cell_styles(base_style, *matching_styles)

    @staticmethod
    def _matches(selector: StyleSelector, context: CellContext) -> bool:
        if selector.sheet_name is not None and selector.sheet_name != context.sheet_name:
            return False
        if selector.role is not None and selector.role != context.role:
            return False
        if selector.component is not None and selector.component != context.component:
            return False
        if selector.row is not None and selector.row != context.row:
            return False
        if selector.col is not None and selector.col != context.col:
            return False
        if selector.row_range is not None:
            start, end = selector.row_range
            if context.row < start or context.row > end:
                return False
        if selector.col_range is not None:
            start, end = selector.col_range
            if context.col < start or context.col > end:
                return False
        if selector.is_header is not None and selector.is_header != context.is_header:
            return False
        if selector.column_idx is not None and selector.column_idx != context.column_idx:
            return False
        if selector.column_name is not None and selector.column_name != context.column_name:
            return False
        return True


class StyleEngine:
    def __init__(self, registry: StyleRegistry) -> None:
        self.registry = registry

    def apply_regions(self, ws: Worksheet, regions: list[StyledRegion]) -> None:
        for region in regions:
            self._apply_region(ws, region)

    def _apply_region(self, ws: Worksheet, region: StyledRegion) -> None:
        for row in range(region.r1, region.r2 + 1):
            for col in range(region.c1, region.c2 + 1):
                column_idx = col - region.c1 + 1
                column_name = None
                if region.column_names is not None:
                    if 1 <= column_idx <= len(region.column_names):
                        column_name = region.column_names[column_idx - 1]

                context = CellContext(
                    sheet_name=ws.title,
                    role=region.role,
                    component=region.component,
                    row=row,
                    col=col,
                    is_header=region.is_header,
                    column_idx=column_idx,
                    column_name=column_name,
                )

                style = self.registry.resolve_style(context)
                apply_cell_style(ws.cell(row=row, column=col), style)
