import { fireEvent, render, screen, within } from "@testing-library/react";
import { ReactFlowProvider } from "@xyflow/react";
import { describe, expect, it, vi } from "vitest";
import { DirectToolGroupCard } from "@/components/flow/DirectToolGroupCard";
import { RetrievalAgentCard } from "@/components/flow/RetrievalAgentCard";
import { buildHorizontalLayout, retrievalAgentItem, retrievalDirectItem } from "@/lib/layout";
import { makeBranch, makeRetrievalStage, makeView } from "@/tests/fixtures";

const base = { onOpen: vi.fn(), onToggleRound: vi.fn(), onToggleBranch: vi.fn(), onToggleRetrievalAgent: vi.fn() };
const nodeProps = { selected: false, zIndex: 1, isConnectable: false, positionAbsoluteX: 0, positionAbsoluteY: 0, dragging: false, draggable: true, selectable: true, deletable: false };
const wrap = (node: React.ReactNode) => render(<ReactFlowProvider>{node}</ReactFlowProvider>);

describe("V8 retrieval stage", () => {
  it("shows an immediate Agent retrieval preview on keyboard focus", () => {
    const branch = makeBranch(1, 1);
    const item = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
    const { container } = wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, buildId: "443", item, expanded: false }} />);
    fireEvent.focus(container.querySelector(".retrievalAgentCard")!);
    const preview = screen.getByRole("dialog", { name: /取数预览/ });
    expect(within(preview).getByText("命中了什么")).toBeInTheDocument();
    expect(within(preview).getByText("初筛得到")).toBeInTheDocument();
  });

  it("loads and presents real tool results only after the preview opens", async () => {
    const branch = makeBranch(1, 1);
    const item = retrievalDirectItem(branch.retrievalStage.directToolGroups[0], 1, 1);
    if (item.itemType !== "retrieval-direct") throw new Error("expected retrieval-direct item");
    item.group.calls = [{ id: "event:4047", eventId: 4047, businessLabel: "领域信息", status: "success", resultSummary: "已读取", detailRef: "event:4047" }];
    item.group.callCount = 1;
    item.group.successCount = 1;
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({ detailKind: "retrieval-event", outcome: { state: "hit", count: 1, message: "读取成功，共返回 1 条记录。" }, items: [{ id: "fact-1", title: "领域事实 1", subtitle: "核实了布里丹寓言的史实背景。", meta: [], sections: [] }], technical: {} }),
    });
    vi.stubGlobal("fetch", fetchMock);
    const { container } = wrap(<DirectToolGroupCard id={item.id} type="directToolGroup" {...nodeProps} data={{ ...base, buildId: "443", item }} />);
    expect(fetchMock).not.toHaveBeenCalled();
    fireEvent.focus(container.querySelector(".directToolCard")!);
    const preview = screen.getByRole("dialog", { name: /取数预览/ });
    expect(await within(preview).findByText("领域事实 1")).toBeInTheDocument();
    expect(fetchMock).toHaveBeenCalledTimes(1);
    vi.unstubAllGlobals();
  });

  it("presents direct tools and delegated agents as peer retrieval operations", () => {
    const branch = makeBranch(1, 1);
    const direct = retrievalDirectItem(branch.retrievalStage.directToolGroups[0], 1, 1);
    const agent = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
    wrap(<><DirectToolGroupCard id={direct.id} type="directToolGroup" {...nodeProps} data={{ ...base, item: direct }} /><RetrievalAgentCard id={agent.id} type="retrievalAgent" {...nodeProps} data={{ ...base, item: agent, expanded: false }} /></>);
    expect(screen.getByText("工具取数")).toBeInTheDocument();
    expect(screen.getByText("Agent 取数")).toBeInTheDocument();
    expect(screen.getByText("初筛整理")).toBeInTheDocument();
    expect(screen.queryByText("数据取舍")).not.toBeInTheDocument();
  });

  it("keeps repeated attempts inside one agent and opens an individual query", () => {
    const branch = makeBranch(1, 1);
    const run = { ...branch.retrievalStage.agentRuns[0], attempts: branch.retrievalStage.agentRuns[0].attempts.concat(Array.from({ length: 10 }, (_, index) => ({ ...branch.retrievalStage.agentRuns[0].attempts[0], id: `event:extra-${index}`, eventId: 900 + index, queryLabel: `额外查询 ${index + 1}`, detailRef: `event:${900 + index}` }))) };
    run.querySummary = { ...run.querySummary, total: 13 };
    const item = retrievalAgentItem(run, 1, 1);
    const onOpen = vi.fn();
    wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, onOpen, item, expanded: true }} />);
    expect(screen.getByText((_, element) => element?.tagName === "P" && element.textContent?.startsWith("13 次") === true)).toBeInTheDocument();
    expect(screen.getAllByRole("listitem")).toHaveLength(13);
    fireEvent.click(screen.getByText("额外查询 1"));
    expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ itemType: "query-attempt", detailRef: "event:900" }), "business");
  });

  it("stacks both parallel and sequential retrieval operations vertically", () => {
    const parallelView = makeView(1, 1, 1);
    parallelView.rounds[0].branches[0].retrievalStage = makeRetrievalStage(1, 1, 2, 1, "parallel");
    const options = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), expandedRetrievalAgents: new Set<string>(), ...base };
    const parallel = buildHorizontalLayout(parallelView, options).nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
    expect(new Set(parallel.map((node) => node.position.x)).size).toBe(1);
    expect(new Set(parallel.map((node) => node.position.y)).size).toBe(parallel.length);

    const sequentialView = makeView(1, 1, 1);
    const sequential = buildHorizontalLayout(sequentialView, options).nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
    expect(new Set(sequential.map((node) => node.position.x)).size).toBe(1);
    expect(new Set(sequential.map((node) => node.position.y)).size).toBe(sequential.length);
  });

  it("keeps unknown-time operations in one neutral column inside the retrieval frame", () => {
    const view = makeView(1, 1, 1);
    const stage = view.rounds[0].branches[0].retrievalStage;
    stage.observedMode = "unknown";
    stage.waves = [];
    stage.directToolGroups.forEach((item) => { item.waveIndex = undefined; item.startedAt = undefined; item.endedAt = undefined; });
    stage.agentRuns.forEach((item) => { item.waveIndex = undefined; item.startedAt = undefined; item.endedAt = undefined; });
    const options = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), expandedRetrievalAgents: new Set<string>(), ...base };
    const layout = buildHorizontalLayout(view, options);
    const operations = layout.nodes.filter((node) => node.type === "directToolGroup" || node.type === "retrievalAgent");
    const frame = layout.nodes.find((node) => node.type === "retrievalStageFrame");
    expect(new Set(operations.map((node) => node.position.x)).size).toBe(1);
    expect(Math.max(...operations.map((node) => node.position.x + Number(node.style?.width || 340)))).toBeLessThanOrEqual(Number(frame?.style?.width || 0));
  });

  it("marks expanded query lists so semantic zoom cannot hide a successful expansion", () => {
    const branch = makeBranch(1, 1);
    const item = retrievalAgentItem(branch.retrievalStage.agentRuns[0], 1, 1);
    const { container } = wrap(<RetrievalAgentCard id={item.id} type="retrievalAgent" {...nodeProps} data={{ ...base, item, expanded: true }} />);
    expect(container.querySelector(".retrievalAgentCard")).toHaveClass("isQueryExpanded");
    expect(container.querySelector(".queryAttemptList")).toBeInTheDocument();
  });

  it("grows the branch frame when query rows are expanded", () => {
    const view = makeView(1, 1, 1);
    const agentId = view.rounds[0].branches[0].retrievalStage.agentRuns[0].id;
    const baseOptions = { expandedRounds: new Set([1]), expandedBranches: new Set(["1:1"]), ...base };
    const collapsed = buildHorizontalLayout(view, { ...baseOptions, expandedRetrievalAgents: new Set<string>() });
    const expanded = buildHorizontalLayout(view, { ...baseOptions, expandedRetrievalAgents: new Set([agentId]) });
    const height = (nodes: typeof collapsed.nodes) => Number(nodes.find((node) => node.type === "branchFrame")?.style?.height || 0);
    expect(height(expanded.nodes)).toBeGreaterThan(height(collapsed.nodes));
  });
});
