"""リンクベースパーサー共通ユーティリティ。

ラベルロール URI 定数と href → concept 名抽出ロジックを提供する。
"""

from __future__ import annotations

import logging
import re
from collections.abc import Callable

from lxml import etree

from xbrl_core.schema.elements import ElementDefinition

logger = logging.getLogger(__name__)

ConceptExtractor = Callable[[str], str | None]
"""href 文字列から concept ローカル名を抽出するコールバック型。"""

# ============================================================
# Label role URI constants
# ============================================================

ROLE_LABEL = "http://www.xbrl.org/2003/role/label"
"""Standard label."""

ROLE_TOTAL_LABEL = "http://www.xbrl.org/2003/role/totalLabel"
"""Total label."""

ROLE_VERBOSE_LABEL = "http://www.xbrl.org/2003/role/verboseLabel"
"""Verbose label."""

ROLE_TERSE_LABEL = "http://www.xbrl.org/2003/role/terseLabel"
"""Terse label."""

ROLE_PERIOD_START_LABEL = "http://www.xbrl.org/2003/role/periodStartLabel"
"""Period start label."""

ROLE_PERIOD_END_LABEL = "http://www.xbrl.org/2003/role/periodEndLabel"
"""Period end label."""

ROLE_NEGATED_LABEL = "http://www.xbrl.org/2003/role/negatedLabel"
"""Negated label."""

# ============================================================
# href → concept name extraction
# ============================================================

_XSD_PREFIX_RE = re.compile(r"^(.+)_\d{4}-\d{2}-\d{2}\.xsd$")
"""Standard taxonomy XSD filename pattern (greedy match)."""


def extract_concept_from_href(href: str) -> str | None:
    """xlink:href のフラグメントから concept ローカル名を抽出する。

    Strategy 1（XSD ファイル名ベース）のみを使用し、フォールバックとして
    フラグメント全体を返す。EDINET 固有の Strategy 2（``_[A-Z]`` 後方スキャン）
    は上位パッケージ側に残す。

    Args:
        href: xlink:href 属性値。

    Returns:
        concept ローカル名。フラグメントが見つからない場合は ``None``。
    """
    if "#" not in href:
        return None
    path_part, fragment = href.rsplit("#", 1)
    if not fragment:
        return None
    basename = path_part.rsplit("/", 1)[-1]

    # Strategy 1: standard taxonomy {prefix}_{YYYY-MM-DD}.xsd
    m = _XSD_PREFIX_RE.match(basename)
    if m is not None:
        prefix = m.group(1)
        expected = prefix + "_"
        if fragment.startswith(expected):
            return fragment[len(expected):]

    # Fallback: return fragment as-is
    return fragment


# ============================================================
# href → namespace URI resolution
# ============================================================


def build_namespace_map(xsd_sources: dict[str, bytes]) -> dict[str, str]:
    """XSD バイト列から XSD ファイル名 → targetNamespace のマッピングを構築する。

    lxml で root 要素の ``targetNamespace`` のみ読み取る軽量実装。
    パース失敗や ``targetNamespace`` 未設定の XSD はスキップされる。

    Args:
        xsd_sources: ``{XSDファイル名: XSD bytes}`` の辞書。

    Returns:
        ``{XSDファイル名: targetNamespace}`` の辞書。
    """
    ns_map: dict[str, str] = {}
    for filename, xsd_bytes in xsd_sources.items():
        try:
            root = etree.fromstring(xsd_bytes)  # noqa: S320
        except etree.XMLSyntaxError:
            logger.debug("build_namespace_map: パース失敗をスキップ: %s", filename)
            continue
        target_ns = root.get("targetNamespace")
        if target_ns:
            ns_map[filename] = target_ns
    return ns_map


def build_namespace_map_from_elements(
    schemas: dict[str, dict[str, ElementDefinition]],
) -> dict[str, str]:
    """パース済み要素定義から XSD ファイル名 → namespace_uri のマッピングを構築する。

    各スキーマの任意の ``ElementDefinition`` から ``namespace_uri`` を取得する。
    要素 0 件のスキーマはスキップされる。

    Args:
        schemas: ``{XSDファイル名: parse_xsd_elements() 結果}`` の辞書。

    Returns:
        ``{XSDファイル名: namespace_uri}`` の辞書。
    """
    ns_map: dict[str, str] = {}
    for filename, elements in schemas.items():
        if not elements:
            continue
        first_elem = next(iter(elements.values()))
        ns_map[filename] = first_elem.namespace_uri
    return ns_map


def resolve_href_namespace(
    href: str,
    namespace_map: dict[str, str],
) -> tuple[str, str] | None:
    """href から名前空間 URI とローカル名を解決する。

    ``extract_concept_from_href`` を内部で再利用してローカル名を取得し、
    href のベースネームから ``namespace_map`` を照合して名前空間 URI を返す。

    Args:
        href: ``xlink:href`` 属性値。
        namespace_map: ``{XSDファイル名: namespace_uri}`` の辞書。

    Returns:
        ``(namespace_uri, local_name)`` のタプル。
        フラグメントなし・マッチなしの場合は ``None``。
    """
    local_name = extract_concept_from_href(href)
    if local_name is None:
        return None

    # href からベースネームを抽出
    if "#" not in href:
        return None
    path_part = href.rsplit("#", 1)[0]
    basename = path_part.rsplit("/", 1)[-1]

    ns_uri = namespace_map.get(basename)
    if ns_uri is None:
        return None

    return (ns_uri, local_name)
