"""XBRL ファクト抽出ユーティリティ。

RawFact + StructuredContext + LabelResolver を結合し、
型付き・ラベル付きの LineItem を生成する。
"""

from __future__ import annotations

import logging
from collections.abc import Sequence
from decimal import Decimal, InvalidOperation

from xbrl_core._linkbase_utils import ROLE_LABEL
from xbrl_core.errors import XbrlParseError
from xbrl_core.models.financial import LabelInfo, LabelSource, LineItem
from xbrl_core.contexts import StructuredContext
from xbrl_core.labels import LabelResolver
from xbrl_core.parser import RawFact

logger = logging.getLogger(__name__)

__all__ = ["build_line_items"]


def build_line_items(
    facts: Sequence[RawFact],
    context_map: dict[str, StructuredContext],
    resolver: LabelResolver | None = None,
    *,
    langs: Sequence[str] = ("en",),
) -> tuple[LineItem, ...]:
    """RawFact 群を LineItem 群に変換する。

    全 RawFact を LineItem に変換する。フィルタは行わない。

    Args:
        facts: ``parse_xbrl_facts()`` が抽出した RawFact のシーケンス。
        context_map: ``structure_contexts()`` が返した Context 辞書。
        resolver: ラベル解決用の LabelResolver。
            None の場合は local_name をフォールバックラベルとして使用する。
        langs: ラベルを解決する言語のシーケンス。

    Returns:
        変換された LineItem のタプル。入力の facts と同じ順序を保持する。

    Raises:
        XbrlParseError: ``context_ref`` が ``context_map`` に
            見つからない RawFact が存在した場合。
    """
    items: list[LineItem] = []
    for fact in facts:
        # 1. Context resolution
        ctx = context_map.get(fact.context_ref)
        if ctx is None:
            raise XbrlParseError(
                "XBRL_PARSE_010",
                f"Fact '{fact.local_name}' (line {fact.source_line}): "
                f"context_ref '{fact.context_ref}' not found in context_map",
                concept=fact.local_name,
                context_ref=fact.context_ref,
            )

        # 2. Label resolution
        labels = _resolve_labels(fact, resolver, langs)

        # 3. Value conversion
        value = _convert_value(fact)

        # 4. LineItem construction
        item = LineItem(
            concept=fact.concept_qname,
            namespace_uri=fact.namespace_uri,
            local_name=fact.local_name,
            labels=labels,
            value=value,
            unit_ref=fact.unit_ref,
            decimals=fact.decimals,
            context_id=fact.context_ref,
            period=ctx.period,
            entity_id=ctx.entity_id,
            dimensions=ctx.dimensions,
            is_nil=fact.is_nil,
            source_line=fact.source_line,
            order=fact.order,
        )
        items.append(item)

    logger.info("Built %d LineItems from %d Facts", len(items), len(facts))
    return tuple(items)


def _resolve_labels(
    fact: RawFact,
    resolver: LabelResolver | None,
    langs: Sequence[str],
) -> tuple[LabelInfo, ...]:
    """Fact に対するラベルを解決する。

    Args:
        fact: 対象の RawFact。
        resolver: ラベルリゾルバ。None の場合はフォールバック。
        langs: 解決する言語のシーケンス。

    Returns:
        LabelInfo のタプル。
    """
    if resolver is None:
        return (
            LabelInfo(
                text=fact.local_name,
                role=ROLE_LABEL,
                lang="",
                source=LabelSource.FALLBACK,
            ),
        )

    labels: list[LabelInfo] = []
    for lang in langs:
        info = resolver.resolve(fact.concept_qname, lang, ROLE_LABEL)
        if info is not None:
            labels.append(info)
        else:
            labels.append(
                LabelInfo(
                    text=fact.local_name,
                    role=ROLE_LABEL,
                    lang=lang,
                    source=LabelSource.FALLBACK,
                )
            )
    return tuple(labels)


def _convert_value(fact: RawFact) -> Decimal | str | None:
    """RawFact の値を適切な型に変換する。

    Args:
        fact: 変換対象の RawFact。

    Returns:
        - nil Fact → ``None``
        - 数値 Fact（``unit_ref is not None``）→ ``Decimal``
        - テキスト Fact（``unit_ref is None``）→ ``str``

    Raises:
        XbrlParseError: 数値 Fact の値が Decimal に変換できない場合。
    """
    if fact.is_nil:
        return None

    if fact.unit_ref is None:
        if fact.value_raw is not None:
            return fact.value_raw
        if fact.value_inner_xml is not None:
            return ""
        raise XbrlParseError(
            "XBRL_PARSE_011",
            f"Fact '{fact.local_name}' (line {fact.source_line}): "
            f"non-nil text fact has value_raw=None",
            concept=fact.local_name,
        )

    if fact.value_raw is None:
        raise XbrlParseError(
            "XBRL_PARSE_012",
            f"Fact '{fact.local_name}' (line {fact.source_line}): "
            f"non-nil numeric fact has value_raw=None",
            concept=fact.local_name,
        )
    try:
        return Decimal(fact.value_raw)
    except InvalidOperation as e:
        raise XbrlParseError(
            "XBRL_PARSE_013",
            f"Fact '{fact.local_name}' (line {fact.source_line}): "
            f"failed to convert value to Decimal: {fact.value_raw!r}",
            concept=fact.local_name,
            value=fact.value_raw,
        ) from e
