smart := import("@platforma-sdk/workflow-tengo:smart")
ll := import("@platforma-sdk/workflow-tengo:ll")
fmt := import("fmt")
maps := import("@platforma-sdk/workflow-tengo:maps")




































anonymizeFields := func(targets, ...opts) {

	options := {}
	if len(opts) > 0 {
		if len(opts) == 1 && is_map(opts[0]) {
			options = opts[0]
		} else {
			ll.panic("anonymizeFields: optional parameters must be a single map argument, got: %v", opts)
		}
	}


	fieldMatcher := options.fieldMatcher
	if is_undefined(fieldMatcher) {
		fieldMatcher = func(fieldName) { return true }
	}

	fieldNameDeriver := options.fieldNameDeriver
	if is_undefined(fieldNameDeriver) {
		fieldNameDeriver = func(originalFieldName, anonymizedFieldName) { return anonymizedFieldName }
	}

	cidBytes := options.cidBytes
	if is_undefined(cidBytes) {
		cidBytes = 20
	}

	groupBy := options.groupBy
	if is_undefined(groupBy) {
		groupBy = func(fieldName) { return fieldName }
	}


	if !is_callable(fieldMatcher) {
		ll.panic("anonymizeFields: fieldMatcher must be a function, got: %v", fieldMatcher)
	}
	if !is_callable(fieldNameDeriver) {
		ll.panic("anonymizeFields: fieldNameDeriver must be a function, got: %v", fieldNameDeriver)
	}
	if !is_callable(groupBy) {
		ll.panic("anonymizeFields: groupBy must be a function, got: %v", groupBy)
	}
	if !is_int(cidBytes) || cidBytes <= 0 {
		ll.panic("anonymizeFields: cidBytes must be a positive integer, got: %v", cidBytes)
	}
	if cidBytes % 5 != 0 {
		ll.panic("anonymizeFields: cidBytes must be a multiple of 5 to maintain base32 encoding alignment, got: %v", cidBytes)
	}

	isSingleTarget := false
	if smart.isReference(targets) {
		isSingleTarget = true
		targets = { "default": targets }
	}

	groupCIDs := {}  // groupId -> concatenated CIDs bytes

	maps.forEach(targets, func(targetKey, target) {
		targetInfo := target.info()

		ll.assert(target.isFinal(), "target resource must be final")

		ll.assert(len(ll.fromStrict(target.outputs())) == 0, "target resource must not have outputs")
		ll.assert(len(ll.fromStrict(target.metaInputs())) == 0, "target resource must not have service inputs")

		maps.forEach(target.inputs(), func(fieldName, field) {
			if !fieldMatcher(fieldName) {
				return
			}

			groupId := groupBy(fieldName)
			value := field.getValue()
			cid := value.info().CanonicalID

			if is_undefined(groupCIDs[groupId]) {
				groupCIDs[groupId] = bytes(0)
			}

			groupCIDs[groupId] = groupCIDs[groupId] + cid
		})
	})

	groupAnonymizedNames := {}  // groupId -> anonymizedFieldName
	groupRepetitionCounts := {}  // anonymizedFieldName -> repetition count
	anonymizedToGroupId := {}  // anonymizedFieldName -> groupId

	maps.forEach(groupCIDs, func(groupId, concatenatedCIDs) {
		hashedCIDs := ll.sha256Encode(concatenatedCIDs)
		encodedCid := ll.base32Encode(hashedCIDs[0:cidBytes])

		repetitionIndex := 0
		if !is_undefined(groupRepetitionCounts[encodedCid]) {
			repetitionIndex = groupRepetitionCounts[encodedCid]
		}
		groupRepetitionCounts[encodedCid] = repetitionIndex + 1
		anonymizedName := encodedCid + "-" + repetitionIndex
		groupAnonymizedNames[groupId] = anonymizedName
		anonymizedToGroupId[anonymizedName] = groupId
	})

	results := {}
	maps.forEach(targets, func(targetKey, target) {
		targetInfo := target.info()
		resultBuilder := smart.structBuilder(targetInfo.Type, target.getData())
		maps.forEach(target.inputs(), func(fieldName, field) {
			value := field.getValue()

			if !fieldMatcher(fieldName) {
				resultBuilder.createInputField(fieldName).set(value)
				return
			}

			groupId := groupBy(fieldName)
			anonymizedFieldName := groupAnonymizedNames[groupId]
			finalFieldName := fieldNameDeriver(fieldName, anonymizedFieldName)

			resultBuilder.createInputField(finalFieldName).set(value)
		})
		results[targetKey] = resultBuilder.lockAndBuild()
	})

	if isSingleTarget {
		return {
			result: results["default"],
			mapping: anonymizedToGroupId
		}
	} else {
		return {
			result: results,
			mapping: anonymizedToGroupId
		}
	}
}

export ll.toStrict({
	anonymizeFields: anonymizeFields
})
