from __future__ import annotations

import copy
from typing import Any, Dict, Iterable, List, Sequence, Tuple


def canonical_node_id(value: Any, width: int = 4) -> str:
    if value is None:
        return ""
    text = str(value).strip()
    if not text:
        return ""
    if text.isdigit():
        return str(int(text)).zfill(width)
    return text


def strip_fields(data: Any, fields: Sequence[str]) -> Any:
    if isinstance(data, dict):
        return {
            key: strip_fields(value, fields)
            for key, value in data.items()
            if key not in set(fields)
        }
    if isinstance(data, list):
        return [strip_fields(item, fields) for item in data]
    return data


def _normalize_node(node: Dict[str, Any]) -> Dict[str, Any]:
    out = copy.deepcopy(node)

    node_id = canonical_node_id(out.get("node_id") or out.get("id"))
    if node_id:
        out["node_id"] = node_id

    if "full_text" not in out and isinstance(out.get("text"), str):
        out["full_text"] = out.get("text", "")
    if "exclusive_text" not in out:
        out["exclusive_text"] = out.get("text", "") if isinstance(out.get("text"), str) else ""

    children = out.get("children")
    if not isinstance(children, list):
        children = out.get("nodes") if isinstance(out.get("nodes"), list) else []
    out["children"] = [_normalize_node(child) for child in children if isinstance(child, dict)]
    out.pop("nodes", None)

    return out


def normalize_navexa_tree_document(document: Dict[str, Any]) -> Dict[str, Any]:
    out = copy.deepcopy(document)
    structure = out.get("structure")
    if isinstance(structure, dict):
        structure = [structure]
    if not isinstance(structure, list):
        structure = []
    out["structure"] = [_normalize_node(node) for node in structure if isinstance(node, dict)]
    return out


def flatten_with_parent(structure: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
    rows: List[Dict[str, Any]] = []

    def walk(nodes: List[Dict[str, Any]], parent_id: str) -> None:
        for node in nodes:
            node_id = canonical_node_id(node.get("node_id") or node.get("id"))
            row = copy.deepcopy(node)
            row["node_id"] = node_id
            row["parent_id"] = parent_id or None
            rows.append(row)
            children = node.get("children") or []
            if isinstance(children, list) and children:
                walk(children, node_id)

    walk(list(structure), "")
    return rows


def build_parent_lookup(structure: Iterable[Dict[str, Any]]) -> Dict[str, str | None]:
    return {
        row["node_id"]: row.get("parent_id")
        for row in flatten_with_parent(structure)
        if row.get("node_id")
    }


def split_selected_with_ancestor_dedupe(
    selected_ids: Sequence[str],
    parent_lookup: Dict[str, str | None],
) -> Tuple[List[str], List[str]]:
    selected_set = set(selected_ids)
    kept: List[str] = []
    dropped: List[str] = []

    for node_id in selected_ids:
        parent_id = parent_lookup.get(node_id)
        has_selected_ancestor = False
        while parent_id:
            if parent_id in selected_set:
                has_selected_ancestor = True
                break
            parent_id = parent_lookup.get(parent_id)

        if has_selected_ancestor:
            dropped.append(node_id)
        else:
            kept.append(node_id)

    return kept, dropped
