#!/usr/bin/env python3
"""Lightweight AI工作区 project lookup index updater.

Writes only inside /Users/bot1/Volumes/root_for_ai/AI工作区/00_AI工作区项目索引.
It does not create Git repos, push, or touch the old remote-repository mapping folder.
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Any

AI_WORK_ROOT = Path("/Users/bot1/Volumes/root_for_ai/AI工作区")
INDEX_ROOT = AI_WORK_ROOT / "00_AI工作区项目索引"
PROJECTS_DIR = INDEX_ROOT / "projects"
INDEX_JSON = INDEX_ROOT / "project_index.json"
INDEX_MD = INDEX_ROOT / "AI工作区项目映射表.md"


def now_local() -> str:
    return datetime.now().astimezone().isoformat(timespec="seconds")


def slugify_folder_name(path: Path) -> str:
    name = path.name.strip()
    # Keep Chinese and common readable chars; replace unsafe separators.
    name = re.sub(r"[\\/:*?\"<>|\s]+", "_", name)
    name = re.sub(r"_+", "_", name).strip("_")
    return name or "unnamed_project"


def split_keywords(text: str) -> list[str]:
    if not text:
        return []
    parts = re.split(r"[,，、;；\n]+", text)
    return [p.strip() for p in parts if p.strip()]


def read_index() -> dict[str, Any]:
    if not INDEX_JSON.exists():
        return {"version": 1, "updated_at": now_local(), "projects": {}}
    try:
        data = json.loads(INDEX_JSON.read_text(encoding="utf-8"))
        if not isinstance(data, dict):
            raise ValueError("index root is not object")
        data.setdefault("version", 1)
        data.setdefault("projects", {})
        return data
    except Exception:
        # Do not destroy unreadable old index silently; keep a backup name.
        backup = INDEX_JSON.with_suffix(INDEX_JSON.suffix + f".unreadable_{datetime.now():%Y%m%d_%H%M%S}.bak")
        INDEX_JSON.rename(backup)
        return {"version": 1, "updated_at": now_local(), "projects": {}}


def project_card(record: dict[str, Any]) -> str:
    deliverables = record.get("key_deliverables") or []
    deliverable_lines = "\n".join(f"- `{x}`" for x in deliverables) if deliverables else "- 暂无"
    keywords = ", ".join(record.get("keywords") or [])
    aliases = ", ".join(record.get("aliases") or [])
    events = record.get("events") or []
    event_lines = "\n".join(
        f"- {e.get('time','')}｜{e.get('profile','')}｜{e.get('event','')}" for e in events[-8:]
    ) or "- 暂无"
    return f"""# {record.get('title') or record['folder_name']}

```yaml
folder_name: "{record['folder_name']}"
project_id: "{record['project_id']}"
status: "{record.get('status','active')}"
path: "{record['path']}"
created_at: "{record.get('created_at','')}"
last_updated: "{record.get('last_updated','')}"
owner_profile: "{record.get('owner_profile','')}"
related_org_or_ip: "{record.get('related_org_or_ip','')}"
use_case: "{record.get('use_case','')}"
```

## Summary

{record.get('summary') or '暂无摘要。'}

## Keywords

{keywords or '暂无'}

## Aliases

{aliases or '暂无'}

## Key deliverables / current useful files

{deliverable_lines}

## Maintenance events

{event_lines}

## Robot lookup note

优先返回本卡片的 `path`，再在该项目目录内做限定范围搜索。不要把本索引等同于 Git/远程仓库映射；本索引只服务 AI工作区项目查找和 RAG 检索。
"""


def write_outputs(data: dict[str, Any]) -> None:
    PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
    data["updated_at"] = now_local()
    INDEX_JSON.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")

    rows = sorted(data.get("projects", {}).values(), key=lambda r: (r.get("last_updated", ""), r.get("folder_name", "")), reverse=True)
    lines = [
        "# AI工作区项目映射表",
        "",
        f"更新时间：{data['updated_at']}",
        "",
        "用途：给机器人和 RAG 快速查询 `/Users/bot1/Volumes/root_for_ai/AI工作区` 下的项目位置、关键词、当前状态和关键交付物。",
        "",
        "维护边界：这是轻量项目查找索引，不是 Git/Gitee/远程仓库映射表；不要在这里记录密钥、长聊天记录、过程日志或大段临时草稿。",
        "",
        "| 项目 | 路径 | 关键词 | 状态 | 更新 |",
        "| --- | --- | --- | --- | --- |",
    ]
    for r in rows:
        kws = "、".join((r.get("keywords") or [])[:10])
        lines.append(f"| {r.get('title') or r.get('folder_name')} | `{r.get('path')}` | {kws} | {r.get('status','active')} | {r.get('last_updated','')} |")
    lines.append("")
    lines.append("## 项目卡片")
    lines.append("")
    for r in rows:
        card_name = slugify_folder_name(Path(r["path"])) + ".md"
        rel = f"projects/{card_name}"
        lines.append(f"- [{r.get('title') or r.get('folder_name')}]({rel})")
        (PROJECTS_DIR / card_name).write_text(project_card(r), encoding="utf-8")
    INDEX_MD.write_text("\n".join(lines) + "\n", encoding="utf-8")


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description="Update lightweight AI工作区 project lookup index")
    ap.add_argument("--path", required=True, help="Project folder path under AI工作区")
    ap.add_argument("--title", default="", help="Human-readable project title")
    ap.add_argument("--summary", default="", help="Short current-state summary")
    ap.add_argument("--keywords", default="", help="Comma/Chinese-comma separated lookup keywords")
    ap.add_argument("--aliases", default="", help="Comma/Chinese-comma separated aliases")
    ap.add_argument("--owner-profile", default="", help="Profile/bot maintaining this record")
    ap.add_argument("--related-org-or-ip", default="", help="所属项目/IP/客户，如 良渚、国博")
    ap.add_argument("--use-case", default="", help="用途，如 详情页、主图、吉祥物、头像")
    ap.add_argument("--status", default="active")
    ap.add_argument("--event", default="", help="Maintenance event description")
    ap.add_argument("--deliverable", action="append", default=[], help="Key deliverable/current file path, relative or absolute")
    args = ap.parse_args(argv)

    path = Path(args.path).expanduser().resolve()
    try:
        path.relative_to(AI_WORK_ROOT.resolve())
    except ValueError:
        print(f"ERROR: path must be under {AI_WORK_ROOT}: {path}", file=sys.stderr)
        return 2
    if not path.exists() or not path.is_dir():
        print(f"ERROR: project path does not exist or is not a directory: {path}", file=sys.stderr)
        return 3
    if path == INDEX_ROOT or INDEX_ROOT in path.parents:
        print("ERROR: do not register the index folder as a project", file=sys.stderr)
        return 4

    data = read_index()
    projects = data.setdefault("projects", {})
    project_id = slugify_folder_name(path)
    existing = projects.get(project_id, {})
    now = now_local()
    keywords = list(dict.fromkeys((existing.get("keywords") or []) + split_keywords(args.keywords)))
    aliases = list(dict.fromkeys((existing.get("aliases") or []) + split_keywords(args.aliases)))
    deliverables = list(dict.fromkeys((existing.get("key_deliverables") or []) + list(args.deliverable or [])))
    events = existing.get("events") or []
    if args.event:
        events.append({"time": now, "profile": args.owner_profile or existing.get("owner_profile", ""), "event": args.event})

    record = {
        "project_id": project_id,
        "folder_name": path.name,
        "title": args.title or existing.get("title") or path.name,
        "path": str(path),
        "status": args.status or existing.get("status") or "active",
        "created_at": existing.get("created_at") or now,
        "last_updated": now,
        "owner_profile": args.owner_profile or existing.get("owner_profile", ""),
        "related_org_or_ip": args.related_org_or_ip or existing.get("related_org_or_ip", ""),
        "use_case": args.use_case or existing.get("use_case", ""),
        "summary": args.summary or existing.get("summary", ""),
        "keywords": keywords,
        "aliases": aliases,
        "key_deliverables": deliverables,
        "events": events[-20:],
    }
    projects[project_id] = record
    write_outputs(data)
    print(json.dumps({"ok": True, "project_id": project_id, "index": str(INDEX_MD), "card": str(PROJECTS_DIR / (project_id + '.md'))}, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
