import { useEffect, useMemo, useRef, useState } from "react";
import { check } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";
import { listen } from "@tauri-apps/api/event";
import { Icon } from "./icons";
import * as api from "./runtime";
import type {
  KnowledgeDocument,
  KnowledgeSummary,
  SourceKind,
  RuntimeOverview,
  SystemSnapshot,
} from "./types";
import {
  MarkdownDocument,
  documentOutline,
  jumpToHeading,
  splitFrontmatter,
} from "./modules/knowledge-render";
import { formatHoloLakeTime } from "./time";

type View =
  | "channel"
  | "knowledge"
  | "market"
  | "history"
  | "portal"
  | "settings";
const nav: [View, string, string][] = [
  ["channel", "我的频道", "channel"],
  ["knowledge", "光湖知识库", "book"],
  ["market", "模块商城", "cube"],
  ["history", "系统演化史", "history"],
  ["portal", "企业门户", "building"],
  ["settings", "设置", "settings"],
];
const labels: Record<SourceKind, string> = {
  USER_MESSAGE: "用户语言",
  PERSONA_RESPONSE: "人格回应",
  EXTERNAL_AI_MESSAGE: "外部 AI",
  SYSTEM_CONTEXT: "系统环境",
  PROTOCOL_EVENT: "协议运行",
  AGENT_ACTION: "Agent 执行",
  TOOL_RESULT: "工具结果",
  SYSTEM_RECEIPT: "系统回执",
};

export default function App() {
  const [data, setData] = useState<SystemSnapshot | null>(null),
    [runtime, setRuntime] = useState<RuntimeOverview | null>(null),
    [view, setView] = useState<View>("channel"),
    [error, setError] = useState(""),
    [busy, setBusy] = useState(false);
  const refresh = () =>
    api
      .snapshot()
      .then(setData)
      .catch((e) => setError(String(e)));
  const refreshRuntime = () =>
    api
      .runtimeOverview()
      .then(setRuntime)
      .catch((e) => setError(String(e)));
  const refreshAll = () => {
    refresh();
    refreshRuntime();
  };
  useEffect(() => {
    refreshAll();
    if (!("__TAURI_INTERNALS__" in window)) return;
    let unlisten: (() => void) | undefined;
    listen("hololake-runtime-event", () => refreshAll()).then((dispose) => {
      unlisten = dispose;
    });
    return () => unlisten?.();
  }, []);
  if (!data)
    return (
      <div className="boot">
        <div className="lake-mark" />
        正在进入 HoloLake…
      </div>
    );
  if (!data.channel) return <Onboarding onDone={refreshAll} error={error} />;
  return (
    <div className="app-shell">
      <div className="titlebar">
        <div className="traffic">
          <i />
          <i />
          <i />
        </div>
        <span>HoloLake</span>
        <span className="version">
          v{data.appVersion.replace("-browser", "")}
        </span>
      </div>
      <aside className="rail">
        <div className="brand-orbit">
          <span />
        </div>
        <nav>
          {nav.map(([id, label, icon]) => (
            <button
              key={id}
              className={view === id ? "active" : ""}
              aria-label={label}
              onClick={() => setView(id)}
            >
              <Icon name={icon} />
              <span>{label}</span>
            </button>
          ))}
        </nav>
        <div className="channel-mini">
          <div className="mini-lake" />
          <div>
            <strong>{data.channel.name}</strong>
            <small>{data.channel.channelId}</small>
          </div>
        </div>
      </aside>
      <main>
        {view === "channel" && (
          <Channel
            data={data}
            runtime={runtime}
            refresh={refreshAll}
            openKnowledge={() => setView("knowledge")}
          />
        )}{" "}
        {view === "knowledge" && <Knowledge data={data} refresh={refresh} />}{" "}
        {view === "market" && <Market />}{" "}
        {view === "history" && <History data={data} />}{" "}
        {view === "portal" && <Portal />}{" "}
        {view === "settings" && (
          <Settings
            data={data}
            runtime={runtime}
            refresh={refreshAll}
            busy={busy}
            setBusy={setBusy}
            setError={setError}
          />
        )}
      </main>
      {error && (
        <div className="toast" role="alert">
          <span>{error}</span>
          <button onClick={() => setError("")}>关闭</button>
        </div>
      )}
    </div>
  );
}

function Onboarding({ onDone, error }: { onDone: () => void; error: string }) {
  const [name, setName] = useState("");
  const go = async () => {
    if (!name.trim()) return;
    await api.createChannel(name.trim());
    onDone();
  };
  return (
    <div className="onboard">
      <div className="stars" />
      <section>
        <div className="origin-light" />
        <h1>为你的频道起一个名字</h1>
        <p>
          这是属于你的私人初始化频道。系统将为它登记唯一编号，并在本机建立私人
          Git。
        </p>
        <label>
          频道名称
          <input
            value={name}
            onChange={(e) => setName(e.target.value)}
            onKeyDown={(e) => e.key === "Enter" && go()}
            placeholder="例如：我的光湖"
            autoFocus
          />
        </label>
        <button onClick={go} disabled={!name.trim()}>
          建立我的频道
        </button>
        {error && <small>{error}</small>}
      </section>
      <footer>GH-AIOS · 光湖语言系统 · 通用人工智能操作平台</footer>
    </div>
  );
}

function Channel({
  data,
  runtime,
  refresh,
  openKnowledge,
}: {
  data: SystemSnapshot;
  runtime: RuntimeOverview | null;
  refresh: () => void;
  openKnowledge: () => void;
}) {
  const [text, setText] = useState("");
  const send = async () => {
    if (!text.trim()) return;
    await api.submitMessage(text.trim());
    setText("");
    refresh();
  };
  return (
    <div className="page channel-page">
      <header>
        <div>
          <h1>{data.channel?.name}</h1>
          <p>个人初始化频道</p>
        </div>
        <button className="soft" onClick={openKnowledge}>
          打开光湖知识库
        </button>
      </header>
      <div className="channel-grid">
        <div className="channel-primary">
          <section className="conversation">
            <div className="welcome">
              <div className="lake-mark" />
              <h2>频道已经准备好</h2>
              <p>
                这里保存你与人格体的语言。系统动作会被分流到下方执行记录，不再混入对话。
              </p>
            </div>
            {data.timeline
              .filter(
                (e) =>
                  e.sourceKind === "USER_MESSAGE" ||
                  e.sourceKind === "PERSONA_RESPONSE" ||
                  e.sourceKind === "EXTERNAL_AI_MESSAGE",
              )
              .slice(0, 8)
              .reverse()
              .map((e) => (
                <article
                  className={
                    e.sourceKind === "USER_MESSAGE"
                      ? "human"
                      : e.sourceKind === "PERSONA_RESPONSE"
                        ? "persona"
                        : "external-ai"
                  }
                  key={e.eventId}
                >
                  <span>{labels[e.sourceKind]}</span>
                  <p>{e.content}</p>
                </article>
              ))}
          </section>
          {runtime &&
            runtime.proposals.some(
              (proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
            ) && (
              <section className="approval-queue">
                <h2>等待你确认的 Agent 动作</h2>
                {runtime.proposals
                  .filter(
                    (proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
                  )
                  .map((proposal) => (
                    <article key={proposal.proposalId}>
                      <div>
                        <strong>{proposal.gir.programId}</strong>
                        <p>
                          {proposal.gir.actions
                            .map((action) => action.operation)
                            .join(" → ")}
                        </p>
                        <small>
                          {proposal.clientId} · {proposal.proposalId}
                        </small>
                      </div>
                      <div>
                        <button
                          className="soft"
                          onClick={async () => {
                            await api.rejectProposal(proposal.proposalId);
                            refresh();
                          }}
                        >
                          驳回
                        </button>
                        <button
                          onClick={async () => {
                            await api.approveProposal(proposal.proposalId);
                            refresh();
                          }}
                        >
                          批准并执行
                        </button>
                      </div>
                    </article>
                  ))}
              </section>
            )}
          <ReceiptStream data={data} />
          <div className="composer">
            <textarea
              value={text}
              onChange={(e) => setText(e.target.value)}
              placeholder={`和${data.channel?.name}说点什么…`}
              onKeyDown={(e) => {
                if (e.key === "Enter" && !e.shiftKey) {
                  e.preventDefault();
                  send();
                }
              }}
            />
            <button onClick={send} aria-label="发送">
              ↗
            </button>
            <small>Enter 发送 · Shift + Enter 换行</small>
          </div>
        </div>
        <aside className="channel-context" aria-label="频道运行状态">
          <article>
            <h2>当前频道</h2>
            <strong>{data.channel?.name}</strong>
            <p>{data.channel?.channelId}</p>
          </article>
          <article>
            <h2>私人 Git</h2>
            <strong>本机已建立</strong>
            <p className="path">{data.channel?.privateGitPath}</p>
          </article>
          <article>
            <h2>公众人格运行时</h2>
            <strong>
              {runtime?.persona.personas[0]?.displayName ?? "尚未建立"}
            </strong>
            <p>
              {runtime?.persona.personas[0]?.state ?? "不会自动生成默认人格"}
            </p>
          </article>
          <article>
            <h2>GLP 实时桥</h2>
            <strong>
              {runtime?.realtime.state ?? "正在启动"} ·{" "}
              {runtime?.realtime.connectedClients ?? 0} 个连接
            </strong>
            <p>{runtime?.realtime.endpoint ?? "仅监听本机回环地址"}</p>
          </article>
          <article>
            <h2>公共更新</h2>
            <strong>
              {data.publicDistributionState ===
              "SIGNED_UPDATE_FEED_READY_NO_RELEASE"
                ? "更新入口在线 · 暂无发行"
                : data.publicDistributionState}
            </strong>
            <p>只接收签名发行，不自动执行普通 Git 拉取。</p>
          </article>
        </aside>
      </div>
    </div>
  );
}

function ReceiptStream({ data }: { data: SystemSnapshot }) {
  const rows = data.timeline
    .filter(
      (e) =>
        e.sourceKind !== "USER_MESSAGE" &&
        e.sourceKind !== "PERSONA_RESPONSE" &&
        e.sourceKind !== "EXTERNAL_AI_MESSAGE",
    )
    .slice(0, 5);
  return (
    <section className="receipt-stream">
      <h2>执行记录</h2>
      {rows.length === 0 ? (
        <p className="empty">
          还没有系统动作。你的语言与执行记录会在这里分开呈现。
        </p>
      ) : (
        rows.map((e) => (
          <div
            className={`receipt ${e.sourceKind.toLowerCase()}`}
            key={e.eventId}
          >
            <span className="kind">{labels[e.sourceKind]}</span>
            <strong>{e.title}</strong>
            <p>{e.content}</p>
            <time>
              {formatHoloLakeTime(e.occurredAt)}
            </time>
          </div>
        ))
      )}
    </section>
  );
}

const tagTints = ["tag-sky", "tag-violet", "tag-amber", "tag-mint", "tag-rose"];
function tagTint(tag: string) {
  let hash = 0;
  for (const char of tag) hash = (hash * 31 + (char.codePointAt(0) || 0)) % 997;
  return tagTints[hash % tagTints.length];
}

interface KnowledgeTreeNode {
  name: string;
  key: string;
  folders: Map<string, KnowledgeTreeNode>;
  documents: KnowledgeSummary[];
}
function buildKnowledgeTree(documents: KnowledgeSummary[]) {
  const root: KnowledgeTreeNode = {
    name: "知识",
    key: "",
    folders: new Map(),
    documents: [],
  };
  for (const document of documents) {
    const segments = document.path.split("/").filter(Boolean);
    segments.pop();
    let cursor = root;
    for (const segment of segments) {
      const key = cursor.key ? `${cursor.key}/${segment}` : segment;
      if (!cursor.folders.has(segment))
        cursor.folders.set(segment, {
          name: segment,
          key,
          folders: new Map(),
          documents: [],
        });
      cursor = cursor.folders.get(segment)!;
    }
    cursor.documents.push(document);
  }
  return root;
}
function treeCount(node: KnowledgeTreeNode): number {
  return (
    node.documents.length +
    [...node.folders.values()].reduce((sum, child) => sum + treeCount(child), 0)
  );
}
function KnowledgeTree({
  node,
  depth,
  expanded,
  active,
  onToggle,
  onOpen,
}: {
  node: KnowledgeTreeNode;
  depth: number;
  expanded: Set<string>;
  active: string | null;
  onToggle: (key: string) => void;
  onOpen: (path: string) => void;
}) {
  const folders = [...node.folders.values()].sort((a, b) =>
    a.name.localeCompare(b.name, "zh-CN"),
  );
  const documents = [...node.documents].sort((a, b) =>
    a.title.localeCompare(b.title, "zh-CN"),
  );
  return (
    <>
      {folders.map((folder) => {
        const open = depth === 0 || expanded.has(folder.key);
        return (
          <div className="tree-branch" key={folder.key}>
            <button
              className="tree-folder"
              style={{ paddingLeft: 10 + depth * 13 }}
              onClick={() => onToggle(folder.key)}
            >
              <span className={open ? "tree-chevron open" : "tree-chevron"}>
                ›
              </span>
              <Icon name="folder" />
              <span>{folder.name}</span>
              <em>{treeCount(folder)}</em>
            </button>
            {open && (
              <KnowledgeTree
                node={folder}
                depth={depth + 1}
                expanded={expanded}
                active={active}
                onToggle={onToggle}
                onOpen={onOpen}
              />
            )}
          </div>
        );
      })}
      {documents.map((document) => (
        <button
          className={
            active === document.path ? "tree-document active" : "tree-document"
          }
          style={{ paddingLeft: 28 + depth * 13 }}
          key={document.path}
          onClick={() => onOpen(document.path)}
        >
          <Icon name="file" />
          <span>{document.title}</span>
        </button>
      ))}
    </>
  );
}

function Knowledge({
  data,
  refresh,
}: {
  data: SystemSnapshot;
  refresh: () => void;
}) {
  const [selected, setSelected] = useState<string | null>(
      data.documents[0]?.path ?? null,
    ),
    [doc, setDoc] = useState<KnowledgeDocument | null>(null),
    [body, setBody] = useState(""),
    [query, setQuery] = useState(""),
    [newTitle, setNewTitle] = useState<string | null>(null),
    [editing, setEditing] = useState(false),
    [inspectorOpen, setInspectorOpen] = useState(true),
    [expanded, setExpanded] = useState<Set<string>>(new Set(["导入"])),
    [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
  const headingScrollTimer = useRef<number | null>(null);
  useEffect(() => {
    if (selected)
      api.readDocument(selected).then((d) => {
        setDoc(d);
        setBody(d.body);
        setEditing(false);
        setActiveHeadingId(null);
      });
  }, [selected]);
  const list = useMemo(
    () =>
      data.documents.filter((d) =>
        d.title.toLowerCase().includes(query.toLowerCase()),
      ),
    [data.documents, query],
  );
  const tree = useMemo(() => buildKnowledgeTree(list), [list]);
  const create = async () => {
    const title = newTitle?.trim();
    if (title) {
      await api.createDocument(title);
      setNewTitle(null);
      await refresh();
      setSelected(`${title.replace(/[\\/:*?\"<>|]/g, "-")}.md`);
    }
  };
  const save = async () => {
    if (doc) {
      await api.saveDocument(doc.path, body);
      setDoc({ ...doc, body });
      setEditing(false);
      refresh();
    }
  };
  const del = async () => {
    if (doc && confirm(`把“${doc.title}”移入回收站？`)) {
      await api.deleteDocument(doc.path);
      setSelected(null);
      setDoc(null);
      refresh();
    }
  };
  const parsed = useMemo(() => splitFrontmatter(doc?.body ?? ""), [doc?.body]);
  const outline = useMemo(() => documentOutline(doc?.body ?? ""), [doc?.body]);
  const stats = useMemo(() => {
    const chars = parsed.content.replace(/\s/g, "").length;
    return { chars, minutes: Math.max(1, Math.ceil(chars / 400)) };
  }, [parsed.content]);
  useEffect(() => {
    if (!activeHeadingId) return;
    document
      .querySelector(".outline-list button.active")
      ?.scrollIntoView({ block: "nearest" });
  }, [activeHeadingId]);
  const handleReaderScroll = (event: React.UIEvent<HTMLDivElement>) => {
    const container = event.currentTarget;
    if (headingScrollTimer.current !== null) return;
    headingScrollTimer.current = window.setTimeout(() => {
      headingScrollTimer.current = null;
      const containerTop = container.getBoundingClientRect().top;
      let current: string | null = null;
      for (const heading of Array.from(
        container.querySelectorAll<HTMLElement>(
          "h1[id], h2[id], h3[id], h4[id]",
        ),
      )) {
        if (heading.getBoundingClientRect().top - containerTop <= 28)
          current = heading.id;
        else break;
      }
      setActiveHeadingId(current);
    }, 100);
  };
  const openWiki = (target: string) => {
    const normalized = target.replace(/[\s·•・\-_|*`]/g, "").toLowerCase();
    const hit = data.documents.find(
      (item) =>
        item.title.replace(/[\s·•・\-_|*`]/g, "").toLowerCase() === normalized,
    );
    if (hit) setSelected(hit.path);
  };
  return (
    <div className="page knowledge-page">
      <header>
        <div>
          <h1>光湖知识库</h1>
          <p>预装模块 · HLP-MOD-KB-0001</p>
        </div>
        <div className="actions">
          <button
            className="soft"
            onClick={async () => {
              await api.importFolder();
              refresh();
            }}
          >
            导入文件夹
          </button>
          <button onClick={() => setNewTitle("")}>新建知识页</button>
        </div>
      </header>
      <div
        className={`knowledge-workspace complete ${inspectorOpen ? "" : "inspector-closed"}`}
      >
        <aside className="knowledge-browser">
          <div className="browser-title">
            <strong>知识目录</strong>
            <span>{data.documents.length} 篇</span>
          </div>
          <input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="搜索知识库"
          />
          {list.length === 0 ? (
            <p className="empty">暂无知识页</p>
          ) : (
            <div className="knowledge-tree">
              <KnowledgeTree
                node={tree}
                depth={0}
                expanded={expanded}
                active={selected}
                onToggle={(key) =>
                  setExpanded((current) => {
                    const next = new Set(current);
                    if (next.has(key)) next.delete(key);
                    else next.add(key);
                    return next;
                  })
                }
                onOpen={setSelected}
              />
            </div>
          )}
        </aside>
        <section className="document-workspace">
          {doc ? (
            <>
              <div className="editor-tools">
                <strong>{doc.title}</strong>
                <span />
                <button onClick={() => setInspectorOpen((value) => !value)}>
                  {inspectorOpen ? "收起大纲" : "展开大纲"}
                </button>
                <button onClick={() => api.exportDocument(doc.path)}>
                  下载
                </button>
                {editing ? (
                  <>
                    <button
                      onClick={() => {
                        setBody(doc.body);
                        setEditing(false);
                      }}
                    >
                      取消
                    </button>
                    <button onClick={save}>保存</button>
                  </>
                ) : (
                  <>
                    <button className="danger" onClick={del}>
                      删除
                    </button>
                    <button onClick={() => setEditing(true)}>编辑</button>
                  </>
                )}
              </div>
              {editing ? (
                <textarea
                  className="editor"
                  aria-label="Markdown 编辑器"
                  value={body}
                  onChange={(e) => setBody(e.target.value)}
                />
              ) : (
                <div className="document-scroll" onScroll={handleReaderScroll}>
                  <header className="reader-heading">
                    <h1>{doc.title}</h1>
                    <p>
                      {stats.chars.toLocaleString()} 字 · 约 {stats.minutes}{" "}
                      分钟读完
                    </p>
                    {parsed.tags.length > 0 && (
                      <div className="meta-tags">
                        {parsed.tags.map((tag) => (
                          <span key={tag} className={tagTint(tag)}>
                            {tag}
                          </span>
                        ))}
                      </div>
                    )}
                    <div className="meta-properties">
                      {Object.entries(parsed.metadata)
                        .filter(([key]) => key !== "tags")
                        .slice(0, 6)
                        .map(([key, value]) => (
                          <span key={key}>
                            <b>{key}</b>
                            {value}
                          </span>
                        ))}
                    </div>
                  </header>
                  <MarkdownDocument
                    body={doc.body}
                    knownTitles={data.documents.map((item) => item.title)}
                    onWiki={openWiki}
                  />
                </div>
              )}
            </>
          ) : (
            <div className="welcome">
              <Icon name="book" />
              <h2>从第一条知识开始</h2>
              <p>新建页面，或导入一个外部文件夹。原文件不会被改写。</p>
              <button onClick={() => setNewTitle("")}>新建知识页</button>
            </div>
          )}
        </section>
        {inspectorOpen && (
          <aside className="document-inspector">
            <header>
              <strong>页面大纲</strong>
              <span>来源与证据</span>
            </header>
            {doc ? (
              <>
                <nav className="outline-list">
                  {outline.length ? (
                    outline.map((item) => (
                      <button
                        className={activeHeadingId === item.id ? "active" : ""}
                        key={item.id}
                        style={{ paddingLeft: 12 + (item.level - 1) * 10 }}
                        onClick={() => {
                          jumpToHeading(item.id);
                          setActiveHeadingId(item.id);
                        }}
                      >
                        {item.title}
                      </button>
                    ))
                  ) : (
                    <p>当前页面没有标题层级。</p>
                  )}
                </nav>
                <dl className="document-evidence">
                  <div>
                    <dt>知识根</dt>
                    <dd>本机私人 Git</dd>
                  </div>
                  <div>
                    <dt>路径</dt>
                    <dd>{doc.path}</dd>
                  </div>
                  <div>
                    <dt>内容指纹</dt>
                    <dd>{doc.sha256.slice(0, 16)}</dd>
                  </div>
                  <div>
                    <dt>状态</dt>
                    <dd>可编辑 · 可回收</dd>
                  </div>
                </dl>
              </>
            ) : (
              <p className="empty">选择页面后显示大纲与证据。</p>
            )}
          </aside>
        )}
      </div>
      {newTitle !== null && (
        <div className="modal-backdrop" onMouseDown={() => setNewTitle(null)}>
          <form
            className="modal"
            onSubmit={(e) => {
              e.preventDefault();
              create();
            }}
            onMouseDown={(e) => e.stopPropagation()}
          >
            <h2>新建知识页</h2>
            <p>名称将成为本地 Markdown 文件名，并进入私人 Git。</p>
            <label>
              知识页名称
              <input
                value={newTitle}
                onChange={(e) => setNewTitle(e.target.value)}
                placeholder="例如：第一条知识"
                autoFocus
              />
            </label>
            <div>
              <button
                type="button"
                className="soft"
                onClick={() => setNewTitle(null)}
              >
                取消
              </button>
              <button type="submit" disabled={!newTitle.trim()}>
                创建
              </button>
            </div>
          </form>
        </div>
      )}
    </div>
  );
}

function Market() {
  return (
    <div className="page simple">
      <header>
        <div>
          <h1>模块商城</h1>
          <p>能力按使用主体分开登记</p>
        </div>
      </header>
      <section className="split-list">
        <div>
          <h2>人格体使用</h2>
          <p>动态脑、技能和执行零件将在签名审核后出现。</p>
          <span>当前没有已发布模块</span>
        </div>
        <div>
          <h2>人类使用</h2>
          <p>行业工作模块将在完成独立验收后出现。</p>
          <span>光湖知识库为预装模块</span>
        </div>
      </section>
    </div>
  );
}
function History({ data }: { data: SystemSnapshot }) {
  return (
    <div className="page simple">
      <header>
        <div>
          <h1>系统演化史</h1>
          <p>事实与计划分开记录</p>
        </div>
      </header>
      <section className="timeline-list">
        {data.timeline.map((e) => (
          <article key={e.eventId}>
            <time>{formatHoloLakeTime(e.occurredAt, true)}</time>
            <div>
              <strong>
                {labels[e.sourceKind]} · {e.title}
              </strong>
              <p>{e.content}</p>
            </div>
          </article>
        ))}
      </section>
    </div>
  );
}
function Portal() {
  return (
    <div className="page simple">
      <header>
        <div>
          <h1>企业门户</h1>
          <p>公共四域的官方入口位于企业服务器</p>
        </div>
      </header>
      <section className="portal">
        <div className="origin-light" />
        <h2>公共内容不嵌入私人频道</h2>
        <p>
          官方通知、四域规则和团队发布由企业门户承载。HoloLake
          只读取经过签名审核的公共发行事实。
        </p>
        <div className="portal-actions">
          <a href="https://guanghu.chat" target="_blank" rel="noreferrer">
            打开企业门户 ↗
          </a>
          <a
            href="https://guanghu.chat/code/user/login"
            target="_blank"
            rel="noreferrer"
          >
            团队代码仓库登录 ↗
          </a>
        </div>
      </section>
    </div>
  );
}
function Settings({
  data,
  runtime,
  refresh,
  busy,
  setBusy,
  setError,
}: {
  data: SystemSnapshot;
  runtime: RuntimeOverview | null;
  refresh: () => void;
  busy: boolean;
  setBusy: (v: boolean) => void;
  setError: (v: string) => void;
}) {
  const [bridgeName, setBridgeName] = useState(""),
    [personaName, setPersonaName] = useState(""),
    [runtimeMessage, setRuntimeMessage] = useState("");
  const update = async () => {
    setBusy(true);
    try {
      const u = await check();
      if (!u) throw new Error("当前已是最新版本，或公共发行尚未发布。");
      if (confirm(`发现 HoloLake ${u.version}，现在安装？`)) {
        await u.downloadAndInstall();
        await relaunch();
      }
    } catch (e) {
      setError(String(e));
    } finally {
      setBusy(false);
    }
  };
  return (
    <div className="page simple">
      <header>
        <div>
          <h1>设置</h1>
          <p>本机节点、公共更新与外部 AI</p>
        </div>
      </header>
      <section className="settings-list">
        <article>
          <div>
            <h2>私人 Git</h2>
            <p>{data.channel?.privateGitPath}</p>
          </div>
          <span className="status ok">已建立</span>
        </article>
        <article>
          <div>
            <h2>公共更新</h2>
            <p>{data.publicUpdateEndpoint}</p>
          </div>
          <button className="soft" onClick={update} disabled={busy}>
            {busy ? "正在检查…" : "检查更新"}
          </button>
        </article>
        <article className="bridge">
          <div>
            <h2>外部编程 AI</h2>
            <p>
              {runtime?.realtime.endpoint ?? "本机实时桥正在启动"} ·{" "}
              {runtime?.realtime.connectedClients ?? 0}{" "}
              个实时连接。连接不授予人格或执行权。
            </p>
          </div>
          <div>
            <input
              value={bridgeName}
              onChange={(e) => setBridgeName(e.target.value)}
              placeholder="给这个 AI 入口起名"
            />
            <button
              onClick={async () => {
                if (bridgeName.trim()) {
                  await api.registerBridge(bridgeName.trim());
                  setBridgeName("");
                  refresh();
                }
              }}
            >
              建立入口
            </button>
          </div>
        </article>
        <article className="bridge">
          <div>
            <h2>公众人格运行时</h2>
            <p>
              {runtime?.persona.personas.length
                ? `${runtime.persona.personas.length} 个本频道试用人格；既有人格验证数 ${runtime.persona.verifiedExistingPersonaCount}`
                : "建立一个本频道可逆试用人格，再由外部 AI 宿主实时承载。"}
            </p>
          </div>
          <div>
            <input
              value={personaName}
              onChange={(e) => setPersonaName(e.target.value)}
              placeholder="给试用人格起名"
            />
            <button
              onClick={async () => {
                if (personaName.trim()) {
                  const persona = await api.registerPersona(personaName.trim());
                  setPersonaName("");
                  setRuntimeMessage(
                    `${persona.displayName} 已建立；尚未验证为既有历史人格。`,
                  );
                  refresh();
                }
              }}
            >
              建立试用人格
            </button>
          </div>
        </article>
        <article>
          <div>
            <h2>实时接入命令</h2>
            <p>
              {runtimeMessage ||
                "先建立一个外部 AI 入口，然后复制本机 GLP 接入命令给外部编程 AI。"}
            </p>
          </div>
          <button
            className="soft"
            disabled={!data.externalAiBridges.length}
            onClick={async () => {
              const invitation = await api.realtimeInvitation();
              const bridge = data.externalAiBridges[0];
              const persona = runtime?.persona.personas[0];
              const command = `${invitation.connectorCommand} --bridge-id ${bridge.bridgeId}${persona ? ` --persona-id ${persona.personaId}` : ""}`;
              await navigator.clipboard.writeText(command);
              setRuntimeMessage(
                "实时接入命令已复制。令牌只在本机 descriptor 文件中。",
              );
            }}
          >
            复制接入命令
          </button>
        </article>
        {data.externalAiBridges.map((b) => (
          <article key={b.bridgeId}>
            <div>
              <h2>{b.displayName}</h2>
              <p>
                {b.bridgeId} ·{" "}
                {runtime?.realtime.protocol ?? "GLP_LOCAL_REALTIME/1"}
              </p>
            </div>
            <span className="status">{b.state}</span>
          </article>
        ))}
        {runtime?.persona.personas.map((persona) => (
          <article key={persona.personaId}>
            <div>
              <h2>{persona.displayName}</h2>
              <p>
                {persona.personaId} · {persona.state}
              </p>
            </div>
            <button
              className="soft danger"
              onClick={async () => {
                if (confirm(`删除试用人格 ${persona.personaId}？`)) {
                  await api.deleteTrialPersona(persona.personaId);
                  refresh();
                }
              }}
            >
              移除试用人格
            </button>
          </article>
        ))}
      </section>
    </div>
  );
}
