/**
 * @file LaneConsolePane.test.tsx
 * @description Test suite for the LaneConsolePane component
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { LaneConsolePane } from "../LaneConsolePane";
import { api } from "../../../lib/api";
import type { Lane } from "../../../lib/types";

vi.mock("../TerminalView", () => ({
  TerminalView: ({ runId }: { runId: string }) => (
    <div data-testid="terminal-view" data-run-id={runId} />
  ),
}));

vi.mock("../../../lib/api", () => ({
  api: {
    lanes: {
      ensure: vi.fn(),
      action: vi.fn(),
      list: vi.fn(),
    },
    run: {
      list: vi.fn().mockResolvedValue({ items: [] }),
      history: vi.fn().mockResolvedValue({ items: [] }),
      get: vi.fn(),
      start: vi.fn(),
    },
  },
  RUN_MODEL_CHOICES: [],
  RUN_EFFORT_CHOICES: [],
}));

const LANE: Lane = {
  id: 1,
  title: "demo",
  cwd: "/workspace/a",
  branch: null,
  kind: "adopted",
  source_repo: null,
  pipeline: "default",
  session_id: null,
  run_id: null,
  stage: "idle",
  stage_since: null,
  status: "idle",
  gate_decision: null,
  ci_status: null,
  needs_action: null,
  links: {},
  stages: {},
  notes: null,
  pipeline_name: "Default",
  pipeline_nodes: [],
  progress: 0,
  stage_seconds: null,
  last_event_seconds: null,
  liveness: "idle" as Lane["liveness"],
  detected_stage: null,
  detected_signal: null,
  slot: null,
  ports: {},
  active_feature_id: null,
};

function baseProps() {
  return {
    lanes: [LANE],
    laneId: 1,
    showLaneSelector: false,
    onLaneIdChange: vi.fn(),
    onLaneCreated: vi.fn(),
    binaryStatus: { found: true, path: "/usr/local/bin/claude" },
    cwdSuggestions: [],
    activeRuns: { items: [] },
    wsConnected: true,
  };
}

describe("LaneConsolePane", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("starts a run through /api/lanes/<id>/start, not /api/run/start", async () => {
    (api.lanes.action as ReturnType<typeof vi.fn>).mockResolvedValue({
      lane: { ...LANE, run_id: "run-1" },
    });
    (api.run.get as ReturnType<typeof vi.fn>).mockResolvedValue({
      id: "run-1",
      laneId: 1,
      status: "running",
      cwd: "/workspace/a",
      model: null,
      permissionMode: null,
      effort: null,
      resumeSessionId: null,
      sessionId: null,
      startedAt: null,
      promptPreview: null,
    });

    render(<LaneConsolePane {...baseProps()} />);

    // Set cwd and prompt
    const cwdInput = screen.getByPlaceholderText(/type to search/i);
    fireEvent.change(cwdInput, { target: { value: "/workspace/a" } });

    const promptTextarea = screen.getByPlaceholderText(/ask claude/i);
    fireEvent.change(promptTextarea, { target: { value: "test prompt" } });

    // Find and click the Run button (the main start button in RunSetup)
    fireEvent.click(screen.getByRole("button", { name: /^run$/i }));

    await waitFor(() =>
      expect(api.lanes.action).toHaveBeenCalledWith(1, "start", expect.any(Object))
    );
    expect(api.run.start).not.toHaveBeenCalled();
    await waitFor(() =>
      expect(screen.getByTestId("terminal-view")).toHaveAttribute("data-run-id", "run-1")
    );
  });

  it("swaps to the newly selected lane's own terminal instead of keeping the old one", async () => {
    const LANE2: Lane = { ...LANE, id: 2, title: "other", cwd: "/workspace/b" };
    const run = (id: string, laneId: number) => ({
      id,
      laneId,
      status: "running" as const,
      cwd: null,
      model: null,
      permissionMode: null,
      effort: null,
      resumeSessionId: null,
      sessionId: null,
      startedAt: null,
      promptPreview: null,
    });
    const props = {
      ...baseProps(),
      lanes: [LANE, LANE2],
      activeRuns: { items: [run("ccam-lane-1", 1), run("ccam-lane-2", 2)] },
    };

    const { rerender } = render(<LaneConsolePane {...props} />);
    await waitFor(() =>
      expect(screen.getByTestId("terminal-view")).toHaveAttribute("data-run-id", "ccam-lane-1")
    );

    rerender(<LaneConsolePane {...props} laneId={2} />);
    await waitFor(() =>
      expect(screen.getByTestId("terminal-view")).toHaveAttribute("data-run-id", "ccam-lane-2")
    );

    // A lane with no live run falls back to its setup form, not the previous
    // lane's terminal.
    rerender(<LaneConsolePane {...props} lanes={[LANE, LANE2, { ...LANE, id: 3 }]} laneId={3} />);
    await waitFor(() => expect(screen.queryByTestId("terminal-view")).not.toBeInTheDocument());
  });

  it("adopts the lane's cwd when the lane list arrives after the pane mounted", async () => {
    // Split view restores its pane lanes from localStorage, so a pane can
    // render with a laneId before GET /api/lanes has answered. laneId never
    // changes afterwards — only the resolved lane does.
    const props = { ...baseProps(), lanes: [], defaultCwd: "/home/tester" };
    const { rerender } = render(<LaneConsolePane {...props} />);
    const cwdInput = screen.getByPlaceholderText(/type to search/i) as HTMLInputElement;
    expect(cwdInput.value).toBe("/home/tester");

    rerender(<LaneConsolePane {...props} lanes={[LANE]} />);
    await waitFor(() => expect(cwdInput.value).toBe(LANE.cwd));
  });

  it("shows a lane dropdown only when showLaneSelector is true", () => {
    const { rerender } = render(<LaneConsolePane {...baseProps()} showLaneSelector />);
    expect(screen.getByTestId("pane-lane-select")).toBeInTheDocument();

    rerender(<LaneConsolePane {...baseProps()} showLaneSelector={false} />);
    expect(screen.queryByTestId("pane-lane-select")).not.toBeInTheDocument();
  });

  it("renders RunSetup when laneId is null and showLaneSelector is false (layout-1, fresh install)", () => {
    render(<LaneConsolePane {...baseProps()} laneId={null} showLaneSelector={false} />);
    expect(screen.getByTestId("console-body")).toBeInTheDocument();
    expect(screen.queryByTestId("pane-empty")).not.toBeInTheDocument();
  });

  it("renders an empty placeholder with selector when laneId is null but showLaneSelector is true (split-view)", () => {
    render(<LaneConsolePane {...baseProps()} laneId={null} showLaneSelector={true} />);
    expect(screen.getByTestId("pane-empty")).toBeInTheDocument();
    expect(screen.getByTestId("pane-lane-select")).toBeInTheDocument();
    expect(screen.queryByTestId("console-body")).not.toBeInTheDocument();
  });
});
