"""Channel grouping utilities.

Provides functions for auto-detecting channel groups and building
hierarchical channel trees from ISO naming conventions.
"""

from __future__ import annotations

from collections import defaultdict

from impakt.channel.model import Channel, ChannelGroup


def auto_group(channels: dict[str, Channel]) -> dict[str, ChannelGroup]:
    """Group channels into X/Y/Z component families.

    Channels are grouped by their ``group_key()`` — they share test object,
    location, measurement, and sense, differing only in direction (X/Y/Z).

    Args:
        channels: Dictionary of channel name -> Channel.

    Returns:
        Dictionary of group key -> ChannelGroup.
    """
    temp: dict[str, dict[str, Channel]] = defaultdict(dict)

    for ch in channels.values():
        if not ch.code.is_valid or not ch.code.is_component():
            continue
        gkey = ch.code.group_key()
        axis = ch.code.direction
        temp[gkey][axis] = ch

    groups: dict[str, ChannelGroup] = {}
    for gkey, axes in temp.items():
        groups[gkey] = ChannelGroup(
            key=gkey,
            x=axes.get("X"),
            y=axes.get("Y"),
            z=axes.get("Z"),
        )

    return groups


def find_resultant_candidates(channels: dict[str, Channel]) -> list[ChannelGroup]:
    """Find all channel groups that have at least 2 components.

    These are candidates for automatic resultant computation.
    """
    groups = auto_group(channels)
    return [g for g in groups.values() if len(g.components()) >= 2]


def build_channel_tree(
    channels: dict[str, Channel],
) -> dict[str, dict[str, dict[str, list[Channel]]]]:
    """Build a hierarchical tree of channels for UI display.

    Returns:
        Nested dict: {test_object_label: {location_label: {measurement_label: [channels]}}}
    """
    tree: dict[str, dict[str, dict[str, list[Channel]]]] = {}

    for ch in channels.values():
        if not ch.code.is_valid:
            obj = "Other"
            loc = "Unknown"
            meas = "Unknown"
        else:
            obj = ch.code.test_object_label
            loc = ch.code.location_label
            meas = ch.code.measurement_label

        tree.setdefault(obj, {}).setdefault(loc, {}).setdefault(meas, []).append(ch)

    # Sort channels within each leaf
    for obj_dict in tree.values():
        for loc_dict in obj_dict.values():
            for meas_key in loc_dict:
                loc_dict[meas_key] = sorted(loc_dict[meas_key], key=lambda c: c.name)

    return tree
