/**
 * input-history.ts unit tests: which stream items become recallable entries, and the
 * ↑/↓ navigation state machine the composer drives (start-from-empty, edit-ends-navigation,
 * pin-at-oldest, stash restore past the newest).
 */
import { describe, expect, it } from "vitest";
import type { ChatItem } from "../src/lib/omni/stream-model";
import { buildSkillsMessage } from "../src/features/chat/skill-use";
import { handoffMessage } from "../src/features/chat/agent-handoff";
import { buildScheduledMessage } from "@prismshadow/penguin-core/markers";
import {
  buildInputHistory,
  caretOnFirstLine,
  caretOnLastLine,
  historyStepBack,
  historyStepForward,
} from "../src/features/chat/input-history";
import type { HistoryNav } from "../src/features/chat/input-history";

let nextId = 0;
const user = (text: string): ChatItem => ({ kind: "user_text", id: nextId++, text });
const steering = (text: string): ChatItem => ({ kind: "user_steering", id: nextId++, text });
const assistant = (text: string): ChatItem => ({
  kind: "assistant_text",
  id: nextId++,
  text,
  streaming: false,
});

describe("buildInputHistory", () => {
  it("collects typed prompts and steering in order, skipping machine-injected texts", () => {
    const goalRound1 = `[goal]\nround: 1\nobjective: fix it\n[/goal]\nfix the bug`;
    const goalRound2 = `[goal]\nround: 2\nobjective: fix it\n[/goal]\nfix the bug`;
    const items: ChatItem[] = [
      user("first question"),
      assistant("answer"),
      user(handoffMessage({ agentId: "agent-a", sessionId: "session-1", workspace: "/tmp/w" })),
      user(buildScheduledMessage("nightly", "2026-08-01T00:00:00Z", "scheduled prompt")),
      user(goalRound1),
      user(goalRound2),
      steering("please also check the tests"),
      user(buildSkillsMessage(["my-skill"], "with a skill")),
      user("first question"),
    ];
    expect(buildInputHistory(items)).toEqual([
      "first question",
      "fix the bug",
      "please also check the tests",
      "with a skill",
      "first question",
    ]);
  });

  it("collapses consecutive duplicates and drops empty bodies (image-only prompts)", () => {
    const items: ChatItem[] = [user("same"), user("same"), user("  "), user("next")];
    expect(buildInputHistory(items)).toEqual(["same", "next"]);
  });
});

describe("history navigation", () => {
  const history = ["one", "two", "three"];

  it("starts from an empty draft at the newest entry and walks back, pinning at the oldest", () => {
    const s1 = historyStepBack(history, null, "");
    expect(s1?.text).toBe("three");
    const s2 = historyStepBack(history, s1!.nav, "three");
    expect(s2?.text).toBe("two");
    const s3 = historyStepBack(history, s2!.nav, "two");
    expect(s3?.text).toBe("one");
    const s4 = historyStepBack(history, s3!.nav, "one");
    expect(s4?.text).toBe("one"); // pinned: repeated ↑ at the oldest stays put
    expect(s4?.nav?.index).toBe(0);
  });

  it("does not start from a non-empty draft, and stashes/restores the draft across a round trip", () => {
    expect(historyStepBack(history, null, "half-written")).toBeNull();
    const back = historyStepBack(history, null, "  ");
    expect(back?.nav?.stash).toBe("  ");
    const fwd = historyStepForward(history, back!.nav, "three");
    expect(fwd).toEqual({ nav: null, text: "  " }); // past the newest: draft restored, navigation over
  });

  it("hands the keys back once the recalled text was edited", () => {
    const nav: HistoryNav = { index: 2, stash: "", recalled: "three" };
    expect(historyStepBack(history, nav, "three edited")).toBeNull();
    expect(historyStepForward(history, nav, "three edited")).toBeNull();
  });

  it("steps forward through newer entries before restoring the stash", () => {
    const nav: HistoryNav = { index: 0, stash: "draft", recalled: "one" };
    const s1 = historyStepForward(history, nav, "one");
    expect(s1?.text).toBe("two");
    const s2 = historyStepForward(history, s1!.nav, "two");
    expect(s2?.text).toBe("three");
    const s3 = historyStepForward(history, s2!.nav, "three");
    expect(s3).toEqual({ nav: null, text: "draft" });
  });

  it("never steps with an empty history", () => {
    expect(historyStepBack([], null, "")).toBeNull();
  });
});

describe("caret line guards", () => {
  it("first/last line checks follow the newlines around the caret", () => {
    const text = "line1\nline2";
    expect(caretOnFirstLine(text, 3)).toBe(true);
    expect(caretOnFirstLine(text, 8)).toBe(false);
    expect(caretOnLastLine(text, 8)).toBe(true);
    expect(caretOnLastLine(text, 3)).toBe(false);
    expect(caretOnFirstLine("", 0)).toBe(true);
    expect(caretOnLastLine("", 0)).toBe(true);
  });
});
