//go:build e2e && unix

package main

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestTreeViewOpensAndReturns(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServer()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}

	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("clusters not ready")
	}

	// Open resources (tree) for demo
	if err := tf.OpenCommand(); err != nil {
		t.Fatal(err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	// Expect Application root and at least a Deployment entry
	if !tf.WaitForPlain("Application [demo]", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("application root not shown")
	}
	if !tf.WaitForPlain("Deployment [", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("deployment node not shown")
	}

	// Return to clusters
	_ = tf.Send("q")
	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("did not return to main view")
	}
}

// MockArgoServerWithRichTree creates a server with multiple resource types for filtering
func MockArgoServerWithRichTree() (*httptest.Server, error) {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/session/userinfo", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200); _, _ = w.Write([]byte(`{}`)) })
	mux.HandleFunc("/api/v1/applications", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(wrapListResponse(`[{"metadata":{"name":"demo","namespace":"argocd"},"spec":{"project":"demo","destination":{"name":"cluster-a","namespace":"default"}},"status":{"sync":{"status":"Synced"},"health":{"status":"Healthy"}}}]`, "1000")))
	})
	mux.HandleFunc("/api/version", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"version":"e2e"}`)) })
	mux.HandleFunc("/api/v1/applications/demo/resource-tree", func(w http.ResponseWriter, r *http.Request) {
		// Rich tree with multiple resource types: Deployment, Service, ConfigMap, Pod
		_, _ = w.Write([]byte(`{"nodes":[
			{"kind":"Deployment","name":"nginx-deployment","namespace":"default","version":"v1","group":"apps","uid":"dep-1","status":"Synced","health":{"status":"Healthy"}},
			{"kind":"ReplicaSet","name":"nginx-rs-abc123","namespace":"default","version":"v1","group":"apps","uid":"rs-1","status":"Synced","health":{"status":"Healthy"},"parentRefs":[{"uid":"dep-1","kind":"Deployment","name":"nginx-deployment","namespace":"default","group":"apps","version":"v1"}]},
			{"kind":"Pod","name":"nginx-pod-xyz789","namespace":"default","version":"v1","uid":"pod-1","status":"Running","health":{"status":"Healthy"},"parentRefs":[{"uid":"rs-1","kind":"ReplicaSet","name":"nginx-rs-abc123","namespace":"default","group":"apps","version":"v1"}]},
			{"kind":"Service","name":"nginx-service","namespace":"default","version":"v1","uid":"svc-1","status":"Synced","health":{"status":"Healthy"}},
			{"kind":"ConfigMap","name":"nginx-config","namespace":"default","version":"v1","uid":"cm-1","status":"Synced"}
		]}`))
	})
	mux.HandleFunc("/api/v1/stream/applications", func(w http.ResponseWriter, r *http.Request) {
		fl, _ := w.(http.Flusher)
		w.Header().Set("Content-Type", "text/event-stream")
		if shouldSendEvent(r, "demo") {
			_, _ = w.Write([]byte(sseEvent(`{"result":{"type":"MODIFIED","application":{"metadata":{"name":"demo","namespace":"argocd"},"spec":{"project":"demo","destination":{"name":"cluster-a","namespace":"default"}},"status":{"sync":{"status":"Synced"},"health":{"status":"Healthy"}}}}}`)))
		}
		if fl != nil {
			fl.Flush()
		}
	})
	srv := httptest.NewServer(mux)
	return srv, nil
}

func TestTreeViewFilter(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithRichTree()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}

	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("clusters not ready")
	}

	// Open resources (tree) for demo
	if err := tf.OpenCommand(); err != nil {
		t.Fatal(err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	// Expect Application root
	if !tf.WaitForPlain("Application [demo]", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("application root not shown")
	}

	// Verify all resources are visible
	if !tf.WaitForPlain("Deployment [", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("deployment not shown")
	}
	if !tf.WaitForPlain("Service [", 2*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("service not shown")
	}

	// Test 1: Enter search mode with /
	if err := tf.OpenSearch(); err != nil {
		t.Log(tf.SnapshotPlain())
		t.Fatal(err)
	}

	// Test 2: Type filter query "pod" and press Enter.
	// Real-time filtering rerenders on each keystroke; under heavy parallel
	// load these renders can interleave with the next keystroke. Send chars
	// one at a time with a small pause so each one is processed by the app
	// before the next arrives, then commit with Enter.
	for _, c := range "pod" {
		_ = tf.Send(string(c))
		time.Sleep(20 * time.Millisecond)
	}
	_ = tf.Enter()

	// Wait for both: the match-count status line AND a frame where the
	// Pod row has the yellow-background highlight rendered. Polling the
	// raw snapshot here lets us exit as soon as we've captured one good
	// frame, instead of paying a fixed render budget.
	hasPodHighlight := func(raw string) bool {
		for _, line := range strings.Split(raw, "\n") {
			yellow := strings.Contains(line, "48;2;224;175;104") ||
				strings.Contains(line, "48;5;179") ||
				strings.Contains(line, "[103m")
			if yellow && strings.Contains(line, "Pod") && strings.Contains(line, "nginx-pod") {
				return true
			}
		}
		return false
	}
	if !waitUntil(t, func() bool {
		return strings.Contains(tf.SnapshotPlain(), "matches") && hasPodHighlight(tf.Snapshot())
	}, 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("match count or Pod highlight not observed within 3s")
	}

	// Test 3: Verify "Pod" is highlighted with background color
	// The match should have yellow/warning background applied to the row containing Pod
	rawSnap := tf.Snapshot()

	// Find any line containing "Pod" with a yellow-background ANSI sequence.
	// Lipgloss v2 optimizes ANSI sequences, so the background may be set at
	// the start of the line rather than immediately before "Pod". The raw
	// buffer accumulates re-renders, so we OR over all of them — any one
	// frame with the highlight is sufficient evidence.
	var podLineHighlighted bool
	var serviceLineHighlighted bool
	for _, line := range strings.Split(rawSnap, "\n") {
		hasYellowBG := strings.Contains(line, "48;2;224;175;104") ||
			strings.Contains(line, "48;5;179") ||
			strings.Contains(line, "[103m")

		if strings.Contains(line, "Pod") && strings.Contains(line, "nginx-pod") && hasYellowBG {
			podLineHighlighted = true
		}
		if strings.Contains(line, "Service") && strings.Contains(line, "nginx-service") && hasYellowBG {
			serviceLineHighlighted = true
		}
	}

	if !podLineHighlighted {
		t.Log("Raw snapshot excerpt (looking for Pod highlight):")
		t.Log(rawSnap[max(0, len(rawSnap)-2000):])
		t.Fatal("Pod row should have yellow background highlight")
	}

	// Also verify that Service is NOT highlighted (it doesn't match "pod")
	if serviceLineHighlighted {
		t.Fatal("Service should NOT be highlighted - it doesn't match 'pod'")
	}

	// Test 4: Press n to navigate to next match (should work since we have match)
	_ = tf.Send("n")

	// Test 5: Exit tree view
	_ = tf.Send("q")
	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("did not return to main view")
	}
}

func TestTreeViewFilterNoMatches(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithRichTree()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}

	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("clusters not ready")
	}

	// Open resources (tree) for demo
	if err := tf.OpenCommand(); err != nil {
		t.Fatal(err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [demo]", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("application root not shown")
	}

	// Enter search mode and type a non-matching query
	if err := tf.OpenSearch(); err != nil {
		t.Fatal(err)
	}

	_ = tf.Send("nonexistent")
	_ = tf.Enter()

	// Should show "no matches" in status
	if !tf.WaitForPlain("no matches", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("'no matches' not shown in status")
	}

	// Exit with q
	_ = tf.Send("q")
	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("did not return to main view")
	}
}

// MockArgoServerWithResourceSyncStatus creates a server with resource sync status in tree view.
func MockArgoServerWithResourceSyncStatus() (*httptest.Server, error) {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/session/userinfo", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200); _, _ = w.Write([]byte(`{}`)) })
	mux.HandleFunc("/api/v1/applications", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		// App with resources array containing sync statuses
		_, _ = w.Write([]byte(wrapListResponse(`[{"metadata":{"name":"demo","namespace":"argocd"},"spec":{"project":"demo","destination":{"name":"cluster-a","namespace":"default"}},"status":{"sync":{"status":"OutOfSync"},"health":{"status":"Healthy"},"resources":[{"group":"apps","kind":"Deployment","name":"nginx-deployment","namespace":"default","status":"OutOfSync","version":"v1"},{"group":"","kind":"Service","name":"nginx-service","namespace":"default","status":"Synced","version":"v1"}]}}]`, "1000")))
	})
	mux.HandleFunc("/api/version", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"version":"e2e"}`)) })
	mux.HandleFunc("/api/v1/applications/demo", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		// Single app endpoint returns resources for tree view loading
		_, _ = w.Write([]byte(`{"metadata":{"name":"demo","namespace":"argocd"},"spec":{"project":"demo","destination":{"name":"cluster-a","namespace":"default"}},"status":{"sync":{"status":"OutOfSync"},"health":{"status":"Healthy"},"resources":[{"group":"apps","kind":"Deployment","name":"nginx-deployment","namespace":"default","status":"OutOfSync","version":"v1"},{"group":"","kind":"Service","name":"nginx-service","namespace":"default","status":"Synced","version":"v1"}]}}`))
	})
	mux.HandleFunc("/api/v1/applications/demo/resource-tree", func(w http.ResponseWriter, r *http.Request) {
		// Tree with Deployment and Service - sync status comes from resources array
		_, _ = w.Write([]byte(`{"nodes":[
			{"kind":"Deployment","name":"nginx-deployment","namespace":"default","version":"v1","group":"apps","uid":"dep-1","health":{"status":"Healthy"}},
			{"kind":"Service","name":"nginx-service","namespace":"default","version":"v1","uid":"svc-1","health":{"status":"Healthy"}}
		]}`))
	})
	mux.HandleFunc("/api/v1/stream/applications", func(w http.ResponseWriter, r *http.Request) {
		fl, _ := w.(http.Flusher)
		w.Header().Set("Content-Type", "text/event-stream")

		// Send initial state (SSE format)
		if shouldSendEvent(r, "demo") {
			_, _ = w.Write([]byte(sseEvent(`{"result":{"type":"MODIFIED","application":{"metadata":{"name":"demo","namespace":"argocd"},"spec":{"project":"demo","destination":{"name":"cluster-a","namespace":"default"}},"status":{"sync":{"status":"OutOfSync"},"health":{"status":"Healthy"},"resources":[{"group":"apps","kind":"Deployment","name":"nginx-deployment","namespace":"default","status":"OutOfSync","version":"v1"},{"group":"","kind":"Service","name":"nginx-service","namespace":"default","status":"Synced","version":"v1"}]}}}}`)))
		}
		if fl != nil {
			fl.Flush()
		}

		// Hold the stream open so the client receives the initial event,
		// then return. We can't block purely on r.Context().Done() because
		// httptest.Server.Close() (called from t.Cleanup) waits for
		// in-flight handlers to return without cancelling their contexts
		// — that would deadlock the test cleanup. 500ms amortizes
		// reconnects under the e2e default ARGONAUT_RETRY_INITIAL_DELAY=10ms
		// without introducing the deadlock risk.
		select {
		case <-r.Context().Done():
		case <-time.After(500 * time.Millisecond):
		}
	})
	srv := httptest.NewServer(mux)
	return srv, nil
}

// TestTreeViewResourceSyncStatus verifies that individual resources (Deployments, Services)
// display their sync status from Application.status.resources in the tree view.
func TestTreeViewResourceSyncStatus(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithResourceSyncStatus()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}

	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("clusters not ready")
	}

	// Open resources (tree) for demo app
	if err := tf.OpenCommand(); err != nil {
		t.Fatal(err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	// Wait for tree view to load
	if !tf.WaitForPlain("Application [demo]", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("application root not shown")
	}

	// Verify Deployment shows OutOfSync status from resources array
	if !tf.WaitForPlain("Deployment [default/nginx-deployment] (Healthy, OutOfSync)", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("Deployment sync status not shown correctly")
	}

	// Verify Service shows Synced status from resources array
	if !tf.WaitForPlain("Service [default/nginx-service] (Healthy, Synced)", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("Service sync status not shown correctly")
	}

	// Exit tree view
	_ = tf.Send("q")
	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("did not return to main view")
	}
}

func TestTreeViewFilterEscapeClearsFilter(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithRichTree()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}

	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Fatal("clusters not ready")
	}

	// Open resources (tree) for demo
	if err := tf.OpenCommand(); err != nil {
		t.Fatal(err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [demo]", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("application root not shown")
	}

	// Enter search, type a query that produces a real-time match count,
	// then Escape without committing. The filter must be discarded:
	// the match-count indicator must NOT remain on the rendered screen.
	if err := tf.OpenSearch(); err != nil {
		t.Fatal(err)
	}
	for _, c := range "pod" {
		_ = tf.Send(string(c))
		time.Sleep(20 * time.Millisecond)
	}

	// Wait for the real-time filter to render its match-count indicator —
	// proves the filter was actually applied while typing.
	if !tf.WaitForScreen("match", 2*time.Second) {
		t.Log(tf.Screen())
		t.Fatal("real-time filter indicator did not appear before Escape")
	}

	// Escape cancels the search and must clear the filter from the model.
	_ = tf.Escape()

	// After Escape the search bar is closed AND the filter indicator
	// ("matches" / "no matches") must not be on screen any more.
	if !waitUntil(t, func() bool {
		s := tf.Screen()
		return !strings.Contains(s, "matches") && !strings.Contains(s, "no match")
	}, 2*time.Second) {
		t.Log(tf.Screen())
		t.Fatal("filter indicator still visible after Escape — filter was not cleared")
	}

	// And the search bar should be closed: pressing q must exit tree view.
	_ = tf.Send("q")
	if !tf.WaitForPlain("cluster-a", 3*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("did not return to main view - escape may not have closed search")
	}
}

// TestAppOfApps_ChildAppEnter_NavigatesToChildApp verifies that pressing Enter on a
// child Application node in an app-of-apps tree navigates to the child app's own tree view,
// and that pressing Escape returns to the parent tree (not the apps list).
func TestAppOfApps_ChildAppEnter_NavigatesToChildApp(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithAppOfApps()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}
	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("clusters not visible")
	}

	// Navigate to parent-app's tree view
	if err := tf.OpenCommand(); err != nil {
		t.Fatalf("open command: %v", err)
	}
	_ = tf.Send("resources parent-app")
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [parent-app]", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("parent-app tree view not loaded")
	}

	// Navigate to child Application node and press Enter. A small gap
	// between j and Enter is needed because the app processes the cursor
	// move on a render frame; Enter applied before that frame would
	// activate the wrong (root) node.
	_ = tf.Send("j")
	time.Sleep(50 * time.Millisecond)
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [child-app]", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("did not navigate to child-app tree view after pressing Enter on child Application node")
	}
	if !tf.WaitForPlain("child-deploy", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("child-app's Deployment not visible in child app tree view")
	}

	// Escape should return to parent tree, not apps list
	_ = tf.Escape()
	if !tf.WaitForPlain("Application [parent-app]", 5*time.Second) {
		snapshot := tf.SnapshotPlain()
		if strings.Contains(snapshot, "cluster-a") {
			t.Log(snapshot)
			t.Fatal("Escape went back to apps list instead of parent-app tree view")
		}
		t.Log(snapshot)
		t.Fatal("did not return to parent-app tree view after Escape")
	}
}

// TestAppOfApps_EscapeFromChildApp_ReturnsToParentTree verifies that pressing Escape
// while in a child app's tree view returns to the parent app's tree view (not the apps list).
func TestAppOfApps_EscapeFromChildApp_ReturnsToParentTree(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithAppOfApps()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}
	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("clusters not visible")
	}

	if err := tf.OpenCommand(); err != nil {
		t.Fatalf("open command: %v", err)
	}
	_ = tf.Send("resources parent-app")
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [parent-app]", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("parent-app tree not loaded")
	}

	_ = tf.Send("j")
	time.Sleep(50 * time.Millisecond)
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [child-app]", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("child-app tree not loaded")
	}

	_ = tf.Escape()

	if !tf.WaitForPlain("Application [parent-app]", 5*time.Second) {
		snapshot := tf.SnapshotPlain()
		if strings.Contains(snapshot, "cluster-a") {
			t.Log(snapshot)
			t.Fatal("Escape went to apps list instead of parent-app tree")
		}
		t.Log(snapshot)
		t.Fatal("did not return to parent-app tree after Escape")
	}
}

// TestTreeView_EscapeFromRegularApp_ReturnsToAppsView verifies that pressing Escape
// from a regular (non-child) tree view still returns to the apps list.
func TestTreeView_EscapeFromRegularApp_ReturnsToAppsView(t *testing.T) {
	t.Parallel()
	tf := NewTUITest(t)
	t.Cleanup(tf.Cleanup)

	srv, err := MockArgoServerWithSyncedResources()
	if err != nil {
		t.Fatalf("mock server: %v", err)
	}
	t.Cleanup(srv.Close)

	cfgPath, err := tf.SetupWorkspace()
	if err != nil {
		t.Fatalf("setup workspace: %v", err)
	}
	if err := WriteArgoConfig(cfgPath, srv.URL); err != nil {
		t.Fatalf("write config: %v", err)
	}
	if err := tf.StartAppArgs([]string{"-argocd-config=" + cfgPath}); err != nil {
		t.Fatalf("start app: %v", err)
	}

	if !tf.WaitForPlain("cluster-a", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("clusters not visible")
	}

	if err := tf.OpenCommand(); err != nil {
		t.Fatalf("open command: %v", err)
	}
	_ = tf.Send("resources demo")
	_ = tf.Enter()

	if !tf.WaitForPlain("Application [demo]", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("demo tree not loaded")
	}

	_ = tf.Escape()

	if !tf.WaitForPlain("cluster-a", 5*time.Second) {
		t.Log(tf.SnapshotPlain())
		t.Fatal("Escape did not return to apps view from a regular tree")
	}
}
