"""Pure reverse index for 1C common-command group membership.""" from __future__ import annotations import re from collections.abc import Iterable, Mapping from typing import Any from .payload import decode_payload_lossless, parse_brace_text _GUID_RE = re.compile( r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ) def _tree_item_at_path(tree: Any, path: tuple[int, ...]) -> Any | None: node = tree for index in path: if not isinstance(node, dict) or not isinstance(node.get("items"), list): return None items = node["items"] if index < 0 or index >= len(items): return None node = items[index] return node def _tree_scalar_at_path(tree: Any, path: tuple[int, ...]) -> str: node = _tree_item_at_path(tree, path) if isinstance(node, dict) and node.get("type") in {"atom", "string"}: return str(node.get("value") or "") return "" def common_command_group_guid(tree: Any) -> str | None: """Return the group GUID stored in an observed CommonCommand Config tree.""" body = _tree_item_at_path(tree, (1, 1, 2)) group_guid = _tree_scalar_at_path(body, (7, 1)).strip().lower() return group_guid if _GUID_RE.fullmatch(group_guid) else None def parse_common_command_tree(data: bytes) -> Any | None: """Decode a CommonCommand payload without raising on unsupported data.""" try: decoded = decode_payload_lossless(data) text = decoded.get("text") if not text or "{" not in text: return None return parse_brace_text(text) except Exception: return None def index_common_command_groups( commands: Iterable[Mapping[str, Any]], payloads: Mapping[str, bytes], ) -> dict[str, Any]: """Build ``CommandGroup GUID -> CommonCommand rows`` from Config payloads.""" command_rows = [dict(item) for item in commands] normalized_payloads = {str(key).strip().lower(): value for key, value in payloads.items()} groups: dict[str, list[dict[str, Any]]] = {} source_missing = 0 undecodable = 0 unassigned = 0 for item in command_rows: guid = str(item.get("guid") or "").strip().lower() data = normalized_payloads.get(guid) if not data: source_missing += 1 continue tree = parse_common_command_tree(data) if tree is None: undecodable += 1 continue group_guid = common_command_group_guid(tree) if group_guid is None: unassigned += 1 continue groups.setdefault(group_guid, []).append(item) return { "groups": groups, "scanned": len(command_rows), "source_missing": source_missing, "undecodable": undecodable, "unassigned": unassigned, }