// Markdown table bounds and transcript copy selection live in the terminal
// rendering path: the daemon e2e sees only committed text, never rendered
// rows, and no harness can drive a mouse selection, so these regressions are
// invisible outside the TUI.
package tui

import (
	"strings"
	"testing"

	"albedo/cli/internal/daemon"
	"github.com/charmbracelet/x/ansi"
)

const tableTranscript = "| Tool | Logged median | Warm in-kernel benchmark |\n" +
	"|:---|---:|---:|\n" +
	"| `files.read` | 3 ms | 0.07 ms |\n" +
	"| `files.paths` | too few isolated calls | 12.7 ms |"

// Tables render inside the transcript's width: every cell that fits survives,
// a narrow table truncates its columns rather than its rows or its syntax,
// and escaped pipes and fenced literal tables are never parsed as tables.
func TestMarkdownTablesRenderWithinTheirBounds(t *testing.T) {
	for _, tc := range []struct {
		name   string
		text   string
		absent string
		want   []string
		width  int
	}{
		{name: "keeps every column it fits", text: tableTranscript, width: 80,
			want: []string{"Tool", "files.read", "files.paths", "12.7 ms"}, absent: "|:---"},
		{name: "narrow truncates columns, not rows or the syntax", text: tableTranscript, width: 38,
			want: []string{"Tool", "files.read", "files.path", "12.7 ms"}, absent: "|:---"},
		{name: "escaped pipes and fenced rows stay literal",
			text: "Intro\n\n| Name | Value |\n| --- | --- |\n| a\\|b | `x\\|y` |\n\n" +
				"```text\n| literal | row |\n| --- | --- |\n```\nOutro",
			width: 72,
			want:  []string{"Intro", "a|b", "x|y", "| literal | row |", "| --- | --- |", "Outro"}, absent: ""},
	} {
		got := ansi.Strip(RenderMarkdownAnsi(tc.text, tc.width))
		for _, value := range tc.want {
			if !strings.Contains(got, value) {
				t.Fatalf("%s: width %d lost %q:\n%s", tc.name, tc.width, value, got)
			}
		}
		if tc.absent != "" && strings.Contains(got, tc.absent) {
			t.Fatalf("%s: width %d exposed %q:\n%s", tc.name, tc.width, tc.absent, got)
		}
		for row := range strings.SplitSeq(got, "\n") {
			if w := ansi.StringWidth(row); w > tc.width {
				t.Fatalf("%s: width %d produced a %d-cell row: %q", tc.name, tc.width, w, row)
			}
		}
	}
}

func TestSelectionSkipsTheRail(t *testing.T) {
	lines := []string{"│ you", "│ can you list the tools", "", "│ albedo"}
	sel := Selection{Anchor: Point{Row: 0, Col: 0}, Head: Point{Row: 3, Col: 8}, Gutter: railWidth}
	if got, want := SelectedText(lines, sel), "you\ncan you list the tools\n\nalbedo"; got != want {
		t.Fatalf("copied %q, want %q", got, want)
	}
	for _, row := range HighlightSelection(lines, sel) {
		if strings.HasPrefix(row, "\x1b[7m") {
			t.Fatalf("highlight covers the rail: %q", row)
		}
	}
}

// A copy is the text: no padding out to the viewport's width or height, no
// chrome, no code block frame or quote bars, and wrapped rows joined back
// into their lines.
func TestSelectionCopiesOnlyText(t *testing.T) {
	m := newTestChatModel(t, &daemon.Session{ID: "s"})
	m.SetSize(60, 70)
	m.appendSettledEntry(HistoryEntry{Kind: EntryUser, Text: "why does the thing break when i do the other thing, it is really annoying"})
	m.appendSettledEntry(HistoryEntry{Kind: EntryTool, ToolName: "bash"})
	m.appendSettledEntry(HistoryEntry{Kind: EntryAssistant, Text: "a paragraph that is long enough that it will **definitely** wrap around the width.\n" +
		"hard break\n\nsee https://example.com/a/really/long/path/without/any/spaces/in/it/at/all\n\n" +
		"- an item that also goes on for a while so it wraps onto a second row\n\n" +
		"```go\nfmt.Println(\"a line of code that is longer than the width of the chat\")\n```\n\n" +
		"> quoted text that goes on long enough to wrap around the width\n> > twice\n\n" +
		"done."})
	m.appendSettledEntry(HistoryEntry{Kind: EntryTurnEnd, Mood: moodDone, ElapsedMs: 5000, Tools: 1})
	m.refreshViewportContent()
	lines := strings.Split(m.Viewport.View(), "\n")
	all := Selection{Anchor: Point{Row: 0, Col: 0}, Head: Point{Row: len(lines) - 1, Col: 60}, Gutter: railWidth}
	want := "why does the thing break when i do the other thing, it is really annoying\n\n" +
		"a paragraph that is long enough that it will definitely wrap around the width.\n" +
		"hard break\n\nsee https://example.com/a/really/long/path/without/any/spaces/in/it/at/all\n\n" +
		"• an item that also goes on for a while so it wraps onto a second row\n\n" +
		"fmt.Println(\"a line of code that is longer than the width of the chat\")\n\n" +
		"quoted text that goes on long enough to wrap around the width\n\ntwice\n\n" +
		"done."
	if got := SelectedText(lines, all); got != want {
		t.Fatalf("copied %q, want %q", got, want)
	}
	// chrome copies when it is all you selected
	for i, line := range lines {
		if strings.Contains(ansi.Strip(line), "bash") {
			row := Selection{Anchor: Point{Row: i, Col: 0}, Head: Point{Row: i, Col: 60}, Gutter: railWidth}
			if got := SelectedText(lines, row); got != "ran bash" {
				t.Fatalf("copied the tool row as %q", got)
			}
		}
	}
}
