// 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.

package translate

import (
	"fmt"
	"strings"

	"github.com/ghodss/yaml"

	"istio.io/api/operator/v1alpha1"

	"istio.io/istio/operator/pkg/name"
	"istio.io/istio/operator/pkg/tpath"
	"istio.io/istio/operator/pkg/util"
	"istio.io/istio/operator/pkg/version"
	oversion "istio.io/istio/operator/version"
)

// ReverseTranslator is a set of mappings to translate between values.yaml and API paths, charts, k8s paths.
type ReverseTranslator struct {
	Version version.MinorVersion
	// APIMapping is Values.yaml path to API path mapping using longest prefix match. If the path is a non-leaf node,
	// the output path is the matching portion of the path, plus any remaining output path.
	APIMapping map[string]*Translation `yaml:"apiMapping,omitempty"`
	// KubernetesPatternMapping defines mapping patterns from k8s resource paths to IstioOperator API paths.
	KubernetesPatternMapping map[string]string `yaml:"kubernetesPatternMapping,omitempty"`
	// KubernetesMapping defines actual k8s mappings generated from KubernetesPatternMapping before each translation.
	KubernetesMapping map[string]*Translation `yaml:"kubernetesMapping,omitempty"`
	// GatewayKubernetesMapping defines actual k8s mappings for gateway components generated from KubernetesPatternMapping before each translation.
	GatewayKubernetesMapping gatewayKubernetesMapping `yaml:"GatewayKubernetesMapping,omitempty"`
	// ValuesToComponentName defines mapping from value path to component name in API paths.
	ValuesToComponentName map[string]name.ComponentName `yaml:"valuesToComponentName,omitempty"`
}

type gatewayKubernetesMapping struct {
	IngressMapping map[string]*Translation
	EgressMapping  map[string]*Translation
}

var (
	// Component enablement mapping. Ex "{{.ValueComponent}}.enabled": Components.{{.ComponentName}}.enabled}", nil},
	componentEnablementPattern = "Components.{{.ComponentName}}.Enabled"
	// addonEnablementPattern defines enablement pattern for addon components in IOP spec.
	addonEnablementPattern = "AddonComponents.{{.ComponentName}}.Enabled"
	// specialComponentPath lists cases of component path of values.yaml we need to have special treatment.
	specialComponentPath = map[string]bool{
		"mixer":                         true,
		"mixer.policy":                  true,
		"mixer.telemetry":               true,
		"gateways":                      true,
		"gateways.istio-ingressgateway": true,
		"gateways.istio-egressgateway":  true,
	}

	skipTranslate = map[name.ComponentName]bool{
		name.IstioBaseComponentName:          true,
		name.IstioOperatorComponentName:      true,
		name.IstioOperatorCustomResourceName: true,
		name.CNIComponentName:                true,
		name.IstiodRemoteComponentName:       true,
	}

	gatewayPathMapping = map[string]name.ComponentName{
		"gateways.istio-ingressgateway": name.IngressComponentName,
		"gateways.istio-egressgateway":  name.EgressComponentName,
	}
)

// initAPIMapping generate the reverse mapping from original translator apiMapping.
func (t *ReverseTranslator) initAPIAndComponentMapping() {
	ts := NewTranslator()
	t.APIMapping = make(map[string]*Translation)
	t.KubernetesMapping = make(map[string]*Translation)
	t.ValuesToComponentName = make(map[string]name.ComponentName)
	for valKey, outVal := range ts.APIMapping {
		t.APIMapping[outVal.OutPath] = &Translation{valKey, nil}
	}
	for cn, cm := range ts.ComponentMaps {
		// we use dedicated translateGateway for gateway instead
		if !skipTranslate[cn] && !cm.SkipReverseTranslate && !cn.IsGateway() {
			t.ValuesToComponentName[cm.ToHelmValuesTreeRoot] = cn
		}
	}
}

// initK8SMapping generates the k8s settings mapping for components that are enabled based on templates.
func (t *ReverseTranslator) initK8SMapping() error {
	outputMapping := make(map[string]*Translation)
	for valKey, componentName := range t.ValuesToComponentName {
		for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping {
			newKey, err := renderComponentName(K8SValKey, valKey)
			if err != nil {
				return err
			}
			if !componentName.IsCoreComponent() && !componentName.IsGateway() {
				outPathTmpl = "Addon" + outPathTmpl
			}
			newVal, err := renderFeatureComponentPathTemplate(outPathTmpl, componentName)
			if err != nil {
				return err
			}
			outputMapping[newKey] = &Translation{newVal, nil}
		}
	}

	t.KubernetesMapping = outputMapping

	igwOutputMapping := make(map[string]*Translation)
	egwOutputMapping := make(map[string]*Translation)
	for valKey, componentName := range gatewayPathMapping {
		mapping := igwOutputMapping
		if componentName == name.EgressComponentName {
			mapping = egwOutputMapping
		}
		for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping {
			newKey, err := renderComponentName(K8SValKey, valKey)
			if err != nil {
				return err
			}
			newP := util.PathFromString(outPathTmpl)
			mapping[newKey] = &Translation{newP[len(newP)-2:].String(), nil}
		}
	}
	t.GatewayKubernetesMapping = gatewayKubernetesMapping{IngressMapping: igwOutputMapping, EgressMapping: egwOutputMapping}
	return nil
}

// NewReverseTranslator creates a new ReverseTranslator for minorVersion and returns a ptr to it.
func NewReverseTranslator() *ReverseTranslator {
	rt := &ReverseTranslator{
		KubernetesPatternMapping: map[string]string{
			"{{.ValueComponentName}}.env":                   "Components.{{.ComponentName}}.K8s.Env",
			"{{.ValueComponentName}}.autoscaleEnabled":      "Components.{{.ComponentName}}.K8s.HpaSpec",
			"{{.ValueComponentName}}.imagePullPolicy":       "Components.{{.ComponentName}}.K8s.ImagePullPolicy",
			"{{.ValueComponentName}}.nodeSelector":          "Components.{{.ComponentName}}.K8s.NodeSelector",
			"{{.ValueComponentName}}.tolerations":           "Components.{{.ComponentName}}.K8s.Tolerations",
			"{{.ValueComponentName}}.podDisruptionBudget":   "Components.{{.ComponentName}}.K8s.PodDisruptionBudget",
			"{{.ValueComponentName}}.podAnnotations":        "Components.{{.ComponentName}}.K8s.PodAnnotations",
			"{{.ValueComponentName}}.priorityClassName":     "Components.{{.ComponentName}}.K8s.PriorityClassName",
			"{{.ValueComponentName}}.readinessProbe":        "Components.{{.ComponentName}}.K8s.ReadinessProbe",
			"{{.ValueComponentName}}.replicaCount":          "Components.{{.ComponentName}}.K8s.ReplicaCount",
			"{{.ValueComponentName}}.resources":             "Components.{{.ComponentName}}.K8s.Resources",
			"{{.ValueComponentName}}.rollingMaxSurge":       "Components.{{.ComponentName}}.K8s.Strategy",
			"{{.ValueComponentName}}.rollingMaxUnavailable": "Components.{{.ComponentName}}.K8s.Strategy",
			"{{.ValueComponentName}}.serviceAnnotations":    "Components.{{.ComponentName}}.K8s.ServiceAnnotations",
		},
	}
	rt.initAPIAndComponentMapping()
	rt.Version = oversion.OperatorBinaryVersion.MinorVersion
	return rt
}

// TranslateFromValueToSpec translates from values.yaml value to IstioOperatorSpec.
func (t *ReverseTranslator) TranslateFromValueToSpec(values []byte, force bool) (controlPlaneSpec *v1alpha1.IstioOperatorSpec, err error) {

	var yamlTree = make(map[string]interface{})
	err = yaml.Unmarshal(values, &yamlTree)
	if err != nil {
		return nil, fmt.Errorf("error when unmarshalling into untype tree %v", err)
	}

	outputTree := make(map[string]interface{})
	err = t.TranslateTree(yamlTree, outputTree, nil)
	if err != nil {
		return nil, err
	}
	outputVal, err := yaml.Marshal(outputTree)
	if err != nil {
		return nil, err
	}

	var cpSpec = &v1alpha1.IstioOperatorSpec{}
	err = util.UnmarshalWithJSONPB(string(outputVal), cpSpec, force)

	if err != nil {
		return nil, fmt.Errorf("error when unmarshalling into control plane spec %v, \nyaml:\n %s", err, outputVal)
	}

	return cpSpec, nil
}

// TranslateTree translates input value.yaml Tree to ControlPlaneSpec Tree.
func (t *ReverseTranslator) TranslateTree(valueTree map[string]interface{}, cpSpecTree map[string]interface{}, path util.Path) error {
	// translate enablement and namespace
	err := t.setEnablementFromValue(valueTree, cpSpecTree)
	if err != nil {
		return fmt.Errorf("error when translating enablement and namespace from value.yaml tree: %v", err)
	}
	// translate with api mapping
	err = t.translateAPI(valueTree, cpSpecTree)
	if err != nil {
		return fmt.Errorf("error when translating value.yaml tree with global mapping: %v", err)
	}

	// translate with k8s mapping
	if err := t.TranslateK8S(valueTree, cpSpecTree); err != nil {
		return err
	}

	if err := t.translateGateway(valueTree, cpSpecTree); err != nil {
		return fmt.Errorf("error when translating gateway with kubernetes mapping: %v", err.Error())
	}
	// translate remaining untranslated paths into component values
	err = t.translateRemainingPaths(valueTree, cpSpecTree, nil)
	if err != nil {
		return fmt.Errorf("error when translating remaining path: %v", err)
	}
	return nil
}

// TranslateK8S is a helper function to translate k8s settings from values.yaml to IstioOperator, except for gateways.
func (t *ReverseTranslator) TranslateK8S(valueTree map[string]interface{}, cpSpecTree map[string]interface{}) error {
	// translate with k8s mapping
	if err := t.initK8SMapping(); err != nil {
		return fmt.Errorf("error when initiating k8s mapping: %v", err)
	}
	if err := t.translateK8sTree(valueTree, cpSpecTree, t.KubernetesMapping); err != nil {
		return fmt.Errorf("error when translating value.yaml tree with kubernetes mapping: %v", err)
	}
	return nil
}

// setEnablementFromValue translates the enablement value of components in the values.yaml
// tree, based on feature/component inheritance relationship.
func (t *ReverseTranslator) setEnablementFromValue(valueSpec map[string]interface{}, root map[string]interface{}) error {
	for _, cni := range t.ValuesToComponentName {
		enabled, pathExist, err := IsComponentEnabledFromValue(cni, valueSpec)
		if err != nil {
			return err
		}
		if !pathExist {
			continue
		}
		tmpl := componentEnablementPattern
		if !cni.IsCoreComponent() && !cni.IsGateway() {
			tmpl = addonEnablementPattern
		}
		ceVal, err := renderFeatureComponentPathTemplate(tmpl, cni)
		if err != nil {
			return err
		}
		outCP := util.ToYAMLPath(ceVal)
		// set component enablement
		if err := tpath.WriteNode(root, outCP, enabled); err != nil {
			return err
		}
	}

	return nil
}

// WarningForGatewayK8SSettings creates deprecated warning messages
// when user try to set kubernetes settings for gateways via values api.
func (t *ReverseTranslator) WarningForGatewayK8SSettings(valuesOverlay string) (string, error) {
	gwOverlay, err := tpath.GetConfigSubtree(valuesOverlay, "gateways")
	if err != nil {
		return "", fmt.Errorf("error getting gateways overlay from valuesOverlayYaml %v", err)
	}
	if gwOverlay == "" {
		return "", nil
	}
	var deprecatedFields []string
	for inPath := range t.GatewayKubernetesMapping.IngressMapping {
		_, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false)
		if err != nil {
			scope.Debug(err.Error())
			continue
		}
		if found {
			deprecatedFields = append(deprecatedFields, inPath)
		}
	}
	for inPath := range t.GatewayKubernetesMapping.EgressMapping {
		_, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false)
		if err != nil {
			scope.Debug(err.Error())
			continue
		}
		if found {
			deprecatedFields = append(deprecatedFields, inPath)
		}
	}
	if len(deprecatedFields) == 0 {
		return "", nil
	}
	warningMessage := fmt.Sprintf("using deprecated values api paths: %s.\n"+
		" please use k8s spec of gateway components instead\n", strings.Join(deprecatedFields, ","))
	return warningMessage, nil
}

// translateGateway handles translation for gateways specific configuration
func (t *ReverseTranslator) translateGateway(valueSpec map[string]interface{}, root map[string]interface{}) error {
	for inPath, outPath := range gatewayPathMapping {
		enabled, pathExist, err := IsComponentEnabledFromValue(outPath, valueSpec)
		if err != nil {
			return err
		}
		if !pathExist && !enabled {
			continue
		}
		gwSpecs := make([]map[string]interface{}, 1)
		gwSpec := make(map[string]interface{})
		gwSpecs[0] = gwSpec
		gwSpec["enabled"] = enabled
		gwSpec["name"] = util.ToYAMLPath(inPath)[1]
		outCP := util.ToYAMLPath("Components." + string(outPath))

		if enabled {
			mapping := t.GatewayKubernetesMapping.IngressMapping
			if outPath == name.EgressComponentName {
				mapping = t.GatewayKubernetesMapping.EgressMapping
			}
			err = t.translateK8sTree(valueSpec, gwSpec, mapping)
			if err != nil {
				return err
			}
		}
		err = tpath.WriteNode(root, outCP, gwSpecs)
		if err != nil {
			return err
		}
	}
	return nil
}

// TranslateK8SfromValueToIOP use reverse translation to convert k8s settings defined in values API to IOP API.
// this ensures that user overlays that set k8s through spec.values
// are not overridden by spec.components.X.k8s settings in the base profiles
func (t *ReverseTranslator) TranslateK8SfromValueToIOP(userOverlayYaml string) (string, error) {
	valuesOverlay, err := tpath.GetConfigSubtree(userOverlayYaml, "spec.values")
	if err != nil {
		scope.Debugf("no spec.values section from userOverlayYaml %v", err)
		return "", nil
	}
	var valuesOverlayTree = make(map[string]interface{})
	err = yaml.Unmarshal([]byte(valuesOverlay), &valuesOverlayTree)
	if err != nil {
		return "", fmt.Errorf("error unmarshalling values overlay yaml into untype tree %v", err)
	}
	iopSpecTree := make(map[string]interface{})
	if err = t.TranslateK8S(valuesOverlayTree, iopSpecTree); err != nil {
		return "", err
	}
	warning, err := t.WarningForGatewayK8SSettings(valuesOverlay)
	if err != nil {
		return "", fmt.Errorf("error handling values gateway k8s settings: %v", err)
	}
	if warning != "" {
		return "", fmt.Errorf(warning)
	}
	iopSpecTreeYAML, err := yaml.Marshal(iopSpecTree)
	if err != nil {
		return "", fmt.Errorf("error marshaling reverse translated tree %v", err)
	}
	iopTreeYAML, err := tpath.AddSpecRoot(string(iopSpecTreeYAML))
	if err != nil {
		return "", fmt.Errorf("error adding spec root: %v", err)
	}
	// overlay the reverse translated iopTreeYAML back to userOverlayYaml
	finalYAML, err := util.OverlayYAML(userOverlayYaml, iopTreeYAML)
	if err != nil {
		return "", fmt.Errorf("failed to overlay the reverse translated iopTreeYAML: %v", err)
	}
	return finalYAML, err
}

// translateStrategy translates Deployment Strategy related configurations from helm values.yaml tree.
func translateStrategy(fieldName string, outPath string, value interface{}, cpSpecTree map[string]interface{}) error {
	fieldMap := map[string]string{
		"rollingMaxSurge":       "maxSurge",
		"rollingMaxUnavailable": "maxUnavailable",
	}
	newFieldName, ok := fieldMap[fieldName]
	if !ok {
		return fmt.Errorf("expected field name found in values.yaml: %s", fieldName)
	}
	outPath += ".rollingUpdate." + newFieldName

	scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath)
	if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), value); err != nil {
		return err
	}
	return nil
}

// translateHPASpec translates HPA related configurations from helm values.yaml tree.
// do not translate if autoscaleEnabled is explicitly set to false
func translateHPASpec(inPath string, outPath string, valueTree map[string]interface{}, cpSpecTree map[string]interface{}) error {
	m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath))
	if err != nil {
		return err
	}
	if found {
		asEnabled, ok := m.(bool)
		if !ok {
			return fmt.Errorf("expect autoscaleEnabled node type to be bool but got: %T", m)
		}
		if !asEnabled {
			return nil
		}
	}

	newP := util.PathFromString(inPath)
	// last path element is k8s setting name
	newPS := newP[:len(newP)-1].String()
	valMap := map[string]string{
		".autoscaleMin": ".minReplicas",
		".autoscaleMax": ".maxReplicas",
	}
	for key, newVal := range valMap {
		valPath := newPS + key
		asVal, found, err := tpath.Find(valueTree, util.ToYAMLPath(valPath))
		if found && err == nil {
			if err := setOutputAndClean(valPath, outPath+newVal, asVal, valueTree, cpSpecTree, true); err != nil {
				return err
			}
		}
	}
	valPath := newPS + ".cpu.targetAverageUtilization"
	asVal, found, err := tpath.Find(valueTree, util.ToYAMLPath(valPath))
	if found && err == nil {
		rs := make([]interface{}, 1)
		rsVal := `
- type: Resource
  resource:
    name: cpu
    targetAverageUtilization: %f`

		rsString := fmt.Sprintf(rsVal, asVal)
		if err = yaml.Unmarshal([]byte(rsString), &rs); err != nil {
			return err
		}
		if err := setOutputAndClean(valPath, outPath+".metrics", rs, valueTree, cpSpecTree, true); err != nil {
			return err
		}
	}

	// There is no direct source from value.yaml for scaleTargetRef value, we need to construct from component name
	if found {
		st := make(map[string]interface{})
		stVal := `
apiVersion: apps/v1
kind: Deployment
name: %s`

		// need to do special handling for gateways and mixer
		// ex. because deployment name should be istio-telemetry instead of istio-mixer.telemetry, we need to get rid of the prefix mixer part.
		if specialComponentPath[newPS] && len(newP) > 2 {
			newPS = newP[1 : len(newP)-1].String()
		}
		// convert from values component name to correct deployment target
		if newPS == "pilot" {
			newPS = "istiod"
		}
		stString := fmt.Sprintf(stVal, newPS)
		if err := yaml.Unmarshal([]byte(stString), &st); err != nil {
			return err
		}
		if err := setOutputAndClean(valPath, outPath+".scaleTargetRef", st, valueTree, cpSpecTree, false); err != nil {
			return err
		}
	}
	return nil
}

// setOutputAndClean is helper function to set value of iscp tree and clean the original value from value.yaml tree.
func setOutputAndClean(valPath, outPath string, outVal interface{}, valueTree, cpSpecTree map[string]interface{}, clean bool) error {
	scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath)

	if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), outVal); err != nil {
		return err
	}
	if !clean {
		return nil
	}
	if _, err := tpath.Delete(valueTree, util.ToYAMLPath(valPath)); err != nil {
		return err
	}
	return nil
}

// translateEnv translates env value from helm values.yaml tree.
func translateEnv(outPath string, value interface{}, cpSpecTree map[string]interface{}) error {
	envMap, ok := value.(map[string]interface{})
	if !ok {
		return fmt.Errorf("expect env node type to be map[string]interface{} but got: %T", value)
	}
	outEnv := make([]map[string]interface{}, len(envMap))
	cnt := 0
	for k, v := range envMap {
		outEnv[cnt] = make(map[string]interface{})
		outEnv[cnt]["name"] = k
		outEnv[cnt]["value"] = fmt.Sprintf("%v", v)
		cnt++
	}
	scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath)
	if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), outEnv); err != nil {
		return err
	}
	return nil
}

// translateK8sTree is internal method for translating K8s configurations from value.yaml tree.
func (t *ReverseTranslator) translateK8sTree(valueTree map[string]interface{},
	cpSpecTree map[string]interface{}, mapping map[string]*Translation) error {
	for inPath, v := range mapping {
		scope.Debugf("Checking for k8s path %s in helm Value.yaml tree", inPath)
		path := util.PathFromString(inPath)
		k8sSettingName := ""
		if len(path) != 0 {
			k8sSettingName = path[len(path)-1]
		}
		if k8sSettingName == "autoscaleEnabled" {
			if err := translateHPASpec(inPath, v.OutPath, valueTree, cpSpecTree); err != nil {
				return fmt.Errorf("error in translating K8s HPA spec: %s", err)
			}
			continue
		}
		m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath))
		if err != nil {
			return err
		}
		if !found {
			scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath)
			continue
		}

		if mstr, ok := m.(string); ok && mstr == "" {
			scope.Debugf("path %s is empty string, skip mapping.", inPath)
			continue
		}
		// Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are
		// the default in destination fields and need not be set explicitly.
		if mint, ok := util.ToIntValue(m); ok && mint == 0 {
			scope.Debugf("path %s is int 0, skip mapping.", inPath)
			continue
		}

		switch k8sSettingName {
		case "env":
			err := translateEnv(v.OutPath, m, cpSpecTree)
			if err != nil {
				return fmt.Errorf("error in translating k8s Env: %s", err)
			}

		case "rollingMaxSurge", "rollingMaxUnavailable":
			err := translateStrategy(k8sSettingName, v.OutPath, m, cpSpecTree)
			if err != nil {
				return fmt.Errorf("error in translating k8s Strategy: %s", err)
			}

		default:
			output := util.ToYAMLPath(v.OutPath)
			scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", output)

			if err := tpath.WriteNode(cpSpecTree, output, m); err != nil {
				return err
			}
		}

		if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil {
			return err
		}
	}
	return nil
}

// translateRemainingPaths translates remaining paths that are not available in existing mappings.
func (t *ReverseTranslator) translateRemainingPaths(valueTree map[string]interface{},
	cpSpecTree map[string]interface{}, path util.Path) error {
	for key, val := range valueTree {
		newPath := append(path, key)
		// value set to nil means no translation needed or being translated already.
		if val == nil {
			continue
		}
		switch node := val.(type) {
		case map[string]interface{}:
			err := t.translateRemainingPaths(node, cpSpecTree, newPath)
			if err != nil {
				return err
			}
		case []interface{}:
			if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), node); err != nil {
				return err
			}
		// remaining leaf need to be put into root.values
		default:
			if t.isEnablementPath(newPath) {
				continue
			}
			if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), val); err != nil {
				return err
			}
		}
	}
	return nil
}

// translateAPI is internal method for translating value.yaml tree based on API mapping.
func (t *ReverseTranslator) translateAPI(valueTree map[string]interface{},
	cpSpecTree map[string]interface{}) error {
	for inPath, v := range t.APIMapping {
		scope.Debugf("Checking for path %s in helm Value.yaml tree", inPath)
		m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath))
		if err != nil {
			return err
		}
		if !found {
			scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath)
			continue
		}
		if mstr, ok := m.(string); ok && mstr == "" {
			scope.Debugf("path %s is empty string, skip mapping.", inPath)
			continue
		}
		// Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are
		// the default in destination fields and need not be set explicitly.
		if mint, ok := util.ToIntValue(m); ok && mint == 0 {
			scope.Debugf("path %s is int 0, skip mapping.", inPath)
			continue
		}

		path := util.ToYAMLPath(v.OutPath)
		scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", path)

		if err := tpath.WriteNode(cpSpecTree, path, m); err != nil {
			return err
		}

		if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil {
			return err
		}
	}
	return nil
}

// isEnablementPath is helper function to check whether paths represent enablement of components in values.yaml
func (t *ReverseTranslator) isEnablementPath(path util.Path) bool {
	if len(path) < 2 || path[len(path)-1] != "enabled" {
		return false
	}

	pf := path[:len(path)-1].String()
	if specialComponentPath[pf] {
		return true
	}

	_, exist := t.ValuesToComponentName[pf]
	return exist
}

// renderComponentName renders a template of the form <path>{{.ComponentName}}<path> with
// the supplied parameters.
func renderComponentName(tmpl string, componentName string) (string, error) {
	type temp struct {
		ValueComponentName string
	}
	return util.RenderTemplate(tmpl, temp{componentName})
}
