import {
  NormalCompletion, Q,
} from '../completion.mts';
import { Evaluate, type PlainEvaluator } from '../evaluator.mts';
import type { ParseNode } from '../parser/ParseNode.mts';
import { StringValue, IsAnonymousFunctionDefinition, type FunctionDeclaration } from '../static-semantics/all.mts';
import { Value } from '../value.mts';
import { NamedEvaluation, BindingInitialization } from './all.mts';
import {
  GetValue,
  PutValue,
  ResolveBinding,
} from '#self';

/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */
//   VariableDeclaration :
//     BindingIdentifier
//     BindingIdentifier Initializer
//     BindingPattern Initializer
function* Evaluate_VariableDeclaration({ BindingIdentifier, Initializer, BindingPattern }: ParseNode.VariableDeclaration): PlainEvaluator {
  if (BindingIdentifier) {
    if (!Initializer) {
      // 1. Return NormalCompletion(empty).
      return NormalCompletion(undefined);
    }
    // 1. Let bindingId be StringValue of BindingIdentifier.
    const bindingId = StringValue(BindingIdentifier);
    // 2. Let lhs be ? ResolveBinding(bindingId).
    const lhs = Q(yield* ResolveBinding(bindingId, undefined, BindingIdentifier.strict));
    // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then
    let value;
    if (IsAnonymousFunctionDefinition(Initializer)) {
      // a. Let value be NamedEvaluation of Initializer with argument bindingId.
      value = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, bindingId));
    } else { // 4. Else,
      // a. Let rhs be the result of evaluating Initializer.
      const rhs = Q(yield* Evaluate(Initializer));
      // b. Let value be ? GetValue(rhs).
      value = Q(yield* GetValue(rhs));
    }
    // 5. Return ? PutValue(lhs, value).
    return Q(yield* PutValue(lhs, value));
  }
  // 1. Let rhs be the result of evaluating Initializer.
  const rhs = Q(yield* Evaluate(Initializer!));
  // 2. Let rval be ? GetValue(rhs).
  const rval = Q(yield* GetValue(rhs));
  // 3. Return the result of performing BindingInitialization for BindingPattern passing rval and undefined as arguments.
  return yield* BindingInitialization(BindingPattern!, rval, Value.undefined);
}

/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */
//   VariableDeclarationList : VariableDeclarationList `,` VariableDeclaration
//
// (implicit)
//   VariableDeclarationList : VariableDeclaration
export function* Evaluate_VariableDeclarationList(VariableDeclarationList: ParseNode.VariableDeclarationList) {
  let next;
  for (const VariableDeclaration of VariableDeclarationList) {
    next = yield* Evaluate_VariableDeclaration(VariableDeclaration);
    Q(next);
  }
  return next;
}

/** https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation */
//   VariableStatement : `var` VariableDeclarationList `;`
export function* Evaluate_VariableStatement({ VariableDeclarationList }: ParseNode.VariableStatement): PlainEvaluator {
  const next = yield* Evaluate_VariableDeclarationList(VariableDeclarationList);
  Q(next);
  return NormalCompletion(undefined);
}
