from dataclasses import dataclass
from typing import Optional

from ..matrix import Cell, Matrix

EC_MAP = {0b01: "L", 0b00: "M", 0b11: "Q", 0b10: "H"}
EC_REVERSE_MAP = {"L": 0b01, "M": 0b00, "Q": 0b11, "H": 0b10}

FORMAT_MASK = 0x5412


def _bch_encode(data: int) -> int:
    """BCH(15,5) encode 5 data bits into 15 bits."""
    # Generator polynomial for BCH(15,5): x^10 + x^8 + x^5 + x^4 + x^2 + x + 1 = 0x537
    generator = 0x537
    encoded = data << 10
    for i in range(4, -1, -1):
        if encoded & (1 << (i + 10)):
            encoded ^= generator << i
    return (data << 10) | (encoded & 0x3FF)


def _get_valid_format_strings() -> list[tuple[int, str, int]]:
    """Return all 32 valid masked format strings: (masked_value, ec_level, mask_pattern)."""
    results = []
    for ec_name, ec_bits in EC_REVERSE_MAP.items():
        for mask in range(8):
            data = (ec_bits << 3) | mask
            format_bits = _bch_encode(data)
            masked = format_bits ^ FORMAT_MASK
            results.append((masked, ec_name, mask))
    return results


VALID_FORMAT_STRINGS = _get_valid_format_strings()


@dataclass
class FormatInformation:
    """Decoded QR code format information."""

    error_correction_level: Optional[str] = None  # 'L', 'M', 'Q', or 'H'
    mask_pattern: Optional[int] = None  # 0-7


def decode_format_version_information(matrix: Matrix) -> FormatInformation:
    """
    Decode format information from a QR code matrix.
    Format information is stored redundantly in two locations.
    Returns FormatInformation with error correction level and mask pattern,
    or None values if the information is incomplete.
    """
    format_cells = Matrix(15, 1)
    size = len(matrix.cells)

    # Location 1: Around the top-left corner
    # First 8 bits: row 8, columns 0-5, 7, 8 (skip column 6 - timing pattern)
    for i in range(6):
        format_cells.cells[0][i] = matrix.cells[8][i]
    format_cells.cells[0][6] = matrix.cells[8][7]
    format_cells.cells[0][7] = matrix.cells[8][8]

    # Next 7 bits: column 8, rows 7, 5-0 (skip row 6 - timing pattern)
    format_cells.cells[0][8] = matrix.cells[7][8]
    for i in range(6):
        format_cells.cells[0][9 + i] = matrix.cells[5 - i][8]

    # Location 2: Bottom-left (vertical) + Top-right (horizontal)
    # First 7 bits: column 8, from bottom upward (rows size-1 down to size-7)
    for i in range(7):
        format_cells.cells[0][i] = format_cells.cells[0][i] | matrix.cells[size - 1 - i][8]

    # Last 8 bits: row 8, right-to-left from columns size-1 down to size-8
    # bit 7 at col size-8, bit 8 at col size-7, ..., bit 14 at col size-1
    for i in range(8):
        format_cells.cells[0][7 + i] = format_cells.cells[0][7 + i] | matrix.cells[8][size - 8 + i]

    known_mask = 0
    known_value = 0
    for i in range(15):
        if format_cells.cells[0][i] != Cell.UNKNOWN:
            known_mask |= 1 << (14 - i)
            if format_cells.cells[0][i] == Cell.FILLED:
                known_value |= 1 << (14 - i)

    best_distance = 15
    matches: list[tuple[str, int]] = []
    for masked_format, ec_level, mask_pattern in VALID_FORMAT_STRINGS:
        diff = (masked_format ^ known_value) & known_mask
        dist = bin(diff).count("1")
        if dist < best_distance:
            best_distance = dist
            matches = [(ec_level, mask_pattern)]
        elif dist == best_distance:
            matches.append((ec_level, mask_pattern))

    if best_distance > 3 or len(matches) == 0:
        return FormatInformation()

    ec_levels = set(m[0] for m in matches)
    mask_patterns = set(m[1] for m in matches)

    return FormatInformation(
        error_correction_level=ec_levels.pop() if len(ec_levels) == 1 else None,
        mask_pattern=mask_patterns.pop() if len(mask_patterns) == 1 else None,
    )
