"""Shared KLayout RDB XML parsing utilities.

This module provides common parsing functions for KLayout Report Database (RDB)
XML output, used by DRC, connectivity, and LVS verification handlers.
"""

from __future__ import annotations

import xml.etree.ElementTree as ET
from typing import Any

__all__ = [
    "parse_polygon_location",
    "extract_xml_string",
    "parse_rdb_cells",
    "parse_rdb_categories_flat",
    "parse_rdb_categories_nested",
    "parse_category_path",
]


def parse_polygon_location(values_elem: ET.Element | None) -> dict[str, Any] | None:
    """Parse polygon coordinates and compute location data.

    Args:
        values_elem: XML element containing polygon coordinates

    Returns:
        Dict with bbox, centroid, and size, or None if parsing fails
    """
    if values_elem is None:
        return None

    value_text = None
    for value_elem in values_elem.findall("value"):
        if value_elem.text and "polygon:" in value_elem.text:
            value_text = value_elem.text
            break

    if value_text is None:
        return None

    try:
        coords_str = value_text.replace("polygon:", "").strip().strip("()")
        if not coords_str:
            return None

        pairs = coords_str.split(";")
        coords = []
        for pair in pairs:
            if "," not in pair:
                continue
            x_str, y_str = pair.split(",", 1)
            coords.append((float(x_str), float(y_str)))

        if not coords:
            return None

        xs = [x for x, y in coords]
        ys = [y for x, y in coords]

        bbox = {
            "min_x": round(min(xs), 3),
            "min_y": round(min(ys), 3),
            "max_x": round(max(xs), 3),
            "max_y": round(max(ys), 3),
        }

        centroid = {
            "x": round(sum(xs) / len(xs), 3),
            "y": round(sum(ys) / len(ys), 3),
        }

        size = {
            "width": round(bbox["max_x"] - bbox["min_x"], 3),
            "height": round(bbox["max_y"] - bbox["min_y"], 3),
        }

        return {
            "bbox": bbox,
            "centroid": centroid,
            "size": size,
        }

    except (ValueError, IndexError):
        return None


def extract_xml_string(
    response: Any, check_name: str = "verification"
) -> tuple[str | None, dict[str, Any] | None]:
    """Extract XML string from various response formats.

    Handles FastAPI error dicts, JSON-wrapped XML, raw strings, and
    unexpected types.

    Args:
        response: Raw response from the FastAPI backend
        check_name: Name of the check for error messages (e.g. "DRC", "connectivity")

    Returns:
        Tuple of (xml_string, None) on success, or (None, error_dict) on failure
    """
    try:
        if isinstance(response, dict) and "detail" in response:
            detail = response["detail"]
            if "does not exist" in str(detail).lower():
                return None, {
                    "error": "File not found",
                    "detail": detail,
                    "suggestion": (
                        "The GDS file does not exist. Please build the cell first "
                        "using 'build_cells' before running checks."
                    ),
                }
            return None, {
                "error": f"{check_name} check failed",
                "detail": detail,
            }

        if isinstance(response, dict) and "content" in response:
            return response["content"], None
        elif isinstance(response, str):
            return response, None
        else:
            return None, {
                "error": f"Unexpected response type: {type(response).__name__}",
                "suggestion": "Expected XML string or dict with 'content' key",
                "response_preview": str(response)[:500],
            }
    except Exception as e:
        return None, {
            "error": f"Failed to extract XML from response: {e}",
            "response_preview": str(response)[:500],
        }


def parse_rdb_cells(root: ET.Element) -> list[str]:
    """Extract cell names from RDB XML <cells> section.

    Args:
        root: Parsed XML root element

    Returns:
        List of cell name strings
    """
    cells = []
    for cell in root.findall(".//cells/cell"):
        name_elem = cell.find("name")
        if name_elem is not None and name_elem.text:
            cells.append(name_elem.text)
    return cells


def parse_rdb_categories_flat(root: ET.Element) -> dict[str, str]:
    """Extract flat category map from RDB XML for DRC-style results.

    Args:
        root: Parsed XML root element

    Returns:
        Dict mapping category name to description
    """
    categories_map = {}
    for category in root.findall(".//categories/category"):
        name_elem = category.find("name")
        desc_elem = category.find("description")
        if name_elem is not None and name_elem.text:
            categories_map[name_elem.text] = (
                desc_elem.text
                if desc_elem is not None and desc_elem.text
                else name_elem.text
            )
    return categories_map


def parse_rdb_categories_nested(root: ET.Element) -> dict[str, dict[str, str]]:
    """Extract nested category map from RDB XML for connectivity/LVS results.

    Handles parent > child hierarchy where categories contain sub-categories.

    Args:
        root: Parsed XML root element

    Returns:
        Nested dict: {parent_name: {child_name: child_description, ...}, ...}
        Top-level categories without children appear as {name: {}}.
    """
    categories: dict[str, dict[str, str]] = {}
    for category in root.findall(".//categories/category"):
        name_elem = category.find("name")
        if name_elem is None or not name_elem.text:
            continue
        parent_name = name_elem.text
        children: dict[str, str] = {}
        for sub_cat in category.findall("categories/category"):
            sub_name_elem = sub_cat.find("name")
            sub_desc_elem = sub_cat.find("description")
            if sub_name_elem is not None and sub_name_elem.text:
                children[sub_name_elem.text] = (
                    sub_desc_elem.text
                    if sub_desc_elem is not None and sub_desc_elem.text
                    else sub_name_elem.text
                )
        categories[parent_name] = children
    return categories


def parse_category_path(text: str) -> tuple[str, str | None]:
    """Parse item category references in multiple formats.

    Handles:
    - Dot-quoted both: 'parent'.'child'
    - Dot-quoted parent only: 'parent'.child
    - Slash-separated: parent/child
    - Plain text: category_name

    Args:
        text: Category reference string from an <item><category> element

    Returns:
        Tuple of (parent, child) where child may be None for flat categories
    """
    if "'." in text:
        parts = text.split("'.", 1)
        parent = parts[0].strip("'")
        child = parts[1].strip("'") if len(parts) > 1 and parts[1] else None
        return parent, child

    if "/" in text:
        parts = text.split("/", 1)
        return parts[0], parts[1] if len(parts) > 1 else None

    return text, None
