from __future__ import annotations

import html
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from vv_agent.skills.errors import SkillError
from vv_agent.skills.models import SkillProperties
from vv_agent.skills.parser import discover_skill_dirs, find_skill_md, read_properties


@dataclass(slots=True)
class PromptSkillEntry:
    name: str
    description: str
    location: str | None = None


def skill_to_prompt_entry(*, properties: SkillProperties, location: str | None = None) -> str:
    lines = [
        "<skill>",
        "<name>",
        html.escape(properties.name),
        "</name>",
        "<description>",
        html.escape(properties.description),
        "</description>",
    ]
    if location:
        lines.extend(["<location>", html.escape(location), "</location>"])
    lines.append("</skill>")
    return "\n".join(lines)


def to_available_skills_xml(skill_dirs: list[Path]) -> str:
    if not skill_dirs:
        return "<available_skills>\n</available_skills>"

    lines = ["<available_skills>"]
    for skill_dir in skill_dirs:
        normalized_dir = Path(skill_dir).resolve()
        properties = read_properties(normalized_dir)
        skill_md_path = find_skill_md(normalized_dir)
        location = str(skill_md_path) if skill_md_path else None
        lines.append(skill_to_prompt_entry(properties=properties, location=location))

    lines.append("</available_skills>")
    return "\n".join(lines)


def _resolve_skill_location(location: str, *, workspace: Path | None = None) -> Path:
    path = Path(location).expanduser()
    if not path.is_absolute() and workspace is not None:
        path = (workspace / path).resolve()
    if path.is_file() and path.name.lower() == "skill.md":
        return path.parent
    return path


def _is_existing_path(value: str, *, workspace: Path | None = None) -> bool:
    raw = Path(value).expanduser()
    if raw.exists():
        return True
    if workspace is not None and not raw.is_absolute():
        return (workspace / raw).exists()
    return False


def _entries_from_skill_directory(*, skill_dir: Path, workspace: Path | None = None) -> list[PromptSkillEntry]:
    try:
        properties = read_properties(skill_dir)
    except SkillError:
        return []

    skill_md_path = find_skill_md(skill_dir)
    if skill_md_path is None:
        return []

    try:
        location = skill_md_path.relative_to(workspace).as_posix() if workspace is not None else skill_md_path.as_posix()
    except ValueError:
        location = skill_md_path.as_posix()

    return [
        PromptSkillEntry(
            name=properties.name,
            description=properties.description,
            location=location,
        )
    ]


def _entries_from_skill_path(*, path: Path, workspace: Path | None = None) -> list[PromptSkillEntry]:
    if path.is_dir() and find_skill_md(path) is None:
        entries: list[PromptSkillEntry] = []
        for skill_dir in discover_skill_dirs(path):
            entries.extend(_entries_from_skill_directory(skill_dir=skill_dir, workspace=workspace))
        return entries

    return _entries_from_skill_directory(skill_dir=path, workspace=workspace)


def metadata_to_prompt_entries(
    available_skills: list[dict[str, Any] | str],
    *,
    workspace: Path | None = None,
) -> list[PromptSkillEntry]:
    """Normalize runtime skill metadata into prompt-friendly entries."""
    entries: list[PromptSkillEntry] = []

    for item in available_skills:
        if isinstance(item, str):
            raw_location = item.strip()
            if not raw_location:
                continue
            if not _is_existing_path(raw_location, workspace=workspace):
                continue
            path = _resolve_skill_location(raw_location, workspace=workspace)
            entries.extend(_entries_from_skill_path(path=path, workspace=workspace))
            continue

        if not isinstance(item, dict):
            continue

        name = str(item.get("name") or "").strip()
        description = str(item.get("description") or "").strip()
        location_value = (
            item.get("location")
            or item.get("skill_md_path")
            or item.get("skill_directory")
            or item.get("directory")
            or item.get("path")
        )
        location = str(location_value).strip() if isinstance(location_value, str) and location_value.strip() else None

        if (not name or not description) and location and _is_existing_path(location, workspace=workspace):
            path = _resolve_skill_location(location, workspace=workspace)
            entries.extend(_entries_from_skill_path(path=path, workspace=workspace))
            continue

        if not name or not description:
            continue

        entries.append(PromptSkillEntry(name=name, description=description, location=location))

    deduped: list[PromptSkillEntry] = []
    seen: set[tuple[str, str | None]] = set()
    for entry in entries:
        key = (entry.name, entry.location)
        if key in seen:
            continue
        seen.add(key)
        deduped.append(entry)

    return deduped
