## Copyright (C) 2026  Solaar Contributors https://pwr-solaar.github.io/Solaar/
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""Shared building blocks for regional keyboard layouts.

Each region (ANSI, ISO_QWERTY, ISO_QWERTZ, ISO_AZERTY, JIS) shares the function
row, nav-cluster, and numpad blocks; only the main alpha block differs (ANSI
includes the row 2 col 13 backslash, ISO doesn't). Regional label overrides on
top of either main block produce the final layout.

Cell positions and groupings adapted from OpenRGB's KeyboardLayoutManager.
Zone IDs are firmware values reported by Logitech HID++ feature 0x8081
(PER_KEY_LIGHTING_V2).
"""

from __future__ import annotations

from ..layout import Cell
from ..layout import Layout

# --- Function row: ESC + F1..F12 (shared across all regions).
FN_ROW: tuple[Cell, ...] = (
    Cell(zone_id=38, row=0, col=0, group="fn_row", label="Esc"),
    Cell(zone_id=55, row=0, col=2, group="fn_row", label="F1"),
    Cell(zone_id=56, row=0, col=3, group="fn_row", label="F2"),
    Cell(zone_id=57, row=0, col=4, group="fn_row", label="F3"),
    Cell(zone_id=58, row=0, col=5, group="fn_row", label="F4"),
    Cell(zone_id=59, row=0, col=6, group="fn_row", label="F5"),
    Cell(zone_id=60, row=0, col=7, group="fn_row", label="F6"),
    Cell(zone_id=61, row=0, col=8, group="fn_row", label="F7"),
    Cell(zone_id=62, row=0, col=9, group="fn_row", label="F8"),
    Cell(zone_id=63, row=0, col=10, group="fn_row", label="F9"),
    Cell(zone_id=64, row=0, col=11, group="fn_row", label="F10"),
    Cell(zone_id=65, row=0, col=12, group="fn_row", label="F11"),
    Cell(zone_id=66, row=0, col=13, group="fn_row", label="F12"),
)

# --- Nav cluster + arrows (shared).
EXTRAS: tuple[Cell, ...] = (
    Cell(zone_id=67, row=0, col=14, group="extras", label="PrtSc"),
    Cell(zone_id=68, row=0, col=15, group="extras", label="ScrLk"),
    Cell(zone_id=69, row=0, col=16, group="extras", label="Pause"),
    Cell(zone_id=70, row=1, col=14, group="extras", label="Ins"),
    Cell(zone_id=71, row=1, col=15, group="extras", label="Home"),
    Cell(zone_id=72, row=1, col=16, group="extras", label="PgUp"),
    Cell(zone_id=73, row=2, col=14, group="extras", label="Del"),
    Cell(zone_id=74, row=2, col=15, group="extras", label="End"),
    Cell(zone_id=75, row=2, col=16, group="extras", label="PgDn"),
    Cell(zone_id=79, row=4, col=15, group="extras", label="↑"),
    Cell(zone_id=77, row=5, col=14, group="extras", label="←"),
    Cell(zone_id=78, row=5, col=15, group="extras", label="↓"),
    Cell(zone_id=76, row=5, col=16, group="extras", label="→"),
)

# --- Numpad block (only on full-size keyboards).
NUMPAD: tuple[Cell, ...] = (
    Cell(zone_id=80, row=1, col=17, group="numpad", label="Num"),
    Cell(zone_id=81, row=1, col=18, group="numpad", label="/"),
    Cell(zone_id=82, row=1, col=19, group="numpad", label="*"),
    Cell(zone_id=83, row=1, col=20, group="numpad", label="-"),
    Cell(zone_id=92, row=2, col=17, group="numpad", label="7"),
    Cell(zone_id=93, row=2, col=18, group="numpad", label="8"),
    Cell(zone_id=94, row=2, col=19, group="numpad", label="9"),
    Cell(zone_id=84, row=2, col=20, height=2.0, group="numpad", label="+"),
    Cell(zone_id=89, row=3, col=17, group="numpad", label="4"),
    Cell(zone_id=90, row=3, col=18, group="numpad", label="5"),
    Cell(zone_id=91, row=3, col=19, group="numpad", label="6"),
    Cell(zone_id=86, row=4, col=17, group="numpad", label="1"),
    Cell(zone_id=87, row=4, col=18, group="numpad", label="2"),
    Cell(zone_id=88, row=4, col=19, group="numpad", label="3"),
    Cell(zone_id=85, row=4, col=20, height=2.0, group="numpad", label="Enter"),
    Cell(zone_id=95, row=5, col=17, width=2.0, group="numpad", label="0"),
    Cell(zone_id=96, row=5, col=19, group="numpad", label="."),
)

# --- Main alpha block, ANSI (104-key). Includes row 2 col 13 backslash and
#     omits POUND (row 3 col 12) + ISO_BACKSLASH (row 4 col 1).
MAIN_ANSI: tuple[Cell, ...] = (
    # Row 1: backtick + numbers + minus/equals + backspace
    Cell(zone_id=50, row=1, col=0, group="main", label="`"),
    Cell(zone_id=27, row=1, col=1, group="main", label="1"),
    Cell(zone_id=28, row=1, col=2, group="main", label="2"),
    Cell(zone_id=29, row=1, col=3, group="main", label="3"),
    Cell(zone_id=30, row=1, col=4, group="main", label="4"),
    Cell(zone_id=31, row=1, col=5, group="main", label="5"),
    Cell(zone_id=32, row=1, col=6, group="main", label="6"),
    Cell(zone_id=33, row=1, col=7, group="main", label="7"),
    Cell(zone_id=34, row=1, col=8, group="main", label="8"),
    Cell(zone_id=35, row=1, col=9, group="main", label="9"),
    Cell(zone_id=36, row=1, col=10, group="main", label="0"),
    Cell(zone_id=42, row=1, col=11, group="main", label="-"),
    Cell(zone_id=43, row=1, col=12, group="main", label="="),
    Cell(zone_id=39, row=1, col=13, group="main", label="Bksp"),
    # Row 2: tab + qwerty + brackets + backslash
    Cell(zone_id=40, row=2, col=0, group="main", label="Tab"),
    Cell(zone_id=17, row=2, col=1, group="main", label="Q"),
    Cell(zone_id=23, row=2, col=2, group="main", label="W"),
    Cell(zone_id=5, row=2, col=3, group="main", label="E"),
    Cell(zone_id=18, row=2, col=4, group="main", label="R"),
    Cell(zone_id=20, row=2, col=5, group="main", label="T"),
    Cell(zone_id=25, row=2, col=6, group="main", label="Y"),
    Cell(zone_id=21, row=2, col=7, group="main", label="U"),
    Cell(zone_id=9, row=2, col=8, group="main", label="I"),
    Cell(zone_id=15, row=2, col=9, group="main", label="O"),
    Cell(zone_id=16, row=2, col=10, group="main", label="P"),
    Cell(zone_id=44, row=2, col=11, group="main", label="["),
    Cell(zone_id=45, row=2, col=12, group="main", label="]"),
    Cell(zone_id=46, row=2, col=13, group="main", label="\\"),
    # Row 3: caps + asdf-row + semi/quote + enter
    Cell(zone_id=54, row=3, col=0, group="main", label="Caps"),
    Cell(zone_id=1, row=3, col=1, group="main", label="A"),
    Cell(zone_id=19, row=3, col=2, group="main", label="S"),
    Cell(zone_id=4, row=3, col=3, group="main", label="D"),
    Cell(zone_id=6, row=3, col=4, group="main", label="F"),
    Cell(zone_id=7, row=3, col=5, group="main", label="G"),
    Cell(zone_id=8, row=3, col=6, group="main", label="H"),
    Cell(zone_id=10, row=3, col=7, group="main", label="J"),
    Cell(zone_id=11, row=3, col=8, group="main", label="K"),
    Cell(zone_id=12, row=3, col=9, group="main", label="L"),
    Cell(zone_id=48, row=3, col=10, group="main", label=";"),
    Cell(zone_id=49, row=3, col=11, group="main", label="'"),
    Cell(zone_id=37, row=3, col=13, group="main", label="Enter"),
    # Row 4: shift + zxcv-row + comma/period/slash + rshift
    Cell(zone_id=105, row=4, col=0, group="main", label="Shift"),
    Cell(zone_id=26, row=4, col=2, group="main", label="Z"),
    Cell(zone_id=24, row=4, col=3, group="main", label="X"),
    Cell(zone_id=3, row=4, col=4, group="main", label="C"),
    Cell(zone_id=22, row=4, col=5, group="main", label="V"),
    Cell(zone_id=2, row=4, col=6, group="main", label="B"),
    Cell(zone_id=14, row=4, col=7, group="main", label="N"),
    Cell(zone_id=13, row=4, col=8, group="main", label="M"),
    Cell(zone_id=51, row=4, col=9, group="main", label=","),
    Cell(zone_id=52, row=4, col=10, group="main", label="."),
    Cell(zone_id=53, row=4, col=11, group="main", label="/"),
    Cell(zone_id=109, row=4, col=13, group="main", label="Shift"),
    # Row 5: bottom row. Space spans cols 3..9 visually.
    Cell(zone_id=104, row=5, col=0, group="main", label="Ctrl"),
    Cell(zone_id=107, row=5, col=1, group="main", label="Win"),
    Cell(zone_id=106, row=5, col=2, group="main", label="Alt"),
    Cell(zone_id=41, row=5, col=3, width=7.0, group="main", label="Space"),
    Cell(zone_id=110, row=5, col=10, group="main", label="AltGr"),
    Cell(zone_id=111, row=5, col=11, group="main", label="Win"),
    Cell(zone_id=98, row=5, col=12, group="main", label="Menu"),
    Cell(zone_id=108, row=5, col=13, group="main", label="Ctrl"),
)

# --- Main alpha block, ISO. Drops the row 2 col 13 backslash (zone 46 is the
#     upper half of the L-shape Enter on ISO, addressed by zone 37) and adds
#     the two ISO-only keys: POUND (zone 47) at row 3 col 12 between ' and
#     Enter, and ISO_BACKSLASH (zone 97) at row 4 col 1 between Shift and Z.
#     Regional layouts override the labels to match local keycaps (# / < on
#     QWERTZ, # / \ on UK QWERTY, * / < on AZERTY).
_ISO_EXTRA_KEYS: tuple[Cell, ...] = (
    Cell(zone_id=47, row=3, col=12, group="main", label="#"),
    Cell(zone_id=97, row=4, col=1, group="main", label="\\"),
)
MAIN_ISO: tuple[Cell, ...] = tuple(c for c in MAIN_ANSI if not (c.row == 2 and c.col == 13)) + _ISO_EXTRA_KEYS

# --- Curated allowlist for unmapped device zones surfaced in the bottom strip.
#     G-keys, logo, media, brightness — the canonical "extras" Logitech firmware
#     actually addresses. Phantom zones (e.g. G515's 47, 97, 99-103, 254) drop.
EXTRAS_ALLOWLIST: frozenset[int] = frozenset(
    {
        153,  # Brightness
        155,  # Play/Pause
        156,  # Mute
        157,  # Next
        158,  # Previous
        180,  # G1
        181,  # G2
        182,  # G3
        183,  # G4
        184,  # G5
        210,  # Logo
    }
)


def _relabel(cells: tuple[Cell, ...], overrides: dict[int, str]) -> tuple[Cell, ...]:
    """Return a new tuple where any cell whose zone_id is in `overrides` has
    its label replaced. Unaffected cells pass through unchanged.
    """
    if not overrides:
        return cells
    return tuple(
        Cell(
            zone_id=c.zone_id,
            row=c.row,
            col=c.col,
            width=c.width,
            height=c.height,
            group=c.group,
            label=overrides[c.zone_id] if c.zone_id in overrides else c.label,
            x=c.x,
            y=c.y,
        )
        for c in cells
    )


def build_layout(
    main_cells: tuple[Cell, ...],
    *,
    include_numpad: bool,
    label_overrides: dict[int, str] | None = None,
    description: str = "",
) -> Layout:
    """Assemble a regional keyboard layout from a chosen main block + the
    shared fn-row / extras / (optionally) numpad blocks. Apply per-zone
    label overrides to every cell whose zone matches.
    """
    cells = FN_ROW + main_cells + EXTRAS
    if include_numpad:
        cells = cells + NUMPAD
    cells = _relabel(cells, label_overrides or {})
    cols = 21 if include_numpad else 17
    return Layout(
        cells=cells,
        rows=6,
        cols=cols,
        extra_zones=EXTRAS_ALLOWLIST,
        description=description,
    )
