import xml.dom.minidom as md


def _validate_label(label: str) -> None:
    if not isinstance(label, str):
        raise TypeError("Tag label must be a string")
    if not label.strip():
        raise ValueError("Tag label cannot be empty")


def _get_tag_at_index(
    doc: md.Document | md.Element,
    label: str,
    index: int = 0,
) -> md.Element:
    _validate_label(label)
    if index < 0:
        raise IndexError("Element index cannot be negative")

    elements = doc.getElementsByTagName(label)
    if not elements:
        raise IndexError(f"Tag '{label}' not found")
    if index >= len(elements):
        raise IndexError(
            f"Tag '{label}' has {len(elements)} element(s), index {index} is out of range"
        )
    return elements[index]


def _get_text_value(element: md.Element, label: str) -> str:
    child = element.firstChild
    if child is None:
        raise AttributeError(f"Tag '{label}' has no child nodes")
    if not isinstance(child, md.Text):
        raise TypeError(f"Tag '{label}' first child is not a text node")
    return child.data


def getValue(nodeList: md.Document | md.Element, label: str) -> str:
    """Return the text content of the first matching XML tag."""
    return getFromXml(nodeList, label, idx=0)


def getElement(doc: md.Document | md.Element, label: str, el: int = 0) -> md.Element:
    """Return the XML element with the given tag name at a specific index."""
    return _get_tag_at_index(doc, label, index=el)


def getFromXml(xml: md.Document | md.Element, label: str, idx: int = 0) -> str:
    """Return the text value of the selected XML tag."""
    element = _get_tag_at_index(xml, label, index=idx)
    return _get_text_value(element, label)


def getValues(xml: md.Document | md.Element, label: str) -> list[str]:
    """Return text values for all matching tags."""
    _validate_label(label)
    elements = xml.getElementsByTagName(label)
    if not elements:
        raise IndexError(f"Tag '{label}' not found")
    return [_get_text_value(element, label) for element in elements]


def getAttribute(
    xml: md.Document | md.Element,
    label: str,
    attribute: str,
    idx: int = 0,
) -> str:
    """Return an attribute value from the selected tag."""
    if not isinstance(attribute, str):
        raise TypeError("Attribute name must be a string")
    if not attribute.strip():
        raise ValueError("Attribute name cannot be empty")

    element = _get_tag_at_index(xml, label, index=idx)
    if not element.hasAttribute(attribute):
        raise KeyError(f"Tag '{label}' has no attribute '{attribute}'")
    return element.getAttribute(attribute)


def tagExists(xml: md.Document | md.Element, label: str) -> bool:
    """Check if at least one tag with the given name exists."""
    _validate_label(label)
    return bool(xml.getElementsByTagName(label))
