"""
Spreadsheet repair engine - analyze, suggest fixes, and stub missing sheets.
"""

from __future__ import annotations

import re
from collections import Counter
from dataclasses import asdict, dataclass
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple

from g_gremlin.a1 import col_index_to_letter, parse_a1_range


class FixConfidence(Enum):
    HIGH = "high"
    MEDIUM = "medium"
    LOW = "low"


@dataclass
class Issue:
    """
    Represents a single repair issue (error/warning/info) with optional fix.
    """

    id: str
    cell: str
    type: str  # ref_error, pattern_anomaly, missing_sheet_ref, orphaned_formula
    severity: str  # error, warning, info
    current_formula: str
    current_value: Any
    diagnosis: str
    suggestion: Optional[Dict[str, Any]] = None
    auto_fixable: bool = False
    needs_review: bool = False
    manual_only: bool = False

    def to_dict(self) -> Dict[str, Any]:
        data = asdict(self)
        # Drop None values for cleaner JSON
        return {k: v for k, v in data.items() if v is not None}

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Issue":
        return cls(
            id=str(data.get("id", "")),
            cell=str(data.get("cell", "")),
            type=str(data.get("type", "")),
            severity=str(data.get("severity", "")),
            current_formula=str(data.get("current_formula", "")),
            current_value=data.get("current_value"),
            diagnosis=str(data.get("diagnosis", "")),
            suggestion=data.get("suggestion"),
            auto_fixable=bool(data.get("auto_fixable", False)),
            needs_review=bool(data.get("needs_review", False)),
            manual_only=bool(data.get("manual_only", False)),
        )


@dataclass
class RepairPlan:
    """
    Aggregate repair plan with issues, missing sheets, pattern anomalies, and health.
    """

    issues: List[Issue]
    missing_sheets: List[Dict[str, Any]]
    pattern_anomalies: List[Dict[str, Any]]
    health_score: int
    summary: Optional[Dict[str, Any]] = None

    def to_dict(self) -> Dict[str, Any]:
        summary = self.summary or summarize_issues(self.issues)
        return {
            "summary": summary,
            "health_score": self.health_score,
            "health_grade": health_grade(self.health_score),
            "issues": [issue.to_dict() for issue in self.issues],
            "missing_sheets": self.missing_sheets,
            "pattern_anomalies": self.pattern_anomalies,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "RepairPlan":
        issues = [Issue.from_dict(item) for item in data.get("issues", [])]
        return cls(
            issues=issues,
            missing_sheets=data.get("missing_sheets", []),
            pattern_anomalies=data.get("pattern_anomalies", []),
            health_score=int(data.get("health_score", 0)),
            summary=data.get("summary"),
        )


# Public API ------------------------------------------------------------


def cells_from_grids(a1_range: str, values_grid: List[List[Any]], formulas_grid: List[List[Any]]) -> List[Dict[str, Any]]:
    """
    Convert value/formula grids into a list of cell metadata dictionaries.
    """
    parsed = parse_a1_range(a1_range)
    cells: List[Dict[str, Any]] = []
    max_rows = max(len(values_grid), len(formulas_grid))
    max_cols = max((len(r) for r in values_grid), default=0)
    max_cols = max(max_cols, max((len(r) for r in formulas_grid), default=0))
    for row_idx in range(max_rows):
        for col_idx in range(max_cols):
            value = _safe_grid_get(values_grid, row_idx, col_idx)
            formula = _safe_grid_get(formulas_grid, row_idx, col_idx)
            # Skip empty cells to avoid bloating the analysis
            if not _has_content(value) and not _has_content(formula):
                continue
            cell_a1 = _cell_from_offsets(parsed, row_idx, col_idx)
            cells.append(
                {
                    "a1": cell_a1,
                    "value": value,
                    "formula": formula if isinstance(formula, str) else "",
                }
            )
    return cells


def analyze_sheet(
    cells: List[Dict[str, Any]],
    existing_sheets: List[str],
    expected_sheet_map: Optional[Dict[str, str]] = None,
) -> RepairPlan:
    """
    Perform repair analysis: detect ref errors, missing sheets, pattern anomalies, and compute health.
    """
    issues: List[Issue] = []
    issues.extend(find_ref_errors(cells))
    issues.extend(find_missing_sheet_refs(cells, existing_sheets))
    pattern_issues, pattern_summary = find_pattern_anomalies(cells, expected_sheet_map=expected_sheet_map)
    issues.extend(pattern_issues)

    missing = analyze_missing_sheets(cells, existing_sheets)
    health = calculate_health_score(cells, issues)

    return RepairPlan(
        issues=issues,
        missing_sheets=missing,
        pattern_anomalies=pattern_summary,
        health_score=health,
    )


# Issue detection -------------------------------------------------------


def find_ref_errors(cells: List[Dict[str, Any]]) -> List[Issue]:
    """
    Find #REF! errors and suggest fixes based on neighboring column patterns.
    """
    issues: List[Issue] = []
    for cell in cells:
        value = str(cell.get("value", ""))
        formula = cell.get("formula", "") or ""
        if "#REF!" not in value and "#REF!" not in formula:
            continue
        suggestion = infer_ref_from_pattern(cell, cells)
        issues.append(
            Issue(
                id=f"ref_{cell.get('a1','')}",
                cell=cell.get("a1", ""),
                type="ref_error",
                severity="error",
                current_formula=formula,
                current_value=cell.get("value"),
                diagnosis="Reference target was deleted or moved",
                suggestion=suggestion,
                auto_fixable=bool(suggestion and suggestion.get("confidence") == FixConfidence.HIGH.value),
            )
        )
    return issues


def infer_ref_from_pattern(broken_cell: Dict[str, Any], all_cells: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """
    Infer what a #REF! should be based on neighboring column patterns.
    Looks for a consistent row offset in the same column.
    """
    row = extract_row(broken_cell.get("a1", ""))
    col = extract_col(broken_cell.get("a1", ""))
    if not row or not col:
        return None
    neighbors = [
        c
        for c in all_cells
        if extract_col(c.get("a1", "")) == col and c.get("a1") != broken_cell.get("a1") and str(c.get("formula", "")).startswith("=")
    ]
    offsets: List[int] = []
    evidence: List[Dict[str, Any]] = []
    for cell in neighbors:
        formula = str(cell.get("formula", ""))
        match = re.search(rf"\$?{col}\$?(\d+)", formula.replace("'", ""), re.IGNORECASE)
        if not match:
            continue
        ref_row = int(match.group(1))
        offset = extract_row(cell.get("a1", "")) - ref_row
        offsets.append(offset)
        evidence.append({"cell": cell.get("a1", ""), "formula": formula})
    if not offsets:
        return None
    most_common, count = Counter(offsets).most_common(1)[0]
    confidence_ratio = count / max(len(offsets), 1)
    if confidence_ratio < 0.6:
        return None
    target_row = row - most_common
    suggested_target = f"{col}{target_row}"
    base_formula = str(broken_cell.get("formula", "")) or ""
    if base_formula:
        suggested_formula = base_formula.replace("#REF!", suggested_target)
    else:
        suggested_formula = f"={suggested_target}"
    confidence = FixConfidence.HIGH.value if confidence_ratio >= 0.9 else FixConfidence.MEDIUM.value
    return {
        "confidence": confidence,
        "method": "pattern_inference",
        "explanation": f"Adjacent formulas imply row offset {most_common} (confidence {confidence_ratio:.2f}).",
        "suggested_formula": suggested_formula,
        "evidence": evidence[:5],
    }


def find_missing_sheet_refs(cells: List[Dict[str, Any]], existing_sheets: List[str]) -> List[Issue]:
    """
    Identify formulas referencing sheets that do not exist.
    """
    issues: List[Issue] = []
    existing_lower = {s.lower() for s in existing_sheets}
    for cell in cells:
        formula = cell.get("formula", "") or ""
        refs = extract_sheet_refs(formula)
        missing_refs = [(sheet, ref) for sheet, ref in refs if sheet.lower() not in existing_lower]
        if not missing_refs:
            continue
        sheet_name = missing_refs[0][0]
        suggestion = build_missing_sheet_suggestion(sheet_name, existing_sheets)
        issues.append(
            Issue(
                id=f"missing_{cell.get('a1','')}",
                cell=cell.get("a1", ""),
                type="missing_sheet_ref",
                severity="error",
                current_formula=formula,
                current_value=cell.get("value"),
                diagnosis=f"References sheet '{sheet_name}' which does not exist",
                suggestion=suggestion,
                auto_fixable=False,
                needs_review=True,
            )
        )
    return issues


# Pattern detection -----------------------------------------------------


def detect_column_pattern(cells: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """
    Detect if formulas in a column follow a consistent pattern (row offset placeholders).
    """
    formula_cells = [c for c in cells if str(c.get("formula", "")).startswith("=")]
    if len(formula_cells) < 2:
        return None
    structures: List[Dict[str, Any]] = []
    for cell in formula_cells:
        row = extract_row(cell.get("a1", ""))
        formula = str(cell.get("formula", "") or "")
        normalized = _normalize_column_formula(formula, row)
        structures.append({"cell": cell.get("a1", ""), "row": row, "formula": formula, "normalized": normalized})
    patterns = Counter(s["normalized"] for s in structures)
    most_common, count = patterns.most_common(1)[0]
    if count < 2:
        return None
    confidence = count / len(structures)
    if confidence < 0.6:
        return None
    return {
        "pattern": most_common,
        "description": "Column offset pattern detected",
        "confidence": confidence,
        "evidence": [{"cell": s["cell"], "formula": s["formula"]} for s in structures if s["normalized"] == most_common],
    }


def detect_row_pattern(cells: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """
    Detect if formulas in a row follow a consistent pattern across columns.
    """
    formula_cells = [c for c in cells if str(c.get("formula", "")).startswith("=")]
    if len(formula_cells) < 2:
        return None
    structures: List[Dict[str, Any]] = []
    for cell in formula_cells:
        col = extract_col(cell.get("a1", ""))
        formula = str(cell.get("formula", "") or "")
        normalized = _normalize_row_formula(formula, col)
        structures.append({"cell": cell.get("a1", ""), "col": col, "formula": formula, "normalized": normalized})
    patterns = Counter(s["normalized"] for s in structures)
    most_common, count = patterns.most_common(1)[0]
    if count < 2:
        return None
    confidence = count / len(structures)
    if confidence < 0.6:
        return None
    return {
        "pattern": most_common,
        "description": "Row pattern detected",
        "confidence": confidence,
        "evidence": [{"cell": s["cell"], "formula": s["formula"]} for s in structures if s["normalized"] == most_common],
    }


def find_pattern_anomalies(
    cells: List[Dict[str, Any]], expected_sheet_map: Optional[Dict[str, str]] = None
) -> Tuple[List[Issue], List[Dict[str, Any]]]:
    """
    Find formulas that break row/column patterns.
    Returns (issues, pattern_summary)
    """
    issues: List[Issue] = []
    pattern_summaries: List[Dict[str, Any]] = []

    # Row-level anomalies
    rows = group_by_row(cells)
    for row_num, row_cells in rows.items():
        formula_cells = [
            c
            for c in row_cells
            if str(c.get("formula", "")).startswith("=") and not _is_summary_or_cross_section(str(c.get("formula", "")))
        ]
        if len(formula_cells) < 3:
            continue
        normalized_map = {
            c["a1"]: _normalize_row_formula(str(c.get("formula", "") or ""), extract_col(c.get("a1", "")))
            for c in formula_cells
        }
        pattern_info = _identify_majority_pattern(normalized_map, min_samples=3)
        if not pattern_info:
            continue
        expected_pattern, anomalies = pattern_info
        for a1, actual in list(anomalies.items()):
            col = extract_col(a1)
            expected_formula = _denormalize_row_pattern(expected_pattern, col)
            cell_meta = _find_cell(a1, formula_cells)
            if _matches_expected_band(cell_meta.get("formula") if cell_meta else "", col, expected_sheet_map):
                del anomalies[a1]
                continue
            issues.append(
                Issue(
                    id=f"pattern_row_{a1}",
                    cell=a1,
                    type="pattern_anomaly",
                    severity="warning",
                    current_formula=str(cell_meta.get("formula", "")) if cell_meta else "",
                    current_value=cell_meta.get("value") if cell_meta else None,
                    diagnosis="Formula breaks row pattern",
                    suggestion={
                        "confidence": FixConfidence.HIGH.value,
                        "method": "row_pattern",
                        "expected_pattern": expected_pattern,
                        "suggested_formula": expected_formula,
                        "difference": f"{actual} vs {expected_pattern}",
                    },
                    auto_fixable=False,
                )
            )
        if anomalies:
            pattern_summaries.append(
                {
                    "scope": f"row_{row_num}",
                    "pattern": expected_pattern,
                    "anomalies": anomalies,
                }
            )

    # Column-level anomalies
    cols = group_by_col(cells)
    for col_letter, col_cells in cols.items():
        formula_cells = [
            c
            for c in col_cells
            if str(c.get("formula", "")).startswith("=") and not _is_summary_or_cross_section(str(c.get("formula", "")))
        ]
        if len(formula_cells) < 3:
            continue
        normalized_map = {
            c["a1"]: _normalize_column_formula(str(c.get("formula", "") or ""), extract_row(c.get("a1", "")))
            for c in formula_cells
        }
        pattern_info = _identify_majority_pattern(normalized_map, min_samples=3)
        if not pattern_info:
            continue
        expected_pattern, anomalies = pattern_info
        for a1, actual in list(anomalies.items()):
            row_num = extract_row(a1)
            expected_formula = _denormalize_column_pattern(expected_pattern, row_num)
            cell_meta = _find_cell(a1, formula_cells)
            if _matches_expected_band(cell_meta.get("formula") if cell_meta else "", col_letter, expected_sheet_map):
                del anomalies[a1]
                continue
            issues.append(
                Issue(
                    id=f"pattern_col_{a1}",
                    cell=a1,
                    type="pattern_anomaly",
                    severity="warning",
                    current_formula=str(cell_meta.get("formula", "")) if cell_meta else "",
                    current_value=cell_meta.get("value") if cell_meta else None,
                    diagnosis="Formula breaks column pattern",
                    suggestion={
                        "confidence": FixConfidence.HIGH.value,
                        "method": "column_pattern",
                        "expected_pattern": expected_pattern,
                        "suggested_formula": expected_formula,
                        "difference": f"{actual} vs {expected_pattern}",
                    },
                    auto_fixable=False,
                )
            )
        if anomalies:
            pattern_summaries.append(
                {
                    "scope": f"col_{col_letter}",
                    "pattern": expected_pattern,
                    "anomalies": anomalies,
                }
            )
    return issues, pattern_summaries


# Missing sheet analysis ------------------------------------------------


def analyze_missing_sheets(cells: List[Dict[str, Any]], existing: List[str]) -> List[Dict[str, Any]]:
    """
    Aggregate missing sheet references and infer stub values where possible.
    """
    missing: Dict[str, Dict[str, Any]] = {}
    existing_lower = {e.lower() for e in existing}
    for cell in cells:
        formula = cell.get("formula", "") or ""
        refs = extract_sheet_refs(formula)
        for sheet_name, cell_ref in refs:
            if sheet_name.lower() in existing_lower:
                continue
            key = sheet_name
            if key not in missing:
                missing[key] = {
                    "sheet_name": sheet_name,
                    "referenced_cells": {},
                    "referencing_formulas": [],
                }
            missing[key]["referencing_formulas"].append(cell.get("a1", ""))
            missing[key]["referenced_cells"].setdefault(cell_ref, {"count": 0})
            missing[key]["referenced_cells"][cell_ref]["count"] += 1

    for sheet_name, info in missing.items():
        for cell_ref in list(info.get("referenced_cells", {}).keys()):
            inferred = infer_stub_value(sheet_name, cell_ref, cells, existing)
            if inferred:
                info["referenced_cells"][cell_ref]["suggested_value"] = inferred["value"]
                info["referenced_cells"][cell_ref]["reason"] = inferred["reason"]
        info["similar_sheets"] = find_similar_sheets(sheet_name, existing)
    return list(missing.values())


def infer_stub_value(
    sheet_name: str, cell_ref: str, all_cells: List[Dict[str, Any]], existing_sheets: List[str]
) -> Optional[Dict[str, Any]]:
    """
    Infer stub values using heuristics and common defaults.
    """
    defaults = {
        "B9": (0.4, "disco-to-pipe rate (typical default)"),
        "B10": (0.25, "win rate (typical default)"),
        "B11": (175000, "ASP (common B2B value)"),
        "B14": (0.5, "upsell win rate (typical default)"),
        "B15": (43750, "upsell ASP (0.25 × B11)"),
        "E2": (0.15, "Q1 pacing (standard pattern)"),
        "F2": (0.20, "Q2 pacing"),
        "G2": (0.30, "Q3 pacing"),
        "H2": (0.35, "Q4 pacing"),
    }
    if cell_ref in defaults:
        value, reason = defaults[cell_ref]
        return {"value": value, "reason": reason}

    # Lightweight similarity heuristic: if a similarly named sheet exists, we cannot fetch it here
    # but we flag that a remap/stub is likely needed.
    similar = find_similar_sheets(sheet_name, existing_sheets)
    if similar:
        best = similar[0]
        return {
            "value": None,
            "reason": f"Consider copying from '{best['name']}' ({best['similarity']:.0%} match)",
        }
    return None


def build_missing_sheet_suggestion(sheet_name: str, existing_sheets: List[str]) -> Dict[str, Any]:
    options: List[Dict[str, Any]] = []
    similar = find_similar_sheets(sheet_name, existing_sheets)
    if similar:
        options.append({"action": "remap", "target_sheet": similar[0]["name"]})
    options.append({"action": "stub", "create_sheet": sheet_name})
    return {
        "confidence": FixConfidence.MEDIUM.value if similar else FixConfidence.LOW.value,
        "method": "similar_sheet_match",
        "options": options,
        "explanation": "Sheet is missing; remap to a similar tab or create a stub.",
    }


# Scoring and helpers ---------------------------------------------------


def calculate_health_score(cells: List[Dict[str, Any]], issues: List[Issue]) -> int:
    """
    Calculate spreadsheet health score (0-100).
    """
    score = 100
    errors = [i for i in issues if i.severity == "error"]
    warnings = [i for i in issues if i.severity == "warning"]
    score -= len(errors) * 5
    score -= len(warnings) * 2

    formula_cells = [c for c in cells if str(c.get("formula", "")).startswith("=")]
    if formula_cells:
        error_rate = len(errors) / len(formula_cells)
        if error_rate < 0.01:
            score += 10
    return max(0, min(100, score))


def health_grade(score: int) -> str:
    if score >= 95:
        return "A+"
    if score >= 90:
        return "A"
    if score >= 85:
        return "B+"
    if score >= 80:
        return "B"
    if score >= 75:
        return "C+"
    if score >= 70:
        return "C"
    if score >= 60:
        return "D"
    return "F"


def summarize_issues(issues: List[Issue]) -> Dict[str, Any]:
    total = len(issues)
    auto = sum(1 for i in issues if i.auto_fixable)
    review = sum(1 for i in issues if i.needs_review)
    manual = sum(1 for i in issues if i.manual_only)
    return {
        "total_issues": total,
        "auto_fixable": auto,
        "needs_review": review,
        "manual_only": manual,
    }


# Utility helpers -------------------------------------------------------


def extract_row(a1: str) -> int:
    # Strip sheet prefix if present (e.g., "Sheet1!A5" -> "A5")
    if "!" in a1:
        a1 = a1.split("!")[-1]
    match = re.search(r"\d+", a1)
    return int(match.group()) if match else 0


def extract_col(a1: str) -> str:
    # Strip sheet prefix if present (e.g., "Sheet1!A5" -> "A5")
    if "!" in a1:
        a1 = a1.split("!")[-1]
    match = re.search(r"[A-Z]+", a1, re.IGNORECASE)
    return match.group().upper() if match else ""


def extract_sheet_refs(formula: str) -> List[Tuple[str, str]]:
    refs: List[Tuple[str, str]] = []
    quoted = re.findall(r"'([^']+)'!(\$?[A-Z]+\$?\d+)", formula)
    unquoted = re.findall(r"([A-Za-z][A-Za-z0-9_ ]*)!(\$?[A-Z]+\$?\d+)", formula)
    refs.extend(quoted)
    refs.extend(unquoted)
    return refs


def group_by_row(cells: List[Dict[str, Any]]) -> Dict[int, List[Dict[str, Any]]]:
    rows: Dict[int, List[Dict[str, Any]]] = {}
    for cell in cells:
        row = extract_row(cell.get("a1", ""))
        rows.setdefault(row, []).append(cell)
    return rows


def group_by_col(cells: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
    cols: Dict[str, List[Dict[str, Any]]] = {}
    for cell in cells:
        col = extract_col(cell.get("a1", ""))
        cols.setdefault(col, []).append(cell)
    return cols


def find_similar_sheets(name: str, existing: List[str]) -> List[Dict[str, Any]]:
    from difflib import SequenceMatcher

    similar: List[Dict[str, Any]] = []
    for sheet in existing:
        ratio = SequenceMatcher(None, name.lower(), sheet.lower()).ratio()
        if ratio > 0.5:
            similar.append({"name": sheet, "similarity": ratio})
    return sorted(similar, key=lambda x: x["similarity"], reverse=True)


def _normalize_column_formula(formula: str, row: int) -> str:
    pattern = re.compile(r"(\$?[A-Z]+)\$?(\d+)")

    def repl(match: re.Match[str]) -> str:
        col = match.group(1)
        ref_row = int(match.group(2))
        delta = ref_row - row
        sign = "+" if delta >= 0 else "-"
        return f"{col}" + "{ROW" + (f"{sign}{abs(delta)}" if delta else "") + "}"

    return pattern.sub(repl, formula)


def _denormalize_column_pattern(pattern: str, row: int) -> str:
    def repl(match: re.Match[str]) -> str:
        col = match.group(1)
        sign = match.group(2)
        number = match.group(3)
        offset = int(number) if number else 0
        target_row = row + offset if sign == "+" else row - offset
        return f"{col}{target_row}"

    return re.sub(r"(\$?[A-Z]+)\{ROW([+-]?)(\d*)\}", repl, pattern)


def _normalize_row_formula(formula: str, col: str) -> str:
    pattern = re.compile(r"(\$?[A-Z]+)\$?(\d+)", re.IGNORECASE)

    def repl(match: re.Match[str]) -> str:
        col_ref = match.group(1)
        row_ref = match.group(2)
        col_clean = col_ref.replace("$", "").upper()
        if col_clean == col:
            prefix = "$" if col_ref.startswith("$") else ""
            return f"{{{prefix}COL}}{row_ref}"
        return match.group(0)

    return pattern.sub(repl, formula)


def _denormalize_row_pattern(pattern: str, col: str) -> str:
    def repl(match: re.Match[str]) -> str:
        prefix = match.group(1)
        row_ref = match.group(2)
        col_ref = f"{prefix}{col}" if prefix else col
        return f"{col_ref}{row_ref}"

    return re.sub(r"\{(\$?)COL\}(\d+)", repl, pattern)


def _identify_majority_pattern(
    normalized_map: Dict[str, str], min_samples: int = 2
) -> Optional[Tuple[str, Dict[str, str]]]:
    if not normalized_map:
        return None
    counts = Counter(normalized_map.values())
    most_common, count = counts.most_common(1)[0]
    if count < min_samples:
        return None
    anomalies = {cell: pattern for cell, pattern in normalized_map.items() if pattern != most_common}
    return most_common, anomalies


def _is_summary_or_cross_section(formula: str) -> bool:
    """
    Heuristic: skip aggregation/long-distance formulas so we don't flag intentional summaries.
    """
    upper = formula.upper()
    aggregation_tokens = ("SUM(", "AVERAGE(", "COUNT(", "SUMIF", "COUNTIF", "SUMPRODUCT", "INDEX(", "MATCH(", "IF(")
    if any(tok in upper for tok in aggregation_tokens):
        return True
    # Cross-sheet references usually indicate intentional jumps.
    if "!" in formula:
        return True
    # Long-distance row references (e.g., summary rows pointing far away)
    rows = [int(m.group(1)) for m in re.finditer(r"\$?[A-Z]+\$?(\d+)", formula)]
    if rows and (max(rows) - min(rows)) > 10:
        return True
    return False


def _matches_expected_band(formula: str, col_letter: str, expected_sheet_map: Optional[Dict[str, str]]) -> bool:
    if not expected_sheet_map:
        return False
    expected_sheet = expected_sheet_map.get(col_letter)
    if not expected_sheet:
        return False
    sheet_refs = [sheet for sheet, _ in extract_sheet_refs(formula)]
    return expected_sheet in sheet_refs


def _safe_grid_get(grid: List[List[Any]], row_idx: int, col_idx: int) -> Any:
    if row_idx >= len(grid):
        return None
    row = grid[row_idx]
    if col_idx >= len(row):
        return None
    return row[col_idx]


def _has_content(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, str) and value.strip() == "":
        return False
    return True


def _cell_from_offsets(parsed, row_idx: int, col_idx: int) -> str:
    row_number = parsed.start_row + row_idx
    col_number = parsed.start_col + col_idx
    col_letters = col_index_to_letter(col_number)
    cell = f"{col_letters}{row_number}"
    return f"{parsed.sheet_name}!{cell}" if parsed.sheet_name else cell


def _find_cell(a1: str, cells: List[Dict[str, Any]]) -> Dict[str, Any]:
    for cell in cells:
        if cell.get("a1") == a1:
            return cell
    return {}
