"use client";

import Link from "next/link";
import { ChevronDown, ChevronRight, ExternalLink, Hash, RotateCw, Search, UserSearch } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { BusinessDrawer, type BusinessDrawerState } from "@/components/BusinessDrawer";
import { EmptyState } from "@/components/StateBlocks";
import type { RawRecord } from "@/lib/api/types";
import type { BusinessTone } from "@/lib/flow-ledger/business";
import { asArray, asRecord, maybeText, text } from "@/lib/flow-ledger/format";

// ---------------------------------------------------------------------------
// tree model
// ---------------------------------------------------------------------------
export type RouteNode = { key: string; action: RawRecord; videos: VideoNode[] };
export type VideoNode = {
  key: string;
  id: string;
  searchQueryId: string;
  batchKind: string;
  pageNumber: number;
  rankInPage: number;
  video: RawRecord;
  title: string;
  author: string;
  decisionLabel: string;
  decisionAction: string;
  score: string;
  tags: string[];
  routes: RouteNode[];
};
export type Forest = { rootVideos: VideoNode[]; queryRoutes: RouteNode[] };

type BatchGroup = { kind: string; label: string; gate: string; videos: VideoNode[] };
type QueryGroup = { id: string; label: string; batches: BatchGroup[] };

// ---------------------------------------------------------------------------
// component
// ---------------------------------------------------------------------------
export function WalkTree({
  actions,
  runId,
  groupByQuery = false,
  queryLabels = {},
  emptyLabel = "本轮没有继续向外扩展"
}: {
  actions: RawRecord[];
  runId: string;
  groupByQuery?: boolean;
  queryLabels?: Record<string, string>;
  emptyLabel?: string;
}) {
  const [drawer, setDrawer] = useState<BusinessDrawerState>(null);
  const [expanded, setExpanded] = useState<Set<string>>(new Set());

  const safeActions = useMemo(() => actions.filter(isRecord), [actions]);
  const forest = useMemo(() => buildForest(safeActions), [safeActions]);
  const keyBuckets = useMemo(() => collectKeys(forest), [forest]);

  // default: show every video with its routes (the flow), brought-back videos collapsed.
  useEffect(() => {
    setExpanded(new Set(keyBuckets.videoKeys));
  }, [keyBuckets]);

  const toggle = useCallback((key: string) => {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key);
      else next.add(key);
      return next;
    });
  }, []);

  const setDepth = useCallback(
    (mode: "collapse" | "routes" | "all") => {
      if (mode === "collapse") setExpanded(new Set());
      else if (mode === "routes") setExpanded(new Set(keyBuckets.videoKeys));
      else setExpanded(new Set(keyBuckets.allKeys));
    },
    [keyBuckets]
  );

  const hasTree = forest.rootVideos.length > 0 || forest.queryRoutes.length > 0;
  const groups = useMemo(
    () => (groupByQuery ? groupRoots(forest.rootVideos, queryLabels) : null),
    [groupByQuery, forest.rootVideos, queryLabels]
  );

  if (!hasTree) return <EmptyState label={emptyLabel} />;

  return (
    <section className="wt-page">
      <div className="wt-controls">
        <span className="wt-controls-label">展开</span>
        <button type="button" onClick={() => setDepth("collapse")}>只看起点</button>
        <button type="button" onClick={() => setDepth("routes")}>看到路线</button>
        <button type="button" onClick={() => setDepth("all")}>全部展开</button>
      </div>

      <div className="wt-tree" role="tree">
        {groups
          ? groups.map((group) => (
              <div className="wt-group" key={group.id || "ungrouped"}>
                <div className="wt-overview-head">
                  <span className="wt-root-chip">搜索词</span>
                  <strong>{group.label}</strong>
                </div>
                {group.batches.map((batch) => (
                  <div className="wt-batch" key={batch.kind || "未分批"}>
                    <div className="wt-batch-head">
                      <span className="wt-batch-label">{batch.label}</span>
                      <span className="wt-batch-count">{batch.videos.length} 条</span>
                      {batch.gate ? <span className="wt-batch-gate">· {batch.gate}</span> : null}
                    </div>
                    <div className="wt-batch-videos">
                      {batch.videos.map((node) => (
                        <VideoRow
                          node={node}
                          depth={0}
                          hideRootIndex
                          runId={runId}
                          expanded={expanded}
                          onToggle={toggle}
                          onOpenDrawer={setDrawer}
                          key={node.key}
                        />
                      ))}
                    </div>
                  </div>
                ))}
              </div>
            ))
          : forest.rootVideos.map((node, index) => (
              <VideoRow
                node={node}
                depth={0}
                index={index}
                runId={runId}
                expanded={expanded}
                onToggle={toggle}
                onOpenDrawer={setDrawer}
                key={node.key}
              />
            ))}
        {forest.queryRoutes.map((node) => (
          <RouteRow
            node={node}
            depth={0}
            runId={runId}
            expanded={expanded}
            onToggle={toggle}
            onOpenDrawer={setDrawer}
            key={node.key}
          />
        ))}
      </div>
      <BusinessDrawer drawer={drawer} onClose={() => setDrawer(null)} />
    </section>
  );
}

// ---------------------------------------------------------------------------
// rows
// ---------------------------------------------------------------------------
function VideoRow({
  node,
  depth,
  index,
  hideRootIndex,
  runId,
  expanded,
  onToggle,
  onOpenDrawer
}: {
  node: VideoNode;
  depth: number;
  index?: number;
  hideRootIndex?: boolean;
  runId: string;
  expanded: Set<string>;
  onToggle: (key: string) => void;
  onOpenDrawer: (drawer: BusinessDrawerState) => void;
}) {
  const open = expanded.has(node.key);
  const hasRoutes = node.routes.length > 0;
  const walked = node.routes.filter((r) => isWalked(r.action)).length;
  const tone = decisionTone(node.decisionLabel);

  return (
    <div className="wt-branch" role="treeitem" aria-expanded={hasRoutes ? open : undefined}>
      <div className={`wt-row video ${tone}`}>
        <Toggle open={open} disabled={!hasRoutes} onClick={() => onToggle(node.key)} />
        <div className="wt-row-main">
          <button
            type="button"
            className="wt-summary-trigger"
            onClick={() => onOpenDrawer(videoDrawer(node))}
            aria-label={`查看摘要：${node.title}`}
            title="查看摘要"
          >
            <span className={`wt-dot ${tone}`} />
            {depth === 0 && hideRootIndex ? null : (
              <span className="wt-kicker">{depth === 0 ? `起点 ${(index ?? 0) + 1}` : "游走到的"}</span>
            )}
          </button>
          <VideoTitleLink runId={runId} video={node.video} title={node.title} />
          <span className="wt-author">{node.author}</span>
          {node.score ? <span className="wt-score">{node.score}</span> : null}
          <span className={`wt-status ${tone}`}>{node.decisionLabel}</span>
        </div>
        <span className="wt-row-right">
          {hasRoutes ? (
            <span className={`wt-badge ${walked ? "go" : "stop"}`}>
              {walked ? `${walked}/${node.routes.length} 路线已游走` : `${node.routes.length} 路线未游走`}
            </span>
          ) : (
            <span className="wt-badge muted">无路线</span>
          )}
          <RowLinks video={node.video} runId={runId} />
        </span>
      </div>
      {open && hasRoutes ? (
        <div className="wt-children">
          {node.routes.map((route) => (
            <RouteRow
              node={route}
              depth={depth + 1}
              runId={runId}
              expanded={expanded}
              onToggle={onToggle}
              onOpenDrawer={onOpenDrawer}
              key={route.key}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

function RouteRow({
  node,
  depth,
  runId,
  expanded,
  onToggle,
  onOpenDrawer
}: {
  node: RouteNode;
  depth: number;
  runId: string;
  expanded: Set<string>;
  onToggle: (key: string) => void;
  onOpenDrawer: (drawer: BusinessDrawerState) => void;
}) {
  const open = expanded.has(node.key);
  const walked = isWalked(node.action);
  const hasVideos = node.videos.length > 0;
  const edge = text(node.action.edge_id, "");
  const targetQuery = asRecord(node.action.target_query);

  return (
    <div className="wt-branch" role="treeitem" aria-expanded={hasVideos ? open : undefined}>
      <div className={`wt-row route ${edgeTone(edge)} ${walked ? "go" : "stop"}`}>
        <Toggle open={open} disabled={!hasVideos} onClick={() => onToggle(node.key)} />
        <button type="button" className="wt-row-main" onClick={() => onOpenDrawer(routeDrawer(node))}>
          <span className="wt-edge-icon">{edgeIcon(edge)}</span>
          <span className="wt-title route">{routeObject(node.action)}</span>
          {targetQuery.text && walked ? <span className="wt-newquery">→《{text(targetQuery.text)}》</span> : null}
          <span className={`wt-walk ${walked ? "go" : "stop"}`}>{walked ? "已游走" : "未游走"}</span>
          {!walked ? <span className="wt-reason">{shortReason(node.action)}</span> : null}
        </button>
        <span className="wt-row-right">
          {hasVideos ? <span className="wt-badge go">游走到 {node.videos.length} 条视频</span> : null}
          {targetQuery.id ? (
            <Link
              className="wt-link"
              href={`/runs/${encodeURIComponent(runId)}/queries/${encodeURIComponent(text(targetQuery.id))}/videos`}
              onClick={(e) => e.stopPropagation()}
            >
              这些视频
            </Link>
          ) : null}
        </span>
      </div>
      {open && hasVideos ? (
        <div className="wt-children">
          {node.videos.map((video) => (
            <VideoRow
              node={video}
              depth={depth + 1}
              runId={runId}
              expanded={expanded}
              onToggle={onToggle}
              onOpenDrawer={onOpenDrawer}
              key={video.key}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

function Toggle({ open, disabled, onClick }: { open: boolean; disabled: boolean; onClick: () => void }) {
  if (disabled) return <span className="wt-toggle empty" />;
  return (
    <button type="button" className="wt-toggle" aria-label={open ? "收起" : "展开"} onClick={onClick}>
      {open ? <ChevronDown size={15} /> : <ChevronRight size={15} />}
    </button>
  );
}

function RowLinks({ video, runId }: { video: RawRecord; runId: string }) {
  const detailHref = videoDetailHref(runId, video);
  if (!detailHref) return null;
  const platformUrl = text(video.platform_url, "");
  const platformLabel = text(video.platform_label, "原视频");
  return (
    <span className="wt-links">
      <Link className="wt-link" href={detailHref} onClick={(e) => e.stopPropagation()}>
        详情
      </Link>
      {platformUrl ? (
        <a className="wt-link" href={platformUrl} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()}>
          <ExternalLink size={11} />
          {platformLabel}
        </a>
      ) : null}
    </span>
  );
}

function VideoTitleLink({ runId, video, title }: { runId: string; video: RawRecord; title: string }) {
  const detailHref = videoDetailHref(runId, video);
  if (!detailHref) return <span className="wt-title">{title}</span>;
  return (
    <Link className="wt-title wt-title-link" href={detailHref}>
      {title}
    </Link>
  );
}

function videoDetailHref(runId: string, video: RawRecord): string {
  // platform content ids (视频号 finder ids) can contain / + = which break path routing;
  // prefer the clean content_discovery_id for the detail route.
  const detailId = maybeText(video.content_discovery_id) || text(video.id, "");
  return detailId
    ? `/runs/${encodeURIComponent(runId)}/videos/${encodeURIComponent(detailId)}`
    : "";
}

// ---------------------------------------------------------------------------
// build
// ---------------------------------------------------------------------------
export function buildForest(actions: RawRecord[]): Forest {
  const routable = actions.filter((a) => !isTerminalEdge(text(a.edge_id, "")));
  const routesBySource = new Map<string, RawRecord[]>();
  const sourceIds = new Set<string>();
  const targetIds = new Set<string>();
  const queryRouteActions: RawRecord[] = [];

  routable.forEach((action) => {
    const sourceVideo = asRecord(action.source_video);
    const sid = text(sourceVideo.id, "");
    if (sid) {
      sourceIds.add(sid);
      const bucket = routesBySource.get(sid) || [];
      bucket.push(action);
      routesBySource.set(sid, bucket);
    } else {
      queryRouteActions.push(action);
    }
    asArray(action.target_videos)
      .filter(isRecord)
      .forEach((v) => {
        const vid = text(v.id, "");
        if (vid) targetIds.add(vid);
      });
  });

  const sourceVideoById = new Map<string, RawRecord>();
  routable.forEach((action) => {
    const sv = asRecord(action.source_video);
    const sid = text(sv.id, "");
    if (sid && !sourceVideoById.has(sid)) sourceVideoById.set(sid, sv);
  });

  const buildRoute = (action: RawRecord, parentKey: string, seen: Set<string>): RouteNode => {
    const key = `${parentKey}>r:${actionKey(action)}`;
    const videos = asArray(action.target_videos)
      .filter(isRecord)
      .map((v) => buildVideo(v, key, routesBySource, seen))
      .filter((v): v is VideoNode => v !== null);
    return { key, action, videos };
  };

  function buildVideo(video: RawRecord, parentKey: string, routes: Map<string, RawRecord[]>, seen: Set<string>): VideoNode | null {
    const id = text(video.id, "");
    const key = `${parentKey}>v:${id || Math.random().toString(36).slice(2)}`;
    const decision = asRecord(video.decision);
    const childRoutes = id && !seen.has(id) ? routes.get(id) || [] : [];
    const nextSeen = new Set(seen);
    if (id) nextSeen.add(id);
    return {
      key,
      id,
      searchQueryId: text(video.search_query_id, ""),
      batchKind: text(video.progressive_batch_kind, ""),
      pageNumber: Number(video.progressive_page_number) || 0,
      rankInPage: Number(video.progressive_item_rank_in_page) || 0,
      video,
      title: cleanTitle(text(video.title, "无标题视频")),
      author: text(video.author, "未知作者"),
      decisionLabel: text(decision.label, "未判断"),
      decisionAction: text(decision.action, ""),
      score: scoreText(decision),
      tags: asArray(video.tags).map((t) => text(t, "")).filter(Boolean),
      routes: childRoutes.map((a) => buildRoute(a, key, nextSeen))
    };
  }

  const rootVideos: VideoNode[] = [...sourceIds]
    .filter((id) => !targetIds.has(id))
    .map((id) => buildVideo(sourceVideoById.get(id) || { id }, "root", routesBySource, new Set<string>()))
    .filter((v): v is VideoNode => v !== null);

  const queryRoutes = queryRouteActions.map((a) => buildRoute(a, "root", new Set<string>()));

  return { rootVideos, queryRoutes };
}

function groupRoots(rootVideos: VideoNode[], queryLabels: Record<string, string>): QueryGroup[] {
  const map = new Map<string, VideoNode[]>();
  rootVideos.forEach((node) => {
    const qid = node.searchQueryId || "";
    if (!map.has(qid)) map.set(qid, []);
    map.get(qid)!.push(node);
  });
  return [...map.entries()].map(([id, videos]) => ({
    id,
    label: queryLabels[id] || (id ? id : "其它来源"),
    batches: groupBatches(videos)
  }));
}

function groupBatches(videos: VideoNode[]): BatchGroup[] {
  const map = new Map<string, VideoNode[]>();
  videos.forEach((node) => {
    const kind = node.batchKind || "";
    if (!map.has(kind)) map.set(kind, []);
    map.get(kind)!.push(node);
  });
  return [...map.entries()]
    .map(([kind, items]) => ({
      kind,
      label: batchLabel(kind),
      gate: batchGate(kind),
      videos: items.slice().sort((a, b) => a.rankInPage - b.rankInPage),
      sortKey: batchSortKey(kind)
    }))
    .sort((a, b) => a.sortKey - b.sortKey)
    .map(({ sortKey: _sortKey, ...rest }) => rest);
}

function batchSortKey(kind: string): number {
  if (kind === "initial_top3") return 10;
  if (kind === "page_remainder") return 11;
  const page = /^page_(\d+)$/.exec(kind);
  if (page) return Number(page[1]) * 10;
  if (kind === "author_works") return 9000;
  return 9999;
}

function batchLabel(kind: string): string {
  if (kind === "initial_top3") return "前 3 条";
  if (kind === "page_remainder") return "同页剩余";
  const page = /^page_(\d+)$/.exec(kind);
  if (page) return `第 ${Number(page[1])} 页`;
  if (kind === "author_works") return "作者作品";
  return "未分批";
}

function batchGate(kind: string): string {
  if (kind === "initial_top3") return "总是看";
  if (kind === "page_remainder") return "命中才看";
  if (/^page_\d+$/.test(kind)) return "命中才翻";
  return "";
}

function collectKeys(forest: Forest): { videoKeys: Set<string>; routeKeys: Set<string>; allKeys: Set<string> } {
  const videoKeys = new Set<string>();
  const routeKeys = new Set<string>();
  const walkVideo = (node: VideoNode) => {
    if (node.routes.length) videoKeys.add(node.key);
    node.routes.forEach(walkRoute);
  };
  const walkRoute = (node: RouteNode) => {
    if (node.videos.length) routeKeys.add(node.key);
    node.videos.forEach(walkVideo);
  };
  forest.rootVideos.forEach(walkVideo);
  forest.queryRoutes.forEach(walkRoute);
  return { videoKeys, routeKeys, allKeys: new Set([...videoKeys, ...routeKeys]) };
}

// ---------------------------------------------------------------------------
// drawers
// ---------------------------------------------------------------------------
function videoDrawer(node: VideoNode): BusinessDrawerState {
  const decision = asRecord(node.video.decision);
  const scoreItems = asArray(decision.score_items).filter(isRecord);
  const reason = text(decision.reason_label, "");
  return {
    title: node.title,
    sections: [
      {
        label: "这条视频的判断",
        value: `${node.decisionLabel}${reason ? `：${reason}` : ""}`,
        tone: drawerTone(node.decisionLabel)
      },
      scoreItems.length
        ? {
            label: "评分明细",
            items: scoreItems.map((item) => `${text(item.label, "评分")}：${text(item.score_label, "未记录")}`),
            tone: "info"
          }
        : { label: "评分明细", value: "本条没有记录评分。", tone: "info" },
      node.tags.length ? { label: "视频标签", value: node.tags.join("  "), tone: "neutral" } : { label: "作者", value: node.author, tone: "neutral" }
    ]
  };
}

function routeDrawer(node: RouteNode): BusinessDrawerState {
  const action = node.action;
  const walked = isWalked(action);
  const detailLines = asArray(action.detail_lines).map((l) => text(l, "")).filter(Boolean);
  return {
    title: `${routeKindLabel(text(action.edge_id, ""))}`,
    sections: [
      {
        label: walked ? "这一步为什么继续" : "这一步为什么停下",
        value: walked
          ? `${routeObject(action)} 通过筛选，继续带回视频。`
          : `${routeObject(action)} 没有继续。原因：${shortReason(action)}。`,
        tone: walked ? "good" : "warn"
      },
      detailLines.length ? { label: "执行明细", items: detailLines, tone: "info" } : { label: "执行明细", value: text(action.summary, "没有更多明细。"), tone: "info" },
      { label: "对流程的影响", value: text(action.impact, walked ? "带回的视频会继续送去判断。" : "这条路线停止，不再带回新视频。"), tone: "neutral" }
    ]
  };
}

// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
function isRecord(value: unknown): value is RawRecord {
  return typeof value === "object" && value !== null;
}

function actionKey(action: RawRecord): string {
  return text(action.id || action.walk_action_id || `${text(action.edge_id, "walk")}-${text(action.trigger_label, "")}`);
}

function isWalked(action: RawRecord): boolean {
  return text(action.status, "") === "success";
}

function isTerminalEdge(edgeId: string): boolean {
  return edgeId === "decision_to_asset" || edgeId === "budget_downgrade" || edgeId === "path_stop";
}

function cleanTitle(value: string): string {
  return value.replace(/#[^\s#]+/g, " ").replace(/\s+/g, " ").trim() || value;
}

function scoreText(decision: RawRecord): string {
  const label = text(decision.score_label, "");
  if (label && label !== "未记录") return label;
  const score = Number(decision.score);
  return Number.isFinite(score) && score > 0 ? `${Math.round(score)} 分` : "";
}

function decisionTone(label: string): string {
  if (label === "入池") return "good";
  if (label === "待复看") return "warn";
  if (label === "淘汰") return "bad";
  if (label.includes("审核拦截")) return "bad";
  if (label.includes("技术问题") || label.includes("重试") || label.includes("补跑")) return "tech";
  return "plain";
}

function drawerTone(label: string): BusinessTone {
  if (label === "入池") return "good";
  if (label === "淘汰") return "bad";
  return "warn";
}

function edgeTone(edgeId: string): string {
  if (edgeId === "hashtag_to_query" || edgeId === "tag_query") return "tag";
  if (edgeId === "author_to_works" || edgeId === "author_works" || edgeId === "video_to_author") return "author";
  if (edgeId === "query_next_page") return "page";
  return "plain";
}

function edgeIcon(edgeId: string) {
  if (edgeId === "hashtag_to_query" || edgeId === "tag_query") return <Hash size={14} />;
  if (edgeId === "author_to_works" || edgeId === "author_works" || edgeId === "video_to_author") return <UserSearch size={14} />;
  if (edgeId === "query_next_page") return <RotateCw size={14} />;
  return <Search size={14} />;
}

function routeKindLabel(edgeId: string): string {
  if (edgeId === "hashtag_to_query" || edgeId === "tag_query") return "从视频标签继续游走";
  if (edgeId === "author_to_works" || edgeId === "author_works" || edgeId === "video_to_author") return "从作者继续游走";
  if (edgeId === "query_next_page") return "翻页继续找";
  return "继续游走";
}

function routeObject(action: RawRecord): string {
  const edge = text(action.edge_id, "");
  const raw = cleanBusinessText(text(action.trigger_label, ""));
  if (edge === "query_next_page") return "翻下一页";
  if (edge === "hashtag_to_query" || edge === "tag_query") {
    const target = asRecord(action.target_query);
    if (target.text && isWalked(action)) return `标签`;
    const candidates = raw.replace(/^候选标签：/, "").split("/")[0]?.trim();
    return candidates ? `标签 ${candidates}` : "标签";
  }
  if (edge === "author_to_works" || edge === "author_works" || edge === "video_to_author") {
    return `作者 ${raw.replace(/^作者：/, "") || "待确认"}`;
  }
  return raw || "继续游走";
}

function shortReason(action: RawRecord): string {
  const reason = cleanBusinessText(text(action.reason_label, ""));
  // technical retry: the source video failed a technical step (Gemini/play_url/OSS),
  // surface the concrete cause instead of the generic sentence.
  if (reason.includes("重试")) {
    const td = asRecord(asRecord(action.source_video).technical_retry_detail);
    const brief = maybeText(td.brief_reason);
    const retry = Number(td.retry_count);
    const suffix = Number.isFinite(retry) && retry > 0 ? `，已重试 ${retry} 次` : "";
    return brief ? `判定失败待重试：${brief}${suffix}` : "判定失败，需重试后再判断";
  }
  if (!reason) return "未达到继续扩展要求";
  if (reason.includes("游走次数上限") || reason.includes("次数上限")) return "已达游走次数上限";
  if (reason.includes("复看")) return "分数在待复看区间，未达入池线（先不向外扩展）";
  return reason.replace(/^不适合继续向外扩展：?/, "");
}

function cleanBusinessText(value: string): string {
  if (!value || value === "系统原因待补充" || value === "这一步没有记录额外原因") return "";
  return value.replaceAll("“", "《").replaceAll("”", "》");
}
