from __future__ import annotations

import json
import secrets
import socket
import threading
import webbrowser
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, quote, unquote, urlparse

from .config import local_db_path
from .store import LocalWorkspaceStore
from .workthreads_board import build_join_prompt


DEFAULT_HOST = "127.0.0.1"


def find_available_port(host: str = DEFAULT_HOST) -> int:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind((host, 0))
        return int(sock.getsockname()[1])


def create_ui_server(
    *,
    host: str = DEFAULT_HOST,
    port: int = 0,
    db_path: Path | None = None,
    token: str | None = None,
) -> tuple[ThreadingHTTPServer, str]:
    if host not in {DEFAULT_HOST, "localhost"}:
        raise ValueError("A2CR UI binds to 127.0.0.1 or localhost only.")
    ui_token = token or secrets.token_urlsafe(24)
    store = LocalWorkspaceStore(db_path or local_db_path())
    actual_port = port or find_available_port(host)

    class A2CRUIHandler(BaseHTTPRequestHandler):
        server_version = "A2CRLocalUI/1"

        def log_message(self, format: str, *args) -> None:  # noqa: A003 - BaseHTTPRequestHandler API
            return

        def do_GET(self) -> None:
            parsed = urlparse(self.path)
            if parsed.path == "/":
                if not self._token_valid(parsed):
                    self._redirect(f"/?token={quote(ui_token)}")
                    return
                self._html(INDEX_HTML)
                return
            if not self._token_valid(parsed):
                self._json({"status": "unauthorized"}, HTTPStatus.UNAUTHORIZED)
                return
            if parsed.path == "/api/state":
                self._json({
                    "status": "ok",
                    "dashboard": store.dashboard(),
                    "workbatons": store.list_workbatons(include_archived=True),
                    "workstash": store.list_work_stash(),
                    "workthreads": store.list_work_threads(include_archived=True, limit=100),
                    "actors": store.list_actors(),
                    "events": store.list_events(),
                })
                return
            if parsed.path == "/api/search":
                query = parse_qs(parsed.query)
                self._json(store.search_contexts(
                    _first(query, "q"),
                    object_type=_first(query, "type") or None,
                    project=_first(query, "project") or None,
                    tag=_first(query, "tag") or None,
                    state=_first(query, "state") or None,
                    agent=_first(query, "agent") or None,
                    date_from=_first(query, "date_from") or None,
                    date_to=_first(query, "date_to") or None,
                    slot=_first(query, "slot") or None,
                    limit=_int(_first(query, "limit"), 20),
                ))
                return
            if parsed.path == "/api/export":
                self._json(store.export_workspace())
                return
            join_prompt_key = self._workthread_suffix_key(parsed.path, "join-prompt")
            if join_prompt_key is not None:
                self._json(self._join_prompt(join_prompt_key, store))
                return
            detail = self._detail(parsed.path, store)
            if detail is not None:
                self._json(detail)
                return
            self._json({"status": "not_found"}, HTTPStatus.NOT_FOUND)

        def do_POST(self) -> None:
            parsed = urlparse(self.path)
            if not self._token_valid(parsed):
                self._json({"status": "unauthorized"}, HTTPStatus.UNAUTHORIZED)
                return
            body = self._read_json()
            if parsed.path == "/api/backup":
                self._json(store.backup_database())
                return
            if parsed.path == "/api/action":
                self._json(self._action(body, store))
                return
            if parsed.path == "/api/workthreads":
                self._json(self._create_work_thread(body, store))
                return
            message_key = self._workthread_suffix_key(parsed.path, "messages")
            if message_key is not None:
                self._json(self._post_work_thread_message(message_key, body, store))
                return
            self._json({"status": "not_found"}, HTTPStatus.NOT_FOUND)

        def _create_work_thread(self, body: dict, store: LocalWorkspaceStore) -> dict:
            thread_key = _text_field(body, "thread_key")
            title = _text_field(body, "title")
            if not thread_key or not title:
                return {"status": "validation_error", "message": "thread_key and title are required"}
            return store.create_work_thread(
                thread_key=thread_key,
                title=title,
                initial_message=_text_field(body, "initial_message"),
                project=_text_field(body, "project"),
                participant_label=_text_field(body, "participant_label"),
                model_source=_text_field(body, "model_source"),
            )

        def _post_work_thread_message(self, thread_key: str, body: dict, store: LocalWorkspaceStore) -> dict:
            message = _text_field(body, "body")
            if not message:
                return {"status": "validation_error", "message": "body is required"}
            return store.post_work_thread_message(
                thread_key=thread_key,
                body=message,
                participant_label=_text_field(body, "participant_label"),
                model_source=_text_field(body, "model_source"),
            )

        def _join_prompt(self, thread_key: str, store: LocalWorkspaceStore) -> dict:
            detail = store.get_work_thread(thread_key=thread_key, include_messages=False)
            if detail.get("status") != "loaded":
                return detail
            return {
                "status": "ok",
                "storage_mode": "local",
                "thread_key": detail["thread_key"],
                "prompt": build_join_prompt(
                    thread_key=detail["thread_key"],
                    title=detail["title"],
                    project_key=detail["project_key"],
                ),
            }

        def _workthread_suffix_key(self, path: str, suffix: str) -> str | None:
            prefix = "/api/workthreads/"
            suffix_path = f"/{suffix}"
            if not path.startswith(prefix) or not path.endswith(suffix_path):
                return None
            encoded_key = path[len(prefix):-len(suffix_path)]
            if not encoded_key:
                return None
            return unquote(encoded_key)

        def _detail(self, path: str, store: LocalWorkspaceStore) -> dict | None:
            prefix_handlers = {
                "/api/workbatons/": lambda key: store.get_workbaton(key),
                "/api/workstash/": lambda key: store.get_work_stash(key),
                "/api/workthreads/": lambda key: store.get_work_thread(
                    thread_key=key,
                    include_messages=True,
                    message_limit=100,
                ),
            }
            for prefix, handler in prefix_handlers.items():
                if path.startswith(prefix):
                    key = unquote(path[len(prefix):])
                    return handler(key)
            return None

        def _action(self, body: dict, store: LocalWorkspaceStore) -> dict:
            object_type = body.get("object_type")
            key = body.get("key")
            action = body.get("action")
            if not isinstance(key, str) or not isinstance(action, str):
                return {"status": "validation_error", "message": "key and action are required"}
            if object_type == "WorkBaton":
                return store.update_workbaton_state(key, action)
            if object_type == "WorkStash" and action == "delete":
                return store.delete_work_stash(key)
            if object_type == "WorkThread" and action == "close":
                return store.close_work_thread(key)
            if object_type == "WorkThread" and action == "archive":
                return store.archive_work_thread(key)
            return {"status": "validation_error", "message": "unsupported action"}

        def _token_valid(self, parsed) -> bool:
            query = parse_qs(parsed.query)
            supplied = _first(query, "token") or self.headers.get("X-A2CR-UI-Token")
            return secrets.compare_digest(supplied or "", ui_token)

        def _read_json(self) -> dict:
            length = int(self.headers.get("Content-Length") or 0)
            if length <= 0:
                return {}
            try:
                data = json.loads(self.rfile.read(length).decode("utf-8"))
                return data if isinstance(data, dict) else {}
            except json.JSONDecodeError:
                return {}

        def _redirect(self, location: str) -> None:
            self.send_response(HTTPStatus.FOUND)
            self.send_header("Location", location)
            self.end_headers()

        def _html(self, body: str) -> None:
            data = body.encode("utf-8")
            self.send_response(HTTPStatus.OK)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Cache-Control", "no-store")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)

        def _json(self, data: dict, status: HTTPStatus = HTTPStatus.OK) -> None:
            payload = json.dumps(data, ensure_ascii=False).encode("utf-8")
            self.send_response(status)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.send_header("Cache-Control", "no-store")
            self.send_header("Content-Length", str(len(payload)))
            self.end_headers()
            self.wfile.write(payload)

    server = ThreadingHTTPServer((host, actual_port), A2CRUIHandler)
    url = f"http://{host}:{actual_port}/?token={quote(ui_token)}"
    return server, url


def _format_startup_message(*, url: str, open_browser: bool) -> str:
    browser_status = (
        "Opening your default browser now."
        if open_browser
        else "Browser auto-open is disabled by --no-browser."
    )
    return "\n".join([
        "A2CR Local UI is running.",
        f"A2CR_UI_URL={url}",
        "Open the full URL above on this computer if the browser does not appear.",
        "The ?token= value is required; a bare 127.0.0.1 URL is rejected.",
        browser_status,
        "Keep this terminal open. Press Ctrl+C to stop the UI.",
    ])


def _open_browser(url: str) -> None:
    try:
        opened = webbrowser.open(url)
    except Exception as exc:  # pragma: no cover - depends on desktop integration
        print(f"Browser auto-open failed: {exc}", flush=True)
        print("Copy and paste A2CR_UI_URL from this terminal instead.", flush=True)
        return
    if not opened:
        print("Browser auto-open did not report success.", flush=True)
        print("Copy and paste A2CR_UI_URL from this terminal instead.", flush=True)


def serve_ui(
    *,
    host: str = DEFAULT_HOST,
    port: int = 0,
    db_path: Path | None = None,
    open_browser: bool = True,
    token: str | None = None,
) -> str:
    server, url = create_ui_server(host=host, port=port, db_path=db_path, token=token)
    if open_browser:
        threading.Timer(0.2, _open_browser, args=(url,)).start()
    print(url, flush=True)
    print(_format_startup_message(url=url, open_browser=open_browser), flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()
    return url


def _first(query: dict[str, list[str]], key: str) -> str:
    values = query.get(key) or [""]
    return values[0]


def _int(value: str, default: int) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def _text_field(body: dict, key: str) -> str | None:
    value = body.get(key)
    if not isinstance(value, str):
        return None
    clean = value.strip()
    return clean or None


INDEX_HTML = r"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A2CR</title>
<style>
:root {
  color-scheme: light;
  --ink: #17201c;
  --muted: #63706a;
  --line: #d7ddd9;
  --panel: #ffffff;
  --soft: #f4f7f5;
  --green: #1e6f54;
  --teal: #0f6b78;
  --amber: #9a6418;
  --red: #9f3131;
  --blue: #2f5d8c;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--ink);
  background: var(--soft);
  font-size: 14px;
}
button, input, select {
  font: inherit;
}
button {
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  padding: 7px 10px;
  border-radius: 6px;
  cursor: pointer;
}
button.primary { background: var(--green); border-color: var(--green); color: #fff; }
button.warn { border-color: #d4a256; color: var(--amber); }
button.danger { border-color: #d79b9b; color: var(--red); }
button.ghost { background: transparent; }
button:hover { filter: brightness(0.97); }
.shell {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  min-height: 100vh;
}
.nav {
  background: #17201c;
  color: #eef6f1;
  padding: 18px 14px;
  display: flex;
  flex-direction: column;
}
.brand {
  font-size: 18px;
  font-weight: 700;
  margin: 0 0 20px;
}
.nav button {
  width: 100%;
  text-align: left;
  margin: 3px 0;
  background: transparent;
  color: #dfe9e4;
  border-color: transparent;
}
.nav button.active {
  background: #24342d;
  border-color: #344a41;
}
.main {
  min-width: 0;
  padding: 18px 22px 28px;
}
.topbar {
  display: flex;
  gap: 10px;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}
.topbar h1 {
  font-size: 22px;
  margin: 0;
}
.toolbar {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}
.stats {
  display: grid;
  grid-template-columns: repeat(5, minmax(120px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.stat, .panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.stat {
  padding: 12px;
}
.stat .value {
  font-size: 24px;
  font-weight: 700;
  margin-top: 4px;
}
.stat .label {
  color: var(--muted);
  font-size: 12px;
}
.panel {
  margin-bottom: 14px;
  overflow: hidden;
}
.panel h2 {
  margin: 0;
  padding: 12px 14px;
  font-size: 15px;
  border-bottom: 1px solid var(--line);
}
table {
  width: 100%;
  border-collapse: collapse;
}
th, td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  text-align: left;
}
th {
  color: var(--muted);
  font-size: 12px;
  background: #f9fbfa;
}
tr.clickable { cursor: pointer; }
tr.clickable:hover { background: #f2f6f4; }
.badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 2px 7px;
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 12px;
  margin: 0 4px 4px 0;
}
.badge.open { color: var(--green); border-color: #91c7b4; }
.badge.closed { color: var(--blue); border-color: #9cb9d5; }
.badge.archived, .badge.stale { color: var(--amber); border-color: #d6b57d; }
.badge.pinned { color: var(--teal); border-color: #88bfca; }
.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 14px;
}
.detail {
  min-height: 360px;
}
.detail-body {
  padding: 14px;
}
pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: #101714;
  color: #eef6f1;
  padding: 12px;
  border-radius: 8px;
  max-height: 520px;
  overflow: auto;
}
.filters {
  padding: 12px 14px;
}
.filter-keyword {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.filter-keyword input {
  flex: 1;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
}
.filter-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.filter-grid label {
  display: block;
  font-size: 10px;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 3px;
}
.filter-grid input,
.filter-grid select {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff;
  box-sizing: border-box;
  font: inherit;
  font-size: 12px;
}
.hidden { display: none; }
.empty {
  padding: 18px;
  color: var(--muted);
}
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}
.project-layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}
.project-list {
  padding: 10px;
}
.project-list button {
  display: block;
  width: 100%;
  text-align: left;
  margin-bottom: 8px;
}
.project-list button.active {
  border-color: var(--green);
  background: #e8f5f0;
  color: var(--green);
}
.project-meta {
  display: block;
  color: var(--muted);
  font-size: 10px;
  margin-top: 3px;
}
.project-thread-form {
  border-bottom: 1px solid var(--line);
}
@media (max-width: 980px) {
  .shell { grid-template-columns: 1fr; }
  .nav { position: sticky; top: 0; z-index: 2; }
  .nav button { width: auto; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .grid { grid-template-columns: 1fr; }
  .project-layout { grid-template-columns: 1fr; }
  .filter-grid { grid-template-columns: 1fr 1fr; }
}
.lang-switcher {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid #344a41;
}
.lang-switcher > span {
  display: block;
  font-size: 10px;
  color: #63706a;
  margin-bottom: 6px;
}
.lang-toggle {
  display: flex;
  border: 1px solid #344a41;
  border-radius: 5px;
  overflow: hidden;
}
.lang-toggle button {
  flex: 1;
  text-align: center;
  padding: 4px 0;
  font-size: 11px;
  background: transparent;
  color: #aaa;
  border: none;
  border-radius: 0;
}
.lang-toggle button.active {
  background: var(--green);
  color: #fff;
  font-weight: 600;
}
.help-steps {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 14px;
  align-items: start;
  margin-bottom: 10px;
}
.help-step-arrow {
  padding-top: 14px;
  font-size: 20px;
  color: var(--muted);
}
.help-step {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  text-align: center;
}
.help-step-num {
  font-size: 20px;
  font-weight: 700;
  color: var(--green);
}
.help-step-label {
  font-size: 14px;
  font-weight: 600;
}
.help-step-sub {
  font-size: 11px;
  color: var(--muted);
}
.help-detail {
  border-top: 2px solid var(--line);
  margin-top: 10px;
  padding-top: 14px;
}
.help-prompt {
  background: #101714;
  color: #eef6f1;
  border-radius: 6px;
  padding: 10px 14px;
  font-size: 12px;
  margin: 8px 0 0;
}
.help-prompt li {
  margin-bottom: 4px;
}
.views-grid {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 8px 16px;
  align-items: baseline;
}
.views-grid dt {
  font-weight: 600;
  font-size: 13px;
}
.views-grid dd {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}
.act-badge {
  display: inline-block;
  border-radius: 4px;
  padding: 2px 6px;
  font-size: 11px;
}
.act-green  { background: #d4edda; color: #1a5c2f; }
.act-red    { background: #fde8e8; color: #9f3131; }
.act-amber  { background: #fff8e6; color: #9a6418; }
.act-neutral{ background: #f4f7f5; color: #63706a; }
.badge.WorkBaton  { background: #e8f5f0; color: var(--green);  border-color: #91c7b4; }
.badge.WorkStash  { background: #fef3e2; color: var(--amber);  border-color: #d6b57d; }
.badge.WorkThread { background: #e8f2f7; color: var(--blue);   border-color: #9cb9d5; }
.badge.pinned-b  { background: #e0f0ff; color: #2f5d8c; border-color: #9cb9d5; }
.badge.archived-b { background: #f4f7f5; color: #63706a; border-color: var(--line); }
.field-card {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 10px;
  margin-bottom: 8px;
}
.field-card.highlight {
  border-left: 3px solid var(--green);
}
.field-label {
  font-size: 10px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 3px;
}
.field-label.highlight { color: var(--green); }
.field-value {
  font-size: 12px;
  line-height: 1.5;
}
.field-value ul {
  margin: 0;
  padding-left: 16px;
}
details > summary {
  cursor: pointer;
  font-size: 11px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 4px 10px;
  display: inline-block;
  user-select: none;
  margin-top: 10px;
}
.msg-list {
  max-height: 300px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.msg-card {
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
}
.msg-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5px 10px;
  background: #f9fbfa;
  border-bottom: 1px solid var(--line);
}
.msg-time {
  font-size: 10px;
  color: var(--muted);
}
.msg-body {
  margin: 0;
  padding: 8px 10px;
  background: #fff;
  font-size: 11px;
}
.settings-panel {
  max-width: 480px;
  margin-bottom: 14px;
}
.pref-item {
  margin-bottom: 14px;
}
.pref-item:last-child { margin-bottom: 0; }
.pref-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 6px;
}
.pref-hint {
  font-size: 10px;
  color: var(--muted);
  margin-top: 4px;
}
.pref-select {
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 5px;
  font-size: 12px;
  background: #fff;
  min-width: 220px;
}
.radio-group {
  display: flex;
  gap: 16px;
}
.radio-group label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 12px;
}
.code-block {
  font-family: monospace;
  font-size: 11px;
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 6px 10px;
  overflow-wrap: anywhere;
}
</style>
</head>
<body>
<div class="shell">
  <aside class="nav">
    <div class="brand">A2CR</div>
    <button class="active" data-view="dashboard">Dashboard</button>
    <button data-view="projects">Projects</button>
    <button data-view="search">Search</button>
    <button data-view="workbatons">WorkBaton</button>
    <button data-view="workstash">WorkStash</button>
    <button data-view="workthreads">WorkThreads</button>
    <button data-view="agents">Agents</button>
    <button data-view="timeline">Timeline</button>
    <button data-view="settings">Settings</button>
    <button data-view="help">Help</button>
    <div class="lang-switcher">
      <span>Language</span>
      <div class="lang-toggle">
        <button id="lang-ja" class="active" onclick="setLang('ja')">日本語</button>
        <button id="lang-en" onclick="setLang('en')">English</button>
      </div>
    </div>
  </aside>
  <main class="main">
    <div class="topbar">
      <h1 id="view-title">Dashboard</h1>
      <div class="toolbar">
        <button class="primary" id="refresh">Refresh</button>
        <button id="backup">Backup</button>
        <button id="export">Export</button>
      </div>
    </div>
    <section id="content"></section>
  </main>
</div>
<script>
const token = new URLSearchParams(location.search).get("token") || "";
let state = null;
let currentView = "dashboard";
let lang = "ja";
let displayTimezone = "local";
let selectedProject = "";

const TITLES = {
  ja: {dashboard:"ダッシュボード", projects:"Projects", search:"検索", workbatons:"WorkBaton",
       workstash:"WorkStash", workthreads:"WorkThreads", agents:"Agents",
       timeline:"Timeline", settings:"Settings", help:"使い方ガイド"},
  en: {dashboard:"Dashboard", projects:"Projects", search:"Search", workbatons:"WorkBaton",
       workstash:"WorkStash", workthreads:"WorkThreads", agents:"Agents",
       timeline:"Timeline", settings:"Settings", help:"User Guide"}
};

function setLang(l) {
  lang = l;
  localStorage.setItem("a2cr_lang", l);
  document.getElementById("lang-ja").classList.toggle("active", l === "ja");
  document.getElementById("lang-en").classList.toggle("active", l === "en");
  document.getElementById("view-title").textContent = TITLES[lang][currentView] || currentView;
  render();
}

function setTimezone(tz) {
  displayTimezone = tz || "local";
  localStorage.setItem("a2cr_timezone", displayTimezone);
  render();
}

const api = async (path, options = {}) => {
  const sep = path.includes("?") ? "&" : "?";
  const res = await fetch(path + sep + "token=" + encodeURIComponent(token), {
    ...options,
    headers: {"Content-Type": "application/json", "X-A2CR-UI-Token": token, ...(options.headers || {})}
  });
  return await res.json();
};

const esc = (value) => String(value ?? "").replace(/[&<>"']/g, ch => ({
  "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;"
}[ch]));

const browserTimezone = () => {
  try {
    return Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
  } catch {
    return "local";
  }
};

const timestampDate = (value) => {
  if (!value) return null;
  let text = String(value);
  text = text.replace(/\.(\d{3})\d+(Z|[+-]\d{2}:?\d{2})$/, ".$1$2");
  if (!/(Z|[+-]\d{2}:?\d{2})$/.test(text)) text += "Z";
  const date = new Date(text);
  return Number.isNaN(date.getTime()) ? null : date;
};

const fmt = (value) => {
  const date = timestampDate(value);
  if (!date) return value ? String(value) : "";
  const timeZone = displayTimezone === "local" ? undefined : displayTimezone;
  try {
    const parts = new Intl.DateTimeFormat("en-US", {
      timeZone,
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
      hourCycle: "h23",
      timeZoneName: "short",
    }).formatToParts(date);
    const part = (type) => parts.find(p => p.type === type)?.value || "";
    const zone = part("timeZoneName");
    return `${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}:${part("second")}${zone ? " " + zone : ""}`;
  } catch {
    return value ? String(value) : "";
  }
};
const fmtSize = (bytes) => {
  if (!bytes && bytes !== 0) return "";
  if (bytes < 1024) return bytes + " B";
  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
  return (bytes / 1048576).toFixed(1) + " MB";
};
const badge = (text, cls = "") => text ? `<span class="badge ${cls}">${esc(text)}</span>` : "";
const actionBadge = (action) => {
  const ja = lang === "ja";
  const labels = {save: ja?"保存":"save", store: ja?"保存":"store",
    delete: ja?"削除":"delete", archive: ja?"アーカイブ":"archive",
    load: ja?"読み込み":"load", resume: ja?"読み込み":"resume"};
  const label = labels[action] || action;
  const cls = ["save","store","create","post_message"].includes(action) ? "act-green"
    : ["delete","archive"].includes(action) ? "act-red"
    : ["stale","close"].includes(action) ? "act-amber" : "act-neutral";
  return `<span class="act-badge ${cls}">${esc(label)}</span>`;
};
const jsonBlock = (obj) => `<pre>${esc(JSON.stringify(obj, null, 2))}</pre>`;

async function loadState() {
  state = await api("/api/state");
  render();
}

function setView(view) {
  currentView = view;
  document.querySelectorAll(".nav button[data-view]").forEach(btn => btn.classList.toggle("active", btn.dataset.view === view));
  document.getElementById("view-title").textContent = TITLES[lang][view] || view;
  render();
}

function render() {
  if (!state) return;
  const content = document.getElementById("content");
  if (currentView === "dashboard") content.innerHTML = renderDashboard();
  if (currentView === "projects") content.innerHTML = renderProjects();
  if (currentView === "search") content.innerHTML = renderSearch();
  if (currentView === "workbatons") content.innerHTML = renderWorkbatons();
  if (currentView === "workstash") content.innerHTML = renderWorkstash();
  if (currentView === "workthreads") content.innerHTML = renderWorkthreads();
  if (currentView === "agents") content.innerHTML = renderAgents();
  if (currentView === "timeline") content.innerHTML = renderTimeline();
  if (currentView === "settings") content.innerHTML = renderSettings();
  if (currentView === "help") content.innerHTML = renderHelp();
}

function renderDashboard() {
  const ja = lang === "ja";
  const counts = state.dashboard.counts;
  return `
    <div class="stats">
      ${stat(ja?"プロジェクト":"Projects", counts.projects)}
      ${stat("WorkBaton", counts.workbatons)}
      ${stat("WorkStash", counts.workstash_entries)}
      ${stat("WorkThreads", counts.workthreads)}
      ${stat(ja?"イベント":"Events", counts.events)}
    </div>
    <div class="grid">
      <div class="panel"><h2>${ja?"プロジェクト":"Projects"}</h2>${table(
        [ja?"プロジェクト":"Project","WorkBaton","WorkStash","Threads",ja?"更新日時":"Updated"],
        state.dashboard.projects.map(p => [
          projectLink(p.project_key), esc(p.workbaton_count), esc(p.workstash_count),
          esc(p.workthread_count), fmt(p.updated_at)
        ])
      )}</div>
      <div class="panel"><h2>${ja?"最近のイベント":"Recent Events"}</h2>${eventList(state.dashboard.recent_events)}</div>
    </div>`;
}

function stat(label, value) {
  return `<div class="stat"><div class="label">${esc(label)}</div><div class="value">${esc(value)}</div></div>`;
}

function table(headers, rows, attrs = "") {
  if (!rows.length) return `<div class="empty">No records</div>`;
  return `<table ${attrs}><thead><tr>${headers.map(h => `<th>${esc(h)}</th>`).join("")}</tr></thead><tbody>${
    rows.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join("")}</tr>`).join("")
  }</tbody></table>`;
}

function projectLink(projectKey) {
  const key = String(projectKey || "");
  if (!key) return "";
  return `<button class="ghost" onclick="openProject('${encodeURIComponent(key)}')">${esc(key)}</button>`;
}

function projectKeys() {
  const keys = new Set((state.dashboard.projects || []).map(p => p.project_key).filter(Boolean));
  (state.workbatons.items || []).forEach(item => { if (item.project_key) keys.add(item.project_key); });
  (state.workstash.entries || []).forEach(item => { if (item.project_key) keys.add(item.project_key); });
  (state.workthreads.threads || []).forEach(item => { if (item.project_key) keys.add(item.project_key); });
  (state.events.events || []).forEach(item => { if (item.project_key) keys.add(item.project_key); });
  return Array.from(keys).sort((a, b) => a.localeCompare(b));
}

function projectSummary(projectKey) {
  const existing = (state.dashboard.projects || []).find(p => p.project_key === projectKey);
  if (existing) return existing;
  const workbatons = byProject(state.workbatons.items, projectKey).length;
  const workstash = byProject(state.workstash.entries, projectKey).length;
  const workthreads = byProject(state.workthreads.threads, projectKey).length;
  return {project_key: projectKey, workbaton_count: workbatons, workstash_count: workstash, workthread_count: workthreads, updated_at: ""};
}

function byProject(items, projectKey) {
  return (items || []).filter(item => (item.project_key || "") === projectKey);
}

function openProject(encodedProject) {
  selectedProject = decodeURIComponent(encodedProject || "");
  if (selectedProject) {
    localStorage.setItem("a2cr_selected_project", selectedProject);
  } else {
    localStorage.removeItem("a2cr_selected_project");
  }
  setView("projects");
}

function renderProjects() {
  const keys = projectKeys();
  if (selectedProject && !keys.includes(selectedProject)) {
    selectedProject = "";
    localStorage.removeItem("a2cr_selected_project");
  }
  const main = selectedProject
    ? renderProjectDetail(selectedProject)
    : renderProjectIndex(keys);
  return `<div class="project-layout">${renderProjectPicker(keys)}<div>${main}</div></div>`;
}

function renderProjectPicker(keys) {
  const rows = keys.map(key => {
    const p = projectSummary(key);
    const meta = `${p.workbaton_count || 0} Baton / ${p.workstash_count || 0} Stash / ${p.workthread_count || 0} Thread`;
    return `<button class="ghost ${key === selectedProject ? "active" : ""}" onclick="openProject('${encodeURIComponent(key)}')">
      <span>${esc(key)}<span class="project-meta">${esc(meta)}</span></span>
    </button>`;
  }).join("");
  return `<div class="panel"><h2>Projects</h2><div class="project-list">
    <button class="ghost ${selectedProject ? "" : "active"}" onclick="openProject('')">
      <span>All projects<span class="project-meta">${keys.length} project${keys.length === 1 ? "" : "s"}</span></span>
    </button>
    ${rows || `<div class="empty">No projects</div>`}
  </div></div>`;
}

function renderProjectIndex(keys) {
  const rows = keys.map(key => {
    const p = projectSummary(key);
    return [
      projectLink(key),
      esc(p.workbaton_count || 0),
      esc(p.workstash_count || 0),
      esc(p.workthread_count || 0),
      fmt(p.updated_at)
    ];
  });
  return `<div class="panel"><h2>Projects</h2>${table(
    ["Project", "WorkBaton", "WorkStash", "WorkThreads", "Updated"],
    rows
  )}</div>`;
}

function renderProjectDetail(projectKey) {
  const summary = projectSummary(projectKey);
  const batons = byProject(state.workbatons.items, projectKey);
  const stash = byProject(state.workstash.entries, projectKey);
  const threads = byProject(state.workthreads.threads, projectKey);
  const events = byProject(state.events.events, projectKey).slice(0, 20);
  return `
    <div class="stats">
      ${stat("WorkBaton", summary.workbaton_count || batons.length)}
      ${stat("WorkStash", summary.workstash_count || stash.length)}
      ${stat("WorkThreads", summary.workthread_count || threads.length)}
      ${stat("Recent Events", events.length)}
    </div>
    <div class="actions">
      <button onclick="searchProject('${encodeURIComponent(projectKey)}')">Search Project</button>
    </div>
    <div class="panel"><h2>${esc(projectKey)} WorkThreads</h2>
      ${projectThreadForm(projectKey)}
      ${table(
        ["Thread", "State", "Messages", "Agents", "Updated"],
        threads.map(item => [
          linkFor("WorkThread", item.thread_key),
          badge(item.state, item.state),
          esc(item.message_count),
          esc(item.participant_count),
          fmt(item.updated_at)
        ])
      )}
    </div>
    <div class="grid">
      <div>
        <div class="panel"><h2>${esc(projectKey)} WorkBaton</h2>${table(
          ["Slot", "State", "No.", "Updated"],
          batons.map(item => [
            linkFor("WorkBaton", item.slot_name),
            [
              item.pinned ? `<span class="badge pinned-b">pinned</span>` : "",
              item.stale ? `<span class="badge stale">stale</span>` : "",
              item.archived ? `<span class="badge archived-b">archived</span>` : ""
            ].join(""),
            esc(item.slot_number ?? ""),
            fmt(item.updated_at)
          ])
        )}</div>
        <div class="panel"><h2>${esc(projectKey)} WorkStash</h2>${table(
          ["Key", "Tags", "Size", "Updated"],
          stash.map(item => [
            linkFor("WorkStash", item.entry_key),
            (item.tags || []).map(t => badge(t)).join(""),
            esc(fmtSize(item.size_bytes)),
            fmt(item.updated_at)
          ])
        )}</div>
      </div>
      <div>
        <div class="panel detail" id="detail"><h2>Detail</h2><div class="empty">Select an item</div></div>
        <div class="panel"><h2>Recent Events</h2>${eventList(events)}</div>
      </div>
    </div>`;
}

function projectThreadForm(projectKey) {
  return `<div class="filters project-thread-form">
    <div class="filter-grid">
      <div><label>Thread key</label><input id="project-thread-key" placeholder="${esc(projectKey)}-review"></div>
      <div><label>Title</label><input id="project-thread-title" placeholder="Project coordination"></div>
      <div><label>Agent</label><input id="project-thread-label" placeholder="planner"></div>
    </div>
    <div class="filter-keyword" style="margin-top:8px;margin-bottom:0">
      <input id="project-thread-message" placeholder="Initial message">
      <button class="primary" onclick="createProjectThread('${encodeURIComponent(projectKey)}')">Create WorkThread</button>
    </div>
  </div>`;
}

function searchProject(encodedProject) {
  const project = decodeURIComponent(encodedProject || "");
  currentView = "search";
  document.querySelectorAll(".nav button[data-view]").forEach(btn => btn.classList.toggle("active", btn.dataset.view === "search"));
  document.getElementById("view-title").textContent = TITLES[lang].search;
  render();
  const input = document.getElementById("project");
  if (input) input.value = project;
  runSearch();
}

async function createProjectThread(encodedProject) {
  const project = decodeURIComponent(encodedProject || "");
  const threadKey = document.getElementById("project-thread-key").value.trim();
  const title = document.getElementById("project-thread-title").value.trim();
  if (!threadKey || !title) {
    alert("thread_key and title are required");
    return;
  }
  const result = await api("/api/workthreads", {method: "POST", body: JSON.stringify({
    thread_key: threadKey,
    title,
    project,
    participant_label: document.getElementById("project-thread-label").value.trim(),
    initial_message: document.getElementById("project-thread-message").value.trim(),
  })});
  if (result.status !== "created") {
    alert(JSON.stringify(result));
    return;
  }
  selectedProject = project;
  localStorage.setItem("a2cr_selected_project", selectedProject);
  await loadState();
}

function renderSearch() {
  const ja = lang === "ja";
  return `
    <div class="panel">
      <div class="filters">
        <div class="filter-keyword">
          <input id="q" placeholder="${ja ? "キーワードで検索..." : "Search by keyword..."}">
          <button class="primary" onclick="runSearch()">${ja ? "検索" : "Search"}</button>
        </div>
        <div class="filter-grid">
          <div>
            <label>${ja ? "種別" : "Type"}</label>
            <select id="type">
              <option value="">${ja ? "すべて" : "All"}</option>
              <option>WorkBaton</option><option>WorkStash</option>
              <option>WorkThread</option>
              <option value="Event">${ja ? "イベント" : "Event"}</option>
            </select>
          </div>
          <div>
            <label>${ja ? "プロジェクト" : "Project"}</label>
            <input id="project">
          </div>
          <div>
            <label>${ja ? "タグ" : "Tag"}</label>
            <input id="tag">
          </div>
          <div>
            <label>${ja ? "状態" : "State"}</label>
            <select id="threadState">
              <option value="">${ja ? "すべて" : "All"}</option>
              <option>open</option><option>closed</option><option>archived</option>
            </select>
          </div>
          <div>
            <label>${ja ? "エージェント" : "Agent"}</label>
            <input id="agent">
          </div>
          <div>
            <label>Slot</label>
            <input id="slot">
          </div>
        </div>
      </div>
      <div id="search-results" class="detail-body"></div>
    </div>`;
}

async function runSearch() {
  const params = new URLSearchParams({
    q: document.getElementById("q").value,
    type: document.getElementById("type").value,
    project: document.getElementById("project").value,
    tag: document.getElementById("tag").value,
    state: document.getElementById("threadState").value,
    agent: document.getElementById("agent").value,
    slot: document.getElementById("slot").value,
    limit: "50"
  });
  const result = await api("/api/search?" + params.toString());
  document.getElementById("search-results").innerHTML = renderSearchResults(result.results || []);
}

function renderSearchResults(results) {
  const ja = lang === "ja";
  const header = `<div style="padding:9px 14px;font-size:12px;font-weight:600;border-bottom:1px solid var(--line);color:var(--muted)">
    ${ja ? "検索結果" : "Results"} <span class="badge">${results.length}${ja ? "件" : ""}</span>
  </div>`;
  if (!results.length) return header + `<div class="empty">${ja ? "— 該当なし —" : "— No results —"}</div>`;
  return header + table(
    [ja?"種別":"Type", ja?"名前":"Name", ja?"プロジェクト":"Project", ja?"スニペット":"Snippet", ja?"更新日時":"Updated"],
    results.map(item => [
      badge(item.object_type, item.object_type),
      linkFor(item.object_type, item.handle),
      esc(item.project_key || ""),
      esc(item.snippet || ""),
      fmt(item.updated_at)
    ])
  );
}

function renderWorkbatons() {
  const ja = lang === "ja";
  const rows = state.workbatons.items.map(item => [
    linkFor("WorkBaton", item.slot_name),
    esc(item.project_key || ""),
    [
      item.pinned   ? `<span class="badge pinned-b">${ja?"ピン留め":"pinned"}</span>` : "",
      item.stale    ? `<span class="badge stale">${ja?"Stale":"stale"}</span>` : "",
      item.archived ? `<span class="badge archived-b">${ja?"アーカイブ":"archived"}</span>` : ""
    ].join(""),
    esc(item.slot_number ?? ""),
    fmt(item.updated_at)
  ]);
  return `<div class="grid">
    <div class="panel"><h2>${ja?"Slot 一覧":"Slots"}</h2>${table(
      [ja?"Slot名":"Slot", ja?"プロジェクト":"Project", ja?"状態":"State", "No.", ja?"更新日時":"Updated"],
      rows
    )}</div>
    <div class="panel detail" id="detail"><h2>${ja?"詳細":"Detail"}</h2><div class="empty">${ja?"Slotを選択してください":"Select a Slot"}</div></div>
  </div>`;
}

function renderWorkstash() {
  const ja = lang === "ja";
  const rows = state.workstash.entries.map(item => [
    linkFor("WorkStash", item.entry_key),
    esc(item.project_key || ""),
    item.tags.map(t => badge(t)).join(""),
    esc(fmtSize(item.size_bytes)),
    fmt(item.updated_at)
  ]);
  return `<div class="grid">
    <div class="panel"><h2>${ja?"エントリー一覧":"Entries"}</h2>${table(
      [ja?"キー":"Key", ja?"プロジェクト":"Project", ja?"タグ":"Tags", ja?"サイズ":"Size", ja?"更新日時":"Updated"],
      rows
    )}</div>
    <div class="panel detail" id="detail"><h2>${ja?"詳細":"Detail"}</h2><div class="empty">${ja?"エントリーを選択してください":"Select an entry"}</div></div>
  </div>`;
}

function renderWorkthreads() {
  const ja = lang === "ja";
  const rows = state.workthreads.threads.map(item => [
    linkFor("WorkThread", item.thread_key),
    esc(item.project_key || ""),
    badge(item.state, item.state),
    esc(item.message_count),
    esc(item.participant_count),
    fmt(item.updated_at)
  ]);
  return `<div class="grid">
    <div class="panel"><h2>${ja?"Thread 一覧":"Threads"}</h2>${table(
      ["Thread", ja?"プロジェクト":"Project", ja?"状態":"State",
       ja?"件数":"Messages", ja?"参加者":"Agents", ja?"更新日時":"Updated"],
      rows
    )}</div>
    <div class="panel detail" id="detail"><h2>${ja?"詳細":"Detail"}</h2><div class="empty">${ja?"Threadを選択してください":"Select a thread"}</div></div>
  </div>`;
}

function renderAgents() {
  const ja = lang === "ja";
  const CLIENT_COLORS = {"claude-code":{bg:"#e8f5f0",color:"#1e6f54"}, "cursor":{bg:"#e8f2f7",color:"#2f5d8c"}};
  return `<div class="panel"><h2>${ja?"エージェント一覧":"Agents"}</h2>
    <div style="overflow-x:auto">${table(
      [ja?"クライアント":"Client", ja?"エージェント":"Agent",
       ja?"モデル":"Model", ja?"セッション":"Session",
       "WorkBaton","WorkStash","Thread Msg", ja?"更新日時":"Updated"],
      state.actors.actors.map(a => {
        const c = CLIENT_COLORS[a.client_name] || {bg:"#f4f7f5",color:"#63706a"};
        const clientBadge = a.client_name
          ? `<span class="badge" style="background:${c.bg};color:${c.color};border-color:${c.color}40">${esc(a.client_name)}</span>`
          : "";
        const sid = a.session_id ? esc(a.session_id.slice(0,8)) + "..." : "";
        const sidFull = esc(a.session_id || "");
        return [
          clientBadge,
          esc(a.agent_label || ""),
          `<span style="color:var(--muted);font-size:11px">${esc(a.model_source || "")}</span>`,
          `<span title="${sidFull}" style="font-family:monospace;font-size:10px;color:var(--muted)">${sid}</span>`,
          `<strong>${esc(a.workbaton_count)}</strong>`,
          `<strong>${esc(a.workstash_count)}</strong>`,
          `<strong>${esc(a.workthread_message_count)}</strong>`,
          fmt(a.updated_at)
        ];
      })
    )}</div></div>`;
}

function renderTimeline() {
  const ja = lang === "ja";
  return `<div class="panel"><h2>${ja?"イベント一覧":"Events"}</h2>${eventList(state.events.events)}</div>`;
}

function renderHelp() {
  const ja = lang === "ja";
  return `
    <div class="panel" style="max-width:720px">
      <h2>${ja ? "A2CR とは" : "What is A2CR"}</h2>
      <div class="detail-body">
        <p>${ja
          ? "AIエージェントが保存した作業状態（WorkBaton・WorkStash・WorkThreads）をローカルで管理・閲覧するためのダッシュボードです。"
          : "A dashboard for viewing and managing AI agent work state (WorkBaton, WorkStash, WorkThreads) stored locally on your machine."}</p>
      </div>
    </div>
    <div class="panel" style="max-width:720px">
      <h2>${ja ? "基本の流れ" : "Basic Flow"}</h2>
      <div class="detail-body">
        <div class="help-steps">
          <div class="help-step">
            <div class="help-step-num">①</div>
            <div class="help-step-label">${ja ? "保存" : "Save"}</div>
            <div class="help-step-sub">${ja ? "AIにWorkBatonへ保存するよう指示する" : "Ask the AI to save to WorkBaton"}</div>
          </div>
          <div class="help-step-arrow">→</div>
          <div class="help-step">
            <div class="help-step-num">②</div>
            <div class="help-step-label">${ja ? "再開" : "Resume"}</div>
            <div class="help-step-sub">${ja ? "新しいAIセッションで「再開して」と指示する" : "Tell the AI to resume in a new session"}</div>
          </div>
        </div>
        <div class="help-detail">
          <strong>${ja ? "① 保存するには" : "① How to save"}</strong>
          <ul style="margin:8px 0;padding-left:18px;font-size:13px">
            ${ja ? `<li>作業の区切りで、AIに保存を依頼します</li>
            <li>AIが <code>save_context</code> ツールを呼び出し、WorkBatonに保存します</li>
            <li>Slot名はAIが自動で命名しますが、指定もできます</li>`
            : `<li>At a natural stopping point, ask the AI to save</li>
            <li>The AI calls <code>save_context</code> and writes a WorkBaton checkpoint</li>
            <li>The AI auto-names the Slot, but you can specify one</li>`}
          </ul>
          <div class="help-prompt"><ul style="margin:0;padding-left:16px">
            ${ja ? `<li>「ここまでの作業をWorkBatonに保存してください」</li>
            <li>「save して」</li>
            <li>「"feature-auth" というSlot名で保存して」</li>`
            : `<li>"Save the current work to WorkBaton"</li>
            <li>"save"</li>
            <li>"Save as slot 'feature-auth'"</li>`}
          </ul></div>
        </div>
        <div class="help-detail" style="margin-top:16px">
          <strong>${ja ? "② 再開するには" : "② How to resume"}</strong>
          <ul style="margin:8px 0;padding-left:18px;font-size:13px">
            ${ja ? `<li>新しいAIセッションを開き、再開を依頼します</li>
            <li>AIが <code>resume_context</code> ツールを呼び出し、前回の状態を復元します</li>
            <li>Slot名を指定すると特定の保存ポイントから再開できます</li>`
            : `<li>Open a new AI session and ask to resume</li>
            <li>The AI calls <code>resume_context</code> to restore the previous state</li>
            <li>Specify a Slot name to resume from a specific checkpoint</li>`}
          </ul>
          <div class="help-prompt"><ul style="margin:0;padding-left:16px">
            ${ja ? `<li>「前回の続きを再開して」</li>
            <li>「WorkBatonを読み込んで作業を再開してください」</li>
            <li>「"feature-auth" のSlotから再開して」</li>`
            : `<li>"Resume from last WorkBaton"</li>
            <li>"Load the WorkBaton and continue"</li>
            <li>"Resume from slot 'feature-auth'"</li>`}
          </ul></div>
        </div>
        <p style="margin-top:14px;padding:10px 14px;background:var(--soft);border-radius:6px;font-size:13px">
          💡 ${ja ? "ダッシュボードでいつでも保存済みの作業状態を確認・管理できます" : "Use the Dashboard to review and manage your saved work state at any time"}
        </p>
      </div>
    </div>
    <div class="panel" style="max-width:720px">
      <h2>${ja ? "各ビューの説明" : "Views Reference"}</h2>
      <div class="detail-body">
        <dl class="views-grid">
          <dt>Dashboard</dt><dd>${ja ? "プロジェクト一覧と最近のイベントを俯瞰する" : "Overview of projects and recent events"}</dd>
          <dt>WorkBaton</dt><dd>${ja ? "作業引き継ぎ用のチェックポイント。Slot名で保存・管理" : "Work handoff checkpoints, saved and managed by Slot name"}</dd>
          <dt>WorkStash</dt><dd>${ja ? "一時的なメモ領域。バトンから参照される補助データ" : "Temporary note area; auxiliary data referenced by WorkBaton"}</dd>
          <dt>WorkThreads</dt><dd>${ja ? "複数AIエージェントの協調作業スペース" : "Collaborative workspace for multi-agent coordination"}</dd>
          <dt>Search</dt><dd>${ja ? "プロジェクト・タグ・エージェントなどで横断検索" : "Cross-search by project, tag, agent, and more"}</dd>
        </dl>
      </div>
    </div>`;
}

function renderSettings() {
  const ja = lang === "ja";
  const d = state.dashboard;
  const savedView = localStorage.getItem("a2cr_default_view") || "dashboard";
  const savedTimezone = displayTimezone || "local";
  const viewOptions = [
    {v:"dashboard",  ja:"Dashboard（デフォルト）", en:"Dashboard (default)"},
    {v:"projects",   ja:"Projects",                en:"Projects"},
    {v:"workbatons", ja:"WorkBaton",               en:"WorkBaton"},
    {v:"workstash",  ja:"WorkStash",               en:"WorkStash"},
    {v:"workthreads",ja:"WorkThreads",             en:"WorkThreads"},
    {v:"search",     ja:"Search",                  en:"Search"},
    {v:"agents",     ja:"Agents",                  en:"Agents"},
    {v:"timeline",   ja:"Timeline",                en:"Timeline"},
  ];
  const timezoneOptions = [
    {v:"local",               ja:"Browser local（ブラウザのタイムゾーン）", en:"Browser local"},
    {v:"UTC",                 ja:"UTC",                              en:"UTC"},
    {v:"Asia/Tokyo",          ja:"Asia/Tokyo（日本時間）",             en:"Asia/Tokyo (Japan)"},
    {v:"America/Los_Angeles", ja:"America/Los_Angeles",              en:"America/Los_Angeles"},
    {v:"America/New_York",    ja:"America/New_York",                 en:"America/New_York"},
    {v:"Europe/London",       ja:"Europe/London",                    en:"Europe/London"},
    {v:"Europe/Berlin",       ja:"Europe/Berlin",                    en:"Europe/Berlin"},
  ];
  return `
    <div class="settings-panel panel">
      <h2>${ja?"表示設定":"Display Preferences"}</h2>
      <div class="detail-body">
        <div class="pref-item">
          <div class="pref-label">${ja?"言語 / Language":"Language"}</div>
          <div class="radio-group">
            <label><input type="radio" name="pref-lang" value="ja" ${lang==="ja"?"checked":""} onchange="setLang(this.value)"> 日本語</label>
            <label><input type="radio" name="pref-lang" value="en" ${lang==="en"?"checked":""} onchange="setLang(this.value)"> English</label>
          </div>
          <div class="pref-hint">${ja?"ブラウザを閉じても保持されます":"Saved across sessions"}</div>
        </div>
        <div class="pref-item">
          <div class="pref-label">${ja?"起動時のページ":"Default page on startup"}</div>
          <select class="pref-select" onchange="localStorage.setItem('a2cr_default_view',this.value)">
            ${viewOptions.map(o => `<option value="${o.v}" ${savedView===o.v?"selected":""}>${ja?o.ja:o.en}</option>`).join("")}
          </select>
          <div class="pref-hint">${ja?"次回起動時から反映されます":"Applied on next load"}</div>
        </div>
        <div class="pref-item">
          <div class="pref-label">${ja?"表示タイムゾーン":"Display time zone"}</div>
          <select class="pref-select" onchange="setTimezone(this.value)">
            ${timezoneOptions.map(o => `<option value="${o.v}" ${savedTimezone===o.v?"selected":""}>${ja?o.ja:o.en}</option>`).join("")}
          </select>
          <div class="pref-hint">${ja
            ? `保存データはUTCのまま、画面表示だけを切り替えます。現在のブラウザ: ${esc(browserTimezone())}`
            : `Stored timestamps remain UTC; only display changes. Current browser: ${esc(browserTimezone())}`}</div>
        </div>
      </div>
    </div>
    <div class="settings-panel panel">
      <h2>${ja?"データベース情報":"Database Info"}</h2>
      <div class="detail-body">
        <div class="pref-item">
          <div class="pref-label">${ja?"データベースパス":"Database Path"}</div>
          <div class="code-block">${esc(d.database_path)}</div>
        </div>
        <div style="display:flex;gap:24px">
          <div class="pref-item">
            <div class="pref-label">${ja?"データベースサイズ":"Database Size"}</div>
            <div style="font-size:13px;font-weight:600">${fmtSize(d.database_size_bytes)}</div>
          </div>
          <div class="pref-item">
            <div class="pref-label">${ja?"動作モード":"Mode"}</div>
            <span class="badge open">local</span>
          </div>
        </div>
      </div>
    </div>`;
}

function eventList(events) {
  const ja = lang === "ja";
  return table(
    [ja?"対象":"Object", ja?"アクション":"Action", ja?"プロジェクト":"Project", ja?"日時":"Time"],
    events.map(e => [
      `${badge(e.object_type || "", e.object_type || "")} ${esc(e.object_key || "")}`,
      actionBadge(e.action || ""),
      esc(e.project_key || ""),
      fmt(e.created_at)
    ])
  );
}

function batonFields(content) {
  if (!content || typeof content !== "object") return "";
  const ja = lang === "ja";
  const FIELDS = [
    {key:"goal",         jaLabel:"目標",           enLabel:"Goal",          highlight:false},
    {key:"current_state",jaLabel:"現在の状態",      enLabel:"Current State", highlight:false},
    {key:"next_action",  jaLabel:"次のアクション",  enLabel:"Next Action",   highlight:true},
    {key:"blockers",     jaLabel:"ブロッカー",      enLabel:"Blockers",      highlight:false},
    {key:"decisions",    jaLabel:"決定事項",        enLabel:"Decisions",     highlight:false},
    {key:"problems",     jaLabel:"課題",            enLabel:"Problems",      highlight:false},
  ];
  return FIELDS.filter(f => {
    const v = content[f.key];
    return v !== undefined && v !== null && v !== "" && !(Array.isArray(v) && v.length === 0);
  }).map(f => {
    const v = content[f.key];
    const label = ja ? f.jaLabel : f.enLabel;
    const display = ja ? `${label} (${f.key})` : label;
    const valueHtml = Array.isArray(v)
      ? `<ul>${v.map(i => `<li>${esc(String(i))}</li>`).join("")}</ul>`
      : `<div>${esc(String(v))}</div>`;
    return `<div class="field-card${f.highlight ? " highlight" : ""}">
      <div class="field-label${f.highlight ? " highlight" : ""}">${esc(display)}</div>
      <div class="field-value">${valueHtml}</div>
    </div>`;
  }).join("");
}

const AGENT_COLORS = [
  {bg:"#e8f5f0", color:"#1e6f54"},
  {bg:"#e8f2f7", color:"#2f5d8c"},
  {bg:"#fff8e6", color:"#9a6418"},
  {bg:"#f4f7f5", color:"#63706a"},
];
function agentColorMap(participants) {
  const map = {};
  (participants || []).forEach((p, i) => {
    const c = AGENT_COLORS[i % AGENT_COLORS.length];
    const key = p.agent_label || p.client_name || "agent";
    map[key] = c;
  });
  return map;
}

function linkFor(type, key) {
  return `<button class="ghost" onclick="openDetail('${esc(type)}','${encodeURIComponent(key)}')">${esc(key)}</button>`;
}

async function openDetail(type, encodedKey) {
  const key = decodeURIComponent(encodedKey);
  const endpoints = {WorkBaton: "/api/workbatons/", WorkStash: "/api/workstash/", WorkThread: "/api/workthreads/"};
  const detail = await api(endpoints[type] + encodeURIComponent(key));
  const target = document.getElementById("detail");
  if (!target) return;
  target.innerHTML = `<h2>${esc(type)} Detail</h2><div class="detail-body">${detailHtml(type, detail)}</div>`;
}

function detailHtml(type, detail) {
  if (detail.status === "not_found") return `<div class="empty">${lang === "ja" ? "見つかりません" : "Not found"}</div>`;
  if (type === "WorkBaton") {
    const ja = lang === "ja";
    return `${batonActions(detail.slot_name, detail)}
      <p>${badge(detail.project_key || "")}${detail.pinned ? `<span class="badge pinned-b">${ja?"ピン留め":"pinned"}</span>` : ""}${detail.stale ? `<span class="badge stale">${ja?"Stale":"stale"}</span>` : ""}${detail.archived ? `<span class="badge archived-b">${ja?"アーカイブ":"archived"}</span>` : ""}</p>
      ${batonFields(detail.content)}
      <details><summary>${ja?"▶ 生 JSON を表示":"▶ Show raw JSON"}</summary>${jsonBlock(detail.content)}</details>
      <h3 style="margin-top:14px">${ja?"参照":"References"}</h3>${referenceList(detail.references)}
      <h3>${ja?"参照元":"Referenced By"}</h3>${referenceList(detail.referenced_by, true)}`;
  }
  if (type === "WorkStash") {
    const ja = lang === "ja";
    return `<div class="actions">
        <button class="danger" onclick="runAction('WorkStash','${esc(detail.entry_key)}','delete')">${ja?"削除":"Delete"}</button>
      </div>
      <p>${badge(detail.project_key || "")}${(detail.tags || []).map(t => badge(t)).join("")}</p>
      <div style="display:flex;gap:16px;margin-bottom:10px;font-size:11px;color:var(--muted)">
        <span>${ja?"サイズ":"Size"}: <strong style="color:var(--ink)">${esc(fmtSize(detail.size_bytes))}</strong></span>
        <span>${ja?"更新":"Updated"}: <strong style="color:var(--ink)">${fmt(detail.updated_at)}</strong></span>
      </div>
      <div class="field-label" style="margin-bottom:4px">${ja?"内容":"Content"}</div>
      <pre>${esc(detail.value || "")}</pre>
      <h3>${ja?"参照元":"Referenced By"}</h3>${referenceList(detail.referenced_by, true)}`;
  }
  if (type === "WorkThread") {
    const ja = lang === "ja";
    const colorMap = agentColorMap(detail.participants);
    const msgCards = (detail.messages || []).map(m => {
      const agentKey = m.agent_label || m.client_name || "agent";
      const c = colorMap[agentKey] || AGENT_COLORS[3];
      return `<div class="msg-card">
        <div class="msg-header">
          <span class="badge" style="background:${c.bg};color:${c.color};border-color:${c.color}40">${esc(agentKey)}</span>
          <span class="msg-time">${fmt(m.created_at)}</span>
        </div>
        <pre class="msg-body">${esc(m.body || "")}</pre>
      </div>`;
    }).join("");
    return `<div class="actions">
        <button onclick="copyJoinPrompt('${encodeURIComponent(detail.thread_key)}')">Copy join prompt</button>
        <button class="warn" onclick="runAction('WorkThread','${esc(detail.thread_key)}','close')">${ja?"クローズ":"Close"}</button>
        <button class="danger" onclick="runAction('WorkThread','${esc(detail.thread_key)}','archive')">${ja?"アーカイブ":"Archive"}</button>
      </div>
      <p>${badge(detail.project_key || "")}${badge(detail.state, detail.state)}</p>
      <h3>${ja?"参加者":"Participants"}</h3>${table(
        [ja?"役割":"Role", ja?"クライアント":"Client", ja?"エージェント":"Agent", ja?"モデル":"Model"],
        (detail.participants || []).map(p => [esc(p.role), esc(p.client_name || ""), esc(p.agent_label || ""), esc(p.model_source || "")])
      )}
      <h3>${ja?"メッセージ":"Messages"} <span class="badge">${(detail.messages||[]).length}${ja?"件":""}</span></h3>
      <div class="msg-list">${msgCards || `<div class="empty">${ja?"メッセージなし":"No messages"}</div>`}</div>
      <h3>${ja?"参照":"References"}</h3>${referenceList(detail.references)}`;
  }
  return jsonBlock(detail);
}

function batonActions(key, detail) {
  const ja = lang === "ja";
  return `<div class="actions">
    <button onclick="runAction('WorkBaton','${esc(key)}','${detail.pinned ? "unpin" : "pin"}')">
      ${detail.pinned ? (ja?"ピン解除":"Unpin") : (ja?"ピン留め":"Pin")}
    </button>
    <button class="warn" onclick="runAction('WorkBaton','${esc(key)}','${detail.stale ? "unstale" : "stale"}')">
      ${detail.stale ? (ja?"最新に戻す":"Fresh") : (ja?"Staleにする":"Stale")}
    </button>
    <button class="warn" onclick="runAction('WorkBaton','${esc(key)}','archive')">${ja?"アーカイブ":"Archive"}</button>
    <button class="danger" onclick="runAction('WorkBaton','${esc(key)}','delete')">${ja?"削除":"Delete"}</button>
  </div>`;
}

function referenceList(items, reverse = false) {
  if (!items || !items.length) return `<div class="empty">No records</div>`;
  return table(reverse ? ["Source", "Handle", "Time"] : ["Target", "Handle", "Time"], items.map(item => [
    badge(reverse ? item.source_type : item.target_type),
    esc(reverse ? item.source_key : item.target_key),
    fmt(item.created_at)
  ]));
}

async function runAction(objectType, key, action) {
  if (["delete", "archive"].includes(action) && !confirm(action + " " + key + "?")) return;
  const result = await api("/api/action", {method: "POST", body: JSON.stringify({object_type: objectType, key, action})});
  if (!["deleted", "archived", "pinned", "unpinned", "stale", "fresh", "closed"].includes(result.status)) alert(JSON.stringify(result));
  await loadState();
}

async function copyJoinPrompt(encodedKey) {
  const key = decodeURIComponent(encodedKey || "");
  const result = await api("/api/workthreads/" + encodeURIComponent(key) + "/join-prompt");
  if (result.status !== "ok") {
    alert(JSON.stringify(result));
    return;
  }
  const prompt = result.prompt || "";
  if (navigator.clipboard && navigator.clipboard.writeText) {
    await navigator.clipboard.writeText(prompt);
    alert("Join prompt copied");
    return;
  }
  window.prompt("Copy join prompt", prompt);
}

document.querySelectorAll(".nav button[data-view]").forEach(btn => btn.addEventListener("click", () => setView(btn.dataset.view)));
document.getElementById("refresh").addEventListener("click", loadState);
document.getElementById("backup").addEventListener("click", async () => {
  const result = await api("/api/backup", {method: "POST", body: "{}"});
  alert(result.backup_path || JSON.stringify(result));
});
document.getElementById("export").addEventListener("click", async () => {
  const result = await api("/api/export");
  const blob = new Blob([JSON.stringify(result, null, 2)], {type: "application/json"});
  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = "a2cr-export.json";
  a.click();
  URL.revokeObjectURL(a.href);
});
// Initialize from localStorage
lang = localStorage.getItem("a2cr_lang") || "ja";
currentView = localStorage.getItem("a2cr_default_view") || "dashboard";
displayTimezone = localStorage.getItem("a2cr_timezone") || "local";
selectedProject = localStorage.getItem("a2cr_selected_project") || "";
document.querySelectorAll(".nav button[data-view]").forEach(btn => btn.classList.toggle("active", btn.dataset.view === currentView));
document.getElementById("lang-ja").classList.toggle("active", lang === "ja");
document.getElementById("lang-en").classList.toggle("active", lang === "en");
document.getElementById("view-title").textContent = TITLES[lang][currentView] || currentView;
loadState();
</script>
</body>
</html>
"""
