package run

import "strings"

// failureProblemHint returns the stable bounded public problem and
// actionable hint for a workflow runtime failure code. It never embeds an
// arbitrary cause. The hint preserves the legacy stable hint strings where they
// existed (#t2602), while Problem is the new distinct bounded public
// problem added by #t2609. Unknown and legacy codes fall back to a generic
// problem so every persisted error_json carries a bounded display-safe message.
func failureProblemHint(code string) (problem, hint string) {
	switch strings.TrimSpace(code) {
	// Dispatch / delivery / storage / recovery outcomes.
	case "message.writer_unavailable":
		return "The message writer is unavailable", "The message writer is temporarily unavailable"
	case "message.delivery_failed":
		return "The message could not be delivered", "The message could not be delivered"
	case "message.receipt_recovery_unavailable":
		return "The message receipt could not be recovered", "The message receipt could not be recovered"
	case "effect.request_invalid":
		return "The effect request is invalid", "The effect request is invalid"
	case "effect.kind_unsupported":
		return "The effect kind is not supported", "The effect kind is not supported"
	case "effect.timed_out":
		return "The workflow effect timed out", "The workflow effect timed out"
	case "callback.responder_unresolvable":
		return "The callback responder could not be resolved", "The callback responder could not be resolved"
	case "callback.responder_changed":
		return "The callback responder changed after delivery", "The callback responder changed after delivery"
	case "approval.writer_unavailable":
		return "The approval writer is unavailable", "The approval writer is temporarily unavailable"
	case "approval.run_unavailable":
		return "The Workflow Run is unavailable for approval", "The Workflow Run is unavailable for approval"
	case "approval.assignee_unavailable":
		return "The approval assignee could not be resolved", "The approval assignee could not be resolved"
	case "approval.delivery_failed":
		return "The approval could not be delivered", "The approval could not be delivered"
	case "delivery.failed":
		return "The delivery failed", "The delivery failed"

	// Reducer authoring / materialization outcomes.
	case "message.destination_invalid":
		return "The message destination is invalid", "The message destination is invalid"
	case "message.scope_invalid":
		return "The message scope is invalid", "The message scope is invalid"
	case "message.target_invalid":
		return "The message target is invalid", "The message target is invalid"
	case "message.responder_invalid":
		return "The message responder is invalid", "The message responder is invalid"
	case "message.responder_select_one_invalid":
		return "The message responder selection is invalid", "The message responder selection is invalid"
	case "message.work_items_invalid":
		return "The message work items are invalid", "The message work items are invalid"
	case "message.text_invalid":
		return "The message text is invalid", "The message text is invalid"
	case "message.collaboration_scope_invalid":
		return "The collaboration scope is invalid", "The collaboration scope is invalid"
	case "message.task_watch_handoff_invalid":
		return "The task watch handoff is invalid", "The task watch handoff is invalid"
	case "wait.until_invalid":
		return "The wait condition is invalid", "The wait condition is invalid"
	case "approval.assignee_invalid":
		return "The approval assignee is invalid", "The approval assignee is invalid"
	case "approval.summary_invalid":
		return "The approval summary is invalid", "The approval summary is invalid"
	case "state.expression_failed":
		return "The state expression failed", "The state expression failed"
	case "state.contract_failed":
		return "The state output does not match the Plan contract", "The state output does not match the Plan contract"
	case "switch.condition_failed":
		return "The switch condition failed", "The switch condition failed"
	case "run.output_contract_failed":
		return "The Run output does not match the Plan contract", "The Run output does not match the Plan contract"
	case "run.invalid_branch_terminal":
		return "The Run ended in an invalid branch terminal", "The Run ended in an invalid branch terminal"
	case "edge.binding_failed":
		return "A workflow edge binding failed", "A workflow edge binding failed"
	case "join.merge_failed":
		return "The join could not merge its inputs", "The join could not merge its inputs"
	case "run.graph_deadlocked":
		return "The workflow graph is deadlocked", "The workflow graph is deadlocked"
	case "run.canceled":
		return "The Workflow Run was canceled", "The Workflow Run was canceled"
	case "fan_out.items_invalid":
		return "The fan-out items are invalid", "The fan-out items are invalid"
	case "run.effect_completion_invalid":
		return "The workflow effect completion is invalid", "The workflow effect completion is invalid"
	case "run.effect_receipt_invalid":
		return "The workflow effect receipt is invalid", "The workflow effect receipt is invalid"
	case "run.reducer_failed":
		return "The workflow reducer failed", "The workflow reducer failed"
	case "run.unhandled_failure":
		return "The Workflow Run encountered an unhandled failure", "The Workflow Run encountered an unhandled failure"

	// Deterministic Run materialization outcomes.
	case "run.plan_version_unavailable":
		return "The exact Workflow PlanVersion is unavailable", "The exact Workflow PlanVersion is unavailable"
	case "run.plan_version_invalid":
		return "The exact Workflow PlanVersion is invalid", "The exact Workflow PlanVersion is invalid"
	case "run.input_invalid":
		return "The Workflow Run input is invalid", "The Workflow Run input is invalid"
	case "run.input_contract_failed":
		return "The Workflow Run input does not match the Plan contract", "The Workflow Run input does not match the Plan contract"
	case "run.state_invalid":
		return "The Workflow Run state is invalid", "The Workflow Run state is invalid"
	case "run.runtime_collection_limit_exceeded":
		return "The Workflow Run exceeded its runtime collection limit", "The Workflow Run exceeded its runtime collection limit"
	case "run.runtime_state_limit_exceeded":
		return "The Workflow Run exceeded its state budget", "The Workflow Run exceeded its state budget"
	case "run.runtime_payload_limit_exceeded":
		return "The Workflow Run exceeded its payload budget", "The Workflow Run exceeded its payload budget"

	// Dynamic work-item outcomes.
	case "work_item.dependency_blocked":
		return "A work item is blocked by an unsuccessful dependency", "A work item is blocked by an unsuccessful dependency"
	case "work_item.failed":
		return "A work item failed", "A work item failed"
	case "work_item.instruction_reference_invalid":
		return "A work item instruction reference is invalid", "A work item instruction reference is invalid"
	case "work_item.resource_limit_exceeded":
		return "A work item exceeded a resource limit", "A work item exceeded a resource limit"

	default:
		return "Workflow Run failed", "Workflow Run failed"
	}
}

// FailureProblem returns the stable bounded public problem for a
// workflow runtime failure code. It never embeds an arbitrary cause and always
// returns a bounded display-safe value for legacy codes.
func FailureProblem(code string) string {
	problem, _ := failureProblemHint(code)
	return problem
}

// FailureHint returns the stable bounded actionable hint for a workflow runtime
// failure code. It never embeds an arbitrary cause and preserves the legacy
// #t2602 stable hint text for codes that had one.
func FailureHint(code string) string {
	_, hint := failureProblemHint(code)
	return hint
}

// failureForCode builds the public safe Failure for a stable runtime failure
// code, deriving the bounded public problem and hint from the code mapping so a
// reducer failure event carries the full public contract (code/stage/path/
// problem/hint/retryable) without any arbitrary cause text. The reducer stage is
// populated by default so every reducer-produced failure identifies its producer
// stage.
func failureForCode(code string) *Failure {
	code = strings.TrimSpace(code)
	problem, hint := failureProblemHint(code)
	return &Failure{
		Code:    code,
		Stage:   "reducer",
		Problem: problem,
		Hint:    hint,
	}
}

// failureForCodeAt builds the public safe Failure for a stable runtime
// failure code with an explicit producer stage and accurate path (typically the
// compiled node id or a lineage path), so Run show/log/effect projection can
// pinpoint the exact failing node.
func failureForCodeAt(code, stage, path string) *Failure {
	failure := failureForCode(code)
	failure.Stage = strings.TrimSpace(stage)
	failure.Path = strings.TrimSpace(path)
	return failure
}

// failureFromCause builds the public safe Failure for a stable runtime
// failure code and a cause. It derives the bounded public problem and hint from
// the code mapping, carries any structured ValidationError diagnostic verbatim
// (bounded, safe authoring text), and sets Message to a bounded safe summary
// derived from the code — never the raw cause.Error() text. The arbitrary cause
// stays in internal observability (the returned error) only and never crosses
// the durable kernel Failure writer.
func failureFromCause(code, stage string, cause error) *Failure {
	code = strings.TrimSpace(code)
	failure := failureForCode(code)
	failure.Stage = strings.TrimSpace(stage)
	if cause != nil {
		if validation, ok := ValidationErrorDiagnostic(cause); ok {
			failure.Message = boundedFailureField(validation.Message, 1024)
			failure.Path = strings.TrimSpace(firstNonEmpty(failure.Path, validation.Path))
			failure.Diagnostics = append(failure.Diagnostics, FailureDiagnostic{
				Code:    boundedFailureField(validation.Code, 1024),
				Path:    boundedFailureField(validation.Path, 1024),
				Field:   boundedFailureField(validation.Field, 1024),
				Index:   validation.Index,
				Message: boundedFailureField(validation.Message, 1024),
			})
			return failure
		}
	}
	// Without a structured diagnostic, the durable Message carries only the
	// stable bounded hint/problem derived from the code mapping.
	failure.Message = firstNonEmpty(failure.Hint, failure.Problem)
	return failure
}

// boundedFailureField trims, forces valid UTF-8, and caps a failure field.
func boundedFailureField(value string, maxRunes int) string {
	value = strings.TrimSpace(strings.ToValidUTF8(value, "�"))
	if runes := []rune(value); len(runes) > maxRunes {
		return string(runes[:maxRunes])
	}
	return value
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
