// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// nolint: gocritic
package ambient

import (
	"fmt"
	"net/netip"
	"strconv"

	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
	v1 "k8s.io/api/core/v1"
	discovery "k8s.io/api/discovery/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"istio.io/api/annotation"
	"istio.io/api/label"
	networkingv1alpha3 "istio.io/api/networking/v1alpha3"
	networkingclient "istio.io/client-go/pkg/apis/networking/v1"
	securityclient "istio.io/client-go/pkg/apis/security/v1"
	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/serviceregistry/kube"
	"istio.io/istio/pilot/pkg/serviceregistry/serviceentry"
	labelutil "istio.io/istio/pilot/pkg/serviceregistry/util/label"
	"istio.io/istio/pkg/cluster"
	"istio.io/istio/pkg/config/constants"
	cfgkube "istio.io/istio/pkg/config/kube"
	"istio.io/istio/pkg/config/labels"
	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/config/schema/kind"
	kubeutil "istio.io/istio/pkg/kube"
	"istio.io/istio/pkg/kube/krt"
	kubelabels "istio.io/istio/pkg/kube/labels"
	"istio.io/istio/pkg/kube/multicluster"
	"istio.io/istio/pkg/log"
	"istio.io/istio/pkg/network"
	"istio.io/istio/pkg/ptr"
	"istio.io/istio/pkg/slices"
	"istio.io/istio/pkg/util/sets"
	"istio.io/istio/pkg/workloadapi"
)

// WorkloadsCollection builds out the core Workload object type used in ambient mode.
// A Workload represents a single addressable unit of compute -- typically a Pod or a VM.
// Workloads can come from a variety of sources; these are joined together to build one complete `Collection[WorkloadInfo]`.
func (a Builder) WorkloadsCollection(
	pods krt.Collection[*v1.Pod],
	nodes krt.Collection[Node],
	meshConfig krt.Singleton[MeshConfig],
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	workloadServices krt.Collection[model.ServiceInfo],
	workloadEntries krt.Collection[*networkingclient.WorkloadEntry],
	serviceEntries krt.Collection[*networkingclient.ServiceEntry],
	endpointSlices krt.Collection[*discovery.EndpointSlice],
	namespaces krt.Collection[*v1.Namespace],
	opts krt.OptionsBuilder,
) krt.Collection[model.WorkloadInfo] {
	WorkloadServicesNamespaceIndex := krt.NewNamespaceIndex(workloadServices)
	EndpointSlicesByIPIndex := endpointSliceAddressIndex(endpointSlices)
	// Workloads coming from pods. There should be one workload for each (running) Pod.
	PodWorkloads := krt.NewCollection(
		pods,
		a.podWorkloadBuilder(
			meshConfig,
			authPoliciesByNs,
			peerAuthsByNs,
			waypoints,
			workloadServices,
			WorkloadServicesNamespaceIndex,
			EndpointSlicesByIPIndex,
			namespaces,
			nodes,
		),
		opts.WithName("PodWorkloads")...,
	)
	// Workloads coming from workloadEntries. These are 1:1 with WorkloadEntry.
	WorkloadEntryWorkloads := krt.NewCollection(
		workloadEntries,
		a.workloadEntryWorkloadBuilder(meshConfig, authPoliciesByNs, peerAuthsByNs, waypoints, workloadServices, WorkloadServicesNamespaceIndex, namespaces),
		opts.WithName("WorkloadEntryWorkloads")...,
	)
	// Workloads coming from serviceEntries. These are inlined workloadEntries (under `spec.endpoints`); these serviceEntries will
	// also be generating `workloadapi.Service` definitions in the `ServicesCollection` logic.
	ServiceEntryWorkloads := krt.NewManyCollection(
		serviceEntries,
		a.serviceEntryWorkloadBuilder(meshConfig, authPoliciesByNs, peerAuthsByNs, waypoints, namespaces, workloadServices),
		opts.WithName("ServiceEntryWorkloads")...,
	)
	// Workloads coming from endpointSlices. These are for *manually added* endpoints. Typically, Kubernetes will insert each pod
	// into the EndpointSlice. This is because Kubernetes has 3 APIs in its model: Service, Pod, and EndpointSlice.
	// In our API, we only have two: Service and Workload.
	// Pod provides much more information than EndpointSlice, so typically we just consume that directly; see method for more details
	// on when we will build from an EndpointSlice.
	EndpointSliceWorkloads := krt.NewManyCollection(
		endpointSlices,
		a.endpointSlicesBuilder(meshConfig, workloadServices),
		opts.WithName("EndpointSliceWorkloads")...)

	NetworkGatewayWorkloads := krt.NewManyFromNothing[model.WorkloadInfo](func(ctx krt.HandlerContext) []model.WorkloadInfo {
		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())
		all := LookupAllNetworkGateway(ctx, a.Networks.NetworkGateways)
		return slices.Map(all, convertGateway(meshCfg))
	}, opts.WithName("NetworkGatewayWorkloads")...)

	Workloads := krt.JoinCollection(
		[]krt.Collection[model.WorkloadInfo]{
			PodWorkloads,
			WorkloadEntryWorkloads,
			ServiceEntryWorkloads,
			EndpointSliceWorkloads,
			NetworkGatewayWorkloads,
		},
		// Each collection has its own unique UID as the key. This guarantees an object can exist in only a single collection
		// This enables us to use the JoinUnchecked optimization.
		append(opts.WithName("Workloads"), krt.WithJoinUnchecked())...)
	return Workloads
}

func MergedGlobalWorkloadsCollection(
	localCluster *multicluster.Cluster,
	localWaypoints krt.Collection[Waypoint],
	localNodeLocalities krt.Collection[Node],
	ctrl *multicluster.Controller,
	workloadEntries krt.Collection[*networkingclient.WorkloadEntry],
	serviceEntries krt.Collection[*networkingclient.ServiceEntry],
	globalNodes krt.Collection[krt.Collection[krt.ObjectWithCluster[Node]]],
	nodesByCluster krt.Index[cluster.ID, krt.Collection[krt.ObjectWithCluster[Node]]],
	meshConfig krt.Singleton[MeshConfig],
	localAuthPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	localPeerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	globalWaypoints krt.Collection[krt.Collection[Waypoint]],
	waypointsByCluster krt.Index[cluster.ID, krt.Collection[Waypoint]],
	localWorkloadServices krt.Collection[model.ServiceInfo],
	globalWorkloadServices krt.Collection[krt.Collection[krt.ObjectWithCluster[model.ServiceInfo]]],
	globalWorkloadServicesByCluster krt.Index[cluster.ID, krt.Collection[krt.ObjectWithCluster[model.ServiceInfo]]],
	globalNetworks NetworkCollections,
	localClusterID cluster.ID,
	flags FeatureFlags,
	domainSuffix string,
	opts krt.OptionsBuilder,
) krt.Collection[model.WorkloadInfo] {
	LocalWorkloadServicesNamespaceIndex := krt.NewNamespaceIndex(localWorkloadServices)
	LocalEndpointSlicesByIPIndex := endpointSliceAddressIndex(localCluster.EndpointSlices())
	LocalPodWorkloads := krt.NewCollection(
		localCluster.Pods(),
		podWorkloadBuilder(
			meshConfig,
			globalNetworks.FetchLocalNetworkID,
			localAuthPoliciesByNs,
			localPeerAuthsByNs,
			localWaypoints,
			localWorkloadServices,
			LocalWorkloadServicesNamespaceIndex,
			LocalEndpointSlicesByIPIndex,
			localCluster.Namespaces(),
			localNodeLocalities,
			domainSuffix,
			localCluster.ID,
			globalNetworks.FetchLocalNetworkID,
			globalNetworks.GatewaysByNetwork,
			flags,
			true, // Remote network workloads will be coalesced; safe to skip precompute
		),
		opts.WithName("LocalPodWorkloads")...,
	)
	LocalPodWorkloadsWithCluster := krt.MapCollection(
		LocalPodWorkloads,
		wrapObjectWithCluster[model.WorkloadInfo](localCluster.ID),
		opts.WithName("LocalPodWorkloadsWithCluster")...,
	)
	LocalWorkloadEntryWorkloads := krt.NewCollection(
		workloadEntries,
		workloadEntryWorkloadBuilder(
			meshConfig,
			globalNetworks.FetchLocalNetworkID,
			localAuthPoliciesByNs,
			localPeerAuthsByNs,
			localWaypoints,
			localWorkloadServices,
			LocalWorkloadServicesNamespaceIndex,
			localCluster.Namespaces(),
			localCluster.ID,
			globalNetworks.FetchLocalNetworkID,
			globalNetworks.GatewaysByNetwork,
			flags,
			true, // Remote network workloads will be coalesced; safe to skip precompute
		),
		opts.WithName("LocalWorkloadEntryWorkloads")...,
	)
	LocalWorkloadEntryWorkloadsWithCluster := krt.MapCollection(
		LocalWorkloadEntryWorkloads,
		wrapObjectWithCluster[model.WorkloadInfo](localCluster.ID),
		opts.WithName("LocalWorkloadEntryWorkloadsWithCluster")...,
	)
	// Workloads coming from serviceEntries. These are inlined workloadEntries (under `spec.endpoints`); these serviceEntries will
	// also be generating `workloadapi.Service` definitions in the `ServicesCollection` logic.
	LocalServiceEntryWorkloads := krt.NewManyCollection(
		serviceEntries,
		serviceEntryWorkloadBuilder(
			meshConfig,
			localAuthPoliciesByNs,
			localPeerAuthsByNs,
			localWaypoints,
			localCluster.Namespaces(),
			localWorkloadServices,
			localCluster.ID,
			globalNetworks.FetchLocalNetworkID,
			globalNetworks.GatewaysByNetwork,
			flags,
		),
		opts.WithName("LocalServiceEntryWorkloads")...,
	)
	LocalServiceEntryWorkloadsWithCluster := krt.MapCollection(
		LocalServiceEntryWorkloads,
		wrapObjectWithCluster[model.WorkloadInfo](localCluster.ID),
		opts.WithName("LocalServiceEntryWorkloadsWithCluster")...,
	)
	// Workloads coming from endpointSlices. These are for *manually added* endpoints. Typically, Kubernetes will insert each pod
	// into the EndpointSlice. This is because Kubernetes has 3 APIs in its model: Service, Pod, and EndpointSlice.
	// In our API, we only have two: Service and Workload.
	// Pod provides much more information than EndpointSlice, so typically we just consume that directly; see method for more details
	// on when we will build from an EndpointSlice.
	LocalEndpointSliceWorkloads := krt.NewManyCollection(
		localCluster.EndpointSlices(),
		endpointSlicesBuilder(meshConfig,
			localWorkloadServices,
			domainSuffix,
			localCluster.ID,
			globalNetworks.FetchLocalNetworkID,
		),
		opts.WithName("LocalEndpointSliceWorkloads")...,
	)
	LocalEndpointSliceWorkloadsWithCluster := krt.MapCollection(
		LocalEndpointSliceWorkloads,
		wrapObjectWithCluster[model.WorkloadInfo](localCluster.ID),
		opts.WithName("LocalEndpointSliceWorkloadsWithCluster")...,
	)

	GlobalNetworkGatewayWorkloads := krt.NewManyFromNothing[model.WorkloadInfo](func(ctx krt.HandlerContext) []model.WorkloadInfo {
		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())
		return slices.Map(LookupAllNetworkGateway(
			ctx,
			globalNetworks.NetworkGateways,
		), convertGateway(meshCfg))
	}, opts.WithName("LocalNetworkGatewayWorkloads")...)
	LocalNetworkGatewayWorkloadsWithCluster := krt.MapCollection(
		GlobalNetworkGatewayWorkloads,
		wrapObjectWithCluster[model.WorkloadInfo](localCluster.ID),
		opts.WithName("LocalNetworkGatewayWorkloadsWithCluster")...,
	)
	GlobalWorkloadInfosWithCluster := multicluster.NestedManyCollectionsFromLocalAndRemote(
		ctrl,
		[]krt.Collection[krt.ObjectWithCluster[model.WorkloadInfo]]{
			LocalPodWorkloadsWithCluster,
			LocalWorkloadEntryWorkloadsWithCluster,
			LocalServiceEntryWorkloadsWithCluster,
			LocalEndpointSliceWorkloadsWithCluster,
			LocalNetworkGatewayWorkloadsWithCluster,
		},
		func(ctx krt.HandlerContext, c *multicluster.Cluster) []krt.Collection[krt.ObjectWithCluster[model.WorkloadInfo]] {
			opts := []krt.CollectionOption{
				krt.WithDebugging(opts.Debugger()),
				krt.WithStop(c.GetStop()),
			}
			endpointSlices := c.EndpointSlices()
			pods := c.Pods()
			waypointsPtr := krt.FetchOne(ctx, globalWaypoints, krt.FilterIndex(waypointsByCluster, c.ID))
			// This usually happens because the event for a new cluster
			// triggers the global services|waypoints|etc. transformations in parallel
			// with this transformation. This Fetch is racing
			// with that computation and will almost always lose.
			// While we're looking for a way to make this ordering predictable
			// to avoid hacks like this, we can deal with eventually consistent
			// collection state for now.
			if waypointsPtr == nil {
				log.Warnf("Cluster %s does not have waypoints, skipping global workloads", c.ID)
				return nil
			}
			waypoints := *waypointsPtr

			namespaces := c.Namespaces()
			clusteredNodesPtr := krt.FetchOne(ctx, globalNodes, krt.FilterIndex(nodesByCluster, c.ID))
			if clusteredNodesPtr == nil {
				log.Warnf("Cluster %s does not have nodes, skipping global workloads", c.ID)
				return nil
			}
			clusteredNodes := *clusteredNodesPtr

			workloadServicesPtr := krt.FetchOne(ctx, globalWorkloadServices, krt.FilterIndex(globalWorkloadServicesByCluster, c.ID))
			if workloadServicesPtr == nil {
				log.Warnf("Cluster %s does not have workload services, skipping global workloads", c.ID)
				return nil
			}

			nodes := krt.MapCollection(clusteredNodes, unwrapObjectWithCluster, append(
				opts,
				krt.WithName(fmt.Sprintf("NodeLocality[%s]", c.ID)),
			)...)

			globalWorkloadServicesWithCluster := *workloadServicesPtr
			globalWorkloadServices := krt.MapCollection(
				globalWorkloadServicesWithCluster,
				unwrapObjectWithCluster[model.ServiceInfo],
				append(
					opts,
					krt.WithName(fmt.Sprintf("WorkloadServices[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)

			WorkloadServicesNamespaceIndex := krt.NewNamespaceIndex(globalWorkloadServices)
			EndpointSlicesByIPIndex := endpointSliceAddressIndex(endpointSlices)
			// Workloads coming from pods. There should be one workload for each (running) Pod.
			PodWorkloads := krt.NewCollection(
				pods,
				podWorkloadBuilder(
					meshConfig,
					globalNetworks.FetchLocalNetworkID,
					localAuthPoliciesByNs,
					localPeerAuthsByNs,
					waypoints,
					globalWorkloadServices,
					WorkloadServicesNamespaceIndex,
					EndpointSlicesByIPIndex,
					namespaces,
					nodes,
					domainSuffix,
					c.ID,
					func(ctx krt.HandlerContext) network.ID {
						return globalNetworks.FetchRemoteSystemNamespaceNetwork(ctx, namespaces)
					},
					globalNetworks.GatewaysByNetwork,
					flags,
					true, // Remote network workloads will be coalesced; safe to skip precompute
				),
				append(
					opts,
					krt.WithName(fmt.Sprintf("PodWorkloads[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			PodWorkloadsWithCluster := krt.MapCollection(
				PodWorkloads,
				wrapObjectWithCluster[model.WorkloadInfo](c.ID),
				append(
					opts,
					krt.WithName(fmt.Sprintf("PodWorkloadsWithCluster[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			// Workloads coming from workloadEntries. These are 1:1 with WorkloadEntry.
			WorkloadEntryWorkloads := krt.NewCollection(
				workloadEntries,
				workloadEntryWorkloadBuilder(
					meshConfig,
					globalNetworks.FetchLocalNetworkID,
					localAuthPoliciesByNs,
					localPeerAuthsByNs,
					waypoints,
					globalWorkloadServices,
					WorkloadServicesNamespaceIndex,
					namespaces,
					c.ID,
					func(ctx krt.HandlerContext) network.ID {
						return globalNetworks.FetchRemoteSystemNamespaceNetwork(ctx, namespaces)
					},
					globalNetworks.GatewaysByNetwork,
					flags,
					true, // Remote network workloads will be coalesced; safe to skip precompute
				),
				append(
					opts,
					krt.WithName(fmt.Sprintf("WorkloadEntryWorkloads[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			WorkloadEntryWorkloadsWithCluster := krt.MapCollection(
				WorkloadEntryWorkloads,
				wrapObjectWithCluster[model.WorkloadInfo](c.ID),
				append(
					opts,
					krt.WithName(fmt.Sprintf("WorkloadEntryWorkloadsWithCluster[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			// Workloads coming from serviceEntries. These are inlined workloadEntries (under `spec.endpoints`); these serviceEntries will
			// also be generating `workloadapi.Service` definitions in the `ServicesCollection` logic.
			ServiceEntryWorkloads := krt.NewManyCollection(
				serviceEntries,
				serviceEntryWorkloadBuilder(
					meshConfig,
					localAuthPoliciesByNs,
					localPeerAuthsByNs,
					waypoints,
					namespaces,
					globalWorkloadServices,
					c.ID,
					func(ctx krt.HandlerContext) network.ID {
						return globalNetworks.FetchRemoteSystemNamespaceNetwork(ctx, namespaces)
					},
					globalNetworks.GatewaysByNetwork,
					flags,
				),
				append(
					opts,
					krt.WithName(fmt.Sprintf("ServiceEntryWorkloads[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			ServiceEntryWorkloadsWithCluster := krt.MapCollection(
				ServiceEntryWorkloads,
				wrapObjectWithCluster[model.WorkloadInfo](c.ID),
				append(
					opts,
					krt.WithName(fmt.Sprintf("ServiceEntryWorkloadsWithCluster[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)
			// Workloads coming from endpointSlices. These are for *manually added* endpoints. Typically, Kubernetes will insert each pod
			// into the EndpointSlice. This is because Kubernetes has 3 APIs in its model: Service, Pod, and EndpointSlice.
			// In our API, we only have two: Service and Workload.
			// Pod provides much more information than EndpointSlice, so typically we just consume that directly; see method for more details
			// on when we will build from an EndpointSlice.
			EndpointSliceWorkloads := krt.NewManyCollection(
				endpointSlices,
				endpointSlicesBuilder(meshConfig,
					globalWorkloadServices,
					domainSuffix,
					c.ID,
					func(ctx krt.HandlerContext) network.ID {
						return globalNetworks.FetchRemoteSystemNamespaceNetwork(ctx, namespaces)
					},
				),
				append(
					opts,
					krt.WithName(fmt.Sprintf("EndpointSliceWorkloads[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...)
			EndpointSliceWorkloadsWithCluster := krt.MapCollection(
				EndpointSliceWorkloads,
				wrapObjectWithCluster[model.WorkloadInfo](c.ID),
				append(
					opts,
					krt.WithName(fmt.Sprintf("EndpointSliceWorkloadsWithCluster[%s]", c.ID)),
					krt.WithMetadata(krt.Metadata{
						multicluster.ClusterKRTMetadataKey: c.ID,
					}),
				)...,
			)

			return []krt.Collection[krt.ObjectWithCluster[model.WorkloadInfo]]{
				PodWorkloadsWithCluster,
				WorkloadEntryWorkloadsWithCluster,
				ServiceEntryWorkloadsWithCluster,
				EndpointSliceWorkloadsWithCluster,
			}
		},
		"WorkloadsInfoWithCluster",
		opts,
	)
	col := krt.NestedJoinWithMergeCollection(
		GlobalWorkloadInfosWithCluster,
		mergeWorkloadInfosWithCluster(localClusterID),
		opts.WithName("MergedGlobalWorkloadsWithCluster")...,
	)
	return krt.MapCollection(col, unwrapObjectWithCluster[model.WorkloadInfo], opts.WithName("MergedGlobalWorkloads")...)
}

func workloadEntryWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	localNetworkGetter func(krt.HandlerContext) network.ID,
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	workloadServices krt.Collection[model.ServiceInfo],
	workloadServicesNamespaceIndex krt.Index[string, model.ServiceInfo],
	namespaces krt.Collection[*v1.Namespace],
	clusterID cluster.ID,
	networkGetter func(krt.HandlerContext) network.ID,
	gatewaysByNetwork krt.Index[network.ID, NetworkGateway],
	flags FeatureFlags,
	canSkipPrecompute bool,
) krt.TransformationSingle[*networkingclient.WorkloadEntry, model.WorkloadInfo] {
	return func(ctx krt.HandlerContext, wle *networkingclient.WorkloadEntry) *model.WorkloadInfo {
		// WLE can put labels in multiple places; normalize this
		wle = serviceentry.ConvertClientWorkloadEntry(wle)
		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())
		policies := buildWorkloadPolicies(ctx, authPoliciesByNs, peerAuthsByNs, meshCfg, wle.Labels, wle.Namespace)

		appTunnel, targetWaypoint, waypointErr := computeWaypoint(ctx, waypoints, namespaces, wle.ObjectMeta)

		fo := []krt.FetchOption{krt.FilterIndex(workloadServicesNamespaceIndex, wle.Namespace), krt.FilterSelectsNonEmpty(wle.GetLabels())}
		if !flags.EnableK8SServiceSelectWorkloadEntries {
			fo = append(fo, krt.FilterGeneric(func(a any) bool {
				return a.(model.ServiceInfo).Source.Kind == kind.ServiceEntry
			}))
		}
		services := krt.Fetch(ctx, workloadServices, fo...)
		network := networkGetter(ctx).String()
		if wle.Spec.Network != "" {
			network = wle.Spec.Network
		}

		// enforce traversing waypoints
		policies = append(policies, implicitWaypointPolicies(flags, ctx, waypoints, targetWaypoint, services)...)

		w := &workloadapi.Workload{
			Uid:                   generateWorkloadEntryUID(clusterID, wle.Namespace, wle.Name),
			Name:                  wle.Name,
			Namespace:             wle.Namespace,
			Network:               network,
			NetworkGateway:        getNetworkGatewayAddress(ctx, network, gatewaysByNetwork),
			ClusterId:             string(clusterID),
			ServiceAccount:        wle.Spec.ServiceAccount,
			Services:              constructServicesFromWorkloadEntry(&wle.Spec, services),
			AuthorizationPolicies: policies,
			Status:                workloadapi.WorkloadStatus_HEALTHY, // TODO: WE can be unhealthy
			Waypoint:              targetWaypoint.GetAddress(),
			ApplicationTunnel:     appTunnel,
			TrustDomain:           pickTrustDomain(meshCfg),
			Locality:              getWorkloadEntryLocality(&wle.Spec),
		}
		if wle.Spec.Weight > 0 {
			w.Capacity = wrappers.UInt32(wle.Spec.Weight)
		}

		if addr, err := netip.ParseAddr(wle.Spec.Address); err == nil {
			w.Addresses = [][]byte{addr.AsSlice()}
		} else {
			w.Hostname = wle.Spec.Address
		}

		w.WorkloadName = kubelabels.WorkloadNameFromWorkloadEntry(wle.Name, wle.Annotations, wle.Labels)
		w.WorkloadType = workloadapi.WorkloadType_POD // XXX(shashankram): HACK to impersonate pod
		w.CanonicalName, w.CanonicalRevision = kubelabels.CanonicalService(wle.Labels, w.WorkloadName)

		setTunnelProtocol(wle.Labels, wle.Annotations, w, flags)
		var waypointStatus model.WaypointBindingStatus
		if targetWaypoint != nil {
			waypointStatus.ResourceName = targetWaypoint.ResourceName()
		} else if waypointErr != nil {
			waypointStatus.Error = waypointErr
		}
		wi := &model.WorkloadInfo{
			Workload:     w,
			Labels:       wle.Labels,
			Source:       model.TypedObject{NamespacedName: types.NamespacedName{Name: wle.Name, Namespace: wle.Namespace}, Kind: kind.WorkloadEntry},
			CreationTime: wle.CreationTimestamp.Time,
			Waypoint:     waypointStatus,
		}
		if canSkipPrecompute && network != localNetworkGetter(ctx).String() {
			// Remote network workload that will be coalesced into a split-horizon workload; skip precompute
			return wi
		}
		return precomputeWorkloadPtr(wi)
	}
}

func (a Builder) workloadEntryWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	workloadServices krt.Collection[model.ServiceInfo],
	workloadServicesNamespaceIndex krt.Index[string, model.ServiceInfo],
	namespaces krt.Collection[*v1.Namespace],
) krt.TransformationSingle[*networkingclient.WorkloadEntry, model.WorkloadInfo] {
	return workloadEntryWorkloadBuilder(
		meshConfig,
		a.Networks.FetchLocalNetworkID,
		authPoliciesByNs,
		peerAuthsByNs,
		waypoints,
		workloadServices,
		workloadServicesNamespaceIndex,
		namespaces,
		a.ClusterID,
		a.Networks.FetchLocalNetworkID,
		a.Networks.GatewaysByNetwork,
		a.Flags,
		false, // No coalesced path; always precompute
	)
}

func computeWaypoint(
	ctx krt.HandlerContext,
	waypoints krt.Collection[Waypoint],
	namespaces krt.Collection[*v1.Namespace],
	workloadMeta metav1.ObjectMeta,
) (*workloadapi.ApplicationTunnel, *Waypoint, *model.StatusMessage) {
	var appTunnel *workloadapi.ApplicationTunnel
	var targetWaypoint *Waypoint
	var waypointErr *model.StatusMessage
	if instancedWaypoint := fetchWaypointForInstance(ctx, waypoints, workloadMeta); instancedWaypoint != nil {
		// we're an instance of a waypoint, set inbound tunnel info if needed
		if db := instancedWaypoint.DefaultBinding; db != nil {
			appTunnel = &workloadapi.ApplicationTunnel{
				Protocol: db.Protocol,
				Port:     db.Port,
			}
		}
	} else if waypoint, err := fetchWaypointForWorkload(ctx, waypoints, namespaces, workloadMeta); err == nil {
		// there is a workload-attached waypoint, point there with a GatewayAddress
		targetWaypoint = waypoint
	} else {
		waypointErr = err
	}
	return appTunnel, targetWaypoint, waypointErr
}

func podWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	localNetworkGetter func(krt.HandlerContext) network.ID,
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	workloadServices krt.Collection[model.ServiceInfo],
	workloadServicesNamespaceIndex krt.Index[string, model.ServiceInfo],
	endpointSlicesAddressIndex krt.Index[TargetRef, *discovery.EndpointSlice],
	namespaces krt.Collection[*v1.Namespace],
	nodes krt.Collection[Node],
	domainSuffix string,
	clusterID cluster.ID,
	networkGetter func(krt.HandlerContext) network.ID,
	gatewaysByNetwork krt.Index[network.ID, NetworkGateway],
	flags FeatureFlags,
	canSkipPrecompute bool,
) krt.TransformationSingle[*v1.Pod, model.WorkloadInfo] {
	return func(ctx krt.HandlerContext, p *v1.Pod) *model.WorkloadInfo {
		// Pod Is Pending but have a pod IP should be a valid workload, we should build it ,
		// Such as the pod have initContainer which is initialing.
		// See https://github.com/istio/istio/issues/48854
		if kubeutil.CheckPodTerminal(p) {
			return nil
		}
		k8sPodIPs := getPodIPs(p)
		if len(k8sPodIPs) == 0 {
			return nil
		}
		podIPs, err := slices.MapErr(k8sPodIPs, func(e v1.PodIP) ([]byte, error) {
			n, err := netip.ParseAddr(e.IP)
			if err != nil {
				return nil, err
			}
			return n.AsSlice(), nil
		})
		if err != nil {
			// Is this possible? Probably not in typical case, but anyone could put garbage there.
			return nil
		}
		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())
		policies := buildWorkloadPolicies(ctx, authPoliciesByNs, peerAuthsByNs, meshCfg, p.Labels, p.Namespace)
		fo := []krt.FetchOption{krt.FilterIndex(workloadServicesNamespaceIndex, p.Namespace), krt.FilterSelectsNonEmpty(p.GetLabels())}
		if !features.EnableServiceEntrySelectPods {
			fo = append(fo, krt.FilterGeneric(func(a any) bool {
				return a.(model.ServiceInfo).Source.Kind == kind.Service
			}))
		}
		services := krt.Fetch(ctx, workloadServices, fo...)
		services = append(services, matchingServicesWithoutSelectors(ctx, p, services, workloadServices, endpointSlicesAddressIndex, domainSuffix)...)
		// Logic from https://github.com/kubernetes/kubernetes/blob/7c873327b679a70337288da62b96dd610858181d/staging/src/k8s.io/endpointslice/utils.go#L37
		// Kubernetes has Ready, Serving, and Terminating. We only have a boolean, which is sufficient for our cases
		status := workloadapi.WorkloadStatus_HEALTHY
		if !IsPodReady(p) || p.DeletionTimestamp != nil {
			status = workloadapi.WorkloadStatus_UNHEALTHY
		}
		// We only check the network of the first IP. This should be fine; it is not supported for a single pod to span multiple networks
		network := networkGetter(ctx).String()

		appTunnel, targetWaypoint, _ := computeWaypoint(ctx, waypoints, namespaces, p.ObjectMeta)

		// enforce traversing waypoints
		policies = append(policies, implicitWaypointPolicies(flags, ctx, waypoints, targetWaypoint, services)...)

		w := &workloadapi.Workload{
			Uid:                   generatePodUID(clusterID, p),
			Name:                  p.Name,
			Namespace:             p.Namespace,
			Network:               network,
			NetworkGateway:        getNetworkGatewayAddress(ctx, network, gatewaysByNetwork),
			ClusterId:             string(clusterID),
			Addresses:             podIPs,
			ServiceAccount:        p.Spec.ServiceAccountName,
			Waypoint:              targetWaypoint.GetAddress(),
			Node:                  p.Spec.NodeName,
			ApplicationTunnel:     appTunnel,
			Services:              constructServices(p, services),
			AuthorizationPolicies: policies,
			Status:                status,
			TrustDomain:           pickTrustDomain(meshCfg),
			Locality:              getPodLocality(ctx, nodes, p),
		}

		if p.Spec.HostNetwork {
			w.NetworkMode = workloadapi.NetworkMode_HOST_NETWORK
		}

		w.WorkloadName = workloadName(p)
		w.WorkloadType = workloadapi.WorkloadType_POD // backwards compatibility
		w.CanonicalName, w.CanonicalRevision = kubelabels.CanonicalService(p.Labels, w.WorkloadName)

		setTunnelProtocol(p.Labels, p.Annotations, w, flags)
		wi := &model.WorkloadInfo{
			Workload:     w,
			Labels:       p.Labels,
			Source:       model.TypedObject{NamespacedName: types.NamespacedName{Name: p.Name, Namespace: p.Namespace}, Kind: kind.Pod},
			CreationTime: p.CreationTimestamp.Time,
		}
		if canSkipPrecompute && network != localNetworkGetter(ctx).String() {
			// Remote network workload that will be coalesced into a split-horizon workload; skip precompute
			return wi
		}
		return precomputeWorkloadPtr(wi)
	}
}

func (a Builder) podWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	workloadServices krt.Collection[model.ServiceInfo],
	workloadServicesNamespaceIndex krt.Index[string, model.ServiceInfo],
	endpointSlicesAddressIndex krt.Index[TargetRef, *discovery.EndpointSlice],
	namespaces krt.Collection[*v1.Namespace],
	nodes krt.Collection[Node],
) krt.TransformationSingle[*v1.Pod, model.WorkloadInfo] {
	return podWorkloadBuilder(
		meshConfig,
		a.Networks.FetchLocalNetworkID,
		authPoliciesByNs,
		peerAuthsByNs,
		waypoints,
		workloadServices,
		workloadServicesNamespaceIndex,
		endpointSlicesAddressIndex,
		namespaces,
		nodes,
		a.DomainSuffix,
		a.ClusterID,
		a.Networks.FetchLocalNetworkID,
		a.Networks.GatewaysByNetwork,
		a.Flags,
		false, // No coalesced path; always precompute
	)
}

func getPodIPs(p *v1.Pod) []v1.PodIP {
	k8sPodIPs := p.Status.PodIPs
	if len(k8sPodIPs) == 0 && p.Status.PodIP != "" {
		k8sPodIPs = []v1.PodIP{{IP: p.Status.PodIP}}
	}
	return k8sPodIPs
}

// matchingServicesWithoutSelectors finds all Services that match a given pod that do not use selectors.
// See https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors for more info.
// For selector service, we query by the selector elsewhere, so this only handles the services that are NOT already found
// by a selector.
// For EndpointSlices that happen to point to the same IP as the pod, but are not directly bound to the pod (via TargetRef),
// we ignore them here. These will produce a model.Workload directly from the EndpointSlice, but with limited information;
// we do not implicitly merge a Pod with an EndpointSlice just based on IP.
func matchingServicesWithoutSelectors(
	ctx krt.HandlerContext,
	p *v1.Pod,
	alreadyMatchingServices []model.ServiceInfo,
	workloadServices krt.Collection[model.ServiceInfo],
	endpointSlicesAddressIndex krt.Index[TargetRef, *discovery.EndpointSlice],
	domainSuffix string,
) []model.ServiceInfo {
	var res []model.ServiceInfo
	// Build out our set of already-matched services to avoid double-selecting a service
	seen := sets.NewWithLength[string](len(alreadyMatchingServices))
	for _, s := range alreadyMatchingServices {
		seen.Insert(s.Service.Hostname)
	}
	tr := TargetRef{
		Kind:      gvk.Pod.Kind,
		Namespace: p.Namespace,
		Name:      p.Name,
		UID:       p.UID,
	}
	// For each IP, find any endpointSlices referencing it.
	matchedSlices := endpointSlicesAddressIndex.Fetch(ctx, tr)
	for _, es := range matchedSlices {
		serviceName, f := es.Labels[discovery.LabelServiceName]
		if !f {
			// Not for a service; we don't care about it.
			continue
		}
		hostname := string(kube.ServiceHostname(serviceName, es.Namespace, domainSuffix))
		if seen.Contains(hostname) {
			// We already know about this service
			continue
		}
		// This pod is included in the EndpointSlice. We need to fetch the Service object for it, by key.
		serviceKey := es.Namespace + "/" + hostname
		svcs := krt.Fetch(ctx, workloadServices, krt.FilterKey(serviceKey), krt.FilterGeneric(func(a any) bool {
			// Only find Service, not Service Entry
			return a.(model.ServiceInfo).Source.Kind == kind.Service
		}))
		if len(svcs) == 0 {
			// no service found
			continue
		}
		// There SHOULD only be one. This is only for `Service` which has unique hostnames.
		svc := svcs[0]
		res = append(res, svc)
	}
	return res
}

func buildWorkloadPolicies(
	ctx krt.HandlerContext,
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	meshCfg *MeshConfig,
	workloadLabels map[string]string,
	workloadNamespace string,
) []string {
	// We need to filter from the policies that are present, which apply to us.
	// We only want label selector ones; global ones are not attached to the final WorkloadInfo
	// In general we just take all of the policies
	opts := []krt.FetchOption{
		krt.FilterSelects(workloadLabels),
	}
	basePolicies := authPoliciesByNs.Fetch(ctx, meshCfg.RootNamespace, opts...)
	if workloadNamespace != meshCfg.RootNamespace {
		if nsPolicies := authPoliciesByNs.Fetch(ctx, workloadNamespace, opts...); len(nsPolicies) > 0 {
			basePolicies = append(basePolicies, nsPolicies...)
		}
	}
	policies := slices.Sort(slices.Map(basePolicies, func(t model.WorkloadAuthorization) string {
		return t.ResourceName()
	}))
	// We could do a non-FilterGeneric but krt currently blows up if we depend on the same collection twice
	auths := fetchPeerAuthentications(ctx, peerAuthsByNs, meshCfg, workloadNamespace, workloadLabels)
	policies = append(policies, convertedSelectorPeerAuthentications(meshCfg.GetRootNamespace(), auths)...)
	return policies
}

func serviceEntryWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	namespaces krt.Collection[*v1.Namespace],
	workloadServices krt.Collection[model.ServiceInfo],
	clusterID cluster.ID,
	networkGetter func(krt.HandlerContext) network.ID,
	gatewaysByNetwork krt.Index[network.ID, NetworkGateway],
	flags FeatureFlags,
) krt.TransformationMulti[*networkingclient.ServiceEntry, model.WorkloadInfo] {
	serviceEntryInfosByNamespaceAndName := krt.NewIndex(workloadServices, "serviceEntryInfosByNamespaceAndName", func(si model.ServiceInfo) []string {
		if si.Source.Kind != kind.ServiceEntry {
			return nil
		}
		return []string{si.Source.NamespacedName.Namespace + "/" + si.Source.NamespacedName.Name}
	})
	return func(ctx krt.HandlerContext, se *networkingclient.ServiceEntry) []model.WorkloadInfo {
		eps := se.Spec.Endpoints
		// If we have a DNS service, endpoints are not required
		implicitEndpoints := len(eps) == 0 &&
			(se.Spec.Resolution == networkingv1alpha3.ServiceEntry_DNS ||
				se.Spec.Resolution == networkingv1alpha3.ServiceEntry_DYNAMIC_DNS ||
				se.Spec.Resolution == networkingv1alpha3.ServiceEntry_DNS_ROUND_ROBIN) &&
			se.Spec.WorkloadSelector == nil
		if len(eps) == 0 && !implicitEndpoints {
			return nil
		}

		nw := networkGetter(ctx)
		// here we don't care about the *service* waypoint (hence it is nil); we are only going to use a subset of the info in
		// `allServices` (since we are building workloads here, not services).
		allServices := serviceEntryInfosByNamespaceAndName.Fetch(ctx, se.Namespace+"/"+se.Name)
		if len(allServices) == 0 {
			// This ServiceEntry was pruned entirely by deduplication in the WorkloadServices collection, it's endpoints should not be sent to the data plane.
			// TODO: Once we write deduplication results to ServiceEntry status, we should consider lowering this to Debug to reduce noise in the logs. For now, we warn.
			log.Warnf("ServiceEntry %s/%s was pruned by deduplication. Endpoints from this ServiceEntry will not be sent to the data plane.", se.Namespace, se.Name)
			return nil
		}
		if implicitEndpoints {
			eps = slices.Map(allServices, func(si model.ServiceInfo) *networkingv1alpha3.WorkloadEntry {
				return &networkingv1alpha3.WorkloadEntry{Address: si.Service.Hostname}
			})
		}
		if len(eps) == 0 {
			return nil
		}
		res := make([]model.WorkloadInfo, 0, len(eps))

		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())

		for i, wle := range eps {
			services := allServices
			if implicitEndpoints {
				// For implicit endpoints, we generate each one from the hostname it was from.
				// Otherwise, use all.
				// [i] is safe here since we these are constructed to mirror each other
				services = []model.ServiceInfo{allServices[i]}
			}

			policies := buildWorkloadPolicies(ctx, authPoliciesByNs, peerAuthsByNs, meshCfg, se.Labels, se.Namespace)

			var appTunnel *workloadapi.ApplicationTunnel
			var targetWaypoint *Waypoint
			// Endpoint does not have a real ObjectMeta, so make one
			if !implicitEndpoints {
				objMeta := metav1.ObjectMeta{
					Name:      se.Name,
					Namespace: se.Namespace,
					Labels:    wle.Labels,
				}
				appTunnel, targetWaypoint, _ = computeWaypoint(ctx, waypoints, namespaces, objMeta)
			}

			// enforce traversing waypoints
			policies = append(policies, implicitWaypointPolicies(flags, ctx, waypoints, targetWaypoint, services)...)

			if wle.Network != "" {
				nw = network.ID(wle.Network)
			}
			w := &workloadapi.Workload{
				Uid:                   generateServiceEntryUID(clusterID, se.Namespace, se.Name, wle.Address),
				Name:                  se.Name,
				Namespace:             se.Namespace,
				Network:               nw.String(),
				NetworkGateway:        getNetworkGatewayAddress(ctx, nw.String(), gatewaysByNetwork),
				ClusterId:             string(clusterID),
				ServiceAccount:        wle.ServiceAccount,
				Services:              constructServicesFromWorkloadEntry(wle, services),
				AuthorizationPolicies: policies,
				Status:                workloadapi.WorkloadStatus_HEALTHY,
				Waypoint:              targetWaypoint.GetAddress(),
				ApplicationTunnel:     appTunnel,
				TrustDomain:           pickTrustDomain(meshCfg),
				Locality:              getWorkloadEntryLocality(wle),
			}
			if wle.Weight > 0 {
				w.Capacity = wrappers.UInt32(wle.Weight)
			}

			if addr, err := netip.ParseAddr(wle.Address); err == nil {
				w.Addresses = [][]byte{addr.AsSlice()}
			} else {
				w.Hostname = wle.Address
			}

			w.WorkloadName, w.WorkloadType = se.Name, workloadapi.WorkloadType_POD // XXX(shashankram): HACK to impersonate pod
			w.CanonicalName, w.CanonicalRevision = kubelabels.CanonicalService(se.Labels, w.WorkloadName)

			setTunnelProtocol(se.Labels, se.Annotations, w, flags)
			res = append(res, precomputeWorkload(model.WorkloadInfo{
				Workload:     w,
				Labels:       se.Labels,
				Source:       model.TypedObject{Kind: kind.WorkloadEntry},
				CreationTime: se.CreationTimestamp.Time,
			}))
		}
		return res
	}
}

func (a Builder) serviceEntryWorkloadBuilder(
	meshConfig krt.Singleton[MeshConfig],
	authPoliciesByNs krt.Index[string, model.WorkloadAuthorization],
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	waypoints krt.Collection[Waypoint],
	namespaces krt.Collection[*v1.Namespace],
	workloadServices krt.Collection[model.ServiceInfo],
) krt.TransformationMulti[*networkingclient.ServiceEntry, model.WorkloadInfo] {
	return serviceEntryWorkloadBuilder(
		meshConfig,
		authPoliciesByNs,
		peerAuthsByNs,
		waypoints,
		namespaces,
		workloadServices,
		a.ClusterID,
		a.Networks.FetchLocalNetworkID,
		a.Networks.GatewaysByNetwork,
		a.Flags,
	)
}

func endpointSlicesBuilder(
	meshConfig krt.Singleton[MeshConfig],
	workloadServices krt.Collection[model.ServiceInfo],
	domainSuffix string,
	clusterID cluster.ID,
	networkGetter func(krt.HandlerContext) network.ID,
) krt.TransformationMulti[*discovery.EndpointSlice, model.WorkloadInfo] {
	return func(ctx krt.HandlerContext, es *discovery.EndpointSlice) []model.WorkloadInfo {
		// EndpointSlices carry port information and a list of IPs.
		// We only care about EndpointSlices that are for a Service.
		// Otherwise, it is just an arbitrary bag of IP addresses for some user-specific purpose, which doesn't have a clear
		// usage for us (if it had some additional info like service account, etc, then perhaps it would be useful).
		serviceName, f := es.Labels[discovery.LabelServiceName]
		if !f {
			return nil
		}
		if es.AddressType == discovery.AddressTypeFQDN {
			// Currently we do not support FQDN. In theory, we could, but its' support in Kubernetes entirely is questionable and
			// may be removed in the near future.
			return nil
		}
		var res []model.WorkloadInfo
		seen := sets.New[string]()
		meshCfg := krt.FetchOne(ctx, meshConfig.AsCollection())

		// The slice must be for a single service, based on the label above.
		serviceKey := es.Namespace + "/" + string(kube.ServiceHostname(serviceName, es.Namespace, domainSuffix))
		svcs := krt.Fetch(ctx, workloadServices, krt.FilterKey(serviceKey), krt.FilterGeneric(func(a any) bool {
			// Only find Service, not Service Entry
			return a.(model.ServiceInfo).Source.Kind == kind.Service
		}))
		if len(svcs) == 0 {
			// no service found
			return nil
		}
		// There SHOULD only be one. This is only Service which has unique hostnames.
		svc := svcs[0]

		// Translate slice ports to our port.
		pl := &workloadapi.PortList{Ports: make([]*workloadapi.Port, 0, len(es.Ports))}
		for _, p := range es.Ports {
			// We must have name and port (Kubernetes should always set these)
			if p.Name == nil {
				continue
			}
			if p.Port == nil {
				continue
			}
			// We only support TCP for now
			if p.Protocol == nil || *p.Protocol != v1.ProtocolTCP {
				continue
			}
			// Endpoint slice port has name (service port name, not containerPort) and port (targetPort)
			// We need to join with the Service port list to translate the port name to
			for _, svcPort := range svc.Service.Ports {
				portName := svc.PortNames[int32(svcPort.ServicePort)]
				if portName.PortName != *p.Name {
					continue
				}
				pl.Ports = append(pl.Ports, &workloadapi.Port{
					ServicePort: svcPort.ServicePort,
					TargetPort:  uint32(*p.Port),
				})
				break
			}
		}
		services := map[string]*workloadapi.PortList{
			serviceKey: pl,
		}

		// Each endpoint in the slice is going to create a Workload
		for _, ep := range es.Endpoints {
			if ep.TargetRef != nil && ep.TargetRef.Kind == gvk.Pod.Kind {
				// Normal case; this is a slice for a pod. We already handle pods, with much more information, so we can skip them
				continue
			}
			// This should not be possible
			if len(ep.Addresses) == 0 {
				continue
			}
			// We currently only support 1 address. Kubernetes will never set more (IPv4 and IPv6 will be two slices), so its mostly undefined.
			key := ep.Addresses[0]
			if seen.InsertContains(key) {
				// Shouldn't happen. Make sure our UID is actually unique
				log.Warnf("IP address %v seen twice in %v/%v", key, es.Namespace, es.Name)
				continue
			}
			health := workloadapi.WorkloadStatus_UNHEALTHY
			if ep.Conditions.Ready == nil || *ep.Conditions.Ready {
				health = workloadapi.WorkloadStatus_HEALTHY
			}
			// Translate our addresses.
			// Note: users may put arbitrary addresses here. It is recommended by Kubernetes to not
			// give untrusted users EndpointSlice write access.
			addresses, err := slices.MapErr(ep.Addresses, func(e string) ([]byte, error) {
				n, err := netip.ParseAddr(e)
				if err != nil {
					log.Warnf("invalid address in endpointslice %v: %v", e, err)
					return nil, err
				}
				return n.AsSlice(), nil
			})
			if err != nil {
				// If any invalid, skip
				continue
			}
			network := networkGetter(ctx)
			w := &workloadapi.Workload{
				Uid:         string(clusterID) + "/discovery.k8s.io/EndpointSlice/" + es.Namespace + "/" + es.Name + "/" + key,
				Name:        es.Name,
				Namespace:   es.Namespace,
				Addresses:   addresses,
				Hostname:    "",
				Network:     network.String(),
				TrustDomain: pickTrustDomain(meshCfg),
				Services:    services,
				Status:      health,
				ClusterId:   string(clusterID),
				// For opaque endpoints, we do not know anything about them. They could be overlapping with other IPs, so treat it
				// as a shared address rather than a unique one.
				NetworkMode:           workloadapi.NetworkMode_HOST_NETWORK,
				AuthorizationPolicies: nil, // Not support. This can only be used for outbound, so not relevant
				ServiceAccount:        "",  // Unknown. TODO: make this possible to express in ztunnel
				Waypoint:              nil, // Not supported. In theory, we could allow it as an EndpointSlice label, but there is no real use case.
				Locality:              nil, // Not supported. We could maybe, there is a "zone", but it doesn't seem to be well supported
			}
			res = append(res, precomputeWorkload(model.WorkloadInfo{
				Workload:     w,
				Labels:       nil,
				Source:       model.TypedObject{Kind: kind.EndpointSlice},
				CreationTime: es.CreationTimestamp.Time,
			}))
		}

		return res
	}
}

func (a Builder) endpointSlicesBuilder(
	meshConfig krt.Singleton[MeshConfig],
	workloadServices krt.Collection[model.ServiceInfo],
) krt.TransformationMulti[*discovery.EndpointSlice, model.WorkloadInfo] {
	return endpointSlicesBuilder(
		meshConfig,
		workloadServices,
		a.DomainSuffix,
		a.ClusterID,
		a.Networks.FetchLocalNetworkID,
	)
}

func setTunnelProtocol(labels, annotations map[string]string, w *workloadapi.Workload, flags FeatureFlags) {
	if flags.EnableMtlsTransportProtocol && labels[label.SecurityTlsMode.Name] == model.IstioMutualTLSModeLabel {
		w.TunnelProtocol = workloadapi.TunnelProtocol_LEGACY_ISTIO_MTLS
	}
	if annotations[annotation.AmbientRedirection.Name] == constants.AmbientRedirectionEnabled {
		// Configured for override
		w.TunnelProtocol = workloadapi.TunnelProtocol_HBONE
	}
	// Otherwise supports tunnel directly
	if model.SupportsTunnel(labels, model.TunnelHTTP) {
		w.TunnelProtocol = workloadapi.TunnelProtocol_HBONE
		w.NativeTunnel = true
	}
	// TODO: add TunnelProtocol_LEGACY_ISTIO_MTLS. This requires Ztunnel to support it for 1 version, though, since a new
	// enum field would be rejected.
}

func pickTrustDomain(mesh *MeshConfig) string {
	if td := mesh.GetTrustDomain(); td != "cluster.local" {
		return td
	}
	return ""
}

func fetchPeerAuthentications(
	ctx krt.HandlerContext,
	peerAuthsByNs krt.Index[string, *securityclient.PeerAuthentication],
	meshCfg *MeshConfig,
	ns string,
	matchLabels map[string]string,
) []*securityclient.PeerAuthentication {
	// Policies in the workload's own namespace apply if they have no selector or their selector matches.
	auths := peerAuthsByNs.Fetch(ctx, ns, krt.FilterGeneric(func(a any) bool {
		sel := a.(*securityclient.PeerAuthentication).Spec.Selector
		if sel == nil {
			return true // No selector matches everything
		}
		return labels.Instance(sel.MatchLabels).SubsetOf(matchLabels)
	}))
	// Mesh-wide policies live in the root namespace and must not have a selector. When the workload is
	// itself in the root namespace, these are already covered by the fetch above.
	if rootNamespace := meshCfg.GetRootNamespace(); ns != rootNamespace {
		rootAuths := peerAuthsByNs.Fetch(ctx, rootNamespace, krt.FilterGeneric(func(a any) bool {
			return a.(*securityclient.PeerAuthentication).Spec.Selector == nil
		}))
		auths = append(auths, rootAuths...)
	}
	return auths
}

func constructServicesFromWorkloadEntry(p *networkingv1alpha3.WorkloadEntry, services []model.ServiceInfo) map[string]*workloadapi.PortList {
	res := map[string]*workloadapi.PortList{}
	for _, svc := range services {
		n := namespacedHostname(svc.Service.Namespace, svc.Service.Hostname)
		pl := &workloadapi.PortList{}
		res[n] = pl
		for _, port := range svc.Service.Ports {
			targetPort := port.TargetPort
			// Named targetPort has different semantics from Service vs ServiceEntry
			if svc.Source.Kind == kind.Service {
				// Service has explicit named targetPorts.
				if named, f := svc.PortNames[int32(port.ServicePort)]; f {
					if named.TargetPortName != "" {
						// This port is a named target port, look it up
						tv, ok := p.Ports[named.TargetPortName]
						if !ok {
							// We needed an explicit port, but didn't find one - skip this port
							continue
						}
						targetPort = tv
					} else {
						tv, ok := p.Ports[named.PortName]
						if ok {
							targetPort = tv
						}
					}
				}
			} else {
				// ServiceEntry has no explicit named targetPorts; targetPort only allows a number
				// Instead, there is name matching between the port names
				if named, f := svc.PortNames[int32(port.ServicePort)]; f {
					// get port name or target port
					tv, ok := p.Ports[named.PortName]
					if ok {
						// if we match one, override it. Otherwise, use the service port
						targetPort = tv
					} else if targetPort == 0 {
						targetPort = port.ServicePort
					}
				}
			}
			pl.Ports = append(pl.Ports, &workloadapi.Port{
				ServicePort: port.ServicePort,
				TargetPort:  targetPort,
			})
		}
	}
	return res
}

func workloadName(pod *v1.Pod) string {
	objMeta, _ := kubeutil.GetWorkloadMetaFromPod(pod)
	return objMeta.Name
}

func constructServices(p *v1.Pod, services []model.ServiceInfo) map[string]*workloadapi.PortList {
	res := map[string]*workloadapi.PortList{}
	for _, svc := range services {
		n := namespacedHostname(svc.Service.Namespace, svc.Service.Hostname)
		pl := &workloadapi.PortList{
			Ports: make([]*workloadapi.Port, 0, len(svc.Service.Ports)),
		}
		res[n] = pl
		for _, port := range svc.Service.Ports {
			targetPort := port.TargetPort
			// The svc.Ports represents the workloadapi.Service, which drops the port name info and just has numeric target Port.
			// TargetPort can be 0 which indicates its a named port. Check if its a named port and replace with the real targetPort if so.
			if named, f := svc.PortNames[int32(port.ServicePort)]; f {
				if named.TargetPortName != "" {
					// Kubernetes Service semantics: explicit named targetPort on the Service
					// Pods only match on TargetPort names
					tp, ok := FindPortName(p, named.TargetPortName)
					if !ok {
						// Port not present for this workload. Exclude the port entirely
						continue
					}
					targetPort = uint32(tp)
				} else if svc.Source.Kind == kind.ServiceEntry && named.PortName != "" {
					// ServiceEntry semantics: no explicit named targetPort field; match by ServiceEntry port name to Pod container port name
					if tp, ok := FindPortName(p, named.PortName); ok {
						targetPort = uint32(tp)
					} else if targetPort == 0 {
						// Fallback to service port if we couldn't resolve a name and targetPort wasn't set
						targetPort = port.ServicePort
					}
				}
			}

			pl.Ports = append(pl.Ports, &workloadapi.Port{
				ServicePort: port.ServicePort,
				TargetPort:  targetPort,
			})
		}
	}
	return res
}

func getPodLocality(ctx krt.HandlerContext, Nodes krt.Collection[Node], pod *v1.Pod) *workloadapi.Locality {
	// NodeName is set by the scheduler after the pod is created
	// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#late-initialization
	node := krt.FetchOne(ctx, Nodes, krt.FilterKey(pod.Spec.NodeName))
	if node == nil {
		if pod.Spec.NodeName != "" {
			log.Warnf("unable to get node %q for pod %q/%q", pod.Spec.NodeName, pod.Namespace, pod.Name)
		}
		return nil
	}
	return node.Locality
}

func getWorkloadEntryLocality(p *networkingv1alpha3.WorkloadEntry) *workloadapi.Locality {
	region, zone, subzone := labelutil.SplitLocalityLabel(p.GetLocality())
	if region == "" && zone == "" && subzone == "" {
		return nil
	}
	return &workloadapi.Locality{
		Region:  region,
		Zone:    zone,
		Subzone: subzone,
	}
}

func implicitWaypointPolicies(
	flags FeatureFlags,
	ctx krt.HandlerContext,
	Waypoints krt.Collection[Waypoint],
	waypoint *Waypoint,
	services []model.ServiceInfo,
) []string {
	if !flags.DefaultAllowFromWaypoint {
		return nil
	}
	serviceWaypointKeys := slices.MapFilter(services, func(si model.ServiceInfo) *string {
		if si.Waypoint.ResourceName == "" || (waypoint != nil && waypoint.ResourceName() == si.Waypoint.ResourceName) {
			return nil
		}
		return ptr.Of(si.Waypoint.ResourceName)
	})
	if len(serviceWaypointKeys) == 0 {
		if waypoint != nil {
			n := implicitWaypointPolicyName(flags, waypoint)
			if n != "" {
				return []string{waypoint.Namespace + "/" + n}
			}
		}
		return nil
	}
	waypoints := krt.Fetch(ctx, Waypoints, krt.FilterKeys(serviceWaypointKeys...))
	if waypoint != nil {
		waypoints = append(waypoints, *waypoint)
	}

	return slices.MapFilter(waypoints, func(w Waypoint) *string {
		policy := implicitWaypointPolicyName(flags, &w)
		if policy == "" {
			return nil
		}
		return ptr.Of(w.Namespace + "/" + policy)
	})
}

func gatewayUID(gw model.NetworkGateway) string {
	return "NetworkGateway/" + string(gw.Network) + "/" + gw.Addr + "/" + strconv.Itoa(int(gw.HBONEPort))
}

// convertGateway always converts a NetworkGateway into a Workload.
// Workloads have a NetworkGateway field, which is effectively a pointer to another object (Service or Workload); in order
// to facilitate this we need to translate our Gateway model down into a WorkloadInfo ztunnel can understand.
func convertGateway(mesh *MeshConfig) func(gw NetworkGateway) model.WorkloadInfo {
	return func(gw NetworkGateway) model.WorkloadInfo {
		wl := &workloadapi.Workload{
			Uid:            gatewayUID(gw.NetworkGateway),
			Name:           gatewayUID(gw.NetworkGateway),
			ServiceAccount: gw.ServiceAccount.Name,
			Namespace:      gw.ServiceAccount.Namespace,
			Network:        gw.Network.String(),
			TrustDomain:    pickTrustDomain(mesh),
		}

		if ip, err := netip.ParseAddr(gw.Addr); err == nil {
			wl.Addresses = append(wl.Addresses, ip.AsSlice())
		} else {
			wl.Hostname = gw.Addr
		}

		return precomputeWorkload(model.WorkloadInfo{Workload: wl})
	}
}

func getNetworkGatewayAddress(
	ctx krt.HandlerContext,
	n string,
	gatewaysByNetwork krt.Index[network.ID, NetworkGateway],
) *workloadapi.GatewayAddress {
	if networks := LookupNetworkGateway(ctx, network.ID(n), gatewaysByNetwork); len(networks) > 0 {
		// Currently only support one, so find the first one that is valid
		for _, net := range networks {
			if net.HBONEPort == 0 {
				continue
			}
			ip, err := netip.ParseAddr(net.Addr)
			if err != nil {
				// This is a hostname...
				return &workloadapi.GatewayAddress{
					Destination: &workloadapi.GatewayAddress_Hostname{
						// probably use from Cidr instead?
						Hostname: &workloadapi.NamespacedHostname{
							Namespace: net.ServiceAccount.Namespace,
							Hostname:  net.Addr,
						},
					},
					HboneMtlsPort: net.HBONEPort,
				}
			}
			// Else it must be an IP
			return &workloadapi.GatewayAddress{
				Destination: &workloadapi.GatewayAddress_Address{
					// probably use from Cidr instead?
					Address: &workloadapi.NetworkAddress{
						Network: net.Network.String(),
						Address: ip.AsSlice(),
					},
				},
				HboneMtlsPort: net.HBONEPort,
			}
		}
	}
	return nil
}

// TargetRef is a subset of the Kubernetes ObjectReference which has some fields we don't care about
type TargetRef struct {
	Kind      string
	Namespace string
	Name      string
	UID       types.UID
}

func (t TargetRef) String() string {
	return t.Kind + "/" + t.Namespace + "/" + t.Name + "/" + string(t.UID)
}

// endpointSliceAddressIndex builds an index from IP Address
func endpointSliceAddressIndex(EndpointSlices krt.Collection[*discovery.EndpointSlice]) krt.Index[TargetRef, *discovery.EndpointSlice] {
	return krt.NewIndex(EndpointSlices, "targetRef", func(es *discovery.EndpointSlice) []TargetRef {
		if es.AddressType == discovery.AddressTypeFQDN {
			// Currently we do not support FQDN.
			return nil
		}
		_, f := es.Labels[discovery.LabelServiceName]
		if !f {
			// Not for a service; we don't care about it.
			return nil
		}
		res := make([]TargetRef, 0, len(es.Endpoints))
		for _, ep := range es.Endpoints {
			if ep.TargetRef == nil || ep.TargetRef.Kind != gvk.Pod.Kind {
				// We only want pods here
				continue
			}
			tr := TargetRef{
				Kind:      ep.TargetRef.Kind,
				Namespace: ep.TargetRef.Namespace,
				Name:      ep.TargetRef.Name,
				UID:       ep.TargetRef.UID,
			}
			res = append(res, tr)
		}
		return res
	})
}

func precomputeWorkloadPtr(w *model.WorkloadInfo) *model.WorkloadInfo {
	return ptr.Of(precomputeWorkload(*w))
}

func precomputeWorkload(w model.WorkloadInfo) model.WorkloadInfo {
	w.AsAddress = model.NewAddressInfo(workloadToAddress(w.Workload))
	w.MarshaledAddress = w.AsAddress.Marshaled
	return w
}

func toAppProtocolFromKube(p v1.ServicePort) workloadapi.AppProtocol {
	return toAppProtocolFromProtocol(cfgkube.ConvertProtocol(p.Port, p.Name, p.Protocol, p.AppProtocol))
}

func toAppProtocolFromProtocol(p protocol.Instance) workloadapi.AppProtocol {
	switch p {
	case protocol.HTTP:
		return workloadapi.AppProtocol_HTTP11
	case protocol.HTTP2:
		return workloadapi.AppProtocol_HTTP2
	case protocol.GRPC:
		return workloadapi.AppProtocol_GRPC
	case protocol.TLS:
		return workloadapi.AppProtocol_TLS
	case protocol.HTTPS:
		return workloadapi.AppProtocol_TLS
	case protocol.TCP:
		return workloadapi.AppProtocol_TCP
	}
	return workloadapi.AppProtocol_UNKNOWN
}
