// Array-free numeric-expression work for the exact recursion slice. The
// reducer retains only immutable syntax recipes and opaque activation-owned
// capabilities. Observation, binding reads, callee capture, calls, and binary
// application remain effects owned by the imperative shell.

///|
#warnings("-unused_field")
priv struct DispatchParameterBindingToken {
  id : Int
}

///|
#warnings("-unused_value")
fn DispatchParameterBindingToken::DispatchParameterBindingToken(
  id~ : Int,
) -> DispatchParameterBindingToken {
  { id, }
}

///|
#warnings("-unused_field")
priv struct DispatchRetainedParameterBindingToken {
  id : Int
}

///|
#warnings("-unused_value")
fn DispatchRetainedParameterBindingToken::DispatchRetainedParameterBindingToken(
  id~ : Int,
) -> DispatchRetainedParameterBindingToken {
  { id, }
}

///|
#warnings("-unused_field")
priv struct DispatchNumericCatchBindingToken {
  id : Int
}

///|
#warnings("-unused_value")
fn DispatchNumericCatchBindingToken::DispatchNumericCatchBindingToken(
  id~ : Int,
) -> DispatchNumericCatchBindingToken {
  { id, }
}

///|
#warnings("-unused_field")
priv struct DispatchCalleeCaptureToken {
  id : Int
}

///|
#warnings("-unused_value")
fn DispatchCalleeCaptureToken::DispatchCalleeCaptureToken(
  id~ : Int,
) -> DispatchCalleeCaptureToken {
  { id, }
}

///|
#warnings("-unused_constructor")
priv enum DispatchNumericBinaryOperator {
  DispatchNumericAdd
  DispatchNumericSubtract
  DispatchNumericStrictEqual
}

///|
#warnings("-unused_constructor-unused_field")
priv enum DispatchNumericExpression {
  DispatchNumericNumber(Double)
  DispatchNumericParameter(DispatchParameterBindingToken)
  DispatchNumericRetainedParameter(DispatchRetainedParameterBindingToken)
  DispatchNumericCatchBinding(DispatchNumericCatchBindingToken)
  DispatchNumericTrustedCallee(DispatchCalleeCaptureToken)
  DispatchNumericClosedArgument(NumericRecursionClosedArgumentRecipe)
  DispatchNumericBinary(
    DispatchNumericBinaryOperator,
    DispatchNumericExpression,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericSingleArgumentCall(
    DispatchCalleeCaptureToken,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericTwoArgumentCall(
    DispatchCalleeCaptureToken,
    DispatchNumericExpression,
    DispatchNumericExpression,
    @token.Loc
  )
}

///|
#warnings("-unused_constructor-unused_field")
priv enum DispatchNumericExpressionObservation {
  DispatchNumericObserveNumber(Double)
  DispatchNumericObserveParameter(DispatchParameterBindingToken)
  DispatchNumericObserveRetainedParameter(DispatchRetainedParameterBindingToken)
  DispatchNumericObserveCatchBinding(DispatchNumericCatchBindingToken)
  DispatchNumericObserveTrustedCallee(DispatchCalleeCaptureToken)
  DispatchNumericObserveClosedArgument
  DispatchNumericObserveBinary(DispatchNumericBinaryOperator, @token.Loc)
  DispatchNumericObserveCall(@token.Loc)
}

///|
#warnings("-unused_constructor-unused_field")
priv enum DispatchNumericExpressionContinuation {
  DispatchAfterNumericExpressionObservation(DispatchNumericExpression)
  DispatchAfterNumericParameterRead(DispatchParameterBindingToken)
  DispatchAfterNumericRetainedParameterRead(
    DispatchRetainedParameterBindingToken
  )
  DispatchAfterNumericCatchBindingRead(DispatchNumericCatchBindingToken)
  DispatchAfterNumericClosedArgumentObservation(Double, Int)
  DispatchAfterNumericBaseCondition(DispatchNumericCallStart)
  DispatchNumericBinaryLeft(
    DispatchNumericBinaryOperator,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericCallCallee(
    DispatchCalleeCaptureToken,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericCallCalleeTwoArgument(
    DispatchCalleeCaptureToken,
    DispatchNumericExpression,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericCallArgument(DispatchCalleeCaptureToken, @token.Loc)
  DispatchNumericCallFirstArgument(
    DispatchCalleeCaptureToken,
    DispatchNumericExpression,
    @token.Loc
  )
  DispatchNumericCallSecondArgument(
    DispatchCalleeCaptureToken,
    Double,
    @token.Loc
  )
}

///|
#warnings("-unused_value")
fn numeric_recursion_root_dispatch_expression(
  plan : NumericRecursionPlan,
  capture : DispatchCalleeCaptureToken,
) -> DispatchNumericExpression {
  match plan.retained_argument {
    None =>
      DispatchNumericSingleArgumentCall(
        capture,
        DispatchNumericNumber(plan.initial_argument),
        plan.root_call_loc,
      )
    Some(recipe) =>
      DispatchNumericTwoArgumentCall(
        capture,
        DispatchNumericNumber(plan.initial_argument),
        DispatchNumericClosedArgument(recipe),
        plan.root_call_loc,
      )
  }
}

///|
#warnings("-unused_value")
fn numeric_recursion_base_condition_dispatch_expression(
  recipe : NumericRecursionExpressionRecipe,
  parameter : DispatchParameterBindingToken,
) -> DispatchNumericExpression {
  DispatchNumericBinary(
    DispatchNumericStrictEqual,
    DispatchNumericParameter(parameter),
    DispatchNumericNumber(NUMERIC_RECURSION_BASE),
    recipe.base_condition_loc,
  )
}

///|
#warnings("-unused_value")
fn numeric_recursion_base_result_dispatch_expression(
  retained_parameter? : DispatchRetainedParameterBindingToken? = None,
) -> DispatchNumericExpression {
  match retained_parameter {
    None => DispatchNumericNumber(NUMERIC_RECURSION_BASE)
    Some(token) => DispatchNumericRetainedParameter(token)
  }
}

///|
#warnings("-unused_value")
fn numeric_recursion_recursive_result_dispatch_expression(
  recipe : NumericRecursionExpressionRecipe,
  parameter : DispatchParameterBindingToken,
  capture : DispatchCalleeCaptureToken,
  retained_parameter? : DispatchRetainedParameterBindingToken? = None,
) -> DispatchNumericExpression {
  let recursive_argument = DispatchNumericBinary(
    DispatchNumericSubtract,
    DispatchNumericParameter(parameter),
    DispatchNumericNumber(NUMERIC_RECURSION_STEP),
    recipe.recursive_subtract_loc,
  )
  let recursive_call = match retained_parameter {
    None =>
      DispatchNumericSingleArgumentCall(
        capture,
        recursive_argument,
        recipe.recursive_call_loc,
      )
    Some(token) =>
      DispatchNumericTwoArgumentCall(
        capture,
        recursive_argument,
        DispatchNumericRetainedParameter(token),
        recipe.recursive_call_loc,
      )
  }
  DispatchNumericBinary(
    DispatchNumericAdd,
    DispatchNumericNumber(NUMERIC_RECURSION_STEP),
    recursive_call,
    recipe.recursive_add_loc,
  )
}

///|
fn snapshot_dispatch_numeric_expression(
  expression : DispatchNumericExpression,
) -> DispatchNumericExpression {
  match expression {
    DispatchNumericNumber(value) => DispatchNumericNumber(value)
    DispatchNumericParameter(token) => DispatchNumericParameter(token)
    DispatchNumericRetainedParameter(token) =>
      DispatchNumericRetainedParameter(token)
    DispatchNumericCatchBinding(token) => DispatchNumericCatchBinding(token)
    DispatchNumericTrustedCallee(token) => DispatchNumericTrustedCallee(token)
    DispatchNumericClosedArgument(recipe) =>
      DispatchNumericClosedArgument(recipe)
    DispatchNumericBinary(operator, left, right, loc) =>
      DispatchNumericBinary(
        operator,
        snapshot_dispatch_numeric_expression(left),
        snapshot_dispatch_numeric_expression(right),
        loc,
      )
    DispatchNumericSingleArgumentCall(token, argument, loc) =>
      DispatchNumericSingleArgumentCall(
        token,
        snapshot_dispatch_numeric_expression(argument),
        loc,
      )
    DispatchNumericTwoArgumentCall(token, first, second, loc) =>
      DispatchNumericTwoArgumentCall(
        token,
        snapshot_dispatch_numeric_expression(first),
        snapshot_dispatch_numeric_expression(second),
        loc,
      )
  }
}

///|
fn snapshot_dispatch_numeric_continuation(
  continuation : DispatchNumericExpressionContinuation,
) -> DispatchNumericExpressionContinuation {
  match continuation {
    DispatchAfterNumericExpressionObservation(expression) =>
      DispatchAfterNumericExpressionObservation(
        snapshot_dispatch_numeric_expression(expression),
      )
    DispatchAfterNumericParameterRead(token) =>
      DispatchAfterNumericParameterRead(token)
    DispatchAfterNumericRetainedParameterRead(token) =>
      DispatchAfterNumericRetainedParameterRead(token)
    DispatchAfterNumericCatchBindingRead(token) =>
      DispatchAfterNumericCatchBindingRead(token)
    DispatchAfterNumericClosedArgumentObservation(value, remaining) =>
      DispatchAfterNumericClosedArgumentObservation(value, remaining)
    DispatchAfterNumericBaseCondition(start) =>
      DispatchAfterNumericBaseCondition(
        DispatchNumericCallStart(
          parameter=start.parameter,
          retained_parameter=start.retained_parameter,
          recursive_callee=start.recursive_callee,
          recipe=start.recipe,
          return_recipe=start.return_recipe,
        ),
      )
    DispatchNumericBinaryLeft(operator, right, loc) =>
      DispatchNumericBinaryLeft(
        operator,
        snapshot_dispatch_numeric_expression(right),
        loc,
      )
    DispatchNumericCallCallee(token, argument, loc) =>
      DispatchNumericCallCallee(
        token,
        snapshot_dispatch_numeric_expression(argument),
        loc,
      )
    DispatchNumericCallCalleeTwoArgument(token, first, second, loc) =>
      DispatchNumericCallCalleeTwoArgument(
        token,
        snapshot_dispatch_numeric_expression(first),
        snapshot_dispatch_numeric_expression(second),
        loc,
      )
    DispatchNumericCallArgument(token, loc) =>
      DispatchNumericCallArgument(token, loc)
    DispatchNumericCallFirstArgument(token, second, loc) =>
      DispatchNumericCallFirstArgument(
        token,
        snapshot_dispatch_numeric_expression(second),
        loc,
      )
    DispatchNumericCallSecondArgument(token, first, loc) =>
      DispatchNumericCallSecondArgument(token, first, loc)
  }
}

///|
fn dispatch_numeric_expression_observation(
  expression : DispatchNumericExpression,
) -> DispatchNumericExpressionObservation {
  match expression {
    DispatchNumericNumber(value) => DispatchNumericObserveNumber(value)
    DispatchNumericParameter(token) => DispatchNumericObserveParameter(token)
    DispatchNumericRetainedParameter(token) =>
      DispatchNumericObserveRetainedParameter(token)
    DispatchNumericCatchBinding(token) =>
      DispatchNumericObserveCatchBinding(token)
    DispatchNumericTrustedCallee(token) =>
      DispatchNumericObserveTrustedCallee(token)
    DispatchNumericClosedArgument(_) => DispatchNumericObserveClosedArgument
    DispatchNumericBinary(operator, _, _, loc) =>
      DispatchNumericObserveBinary(operator, loc)
    DispatchNumericSingleArgumentCall(_, _, loc) =>
      DispatchNumericObserveCall(loc)
    DispatchNumericTwoArgumentCall(_, _, _, loc) =>
      DispatchNumericObserveCall(loc)
  }
}

///|
fn dispatch_numeric_binary_operator_to_ast(
  operator : DispatchNumericBinaryOperator,
) -> @ast.BinOp {
  match operator {
    DispatchNumericAdd => @ast.Add
    DispatchNumericSubtract => @ast.Sub
    DispatchNumericStrictEqual => @ast.EqEqEq
  }
}

///|
fn begin_dispatch_numeric_expression(
  state : ActivationDispatchState,
  expression : DispatchNumericExpression,
) -> (ActivationDispatchState, ActivationDispatchDecision) raise InvalidDispatchTransition {
  guard state.phase is DispatchReady else {
    invalid_dispatch_transition("numeric expression dispatch requires Ready")
  }
  let retained = snapshot_dispatch_numeric_expression(expression)
  schedule_dispatch_production_work(
    push_dispatch_frame(
      state,
      DispatchContinuationFrame(
        DispatchContinueNumericExpression(
          DispatchAfterNumericExpressionObservation(retained),
        ),
      ),
    ),
    DispatchNumericObserveExpressionStep(
      dispatch_numeric_expression_observation(retained),
    ),
  )
}

///|
fn resume_dispatch_numeric_expression_outcome(
  state : ActivationDispatchState,
  continuation : DispatchNumericExpressionContinuation,
  value : Value,
) -> DispatchCompletionResume raise InvalidDispatchTransition {
  guard state.phase is DispatchReady else {
    invalid_dispatch_transition("numeric expression resume requires Ready")
  }
  match continuation {
    DispatchAfterNumericExpressionObservation(DispatchNumericNumber(number)) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "numeric expression observation returned a non-sentinel value",
        )
      }
      DispatchResumeCompletion(DispatchNormal(Number(number)))
    }
    DispatchAfterNumericExpressionObservation(
      DispatchNumericClosedArgument(recipe)
    ) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "closed numeric argument observation returned a non-sentinel value",
        )
      }
      if recipe.observation_count <= 1 {
        DispatchResumeCompletion(DispatchNormal(Number(recipe.value)))
      } else {
        dispatch_resume_decision(
          schedule_dispatch_production_work(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchAfterNumericClosedArgumentObservation(
                    recipe.value,
                    recipe.observation_count - 1,
                  ),
                ),
              ),
            ),
            DispatchNumericObserveExpressionStep(
              DispatchNumericObserveClosedArgument,
            ),
          ),
        )
      }
    }
    DispatchAfterNumericParameterRead(_) => {
      guard value is Number(_) else {
        invalid_dispatch_transition(
          "numeric parameter capability returned a non-number",
        )
      }
      DispatchResumeCompletion(DispatchNormal(value))
    }
    DispatchAfterNumericRetainedParameterRead(_) => {
      guard value is Number(_) else {
        invalid_dispatch_transition(
          "numeric retained parameter capability returned a non-number",
        )
      }
      DispatchResumeCompletion(DispatchNormal(value))
    }
    DispatchAfterNumericCatchBindingRead(_) =>
      DispatchResumeCompletion(DispatchNormal(value))
    DispatchAfterNumericClosedArgumentObservation(final_value, remaining) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "closed numeric argument observation returned a non-sentinel value",
        )
      }
      if remaining <= 1 {
        DispatchResumeCompletion(DispatchNormal(Number(final_value)))
      } else {
        dispatch_resume_decision(
          schedule_dispatch_production_work(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchAfterNumericClosedArgumentObservation(
                    final_value,
                    remaining - 1,
                  ),
                ),
              ),
            ),
            DispatchNumericObserveExpressionStep(
              DispatchNumericObserveClosedArgument,
            ),
          ),
        )
      }
    }
    _ =>
      dispatch_resume_decision(
        resume_dispatch_numeric_expression_effect(state, continuation, value),
      )
  }
}

///|
fn resume_dispatch_numeric_expression_effect(
  state : ActivationDispatchState,
  continuation : DispatchNumericExpressionContinuation,
  value : Value,
) -> (ActivationDispatchState, ActivationDispatchDecision) raise InvalidDispatchTransition {
  guard state.phase is DispatchReady else {
    invalid_dispatch_transition("numeric expression resume requires Ready")
  }
  match continuation {
    DispatchAfterNumericExpressionObservation(expression) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "numeric expression observation returned a non-sentinel value",
        )
      }
      match expression {
        DispatchNumericNumber(_) =>
          invalid_dispatch_transition(
            "numeric literal completion must remain in the routing loop",
          )
        DispatchNumericParameter(token) =>
          schedule_dispatch_production_work(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchAfterNumericParameterRead(token),
                ),
              ),
            ),
            DispatchNumericReadParameter(token),
          )
        DispatchNumericRetainedParameter(token) =>
          schedule_dispatch_production_work(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchAfterNumericRetainedParameterRead(token),
                ),
              ),
            ),
            DispatchNumericReadRetainedParameter(token),
          )
        DispatchNumericCatchBinding(token) =>
          schedule_dispatch_production_work(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchAfterNumericCatchBindingRead(token),
                ),
              ),
            ),
            DispatchNumericReadCatchBinding(token),
          )
        // SingleArgumentCall is the only producer of this internal node. It
        // pushes the matching NumericCallCallee frame before capture starts,
        // so the sentinel completion deliberately resumes that paired frame.
        DispatchNumericTrustedCallee(token) =>
          schedule_dispatch_production_work(
            state,
            DispatchNumericCaptureTrustedCallee(token),
          )
        DispatchNumericClosedArgument(_) =>
          invalid_dispatch_transition(
            "closed numeric argument must remain in its observation loop",
          )
        DispatchNumericBinary(operator, left, right, loc) =>
          begin_dispatch_numeric_expression(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchNumericBinaryLeft(operator, right, loc),
                ),
              ),
            ),
            left,
          )
        DispatchNumericSingleArgumentCall(token, argument, loc) =>
          begin_dispatch_numeric_expression(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchNumericCallCallee(token, argument, loc),
                ),
              ),
            ),
            DispatchNumericTrustedCallee(token),
          )
        DispatchNumericTwoArgumentCall(token, first, second, loc) =>
          begin_dispatch_numeric_expression(
            push_dispatch_frame(
              state,
              DispatchContinuationFrame(
                DispatchContinueNumericExpression(
                  DispatchNumericCallCalleeTwoArgument(
                    token, first, second, loc,
                  ),
                ),
              ),
            ),
            DispatchNumericTrustedCallee(token),
          )
      }
    }
    DispatchAfterNumericParameterRead(_) => {
      guard value is Number(_) else {
        invalid_dispatch_transition(
          "numeric parameter capability returned a non-number",
        )
      }
      invalid_dispatch_transition(
        "numeric parameter completion must remain in the routing loop",
      )
    }
    DispatchAfterNumericRetainedParameterRead(_) =>
      invalid_dispatch_transition(
        "numeric retained parameter completion must remain in the routing loop",
      )
    DispatchAfterNumericCatchBindingRead(_) =>
      invalid_dispatch_transition(
        "numeric catch completion must remain in the routing loop",
      )
    DispatchAfterNumericClosedArgumentObservation(_, _) =>
      invalid_dispatch_transition(
        "closed numeric argument completion must remain in the routing loop",
      )
    DispatchAfterNumericBaseCondition(start) =>
      match value {
        Bool(true) =>
          begin_dispatch_numeric_statement_observation(
            state,
            DispatchNumericObserveReturnStatement,
            DispatchAfterNumericReturnStatement(
              numeric_recursion_base_result_dispatch_expression(
                retained_parameter=start.retained_parameter,
              ),
            ),
          )
        Bool(false) => {
          let expression = numeric_recursion_recursive_result_dispatch_expression(
            start.recipe,
            start.parameter,
            start.recursive_callee,
            retained_parameter=start.retained_parameter,
          )
          match start.return_recipe {
            NumericRecursionDirectReturn =>
              begin_dispatch_numeric_statement_observation(
                state,
                DispatchNumericObserveReturnStatement,
                DispatchAfterNumericReturnStatement(expression),
              )
            NumericRecursionProtectedReturn(finalizer) =>
              begin_dispatch_numeric_statement_observation(
                state,
                DispatchNumericObserveFunctionTryStatement,
                DispatchAfterNumericFunctionTryStatement(
                  DispatchNumericProtectedReturnStart(expression~, finalizer~),
                ),
              )
          }
        }
        _ =>
          invalid_dispatch_transition(
            "numeric base condition completed with a non-boolean",
          )
      }
    DispatchNumericBinaryLeft(operator, right, loc) => {
      guard value is Number(_) else {
        invalid_dispatch_transition(
          "numeric binary left operand completed with a non-number",
        )
      }
      begin_dispatch_numeric_expression(
        push_dispatch_frame(
          state,
          DispatchContinuationFrame(
            DispatchApplyNumericBinaryRight(
              DispatchBinaryResume(
                op=dispatch_numeric_binary_operator_to_ast(operator),
                left=value,
                loc~,
              ),
            ),
          ),
        ),
        right,
      )
    }
    DispatchNumericCallCallee(token, argument, loc) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "numeric callee capture returned a non-sentinel value",
        )
      }
      begin_dispatch_numeric_expression(
        push_dispatch_frame(
          state,
          DispatchContinuationFrame(
            DispatchContinueNumericExpression(
              DispatchNumericCallArgument(token, loc),
            ),
          ),
        ),
        argument,
      )
    }
    DispatchNumericCallCalleeTwoArgument(token, first, second, loc) => {
      guard value is Undefined else {
        invalid_dispatch_transition(
          "numeric callee capture returned a non-sentinel value",
        )
      }
      begin_dispatch_numeric_expression(
        push_dispatch_frame(
          state,
          DispatchContinuationFrame(
            DispatchContinueNumericExpression(
              DispatchNumericCallFirstArgument(token, second, loc),
            ),
          ),
        ),
        first,
      )
    }
    DispatchNumericCallArgument(token, loc) => {
      guard value is Number(argument) else {
        invalid_dispatch_transition(
          "numeric call argument completed with a non-number",
        )
      }
      schedule_dispatch_production_work(
        state,
        DispatchNumericStartCapturedCall(token, argument, loc),
      )
    }
    DispatchNumericCallFirstArgument(token, second, loc) => {
      guard value is Number(first) else {
        invalid_dispatch_transition(
          "numeric first call argument completed with a non-number",
        )
      }
      begin_dispatch_numeric_expression(
        push_dispatch_frame(
          state,
          DispatchContinuationFrame(
            DispatchContinueNumericExpression(
              DispatchNumericCallSecondArgument(token, first, loc),
            ),
          ),
        ),
        second,
      )
    }
    DispatchNumericCallSecondArgument(token, first, loc) => {
      guard value is Number(second) else {
        invalid_dispatch_transition(
          "numeric second call argument completed with a non-number",
        )
      }
      schedule_dispatch_production_work(
        state,
        DispatchNumericStartCapturedTwoArgumentCall(token, first, second, loc),
      )
    }
  }
}