"""Extract property descriptions from component class docstrings.

Dash component classes have structured docstrings generated by
``dash-generate-components`` in the format::

    Keyword arguments:

    - prop_name (type_string; optional):
        Description text that may span
        multiple lines.

This module parses that format and returns the first sentence of the
description for a given property.
"""

from __future__ import annotations

import re

from dash.mcp.types import MCPInput

from .base import InputDescriptionSource

_PROP_RE = re.compile(
    r"^[ ]*- (\w+) \([^)]+\):\s*\n((?:[ ]+.+\n)*)",
    re.MULTILINE,
)

_cache: dict[type, dict[str, str]] = {}

_SENTENCE_END = re.compile(r"(?<=[.!?])\s")


class DocstringPropDescription(InputDescriptionSource):
    """Extract property description from the component's docstring."""

    @classmethod
    def describe(cls, param: MCPInput) -> list[str]:
        component = param.get("component")
        if component is None:
            return []
        desc = _get_prop_description(type(component), param["property"])
        return [desc] if desc else []


def _get_prop_description(cls: type, prop: str) -> str | None:
    props = _parse_docstring(cls)
    return props.get(prop)


def _parse_docstring(cls: type) -> dict[str, str]:
    if cls in _cache:
        return _cache[cls]

    doc = getattr(cls, "__doc__", None)
    if not doc:
        _cache[cls] = {}
        return _cache[cls]

    props: dict[str, str] = {}
    for match in _PROP_RE.finditer(doc):
        prop_name = match.group(1)
        raw_desc = match.group(2)
        lines = [line.strip() for line in raw_desc.strip().splitlines()]
        desc = " ".join(lines)
        if desc:
            props[prop_name] = _first_sentence(desc)

    _cache[cls] = props
    return props


def _first_sentence(text: str) -> str:
    m = _SENTENCE_END.search(text)
    if m:
        return text[: m.start() + 1].rstrip()
    return text
