func getDeploymentReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) {
	var deploy appsv1.Deployment
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &deploy)
	if err != nil {
		return "", "", err
	}
	desiredReplicas := deploy.Status.Replicas
	readyReplicas := deploy.Status.ReadyReplicas
	ready := fmt.Sprintf("%d/%d", readyReplicas, desiredReplicas)
	return ready, "", nil
}
getDeploymentReadyStatus returns the ready & status value of a Deployment
which is based off the table cell values computed by printDeployment from
https://github.com/kubernetes/kubernetes/blob/v1.22.1/pkg/printers/internalversion/printers.go.
nolint:unparam
package printers
import (
	"fmt"
	"sort"
	"strings"
	"time"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	eventsv1 "k8s.io/api/events/v1"
	policyv1 "k8s.io/api/policy/v1"
	storagev1 "k8s.io/api/storage/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/duration"
	"k8s.io/client-go/util/jsonpath"
	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
	"github.com/tohjustin/kube-lineage/internal/graph"
)
const (
	cellUnknown       = "<unknown>"
	cellNotApplicable = "-"
)
var (
	objectColumnDefinitions = []metav1.TableColumnDefinition{
		{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
		{Name: "Ready", Type: "string", Description: "The readiness state of this object."},
		{Name: "Status", Type: "string", Description: "The status of this object."},
		{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
		{Name: "Relationships", Type: "array", Description: "The relationships this object has with its parent.", Priority: -1},
	}
	objectReadyReasonJSONPath = newJSONPath("reason", "{.status.conditions[?(@.type==\"Ready\")].reason}")
	objectReadyStatusJSONPath = newJSONPath("status", "{.status.conditions[?(@.type==\"Ready\")].status}")
)
func createShowGroupFn(nodeMap graph.NodeMap, showGroup bool, maxDepth uint) func(string) bool {
	if showGroup {
		return func(_ string) bool {
			return true
		}
	}
	kindToGroupSetMap := map[string](map[string]struct{}){}
	for _, node := range nodeMap {
		if maxDepth != 0 && node.Depth > maxDepth {
			continue
		}
		if _, ok := kindToGroupSetMap[node.Kind]; !ok {
			kindToGroupSetMap[node.Kind] = map[string]struct{}{}
		}
		kindToGroupSetMap[node.Kind][node.Group] = struct{}{}
	}
	return func(kind string) bool {
		return len(kindToGroupSetMap[kind]) > 1
	}
}
func createShowNamespaceFn(nodeMap graph.NodeMap, showNamespace bool, maxDepth uint) func(schema.GroupKind) bool {
	showNS := showNamespace || shouldShowNamespace(nodeMap, maxDepth)
	if !showNS {
		return func(_ schema.GroupKind) bool {
			return false
		}
	}
	clusterScopeGKSet := map[schema.GroupKind]struct{}{}
	for _, node := range nodeMap {
		if maxDepth != 0 && node.Depth > maxDepth {
			continue
		}
		gk := node.GroupVersionKind().GroupKind()
		if !node.Namespaced {
			clusterScopeGKSet[gk] = struct{}{}
		}
	}
	return func(gk schema.GroupKind) bool {
		_, isClusterScopeGK := clusterScopeGKSet[gk]
		return !isClusterScopeGK
	}
}
func shouldShowNamespace(nodeMap graph.NodeMap, maxDepth uint) bool {
	nsSet := map[string]struct{}{}
	for _, node := range nodeMap {
		if maxDepth != 0 && node.Depth > maxDepth {
			continue
		}
		ns := node.Namespace
		if _, ok := nsSet[ns]; !ok {
			nsSet[ns] = struct{}{}
		}
	}
	return len(nsSet) > 1
}
func newJSONPath(name, jsonPath string) *jsonpath.JSONPath {
	jp := jsonpath.New(name).AllowMissingKeys(true)
	if err := jp.Parse(jsonPath); err != nil {
		panic(err)
	}
	return jp
}
func getNestedString(data map[string]interface{}, jp *jsonpath.JSONPath) (string, error) {
	values, err := jp.FindResults(data)
	if err != nil {
		return "", err
	}
	strValues := []string{}
	for arrIx := range values {
		for valIx := range values[arrIx] {
			strValues = append(strValues, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
		}
	}
	str := strings.Join(strValues, ",")
	return str, nil
}
func getObjectReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) {
	data := u.UnstructuredContent()
	ready, err := getNestedString(data, objectReadyStatusJSONPath)
	if err != nil {
		return "", "", err
	}
	status, err := getNestedString(data, objectReadyReasonJSONPath)
	if err != nil {
		return ready, "", err
	}
	return ready, status, nil
}
func getAPIServiceReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) {
	var apisvc apiregistrationv1.APIService
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &apisvc)
	if err != nil {
		return "", "", err
	}
	var ready, status string
	for _, condition := range apisvc.Status.Conditions {
		if condition.Type == apiregistrationv1.Available {
			ready = string(condition.Status)
			if condition.Status != apiregistrationv1.ConditionTrue {
				status = condition.Reason
			}
		}
	}
	return ready, status, nil
}
func getDaemonSetReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) {
	var ds appsv1.DaemonSet
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &ds)
	if err != nil {
		return "", "", err
	}
	desiredReplicas := ds.Status.DesiredNumberScheduled
	readyReplicas := ds.Status.NumberReady
	ready := fmt.Sprintf("%d/%d", readyReplicas, desiredReplicas)
	return ready, "", nil
}