"""Index builder and task extractor for the unified ingest pipeline.

Generates a lightweight index (index/index.jsonl) optimized for local models
on tablets, and extracts actionable items (TODOs, decisions) into tasks/.
"""

from __future__ import annotations

import hashlib
import json
import logging
import re
import time
from pathlib import Path
from uuid import uuid4

logger = logging.getLogger(__name__)

__all__ = [
    "TASK_PATTERNS",
    "build_index",
    "extract_tasks",
    "read_index",
]

TASK_PATTERNS = [
    r"\bTODO\b",
    r"\bFIXME\b",
    r"\bTODO:\s*",
    r"\bACTION:\s*",
    r"\budělat\b",
    r"\bnutné\b",
    r"\bje potřeba\b",
    r"\brozhodnuto\b",
    r"\bPOTŘEBA\b",
    r"\bTODO\b.*",
]

CATEGORY_PRIORITY = {
    "ai-export": 1,
    "note": 2,
    "doc": 3,
    "code": 4,
    "media": 5,
    "archive": 6,
    "other": 7,
    "duplicate": 8,
}


def _summarize_text(text: str, max_chars: int = 200) -> str:
    text = text.strip()
    if len(text) <= max_chars:
        return text
    return text[:max_chars] + "..."


def _read_text_safe(path: Path, max_bytes: int = 10_000) -> str:
    try:
        with open(path, encoding="utf-8", errors="replace") as f:
            return f.read(max_bytes)
    except OSError:
        return ""


def build_index(shared_root: str | Path | None = None) -> dict:
    root = Path(shared_root).expanduser().resolve() if shared_root else None
    if root is None:
        from cyberbrain26.product.shared_root import resolve_shared_root
        root = resolve_shared_root()

    sorted_dir = root / "sorted"
    index_dir = root / "index"
    index_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
    index_path = index_dir / "index.jsonl"

    entries: list[dict] = []

    if sorted_dir.exists():
        for cat_dir in sorted(sorted_dir.iterdir()):
            if not cat_dir.is_dir():
                continue
            category = cat_dir.name
            for f in sorted(cat_dir.rglob("*")):
                if not f.is_file():
                    continue
                sha256 = hashlib.sha256(f.read_bytes()).hexdigest()
                text = _read_text_safe(f)
                summary = _summarize_text(text)
                priority = CATEGORY_PRIORITY.get(category, 99)
                entries.append({
                    "id": uuid4().hex,
                    "src_rel": str(f.relative_to(root)),
                    "type": category,
                    "priority": priority,
                    "summary": summary,
                    "path_rel": str(f.relative_to(root)),
                    "sha256": sha256,
                    "ts": time.time(),
                })

    entries.sort(key=lambda e: e["priority"])

    tmp_path = index_path.with_suffix(".tmp")
    content = "".join(json.dumps(e, separators=(",", ":"), ensure_ascii=False, allow_nan=False) + "\n" for e in entries)
    tmp_path.write_text(content, encoding="utf-8")
    tmp_path.replace(index_path)

    return {
        "index_path": str(index_path),
        "entries_count": len(entries),
        "categories": {cat: sum(1 for e in entries if e["type"] == cat) for cat in set(e["type"] for e in entries)},
    }


def read_index(shared_root: str | Path | None = None) -> list[dict]:
    root = Path(shared_root).expanduser().resolve() if shared_root else None
    if root is None:
        from cyberbrain26.product.shared_root import resolve_shared_root
        root = resolve_shared_root()
    index_path = root / "index" / "index.jsonl"
    if not index_path.exists():
        return []
    entries: list[dict] = []
    with open(index_path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                try:
                    entries.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
    return entries


def _extract_task_lines(text: str) -> list[str]:
    tasks: list[str] = []
    seen: set[str] = set()
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        for pat in TASK_PATTERNS:
            if re.search(pat, line, re.IGNORECASE):
                if line not in seen:
                    seen.add(line)
                    tasks.append(line)
                break
    return tasks


def extract_tasks(shared_root: str | Path | None = None) -> dict:
    root = Path(shared_root).expanduser().resolve() if shared_root else None
    if root is None:
        from cyberbrain26.product.shared_root import resolve_shared_root
        root = resolve_shared_root()

    tasks_dir = root / "tasks"
    tasks_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
    tasks_json_path = tasks_dir / "tasks.json"
    master_tasks_path = tasks_dir / "MASTER_TASKS.md"

    existing_tasks: list[dict] = []
    if tasks_json_path.exists():
        try:
            existing_tasks = json.loads(tasks_json_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            existing_tasks = []
    seen_task_hashes = {t.get("hash") for t in existing_tasks if t.get("hash")}

    new_tasks: list[dict] = list(existing_tasks)
    all_task_lines: list[tuple[str, str]] = []

    sorted_dir = root / "sorted"
    if sorted_dir.exists():
        for f in sorted(sorted_dir.rglob("*")):
            if not f.is_file():
                continue
            ext = f.suffix.lower()
            if ext not in (".md", ".txt", ".rst", ".org", ".json", ".py", ".sh", ".yaml", ".yml", ".toml"):
                continue
            text = _read_text_safe(f, max_bytes=50_000)
            lines = _extract_task_lines(text)
            src_rel = str(f.relative_to(root))
            for line in lines:
                all_task_lines.append((src_rel, line))

    for src_rel, line in all_task_lines:
        task_hash = hashlib.sha256(line.encode("utf-8")).hexdigest()
        if task_hash in seen_task_hashes:
            continue
        seen_task_hashes.add(task_hash)
        new_tasks.append({
            "id": uuid4().hex,
            "source": src_rel,
            "text": line,
            "hash": task_hash,
            "status": "open",
            "ts": time.time(),
        })

    tasks_json_path.write_text(
        json.dumps(new_tasks, indent=2, ensure_ascii=False, allow_nan=False) + "\n",
        encoding="utf-8",
    )
    try:
        tasks_json_path.chmod(0o600)
    except OSError:
        pass

    md_lines = ["# MASTER_TASKS", "", f"*Generováno: {time.strftime('%Y-%m-%d %H:%M:%S')}*", "", ""]
    if new_tasks:
        md_lines.append("## Otevřené úkoly")
        md_lines.append("")
        for t in new_tasks:
            if t.get("status") == "open":
                md_lines.append(f"- [ ] {t['text']}  *(zdroj: {t['source']})*")
        md_lines.append("")
        md_lines.append("## Zpracované")
        md_lines.append("")
        for t in new_tasks:
            if t.get("status") != "open":
                md_lines.append(f"- [x] {t['text']}  *(zdroj: {t['source']})*")
    else:
        md_lines.append("*Žádné úkoly nenalezeny.*")
    master_tasks_path.write_text("\n".join(md_lines) + "\n", encoding="utf-8")
    try:
        master_tasks_path.chmod(0o600)
    except OSError:
        pass

    return {
        "tasks_json": str(tasks_json_path),
        "master_tasks": str(master_tasks_path),
        "total_tasks": len(new_tasks),
        "new_tasks": len(new_tasks) - len(existing_tasks),
    }
