"""Field classification logic and naming helpers for semantics parsing."""

from __future__ import annotations

import re
from typing import Any, Dict, List, Optional, Tuple

from .ir import LibraryChildIR, OptionIR, StructuralChildIR


# -- Importance ranking ------------------------------------------------

_IMPORTANCE_RANK = {"high": 3, "medium": 2, "low": 1}


# -- Naming helpers ----------------------------------------------------

def _camel_to_kebab(s: str) -> str:
    """Convert CamelCase or camelCase to kebab-case.

    Handles uppercase runs (e.g. ``"HTML"`` stays ``"html"``) and mixed
    sequences (e.g. ``"HTMLParser"`` becomes ``"html-parser"``).
    """
    s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1-\2", s)
    s = re.sub(r"([a-z])([A-Z])", r"\1-\2", s)
    return s.lower()


def machine_name_to_directive(name: str) -> str:
    """Convert an H5P machine name to an RST directive name.

    >>> machine_name_to_directive("H5P.TrueFalse")
    'h5p-true-false'
    >>> machine_name_to_directive("H5P.MultiChoice")
    'h5p-multi-choice'
    """
    parts = name.split(".")
    return "-".join(_camel_to_kebab(p) for p in parts)


def field_name_to_option(name: str) -> str:
    """Convert a camelCase H5P field name to a kebab-case RST option name.

    >>> field_name_to_option("enableRetry")
    'enable-retry'
    >>> field_name_to_option("confirmCheckDialog")
    'confirm-check-dialog'
    """
    return _camel_to_kebab(name)


# -- Skip predicates ---------------------------------------------------

_SKIP_TYPES = frozenset({"image", "video", "audio", "file"})

_SKIP_GROUP_NAMES = frozenset({"media", "overallFeedback"})


def _should_skip(field: Dict[str, Any]) -> bool:
    """Return True if a field should be discarded."""
    ftype = field.get("type")

    # Rule 3: media asset types
    if ftype in _SKIP_TYPES:
        return True

    # Rule 2: deprecated
    if field.get("deprecated", False):
        return True

    if ftype == "group":
        # Rule 1: common groups
        if field.get("common", False):
            return True
        # Rule 4 & 5: specifically named groups
        if field.get("name") in _SKIP_GROUP_NAMES:
            return True
        # Optional groups that are not behaviour
        if field.get("optional", False) and field.get("name") != "behaviour":
            return True

    return False


# -- Field → OptionIR -------------------------------------------------

_SIMPLE_TYPES = frozenset({"text", "boolean", "number", "select"})


def _make_option(
    field: Dict[str, Any],
    name_prefix: str = "",
) -> OptionIR:
    """Build an OptionIR from a semantics field dict."""
    fname = field["name"]
    if name_prefix:
        name = name_prefix + "-" + field_name_to_option(fname)
    else:
        name = field_name_to_option(fname)

    choices: Tuple[Tuple[str, str], ...] = ()
    if field.get("type") == "select":
        choices = tuple(
            (opt["value"], opt["label"])
            for opt in field.get("options", [])
        )

    return OptionIR(
        name=name,
        field_name=fname,
        field_type=field["type"],
        label=field.get("label", ""),
        description=field.get("description", ""),
        required=not field.get("optional", False),
        default=field.get("default"),
        choices=choices,
    )


def _is_body_candidate(field: Dict[str, Any]) -> bool:
    """True if the field is a text field with the html widget."""
    return field.get("type") == "text" and field.get("widget") == "html"


# -- Group flattening --------------------------------------------------

def _flatten_group(
    fields: List[Dict[str, Any]],
    prefix: str,
) -> List[OptionIR]:
    """Flatten a group's fields into options with the given prefix.

    Nested groups are recursively flattened with an accumulated prefix.
    """
    options: List[OptionIR] = []
    for field in fields:
        if _should_skip(field):
            continue
        ftype = field.get("type")
        if ftype == "group":
            sub = field_name_to_option(field["name"])
            nested_prefix = prefix + "-" + sub if prefix else sub
            options.extend(_flatten_group(field.get("fields", []), nested_prefix))
        elif ftype in _SIMPLE_TYPES:
            options.append(_make_option(field, name_prefix=prefix))
    return options


# -- Main classifier ---------------------------------------------------

def classify_fields(
    fields: List[Dict[str, Any]],
    parent_directive_name: str,
) -> Tuple[
    Optional[OptionIR],
    Tuple[OptionIR, ...],
    Tuple[StructuralChildIR, ...],
    Tuple[LibraryChildIR, ...],
]:
    """Classify semantics fields into body, options, and children.

    Returns ``(body, options, structural_children, library_children)``.
    """
    body_candidates: List[Tuple[int, int, OptionIR]] = []
    options: List[OptionIR] = []
    structural_children: List[StructuralChildIR] = []
    library_children: List[LibraryChildIR] = []

    for idx, field in enumerate(fields):
        if _should_skip(field):
            continue

        ftype = field.get("type")

        # Group → flatten into parent options
        if ftype == "group":
            is_behaviour = field.get("name") == "behaviour"
            prefix = "" if is_behaviour else field_name_to_option(field["name"])
            options.extend(
                _flatten_group(field.get("fields", []), prefix),
            )
            continue

        # List → structural child or library child
        if ftype == "list":
            inner = field.get("field", {})
            inner_type = inner.get("type")
            entity = field.get("entity", field.get("name", "item"))
            child_directive = "%s-%s" % (parent_directive_name, entity)
            min_count = field.get("min", 0)
            max_count = field.get("max")

            if inner_type == "group":
                child_body, child_opts, _, _ = classify_fields(
                    inner.get("fields", []),
                    child_directive,
                )
                structural_children.append(StructuralChildIR(
                    directive_name=child_directive,
                    entity=entity,
                    body=child_body,
                    options=child_opts,
                    min_count=min_count,
                    max_count=max_count,
                ))
            elif inner_type == "library":
                library_children.append(LibraryChildIR(
                    directive_name=child_directive,
                    entity=entity,
                    libraries=tuple(inner.get("options", [])),
                ))
            continue

        # Body candidate: text + html widget
        if _is_body_candidate(field):
            rank = _IMPORTANCE_RANK.get(
                field.get("importance", "low"), 0,
            )
            body_candidates.append((rank, idx, _make_option(field)))
            continue

        # Simple option
        if ftype in _SIMPLE_TYPES:
            options.append(_make_option(field))

    # Select body: highest importance, first wins
    body: Optional[OptionIR] = None
    if body_candidates:
        body_candidates.sort(key=lambda x: (-x[0], x[1]))
        body = body_candidates[0][2]
        for _, _, opt in body_candidates[1:]:
            options.append(opt)

    return body, tuple(options), tuple(structural_children), tuple(library_children)
