"""
ASL Dictionary (asldict) dataset loader.

ASLDict is a collection of isolated American Sign Language signs
from online ASL dictionaries. Each video contains a single sign.

Dataset structure:
    {SLTK_DATASETS_ROOT}/singlevideo/asldict/
        metadata.json              # Word entries with video links
        videos/*.mp4               # Downloaded video files

Metadata format:
    [
        {
            "word": "abandon",
            "word_page_link": "...",
            "entries": [
                {
                    "entry_id": "1",
                    "entry_word": "abandon",
                    "entry_source": "aslpro",
                    "entry_video_link": "..."
                },
                ...
            ]
        },
        ...
    ]

Usage:
    from sltk.data.datasets import ASLDictDataset

    # Uses default paths from environment or hardcoded defaults
    dataset = ASLDictDataset()

    # Or specify path explicitly
    dataset = ASLDictDataset(root="/path/to/asldict")

    sample = dataset[0]
    print(sample.glosses)  # ['abandon']
    print(sample.video_path)  # Path to video
"""

import hashlib
import json
from pathlib import Path
from typing import Any

from sltk.data.core import Sample, Segment, SegmentList
from sltk.data.datasets.base import BaseDataset
from sltk.exceptions import SampleNotFoundError


def _get_default_video_root() -> Path:
    """Get default video root from config."""
    from sltk.config import get_default_dataset_roots

    return get_default_dataset_roots()["singlevideo"] / "asldict"


class ASLDictDataset(BaseDataset):
    """
    ASL Dictionary dataset loader.

    This is an isolated signs dataset - each sample is a single sign video.
    """

    NAME = "asldict"
    TASKS = ["isolated_sign_recognition"]
    LANGUAGES = ["asl"]
    CITATION = "ASL Dictionary Collection"

    # Computed dynamically to respect environment variables
    @property
    def DEFAULT_VIDEO_ROOT(self) -> Path:  # type: ignore[override]
        return _get_default_video_root()

    def __init__(
        self,
        root: str | Path | None = None,
        split: str = "all",
        feature_root: str | Path | None = None,
        load_poses: bool = False,
        pose_format: str = "mediapipe",
        fps: float = 30.0,
        source_filter: list[str] | None = None,
        word_filter: list[str] | None = None,
    ):
        """
        Initialize ASLDict dataset.

        Args:
            root: Path to asldict directory
            split: Split name ('all' - no official splits)
            feature_root: Path to features directory (if any)
            load_poses: Whether to load pose features
            pose_format: Pose format to load
            fps: Video FPS (default 30)
            source_filter: Only include entries from specific sources
            word_filter: Only include specific words
        """
        root = root or _get_default_video_root()
        self.source_filter = source_filter
        self.word_filter = word_filter

        # Load metadata before parent init
        self._root = Path(root)
        self._metadata = self._load_metadata()
        self._entry_index = self._build_entry_index()

        super().__init__(
            root=root,
            split=split,
            feature_root=feature_root,
            load_poses=load_poses,
            pose_format=pose_format,
            fps=fps,
        )

    def _load_metadata(self) -> list[dict[str, Any]]:
        """Load metadata.json."""
        metadata_path = self._root / "metadata.json"
        if not metadata_path.exists():
            return []

        with open(metadata_path, encoding="utf-8") as f:
            return json.load(f)

    def _build_entry_index(self) -> dict[str, dict[str, Any]]:
        """Build index mapping entry IDs to their info."""
        index = {}

        for word_entry in self._metadata:
            word = word_entry.get("word", "")

            # Apply word filter
            if self.word_filter and word.lower() not in [w.lower() for w in self.word_filter]:
                continue

            for entry in word_entry.get("entries", []):
                source = entry.get("entry_source", "")

                # Apply source filter
                if self.source_filter and source not in self.source_filter:
                    continue

                # Create unique ID from entry
                entry_id = entry.get("entry_id", "")
                entry_word = entry.get("entry_word", word)

                # Generate stable ID
                video_link = entry.get("entry_video_link", "")
                unique_id = self._generate_id(word, entry_id, video_link)

                index[unique_id] = {
                    "word": word,
                    "entry_id": entry_id,
                    "entry_word": entry_word,
                    "source": source,
                    "video_link": video_link,
                    "word_page_link": word_entry.get("word_page_link", ""),
                }

        return index

    def _generate_id(self, word: str, entry_id: str, video_link: str) -> str:
        """Generate a stable unique ID for an entry."""
        # Use hash of video link for uniqueness
        if video_link:
            link_hash = hashlib.md5(video_link.encode()).hexdigest()[:8]
            return f"{word}_{entry_id}_{link_hash}"
        return f"{word}_{entry_id}"

    def _load_index(self) -> list[str]:
        """Build index of all entries."""
        return list(self._entry_index.keys())

    def _load_sample(self, sample_id: str) -> Sample:
        """Load a single ASLDict sample."""
        if sample_id not in self._entry_index:
            raise SampleNotFoundError(sample_id, self.NAME)

        entry = self._entry_index[sample_id]
        word = entry["word"]

        # Find video file
        video_path = self._find_video(sample_id, entry)

        # For isolated signs, the whole video is one segment
        segments = SegmentList(
            [
                Segment(
                    start=0.0,
                    end=float("inf"),  # Unknown duration
                    label=word,
                    tier="gloss",
                    metadata={"source": entry["source"]},
                )
            ]
        )

        return Sample(
            id=sample_id,
            video_path=video_path,
            poses=None,
            segments=segments,
            glosses=[word],
            dataset=self.NAME,
            split=self.split,
            metadata={
                "entry_id": entry["entry_id"],
                "entry_word": entry["entry_word"],
                "source": entry["source"],
                "video_link": entry["video_link"],
                "word_page_link": entry["word_page_link"],
            },
        )

    def _find_video(self, sample_id: str, entry: dict) -> Path | None:
        """Find video file for an entry."""
        video_dir = self.root / "videos"
        if not video_dir.exists():
            return None

        # Try various naming conventions
        patterns = [
            f"{sample_id}.mp4",
            f"{entry['word']}_{entry['entry_id']}.mp4",
            f"{entry['word']}.mp4",
        ]

        # Also try extracting filename from video link
        video_link = entry.get("video_link", "")
        if video_link:
            # Extract filename from URL
            link_filename = video_link.split("/")[-1].split("?")[0]
            if link_filename:
                patterns.append(link_filename)

        for pattern in patterns:
            candidate = video_dir / pattern
            if candidate.exists():
                return candidate

        # If exact match not found, try to find by word
        word = entry["word"]
        for video_file in video_dir.glob(f"{word}*.mp4"):
            return video_file

        return None

    def get_vocabulary(self) -> list[str]:
        """Get all unique words in the dataset."""
        words = set()
        for entry in self._entry_index.values():
            words.add(entry["word"])
        return sorted(words)

    def get_sources(self) -> list[str]:
        """Get all unique data sources."""
        sources = set()
        for entry in self._entry_index.values():
            sources.add(entry["source"])
        return sorted(sources)

    def filter_by_word(self, word: str) -> list[str]:
        """Get sample IDs for a specific word."""
        return [
            sid for sid, entry in self._entry_index.items() if entry["word"].lower() == word.lower()
        ]

    def filter_by_source(self, source: str) -> list[str]:
        """Get sample IDs from a specific source."""
        return [sid for sid, entry in self._entry_index.items() if entry["source"] == source]

    def get_word_counts(self) -> dict[str, int]:
        """Get number of entries per word."""
        counts: dict[str, int] = {}
        for entry in self._entry_index.values():
            word = entry["word"]
            counts[word] = counts.get(word, 0) + 1
        return counts

    def summary(self) -> dict[str, Any]:
        """Get dataset summary."""
        base_summary = super().summary()
        base_summary.update(
            {
                "vocabulary_size": len(self.get_vocabulary()),
                "sources": self.get_sources(),
                "total_words": len(self._metadata),
            }
        )
        return base_summary
