




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

createContext := func() {
	return {
		activeSubscriptions: {},
		resolvedStates: {}
	}
}


RTYPE_AWAITER := { Name: "awaiter", Version: "1" }

awaitOneState := func(field, path, state) {
	if field.isErrorSet() && !field.isValueSet() {




		return { type: "Ready" }
	}

	if len(path) > 0 {
		step := path[0]
		rPath := path[1:]

		if !field.isValueSet() {
			return {
				type: "Subscribe",
				subscriptions: [{
					resource: field.id.ResourceID,
					event: "InputSet"
				}]
			}
		}

		resource := field.getValue()

		if is_string(step) {
			nextField := resource.inputs()[step]
			if nextField == undefined {
				if resource.info().InputsLocked {
					ll.panic("field %v not found and inputs locked", step)
				}
				return {
					type: "Subscribe",
					subscriptions: [{
						resource: resource.id,
						event: "FieldCreated"
					}]
				}
			}
			return awaitOneState(nextField, rPath, state)
		} else if is_map(step) {
			if is_undefined(step.wildcard) == is_undefined(step.match) {
				ll.panic("Unknown step structure: %v", step)
			}

			matcher := undefined

			if !is_undefined(step.wildcard) {
				if step.wildcard != "*" {
					ll.panic("Complex wildcards are not supported, use { match: \"{regexp}\" }: %v", step.wildcard)
				}
				matcher = func(field) {
					return true
				}
			} else {
				regex := regexp.compile(step.match)
				if is_error(regex) {
					ll.panic("Error parsing regex %v: %v", step.match, regex)
				}
				matcher = func(field) {
					return regex.match(field)
				}
			}

			if !resource.info().InputsLocked {
				return {
					type: "Subscribe",
					subscriptions: [{
						resource: resource.id,
						event: "InputsLocked"
					}]
				}
			}

			subscriptions := []
			maps.forEach(resource.inputs(), func(nextFieldName, nextField) {
				if !matcher(nextFieldName) {
					return
				}
				res := awaitOneState(nextField, rPath, state)
				if res.type == "Ready" {
					return
				} else if res.type == "Subscribe" {
					subscriptions = append(subscriptions, res.subscriptions...)
				} else {
					ll.panic("Unknown result type: %v", res.type)
				}
			})
			if len(subscriptions) == 0 {
				return { type: "Ready" }
			} else {
				return {
					type: "Subscribe",
					subscriptions: subscriptions
				}
			}
		} else {
			ll.panic("Malformed state await request path: %v", step)
		}
	} else {
		if state == "Exists" {
			return { type: "Ready" }
		} else {
			if !field.isSet() {
				return {
					type: "Subscribe",
					subscriptions: [{
						resource: field.id.ResourceID,
						event: "InputSet"
					}]
				}
			}
			resource := field.getValue()
			if state == "Set" {
				return { type: "Ready" }
			} else if state == "InputsLocked" {
				if resource.info().InputsLocked {
					return { type: "Ready" }
				} else {
					return {
						type: "Subscribe",
						subscriptions: [{
							resource: resource.id,
							event: "InputsLocked"
						}]
					}
				}
			} else if state == "AllInputsSet" {
				if resource.info().AllInputsSet {
					return { type: "Ready" }
				} else {
					return {
						type: "Subscribe",
						subscriptions: [{
							resource: resource.id,
							event: "AllInputsSet"
						}]
					}
				}
			} else if state == "ResourceReady" || state == "Final" {


				if resource.isFinal() {
					return { type: "Ready" }
				} else {
					return {
						type: "Subscribe",
						subscriptions: [{
							resource: resource.id,
							event: "Final" // will also effectively subscribe to ResourceDuplicate and ResourceError
						}]
					}
				}
			} else {
				ll.panic("Unknown await state: %v", state)
			}
		}
	}
}

_renderer := smart.resource(ll.getCurrentTemplateRenderer())


await := func(rootField, ctxStorageResource, requests) {
	pssKey := "await:" + string(rootField.id.ResourceID) + ":" + rootField.id.Name
	prevSubscriptions := ctxStorageResource.kvGetAsJson(pssKey)

	lastSubscriptionKey := undefined
	if !is_undefined(prevSubscriptions) {
		if is_undefined(prevSubscriptions.lastSubscriptionKey) {

			ll.panic("Unexpected state, desired state was reached in the previous call")
		} else {
			lastSubscriptionKey = prevSubscriptions.lastSubscriptionKey
		}
	}

	subscriptions := {}

	addSubscription := func(subscription) {
		key := string(subscription.resource) + ":" + subscription.event
		if !is_undefined(subscriptions[key]) {
			return false
		} else {
			subscriptions[key] = subscription
			return true
		}
	}

	for _, request in requests {
		if !is_map(request) || !is_array(request.path) || !is_string(request.state) {
			ll.panic("Malformed state await request: %v", request)
		}
		res := awaitOneState(rootField, request.path, request.state)
		if res.type == "Ready" {
			continue
		} else if res.type == "Subscribe" {
			for subscription in res.subscriptions {
				addSubscription(subscription)
			}
		} else {
			ll.panic("Unknown result type %v", res.type)
		}
	}









	firstSubscriptionKey := undefined
	firstSubscription := undefined
	lastSubscriptionKeyPresent := false
	maps.forEach(subscriptions, func(key, sub) {
		if is_undefined(firstSubscriptionKey) {
			firstSubscriptionKey = key
			firstSubscription = sub
		}
		if !is_undefined(lastSubscriptionKey) && lastSubscriptionKey == key {
			lastSubscriptionKeyPresent = true
		}
	})



	if lastSubscriptionKeyPresent {
		return false
	}


	if !is_undefined(firstSubscriptionKey) {

		fullEventId := pssKey + ":" + firstSubscriptionKey
		targetResource := firstSubscription.resource
		targetEvents := [firstSubscription.event]

		if firstSubscription.event == "Final" { // special case
			if !feats.fullFeaturedApi {






				target := smart.resource(targetResource)
				isEphemeral := target.info().Features.ephemeral
				if !isEphemeral { // doing proxy-object workaround for pure resources only
					awaiterBuilder := smart.ephemeralBuilder(RTYPE_AWAITER)
					awaiterBuilder.createInputField("target").set(target)
					awaiter := awaiterBuilder.lockAndBuild()
					_renderer.createDynamicField("tmp:" + fullEventId).set(awaiter)
					targetResource = awaiter.info().ID // converting to global resource id; this is a hack to overcome the fact that tx.subscribeTo can't work with local ids
				}
				targetEvents = ["ResourceReady", "ResourceError"]

			} else {
				targetEvents = ["ResourceReady", "ResourceError", "ResourceDuplicate"]
			}
		} else if firstSubscription.event == "FieldCreated" { // another special case

			targetEvents = ["FieldCreated", "InputsLocked"]
		}

		e := {}
		for event in targetEvents {
			e[event] = true
		}
		tx.subscribeTo(targetResource, fullEventId, e)

	}


	if !is_undefined(lastSubscriptionKey) {
		tx.unsubscribeFrom(pssKey + ":" + lastSubscriptionKey)
	}


	newPrevSubscriptions := {}
	if !is_undefined(firstSubscriptionKey) {
		newPrevSubscriptions.lastSubscriptionKey = firstSubscriptionKey
	}

	ctxStorageResource.kvSetJson(pssKey, newPrevSubscriptions)

	return is_undefined(firstSubscriptionKey)
}

export ll.toStrict({
	await: await
})
