"""Skills — load and format SKILL.md files for prompt injection."""

from __future__ import annotations

import os
import re
from dataclasses import dataclass, field
from typing import Any


@dataclass
class Skill:
    """A loaded skill."""

    name: str
    description: str
    file_path: str
    base_dir: str
    source: str  # "user", "project", or "path"
    disable_model_invocation: bool = False


@dataclass
class ResourceDiagnostic:
    """A diagnostic message from skill loading."""

    path: str
    message: str
    level: str = "warning"  # "warning" | "error"


@dataclass
class LoadSkillsResult:
    """Result of loading skills."""

    skills: list[Skill] = field(default_factory=list)
    diagnostics: list[ResourceDiagnostic] = field(default_factory=list)


# Validation
_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9-]*$")
NAME_MAX_LENGTH = 64
DESCRIPTION_MAX_LENGTH = 1024


def _parse_frontmatter(content: str) -> dict[str, Any]:
    """Parse YAML frontmatter from markdown content."""
    if not content.startswith("---"):
        return {}
    end = content.find("---", 3)
    if end < 0:
        return {}
    frontmatter = content[3:end].strip()
    result: dict[str, Any] = {}
    for line in frontmatter.split("\n"):
        line = line.strip()
        if ":" in line:
            key, _, value = line.partition(":")
            key = key.strip()
            value = value.strip().strip('"').strip("'")
            if value.lower() == "true":
                result[key] = True
            elif value.lower() == "false":
                result[key] = False
            else:
                result[key] = value
    return result


def _load_skill_from_file(
    filepath: str,
    base_dir: str,
    source: str,
    diagnostics: list[ResourceDiagnostic],
) -> Skill | None:
    """Load a single skill from a SKILL.md file."""
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            content = f.read()
    except Exception as e:
        diagnostics.append(ResourceDiagnostic(path=filepath, message=str(e), level="error"))
        return None

    fm = _parse_frontmatter(content)
    dir_name = os.path.basename(os.path.dirname(filepath))
    name = fm.get("name", dir_name)
    description = fm.get("description", "")
    disable_model = fm.get("disable_model_invocation", fm.get("disableModelInvocation", False))

    # Validate name
    if not name or len(name) > NAME_MAX_LENGTH:
        diagnostics.append(ResourceDiagnostic(
            path=filepath,
            message=f"Invalid skill name: '{name}' (max {NAME_MAX_LENGTH} chars)",
        ))
        return None

    if not _NAME_PATTERN.match(name):
        diagnostics.append(ResourceDiagnostic(
            path=filepath,
            message=f"Invalid skill name: '{name}' (must be lowercase a-z, 0-9, hyphens)",
        ))
        return None

    if not description:
        diagnostics.append(ResourceDiagnostic(
            path=filepath,
            message=f"Skill '{name}' missing description",
        ))
        return None

    if len(description) > DESCRIPTION_MAX_LENGTH:
        diagnostics.append(ResourceDiagnostic(
            path=filepath,
            message=f"Skill '{name}' description exceeds {DESCRIPTION_MAX_LENGTH} chars",
        ))

    return Skill(
        name=name,
        description=description,
        file_path=filepath,
        base_dir=base_dir,
        source=source,
        disable_model_invocation=bool(disable_model),
    )


def load_skills_from_dir(
    directory: str,
    source: str = "user",
) -> LoadSkillsResult:
    """Load skills from a directory.

    If SKILL.md exists in the directory, treat it as a single skill.
    Otherwise, recurse into subdirectories looking for SKILL.md files.
    """
    result = LoadSkillsResult()

    if not os.path.isdir(directory):
        return result

    # Check for SKILL.md at root
    root_skill = os.path.join(directory, "SKILL.md")
    if os.path.isfile(root_skill):
        skill = _load_skill_from_file(root_skill, directory, source, result.diagnostics)
        if skill:
            result.skills.append(skill)
        return result

    # Recurse into subdirectories
    try:
        entries = os.listdir(directory)
    except Exception:
        return result

    for entry in sorted(entries):
        entry_path = os.path.join(directory, entry)

        # Check .md files in root
        if os.path.isfile(entry_path) and entry.endswith(".md"):
            continue  # Only SKILL.md matters

        # Check subdirectories
        if os.path.isdir(entry_path):
            skill_file = os.path.join(entry_path, "SKILL.md")
            if os.path.isfile(skill_file):
                skill = _load_skill_from_file(skill_file, entry_path, source, result.diagnostics)
                if skill:
                    result.skills.append(skill)

    return result


def load_skills(
    skill_paths: list[str] | None = None,
    project_dir: str | None = None,
) -> LoadSkillsResult:
    """Load skills from user dir, project dir, and explicit paths."""
    from pi_agents.coding_agent.config import get_agent_dir, CONFIG_DIR_NAME

    combined = LoadSkillsResult()
    seen_names: set[str] = set()

    def merge(result: LoadSkillsResult) -> None:
        for skill in result.skills:
            if skill.name in seen_names:
                combined.diagnostics.append(ResourceDiagnostic(
                    path=skill.file_path,
                    message=f"Duplicate skill name: '{skill.name}'",
                ))
                continue
            seen_names.add(skill.name)
            combined.skills.append(skill)
        combined.diagnostics.extend(result.diagnostics)

    # User skills
    user_skills_dir = os.path.join(get_agent_dir(), "skills")
    merge(load_skills_from_dir(user_skills_dir, "user"))

    # Project skills
    if project_dir:
        project_skills_dir = os.path.join(project_dir, CONFIG_DIR_NAME, "skills")
        merge(load_skills_from_dir(project_skills_dir, "project"))

    # Explicit paths
    if skill_paths:
        for sp in skill_paths:
            merge(load_skills_from_dir(sp, "path"))

    return combined


def format_skills_for_prompt(skills: list[Skill]) -> str:
    """Format skills into XML for system prompt injection.

    Filters out skills with disable_model_invocation=True.
    """
    available = [s for s in skills if not s.disable_model_invocation]
    if not available:
        return ""

    lines = [
        "The following skills provide specialized instructions for specific tasks.",
        "<available_skills>",
    ]
    for skill in available:
        lines.append("  <skill>")
        lines.append(f"    <name>{skill.name}</name>")
        lines.append(f"    <description>{skill.description}</description>")
        lines.append(f"    <location>{skill.file_path}</location>")
        lines.append("  </skill>")
    lines.append("</available_skills>")

    return "\n".join(lines)
