"""技能注册表与 Markdown frontmatter 解析工具。

提供 Skill 文件的 frontmatter 解析、正文提取、注册表管理和目录扫描功能，
供搜索 Agent 装配层使用。
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from loguru import logger

if TYPE_CHECKING:
    from pathlib import Path

_FRONTMATTER_FIELDS = {"name", "description", "always", "task_type"}


def _extract_frontmatter_lines(text: str) -> tuple[list[str], int] | None:
    """提取 frontmatter 行与正文起始偏移。

    参数:
        text: 原始 Markdown 文本。

    返回:
        (frontmatter 行列表, 正文起始字节偏移) 二元组；
        若不存在完整 frontmatter 则返回 None。
    """
    lines = text.splitlines(keepends=True)
    if not lines or lines[0].strip() != "---":
        return None

    offset = len(lines[0])
    frontmatter_lines: list[str] = []
    for line in lines[1:]:
        if line.strip() == "---":
            return frontmatter_lines, offset + len(line)
        frontmatter_lines.append(line)
        offset += len(line)

    logger.debug("frontmatter 缺少结束分隔符，按普通正文处理")
    return None


def strip_frontmatter(text: str) -> str:
    """移除 Markdown 文本开头的 frontmatter，并返回正文。

    参数:
        text: 原始 Markdown 文本。

    返回:
        去除 frontmatter 后的正文；若 frontmatter 不完整或不存在，则返回原文。
    """
    extracted = _extract_frontmatter_lines(text)
    if extracted is None:
        return text

    _, body_start = extracted
    return text[body_start:]


def parse_frontmatter(text: str) -> dict[str, str]:
    """解析 Markdown frontmatter 中的目标字段。

    仅识别 ``name``、``description``、``always``、``task_type`` 四个字段，
    其余字段会被忽略。引号包裹的值会自动去除引号。

    参数:
        text: 原始 Markdown 文本。

    返回:
        仅包含目标字段的字符串字典。
        若不存在完整 frontmatter，则返回空字典。
    """
    extracted = _extract_frontmatter_lines(text)
    if extracted is None:
        return {}

    frontmatter_lines, _ = extracted
    parsed: dict[str, str] = {}
    for raw_line in frontmatter_lines:
        line = raw_line.strip()
        if not line or ":" not in line:
            continue

        key, _, raw_value = line.partition(":")
        normalized_key = key.strip()
        if normalized_key not in _FRONTMATTER_FIELDS:
            continue

        value = raw_value.strip()
        if len(value) >= 2 and (
            (value.startswith('"') and value.endswith('"'))
            or (value.startswith("'") and value.endswith("'"))
        ):
            value = value[1:-1]
        parsed[normalized_key] = value

    return parsed


class SkillRegistry:
    """管理技能名称到文件路径映射并读取技能正文。

    通过 ``set_paths`` 注入名称→路径映射后，
    可用 ``read`` 按名读取技能 Markdown 正文（自动去除 frontmatter）。
    """

    def __init__(self) -> None:
        self._paths: dict[str, Path] = {}

    def set_paths(self, mapping: dict[str, Path]) -> None:
        """注入技能名称到文件路径的映射。

        参数:
            mapping: 技能名到 Markdown 文件路径的映射。
        """
        self._paths = dict(mapping)
        logger.debug("SkillRegistry 已载入 {} 个技能路径", len(self._paths))

    def read(self, name: str) -> str:
        """读取指定技能文件，并返回去除 frontmatter 后的正文。

        参数:
            name: 技能名称。

        返回:
            技能 Markdown 正文。

        异常:
            KeyError: 技能名称未注册时抛出。
        """
        try:
            path = self._paths[name]
        except KeyError:
            logger.error("技能未注册: {}", name)
            raise

        logger.debug("读取技能文件: name={}, path={}", name, path)
        return strip_frontmatter(path.read_text(encoding="utf-8"))


def discover_skills(
    skills_dir: Path,
) -> tuple[str, dict[str, str], str, SkillRegistry]:
    """扫描 skills 目录，按 frontmatter 分类返回。

    遍历 ``*.md`` 文件，根据 frontmatter 的 ``always`` / ``task_type`` 字段分类：

    - ``always=true`` 的 skill 拼入 ``always_skills_text``
    - 有 ``task_type`` 的 skill 加入 ``task_skill_map``
    - 非 always 的 skill 生成 ``catalog_text`` 并注册到 registry

    参数:
        skills_dir: Skill 文件目录。

    返回:
        ``(always_skills_text, task_skill_map, catalog_text, registry)`` 四元组。
    """
    if not skills_dir.exists():
        return "", {}, "", SkillRegistry()

    always_parts: list[str] = []
    task_skill_map: dict[str, str] = {}
    catalog_lines: list[str] = []
    registry_paths: dict[str, Path] = {}

    for path in sorted(skills_dir.glob("*.md")):
        raw = path.read_text(encoding="utf-8")
        meta = parse_frontmatter(raw)
        if "name" not in meta:
            logger.warning("跳过无 name 的 skill 文件: {}", path)
            continue

        body = strip_frontmatter(raw)
        name = meta["name"]
        desc = meta.get("description", "")
        task_type = meta.get("task_type", "")
        is_always = str(meta.get("always", "false")).lower() == "true"

        if is_always:
            always_parts.append(body)
        else:
            if task_type:
                task_skill_map[task_type] = body
            catalog_lines.append(f"- **{name}**: {desc}")
            registry_paths[name] = path

    always_text = "\n\n---\n\n".join(always_parts)
    catalog_text = "\n".join(catalog_lines)

    registry = SkillRegistry()
    registry.set_paths(registry_paths)

    return always_text, task_skill_map, catalog_text, registry
