package cli

import (
	"sort"
	"strings"
	"testing"
)

func dir(path string) remoteEntry {
	return remoteEntry{path: path, isDir: true, orderKey: pathOrderKey(path)}
}
func file(path string) remoteEntry {
	return remoteEntry{path: path, orderKey: pathOrderKey(path)}
}

func deleted(e remoteEntry) remoteEntry {
	e.deleted = true
	return e
}

func emptyDirPaths(entries []remoteEntry) []string {
	// Reproduce the production ordering (see listRemoteEntries) so the sort rule
	// and the emptyDirs scan rule are always exercised together, rather than
	// trusting hand-ordered literals.
	sort.Slice(entries, func(i, j int) bool {
		return entries[i].orderKey < entries[j].orderKey
	})
	got := emptyDirs(entries)
	paths := make([]string, len(got))
	for i, e := range got {
		paths[i] = e.path
	}
	sort.Strings(paths)
	return paths
}

func TestEmptyDirs(t *testing.T) {
	tests := []struct {
		name    string
		entries []remoteEntry
		want    []string
	}{
		{
			name: "dir with only a deleted file is empty",
			entries: []remoteEntry{
				dir("a"),
				deleted(file("a/gone.js")),
			},
			want: []string{"a"},
		},
		{
			name: "dir with a surviving file is kept",
			entries: []remoteEntry{
				dir("a"),
				file("a/live.js"),
			},
			want: []string{},
		},
		{
			name: "bottom-up cascade removes parent and child",
			entries: []remoteEntry{
				dir("a"),
				dir("a/b"),
				deleted(file("a/b/gone.js")),
			},
			want: []string{"a"},
		},
		{
			name: "deep file deleted but sibling file alive keeps parent",
			entries: []remoteEntry{
				dir("a"),
				dir("a/b"),
				deleted(file("a/b/gone.js")),
				file("a/c.js"),
			},
			want: []string{"a/b"},
		},
		{
			name: "sibling with shared name prefix is not swallowed",
			entries: []remoteEntry{
				dir("a"),
				deleted(file("a/gone.js")),
				file("ab/live.js"),
			},
			want: []string{"a"},
		},
		{
			name: "sibling colliding on base name does not hide live children",
			entries: []remoteEntry{
				dir("a"),
				file("a.js"),
				file("a/live.js"),
			},
			want: []string{},
		},
		{
			name: "empty dir with no children is removed",
			entries: []remoteEntry{
				dir("a"),
			},
			want: []string{"a"},
		},
		{
			name: "nested dirs with no files at all are all removed",
			entries: []remoteEntry{
				dir("a"),
				dir("a/b"),
				dir("a/c"),
			},
			want: []string{"a"},
		},
		{
			name: "empty dir nested under a dir holding a live file keeps outer, removes inner",
			entries: []remoteEntry{
				dir("a"),
				dir("a/b"),
				dir("a/b/c"),
				file("a/live.js"),
			},
			want: []string{"a/b"},
		},
		{
			name: "live file under a nested dir keeps the whole chain",
			entries: []remoteEntry{
				dir("a"),
				dir("a/b"),
				file("a/b/live.js"),
			},
			want: []string{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := emptyDirPaths(tt.entries)
			if strings.Join(got, ",") != strings.Join(tt.want, ",") {
				t.Fatalf("emptyDirs = %v, want %v", got, tt.want)
			}
		})
	}
}
