"""
A1 range helpers for g-gremlin.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Optional


@dataclass
class ParsedRange:
    sheet_name: str
    start_row: int  # 1-based
    start_col: int  # 1-based
    end_row: Optional[int]
    end_col: Optional[int]


_CELL_RE = re.compile(r"^([A-Za-z]+)(\d+)?$")


def parse_a1_range(a1_range: str) -> ParsedRange:
    """
    Parse ranges like 'Leads!A1:Z' or 'Sheet1!B2:F20' into components.
    """
    sheet_name = ""
    range_part = a1_range
    if "!" in a1_range:
        sheet_name, range_part = a1_range.split("!", 1)

    start_str, end_str = _split_range_part(range_part)
    start_col_letters, start_row = _parse_cell(start_str)
    end_col_letters = None
    end_row = None

    if end_str:
        end_col_letters, end_row = _parse_cell(end_str, allow_empty_row=True)

    start_col = _letters_to_index(start_col_letters)
    end_col = _letters_to_index(end_col_letters) if end_col_letters else None

    if start_row is None:
        start_row = 1

    return ParsedRange(
        sheet_name=sheet_name,
        start_row=start_row,
        start_col=start_col,
        end_row=end_row,
        end_col=end_col,
    )


def col_index_to_letter(col_index_1_based: int) -> str:
    """Convert 1-based column number to Excel-style letters (1 -> 'A', 27 -> 'AA')."""
    if col_index_1_based < 1:
        raise ValueError("Column index must be 1 or greater.")
    letters = ""
    index = col_index_1_based
    while index > 0:
        index, remainder = divmod(index - 1, 26)
        letters = chr(65 + remainder) + letters
    return letters


def cell_a1_from_offsets(
    parsed: ParsedRange, data_row_index: int, data_col_index: int
) -> str:
    """
    Build an A1 reference from zero-based data row/col offsets.
    """
    if data_row_index < 0 or data_col_index < 0:
        raise ValueError("Row and column offsets must be non-negative.")

    sheet_row = parsed.start_row + 1 + data_row_index
    sheet_col = parsed.start_col + data_col_index
    col_letters = col_index_to_letter(sheet_col)
    cell_ref = f"{col_letters}{sheet_row}"
    return f"{parsed.sheet_name}!{cell_ref}" if parsed.sheet_name else cell_ref


def _split_range_part(range_part: str) -> tuple[str, Optional[str]]:
    if ":" in range_part:
        start, end = range_part.split(":", 1)
        return start, end or None
    return range_part, None


def _parse_cell(cell: str, allow_empty_row: bool = False) -> tuple[str, Optional[int]]:
    match = _CELL_RE.match(cell)
    if not match:
        raise ValueError(f"Invalid A1 cell reference: '{cell}'")
    letters, row_str = match.groups()
    if row_str:
        return letters, int(row_str)
    if allow_empty_row:
        return letters, None
    raise ValueError(f"Row number missing in cell reference: '{cell}'")


def _letters_to_index(letters: str) -> int:
    if not letters:
        raise ValueError("Column letters are required.")
    letters = letters.upper()
    value = 0
    for char in letters:
        value = value * 26 + (ord(char) - ord("A") + 1)
    return value
