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

/** https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization */
export function* KeyedBindingInitialization(node: ParseNode.BindingElement | ParseNode.SingleNameBinding, value: Value, environment: EnvironmentRecord | UndefinedValue, propertyName: PropertyKeyValue) {
  if (node.type === 'BindingElement') {
    // 1. Let v be ? GetV(value, propertyName).
    let v = Q(yield* GetV(value, propertyName));
    // 2. If Initializer is present and v is undefined, then
    if (node.Initializer && v === Value.undefined) {
      // a. Let defaultValue be the result of evaluating Initializer.
      const defaultValue = Q(yield* Evaluate(node.Initializer));
      // b. Set v to ? GetValue(defaultValue).
      v = Q(yield* GetValue(defaultValue));
    }
    // 2. Return the result of performing BindingInitialization for BindingPattern passing v and environment as arguments.
    return yield* BindingInitialization(node.BindingPattern, v, environment);
  } else {
    // 1. Let bindingId be StringValue of BindingIdentifier.
    const bindingId = StringValue(node.BindingIdentifier);
    // 2. Let lhs be ? ResolveBinding(bindingId, environment).
    const lhs = Q(yield* ResolveBinding(bindingId, environment, node.BindingIdentifier.strict));
    // 3. Let v be ? GetV(value, propertyName).
    let v = Q(yield* GetV(value, propertyName));
    if (node.Initializer && v === Value.undefined) {
      // a. If IsAnonymousFunctionDefinition(Initializer) is true, then
      if (IsAnonymousFunctionDefinition(node.Initializer)) {
        // i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId.
        v = (yield* NamedEvaluation(node.Initializer as FunctionDeclaration, bindingId)) as Value;
      } else { // b. Else,
        // i. Let defaultValue be the result of evaluating Initializer.
        const defaultValue = Q(yield* Evaluate(node.Initializer));
        // ii. Set v to ? GetValue(defaultValue).
        v = Q(yield* GetValue(defaultValue));
      }
    }
    // 5. If environment is undefined, return ? PutValue(lhs, v).
    if (environment === Value.undefined) {
      return Q(yield* PutValue(lhs, v));
    }
    // 6. Return InitializeReferencedBinding(lhs, v).
    return yield* InitializeReferencedBinding(lhs, v);
  }
}
