



tx := import("tx")
ll := import("@platforma-sdk/workflow-tengo:ll")
feats := import("@platforma-sdk/workflow-tengo:feats")

oop := import("@platforma-sdk/workflow-tengo:oop")
maps := import("@platforma-sdk/workflow-tengo:maps")
json := import("json")
times := import("times")
constants := import("@platforma-sdk/workflow-tengo:constants")
ffDefault := import("@platforma-sdk/workflow-tengo:ll.get-future-field-default")






resource := undefined




field := undefined




resourceBuilder := undefined






structBuilder := undefined




mapBuilder := undefined




ephemeralMapBuilder := undefined






ephemeralBuilder := undefined






createNullResource := undefined







createValueResource := undefined






createJsonResource := undefined







createMapResource := undefined







createBinaryMapResource := undefined







createBlobMapResource := undefined






isResource := func(f) {
	return ll.isStrict(f) && maps.containsKey(f, "_type") && f["_type"] == "resource"
}




isField := func(f) {
	return ll.isStrict(f) && maps.containsKey(f, "_type") && f["_type"] == "field"
}




isReference := func(f) {
	return isField(f) || isResource(f)
}











_getFutureField := func(from, name, fieldType, isEph) {
	ll.assert(isReference(from), "'from' must be either a resource or a field, got: %#v", from)
	if is_bytes(name) {
		name = string(name)
	}
	ll.assert(is_string(name) && name != "", "field name must be a non-empty string")

	futureField := tx.createFutureFieldID(fieldType, name, !isEph)
	f := field(futureField.ResourceFID)
	f.set(from)

	return field(futureField.ResultFID)
}















_buildFutureField := func(resourceOrField, nameOrPath, ...eph) {
	globalIsEph := undefined
	if len(eph) > 0 && is_bool(eph[0]) {
		globalIsEph = eph[0]
	}


	optional := false


	processSingleElement := func(resource, element) {

		fieldType := "input"
		fieldName := undefined
		isEph := globalIsEph

		if is_map(element) {
			if !is_undefined(globalIsEph) {
				ll.panic("global eph is not supported for structural path elements")
			}

			if !is_undefined(element.type) {
				fieldType = element.type
			}

			fieldName = element.name
			optional = optional || element.optional
			isEph = element.eph
		} else {
			fieldName = element
		}

		if optional {
			return resource.getFutureFieldOptional(fieldName, fieldType, isEph)
		} else {
			return resource.getFutureField(fieldName, fieldType, isEph)
		}
	}

	if is_array(nameOrPath) {
		current := resourceOrField
		for i, pathElement in nameOrPath {
			current = processSingleElement(current, pathElement)
		}
		return current
	} else {
		return processSingleElement(resourceOrField, nameOrPath)
	}
}




_getFutureInputField := func(resourceOrField, nameOrPath, ...eph) {
	return _buildFutureField(resourceOrField, nameOrPath, eph...)
}




isMapResource := func(r) {
	return isResource(r) && (
		r.info().Type.Name == constants.RTYPE_MAP.Name ||
			r.info().Type.Name == constants.RTYPE_MAP_ALT.Name) // @TODO get rid of alt name

}




isEphMapResource := func(r) {
	return isResource(r) &&  r.info().Type.Name == constants.RTYPE_MAP_EPH.Name
}




isLockedEphMapResource := func(r) {
	return isEphMapResource(r) && r.info().InputsLocked
}




isAnyMapResource := func(r) {
	return isMapResource(r) || isEphMapResource(r)
}




isJsonResource := func(r) {
	return isResource(r) && r.info().Type.Name == constants.RTYPE_JSON.Name
}




isNullResource := func(r) {
	return isResource(r) && r.info().Type.Name == constants.RTYPE_NULL.Name
}


_hasReferenceValues := func(object) {
	if isReference(object) {

		return true
	} else if ll.isMap(object) {
		for _, v in object {
			if _hasReferenceValues(v) {
				return true
			}
		}
	} else if is_array(object) {
		for v in object {
			if _hasReferenceValues(v) {
				return true
			}
		}
	}
	return false
}





_toRefOrJson := func(object) {
	if isReference(object) {

		return object
	} else if ll.isMap(object) {


		if !_hasReferenceValues(object)  {
			return createJsonResource(object)
		}

		mb := mapBuilder()
		for k, v in object {
			mb.add(k, _toRefOrJson(v))
		}

		return mb.build()
	} else if is_array(object) {


		ll.assert(!_hasReferenceValues(object), "array can't contain references")

		return createJsonResource(object)
	} else {
		return createJsonResource(object)
	}
}

resourceTypesEqual := func(rType1, rType2) {
	return rType1.Name == rType2.Name && rType1.Version == rType2.Version
}






resource = func(r) {
	self := undefined
	strictSelf := undefined

	if isResource(r) {
		return r
	}

	id := ll.isStrict(r) ? r.ID : r

	if !is_int(id) {
		ll.panic("resource id should be int for ", r)
	}

	if id == 0 {
		ll.panic("null resource id for ", r)
	}

	self = {
		_type: "resource",




		id: id,



























		info: func() {
			_resourceInfo := tx.getResource(self.id)

			_resourceInfo.Type = { Name: _resourceInfo.Type.Name, Version: _resourceInfo.Type.Version }
			return _resourceInfo
		},







		checkResourceType: func(expectedType) {
			return resourceTypesEqual(self.info().Type, expectedType)
		},




		isFinal: func() {

			if false /* feats.fullFeaturedApi */ {
				return self.info().IsFinal
			}

			info := self.info()

			return info.ResourceReady || info.IsDuplicate || info.HasErrors
		},

		isEphemeral: func() {
			resourceInfo := self.info()

			if !maps.containsKey(resourceInfo, "Features") {



				return true
			}

			return resourceInfo.Features.ephemeral
		},






		getData: func() {
			if self.hasError() {
				ll.panic("resource has errors: ", self.info())
			}
			return self.info().Data;
		},




		getDataAsJson: func() {
			return json.decode(self.getData());
		},




		hasError: func() {
			return self._hasField("OneTimeWritables", "resourceError")
		},





		hasInputErrors: func() {
			return self.info().HasErrors
		},




		getError: func() {
			if !self.info().HasErrors {
				ll.panic("no error set in resource")
			}
			return self.getField("resourceError").getError()
		},

		_hasField: func(fieldMapName, fieldName) {
			return ll.fromStrict(self.info()[fieldMapName])[fieldName] != undefined
		},






		hasInputField: func(name) {
			return self._hasField("Inputs", name)
		},






		hasOutputField: func(name) {
			return self._hasField("Outputs", name)
		},






		hasField: func(name) {
			return self._hasField("Inputs", name) ||
				self._hasField("Outputs", name) ||
				self._hasField("DynamicFields", name) ||
				self._hasField("OneTimeWritables", name)
		},


		_inputs: undefined,



		inputs: func() {
			if self._inputs == undefined {
				info := self.info()
				inputs := maps.mapValues(info.Inputs, field)

				if !info.InputsLocked {
					return inputs
				}
				self._inputs = inputs
			}
			return self._inputs
		},


		_metaInputs: undefined,



		metaInputs: func() {
			if self._metaInputs == undefined {
				info := self.info()

				if !maps.containsKey(info, "ServiceFields") {


					self._metaInputs = {}
					return self._metaInputs
				}

				svcs := maps.mapValues(info.ServiceFields, field)

				if !info.InputsLocked {
					return svcs
				}
				self._metaInputs = svcs
			}
			return self._metaInputs
		},





		serviceInputs: func() {
			return self.metaInputs()
		},


		_outputs: undefined,



		outputs: func() {
			if(self._outputs == undefined) {
				info := self.info()
				outputs := ll.toStrict(maps.mapValues(info.Outputs, field))
				if !info.OutputsLocked {
					return outputs
				}
				self._outputs = outputs
			}
			return self._outputs
		},






		getField: func(name) {
			if(!self.hasField(name)) {
				ll.panic("field %v is not found", name)
			}

			return field(ll.fieldId(self.id, name))
		},











		getFutureField: func(name, fieldType, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 && is_bool(eph[0]) {
				isEph = eph[0]
			}
			return _getFutureField(strictSelf, name, fieldType, isEph)
		},












		getFutureFieldOptional: func(name, fieldType, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 && is_bool(eph[0]) {
				isEph = eph[0]
			}

			if is_bytes(name) {
				name = string(name)
			}
			ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
			fieldId := ffDefault.getFutureFieldWithDefault(self.id, name, fieldType, isEph)
			return field(fieldId)
		},













		getFutureInputField: func(nameOrPath, ...eph) {
			return _getFutureInputField(strictSelf, nameOrPath, eph...)
		},










		getFutureOutputField: func(name, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 {
				isEph = eph[0]
			}

			if !feats.pureFutureFields && !isEph {


				return self.getFutureField(name, "output", true).expectNonEphemeral()
			}

			return self.getFutureField(name, "output", isEph)
		},











		buildFutureField: func(nameOrPath) {
			return _buildFutureField(self, nameOrPath)
		},






		get: func(name) {
			return self.getField(name).getValue()
		},







		getFieldOrResource: func(name) {
			field := self.getField(name)
			if field.isSet() {
				return field.getValue()
			} else {
				return field
			}
		},







		createDynamicField: func(name) {
			if is_bytes(name) {
				name = string(name)
			}
			ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
			fid := ll.fieldId(self.id, name)
			tx.createField(fid, constants.FTYPE_DYNAMIC)
			return field(fid)
		},










		kvGet: func(key) {
			return tx.resourceKeyValueGet(self.id, key)
		},







		kvGetFlag: func(key) {
			ll.assert(feats.fullFeaturedApi, "smart.resource.kvGetFlag: the feature is not available on older backends. Please, update the backend.")
			return tx.resourceKeyValueGetFlag(self.id, key)
		},








		kvSet: func(key, value) {
			tx.resourceKeyValueSet(self.id, key, value)
		},







		kvSetFlag: func(key, flag) {
			ll.assert(feats.fullFeaturedApi, "smart.resource.kvSetFlag: the feature is not available on older backends. Please, update the backend.")
			tx.resourceKeyValueSetFlag(self.id, key, flag)
		},








		kvGetAsString: func(key) {
			value := tx.resourceKeyValueGet(self.id, key)
			if is_undefined(value) {
				return undefined
			} else {
				return string(value)
			}
		},








		kvGetAsJson: func(key) {
			bytes := self.kvGet(key)
			if is_undefined(bytes){
				return undefined
			} else {
				return json.decode(bytes)
			}
		},







		kvSetJson: func(key, value) {
			self.kvSet(key, json.encode(value))
		}
	}

	strictSelf = ll.toStrict(self)

	return strictSelf
}

field = func(f) {
	self := undefined
	strictSelf := undefined

	id := ll.isFieldId(f) ? f : f.ID

	if (is_undefined(id)) {
		ll.panic("undefined id in smart.field. <f>="+f)
	}

	self = {
		_type: "field",








		id: id,




		name: func() {
			return self.id.Name
		},




		resource: func() {
			return resource(self.id.ResourceID)
		},











		info: func() {
			return tx.getField(self.id)
		},




		isSet: func() {
			return self.info().IsSet
		},




		isValueSet: func() {
			return self.info().Value != 0
		},




		isErrorSet: func() {
			return self.info().Error != 0
		},







		isEphemeral: func() {
			fieldInfo := self.info()

			if !maps.containsKey(fieldInfo, "Features") {



				return true
			}

			isParentResourceEphemeral := fieldInfo.Features.ephemeral
			if isParentResourceEphemeral {
				return true
			}

			return fieldInfo.Type != constants.FTYPE_INPUT &&
				fieldInfo.Type != constants.FTYPE_OUTPUT
		},

		expectNonEphemeral: func() {
			rr := field(self.id)
			rr.isEphemeral = func() { return false }
			return rr
		},







		setCache: func(time) {
			ll.assert(is_int(time) && time >= times.millisecond,
				"smart.field.setCache: cache time must be an integer >= millisecond. Did you forget to import a standard tengo library 'times'?")

			tx.cacheSetToField(self.id, time)
			return strictSelf
		},






		setCacheMillis: func(millis) {
			ll.assert(is_int(millis) && millis > 0,
				"smart.field.setCacheMillis: cache time must be a number of milliseconds (> 0)")
			return self.setCache(millis * times.millisecond)
		},






		setCacheSeconds: func(seconds) {
			ll.assert(is_int(seconds) && seconds > 0,
				"smart.field.setCacheSeconds: cache time must be a number of seconds (> 0)")
			return self.setCache(seconds * times.second)
		},






		setCacheMinutes: func(minutes) {
			ll.assert(is_int(minutes) && minutes > 0,
				"smart.field.setCacheMinutes: cache time must be a number of minutes (> 0)")
			return self.setCache(minutes * times.minute)
		},






		setCacheHours: func(hours) {
			ll.assert(is_int(hours) && hours > 0, "smart.field.setCacheHours: cache time must be a number of hours (> 0)")
			return self.setCache(hours * times.hour)
		},






		setCacheDays: func(days) {
			ll.assert(is_int(days) && days > 0, "smart.field.setCacheDays: cache time must be a number of days (> 0)")
			return self.setCache(days * 24 * times.hour)
		},




		getValue: func() {
			if self.isErrorSet() {
				err := self.getError().getDataAsJson()
				if !is_undefined(err.message) {
					err = err.message
				}
				ll.panic("smart.field.getValue: field %v has error: %v", self.name(), err)
			}

			ll.assert(self.isSet(), "smart.field.getValue: field %v is not set", self.name())

			return resource(self.info().Value)
		},




		getError: func() {
			return resource(self.info().Error)
		},






		setJson: func(value) {
			self.set(createJsonResource(value))
			return strictSelf
		},






		setMap: func(keyToValue) {
			self.set(createMapResource(keyToValue))
			return strictSelf
		},






		set: func(reference) {
			ll.assert(!self.isSet(), "field %q is already set", self.name())

			if isResource(reference) {
				tx.setFieldToResource(self.id, reference.id)
			} else if isField(reference) {
				tx.setFieldToField(self.id, reference.id)
			} else {
				ll.panic("smart resource or smart field expected (target field %v), got %#v: ", self.name(), reference)
			}

			return strictSelf
		},







		setRefOrJson: func(refOrJson) {
			if is_undefined(refOrJson) {
				ll.panic("attempt to assign undefined value to field %v", self.name())
			}
			self.set(_toRefOrJson(refOrJson))
		},




		getValueAsJson: func() {
			return self.getValue().getDataAsJson()
		},











		getFutureField: func(name, fieldType, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 && is_bool(eph[0]) {
				isEph = eph[0]
			}
			return _getFutureField(strictSelf, name, fieldType, isEph)
		},












		getFutureFieldOptional: func(name, fieldType, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 && is_bool(eph[0]) {
				isEph = eph[0]
			}

			if is_bytes(name) {
				name = string(name)
			}
			ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
			fieldId := ffDefault.getFutureFieldWithDefault(self.id, name, fieldType, isEph)
			return field(fieldId)
		},













		getFutureInputField: func(nameOrPath, ...eph) {
			return _getFutureInputField(strictSelf, nameOrPath, eph...)
		},










		getFutureOutputField: func(name, ...eph) {
			isEph := strictSelf.isEphemeral()
			if len(eph) > 0 {
				isEph = eph[0]
			}

			if !feats.pureFutureFields && !isEph {


				return self.getFutureField(name, "output", true).expectNonEphemeral()
			}

			return self.getFutureField(name, "output", isEph)
		},











		buildFutureField: func(nameOrPath) {
			return _buildFutureField(strictSelf, nameOrPath)
		},




		reset: func() {
			ll.assert(feats.fullFeaturedApi, "smart.field.reset: the feature is not available on older backends. Please, update the backend.")
			tx.resetField(self.id)
		},




		remove: func() {
			ll.assert(feats.fullFeaturedApi, "smart.field.remove: the feature is not available on older backends. Please, update the backend.")
			tx.removeField(self.id)
		}
	}

	strictSelf = ll.toStrict(self)

	return strictSelf
}







resourceBuilder = func(r) {
	super := resource(r)

	self := undefined


	built := false
	assertNotBuilt := func() {
		ll.assert(!built, "builder methods must not be used after a build() was called")
	}

	self = ll.toStrict(oop.inherit(super, {






		createField: func(name, fieldType) {
			assertNotBuilt()
			if is_bytes(name) {
				name = string(name)
			}
			ll.assert(is_string(name) && name != "", "field name must be a non-empty string")
			fid := ll.fieldId(super.id, name)
			tx.createField(fid, fieldType)
			return field(fid)
		},







		createMetaField: func(name) {
			assertNotBuilt()
			return self.createField(name, constants.FTYPE_SERVICE)
		},








		createServiceField: func(name) {
			return self.createMetaField(name)
		},






		createInputField: func(name) {
			assertNotBuilt()
			return self.createField(name, constants.FTYPE_INPUT)
		},






		createOutputField: func(name) {
			assertNotBuilt()
			return self.createField(name, constants.FTYPE_OUTPUT)
		},




		lockOutputs: func() {
			assertNotBuilt()
			tx.lockOutputs(super.id)
		},




		lockInputs: func() {
			assertNotBuilt()
			tx.lockInputs(super.id)
		},




		lock: func() {
			assertNotBuilt()
			self.lockInputs()
			self.lockOutputs()
		},




		lockAndBuild: func() {
			self.lock()
			built = true
			return super
		},




		lockOutputsAndBuild: func() {
			self.lockOutputs()
			built = true
			return super
		},




		lockInputsAndBuild: func() {
			self.lockInputs()
			built = true
			return super
		},




		buildUnlocked: func() {
			built = true
			return super
		}
	}))

	return self
}







structBuilder = func(resourceType, ...data) {
	id := undefined
	if len(data) > 0 && !is_undefined(data[0]) {
		id = tx.createStruct(resourceType.Name, resourceType.Version, data[0])
	} else {
		id = tx.createStruct(resourceType.Name, resourceType.Version)
	}

	return resourceBuilder(id)
}





_mapBuilder := func(isEph, ...resourceType) {
	mapType := undefined
	if len(resourceType) > 0 {
		mapType = resourceType[0]
	} else {
		mapType = isEph ? constants.RTYPE_MAP_EPH : constants.RTYPE_MAP
	}

	builder := isEph ? ephemeralBuilder(mapType) : structBuilder(mapType)

	builder.lockOutputs()

	self := undefined

	applyCache := func(field, ...opts) {
		if len(opts) > 0 {
			opt := opts[0]
			if is_map(opt) && !is_undefined(opt.cache) && opt.cache > 0 {
				field.setCache(opt.cache)
			}
		}
	}

	self = ll.toStrict(oop.inherit(builder, {








		addRef: func(key, value, ...opts) {
			field := builder.createInputField(key)
			applyCache(field, opts...)
			field.set(value)
			return self
		},









		addJson: func(key, value, ...opts) {
			field := builder.createInputField(key)
			applyCache(field, opts...)
			field.set(createJsonResource(value))
			return self
		},









		add: func(key, value, ...opts) {
			field := builder.createInputField(key)
			applyCache(field, opts...)
			field.setRefOrJson(value)
			return self
		},









		addMeta: func(key, value) {
			builder.createMetaField(key).setRefOrJson(value)
			return self
		},










		addService: func(key, value) {
			return self.addMeta(key, value)
		},






		build: func() {
			return builder.lockAndBuild()
		}
	}))

	return self
}






mapBuilder = func(...resourceType) {
	return _mapBuilder(false, resourceType...)
}






ephemeralMapBuilder = func(...resourceType) {
	return _mapBuilder(true, resourceType...)
}

ephemeralBuilder = func(resourceType, ...data) {
	id := undefined
	if (len(data) > 0) {
		id = tx.createEphemeral(resourceType.Name, resourceType.Version, data[0])
	} else {
		id = tx.createEphemeral(resourceType.Name, resourceType.Version)
	}

	return resourceBuilder(id)
}

createNullResource = func(...data) {
	ll.assert(len(data) <= 1, "can't create null resource with more than one argument")
	if len(data) > 0 && !is_undefined(data[0]) {
		return resource(tx.createValue(constants.RTYPE_NULL.Name, constants.RTYPE_NULL.Version, data[0]))
	} else {
		return resource(tx.createValue(constants.RTYPE_NULL.Name, constants.RTYPE_NULL.Version, ""))
	}
}

createValueResource = func(resourceType, data) {
	ll.assert(data != undefined, "can't create resource with undefined data")
	return resource(tx.createValue(resourceType.Name, resourceType.Version, data))
}

createJsonResource = func(value) {
	ll.assert(value != undefined, "can't create Json value from undefined")



	ll.assert(!ll.isStrict(value), "can't encode strict map: ", value)


	encoded := json.encode(value)

	return createValueResource(constants.RTYPE_JSON, encoded)
}

createMapResourceWithType := func(resourceType, keyToValue) {
	builder := structBuilder(resourceType)

	for key, value in keyToValue {
		builder.createInputField(key).setRefOrJson(value)
	}

	return builder.lockAndBuild()
}

createMapResource = func(keyToValue) {
	return createMapResourceWithType(constants.RTYPE_MAP, keyToValue)
}

createBinaryMapResource = func(keyToValue) {
	return createMapResourceWithType(constants.RTYPE_BINARY_MAP, keyToValue)
}

createBlobMapResource = func(keyToValue) {
	return createMapResourceWithType(constants.RTYPE_BLOB_MAP, keyToValue)
}

export ll.toStrict({
	isResource: isResource,
	isField: isField,
	isReference: isReference,
	isMapResource: isMapResource,
	isEphMapResource: isEphMapResource,
	isAnyMapResource: isAnyMapResource,
	isLockedEphMapResource: isLockedEphMapResource,
	isJsonResource: isJsonResource,
	isNullResource: isNullResource,
	resource: resource,
	field: field,
	resourceBuilder: resourceBuilder,
	structBuilder: structBuilder,
	mapBuilder: mapBuilder,
	ephemeralMapBuilder: ephemeralMapBuilder,
	ephemeralBuilder: ephemeralBuilder,
	createValueResource: createValueResource,
	createJsonResource: createJsonResource,
	createMapResourceWithType: createMapResourceWithType,
	createMapResource: createMapResource,
	createBinaryMapResource: createBinaryMapResource,
	createBlobMapResource: createBlobMapResource,
	createNullResource: createNullResource
})
