from __future__ import annotations

import re
from typing import Iterable

from excelminer.model.graph import WorkbookGraph

_A1_RE = re.compile(r"^\$?([A-Z]{1,3})\$?(\d+)$", re.IGNORECASE)


def _normalize_sheet_key(name: str) -> str:
    return re.sub(r"\s+", " ", (name or "").strip())


def _col_to_index(col: str) -> int:
    idx = 0
    for ch in (col or "").upper():
        if not ch.isalpha():
            continue
        idx = idx * 26 + (ord(ch) - ord("A") + 1)
    return idx


def a1_to_rowcol(address: str) -> tuple[int, int] | None:
    if not address:
        return None
    m = _A1_RE.match(address.strip())
    if not m:
        return None
    col = _col_to_index(m.group(1))
    row = int(m.group(2))
    if row < 1 or col < 1:
        return None
    return row, col


def parse_a1_range(ref: str) -> tuple[int, int, int, int] | None:
    if not ref:
        return None
    ref = ref.replace("$", "").strip()
    parts = ref.split(":")
    if len(parts) == 1:
        start = a1_to_rowcol(parts[0])
        if start is None:
            return None
        r, c = start
        return r, c, r, c
    if len(parts) != 2:
        return None
    start = a1_to_rowcol(parts[0])
    end = a1_to_rowcol(parts[1])
    if start is None or end is None:
        return None
    r1, c1 = start
    r2, c2 = end
    if r2 < r1:
        r1, r2 = r2, r1
    if c2 < c1:
        c1, c2 = c2, c1
    return r1, c1, r2, c2


def cell_in_ranges(
    row: int, col: int, ranges: Iterable[tuple[int, int, int, int]]
) -> bool:
    for r1, c1, r2, c2 in ranges:
        if r1 <= row <= r2 and c1 <= col <= c2:
            return True
    return False


def pivot_ranges_by_sheet(
    graph: WorkbookGraph,
) -> dict[str, list[tuple[int, int, int, int]]]:
    ranges: dict[str, list[tuple[int, int, int, int]]] = {}
    for node in graph.nodes.values():
        if node.kind != "pivot_table":
            continue
        sheet = str(node.attrs.get("sheet") or "").strip()
        ref = (
            node.attrs.get("pivot_ref")
            or node.attrs.get("ref")
            or node.attrs.get("range")
            or ""
        )
        if not sheet or not ref:
            continue
        rng = parse_a1_range(str(ref))
        if rng is None:
            continue
        key = _normalize_sheet_key(sheet)
        ranges.setdefault(key, []).append(rng)
    return ranges
