import { Value } from '../value.mts';
import {
  NormalCompletion,
  Q, X,
} from '../completion.mts';
import { Evaluate, type PlainEvaluator } from '../evaluator.mts';
import {
  StringValue,
  IsAnonymousFunctionDefinition,
} from '../static-semantics/all.mts';
import type { ParseNode } from '../parser/ParseNode.mts';
import { __ts_cast__ } from '../helpers.mts';
import { NamedEvaluation, BindingInitialization } from './all.mts';
import {
  Assert,
  GetValue,
  InitializeReferencedBinding,
  IteratorStep,
  PutValue,
  ResolveBinding,
  ArrayCreate,
  CreateDataPropertyOrThrow,
  ToString,
  F,
  type IteratorRecord,

  IteratorStepValue,
  UndefinedValue, type EnvironmentRecord, type FunctionDeclaration,
} from '#self';

/** https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-iteratorbindinginitialization */
// FormalParameters :
//   [empty]
//   FormalParameterList `,` FunctionRestParameter
export function* IteratorBindingInitialization_FormalParameters(FormalParameters: ParseNode.FormalParameters, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  if (FormalParameters.length === 0) {
    // 1. Return NormalCompletion(empty).
    return NormalCompletion(undefined);
  }

  for (const FormalParameter of FormalParameters.slice(0, -1)) {
    Q(yield* IteratorBindingInitialization_FormalParameter(FormalParameter, iteratorRecord, environment));
  }

  const last = FormalParameters[FormalParameters.length - 1];
  if (last.type === 'BindingRestElement') {
    return yield* IteratorBindingInitialization_FunctionRestParameter(last, iteratorRecord, environment);
  }
  return yield* IteratorBindingInitialization_FormalParameter(last, iteratorRecord, environment);
}

// FormalParameter : BindingElement
function IteratorBindingInitialization_FormalParameter(BindingElement: ParseNode.FormalParametersElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  // TODO
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return IteratorBindingInitialization_BindingElement(BindingElement as any, iteratorRecord, environment);
}

// FunctionRestParameter : BindingRestElement
function IteratorBindingInitialization_FunctionRestParameter(FunctionRestParameter: ParseNode.FunctionRestParameter, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  return IteratorBindingInitialization_BindingRestElement(FunctionRestParameter, iteratorRecord, environment);
}

// BindingElement :
//   SingleNameBinding
//   BindingPattern
function IteratorBindingInitialization_BindingElement(BindingElement: ParseNode.BindingElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  if ('BindingPattern' in BindingElement) {
    return IteratorBindingInitialization_BindingPattern(BindingElement, iteratorRecord, environment);
  }
  return IteratorBindingInitialization_SingleNameBinding(BindingElement, iteratorRecord, environment);
}

// SingleNameBinding : BindingIdentifier Initializer?
function* IteratorBindingInitialization_SingleNameBinding({ BindingIdentifier, Initializer }: ParseNode.SingleNameBinding, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator {
  // 1. Let bindingId be StringValue of BindingIdentifier.
  const bindingId = StringValue(BindingIdentifier);
  // 2. Let lhs be ? ResolveBinding(bindingId, environment).
  const lhs = Q(yield* ResolveBinding(bindingId, environment, BindingIdentifier.strict));
  let v: Value = Value.undefined;
  // 3. If iteratorRecord.[[Done]] is false, then
  if (iteratorRecord.Done === Value.false) {
    // a. Let next be ? IteratorStepValue(iteratorRecord).
    const next = Q(yield* IteratorStepValue(iteratorRecord));
    // d. If next is not DONE,
    if (next !== 'done') {
      v = next;
    }
  }
  // 5. If Initializer is present and v is undefined, then
  if (Initializer && v === Value.undefined) {
    if (IsAnonymousFunctionDefinition(Initializer)) {
      v = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, bindingId));
    } else {
      const defaultValue = Q(yield* Evaluate(Initializer));
      v = Q(yield* GetValue(defaultValue));
    }
  }
  // 6. If environment is undefined, return ? PutValue(lhs, v).
  if (environment === Value.undefined) {
    return Q(yield* PutValue(lhs, v));
  }
  // 7. Return InitializeReferencedBinding(lhs, v).
  return yield* InitializeReferencedBinding(lhs, X(v));
}

// BindingRestElement :
//   `...` BindingIdentifier
//   `...` BindingPattern
function* IteratorBindingInitialization_BindingRestElement({ BindingIdentifier, BindingPattern }: ParseNode.BindingRestElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  if (BindingIdentifier) {
    // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment).
    const lhs = Q(yield* ResolveBinding(StringValue(BindingIdentifier), environment, BindingIdentifier.strict));
    // 2. Let A be ! ArrayCreate(0).
    const A = X(ArrayCreate(0));
    // 3. Let n be 0.
    let n = 0;
    // 4. Repeat,
    while (true) {
      let next: 'done' | Value = 'done';
      // a. If iteratorRecord.[[Done]] is false, then
      if (iteratorRecord.Done === Value.false) {
        // i. Let next be ? IteratorStepValue(iteratorRecord).
        next = Q(yield* IteratorStepValue(iteratorRecord));
      }
      if (next === 'done') {
        // i. If environment is undefined, return ? PutValue(lhs, A).
        if (environment === Value.undefined) {
          return Q(yield* PutValue(lhs, A));
        }
        // ii. Return InitializeReferencedBinding(lhs, A).
        return yield* InitializeReferencedBinding(lhs, A);
      }
      // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next).
      X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), next));
      // g. Set n to n + 1.
      n += 1;
    }
  } else {
    // 1. Let A be ! ArrayCreate(0).
    const A = X(ArrayCreate(0));
    // 2. Let n be 0.
    let n = 0;
    // 3. Repeat,
    while (true) {
      let next: 'done' | Value = 'done';
      // a. If iteratorRecord.[[Done]] is false, then
      if (iteratorRecord.Done === Value.false) {
        // i. Let next be ? IteratorStepValue(iteratorRecord).
        next = Q(yield* IteratorStepValue(iteratorRecord));
      }
      // b. If next is done, then
      if (next === 'done') {
        // i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments.
        return yield* BindingInitialization(BindingPattern!, A, environment);
      }
      // f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next).
      X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), Q(next)));
      // g. Set n to n + 1.
      n += 1;
    }
  }
}

function* IteratorBindingInitialization_BindingPattern({ BindingPattern, Initializer }: ParseNode.BindingElement, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue) {
  let v: Value = Value.undefined;
  // 1. If iteratorRecord.[[Done]] is false, then
  if (iteratorRecord.Done === Value.false) {
    // a. Let next be ? IteratorStepValue(iteratorRecord).
    const next = Q(yield* IteratorStepValue(iteratorRecord));
    if (next !== 'done') {
      v = next;
    }
  }
  // 3. If Initializer is present and v is undefined, then
  if (Initializer && v instanceof UndefinedValue) {
    // a. Let defaultValue be the result of evaluating Initializer.
    const defaultValue = Q(yield* Evaluate(Initializer));
    // b. Set v to ? GetValue(defaultValue).
    v = Q(yield* GetValue(defaultValue));
  }
  // 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments.
  return yield* BindingInitialization(BindingPattern, X(v), environment);
}

function* IteratorDestructuringAssignmentEvaluation(node: ParseNode.Elision, iteratorRecord: IteratorRecord): PlainEvaluator {
  Assert(node.type === 'Elision');
  // 1. If iteratorRecord.[[Done]] is false, then
  if (iteratorRecord.Done === Value.false) {
    // a. Perform ? IteratorStep(iteratorRecord).
    Q(yield* IteratorStep(iteratorRecord));
  }
  // 2. Return NormalCompletion(empty).
  return NormalCompletion(undefined);
}

export function* IteratorBindingInitialization_ArrayBindingPattern({ BindingElementList, BindingRestElement }: ParseNode.ArrayBindingPattern, iteratorRecord: IteratorRecord, environment: EnvironmentRecord | UndefinedValue): PlainEvaluator {
  for (const BindingElement of BindingElementList) {
    if (BindingElement.type === 'Elision') {
      Q(yield* IteratorDestructuringAssignmentEvaluation(BindingElement, iteratorRecord));
    } else {
      // TODO
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      Q(yield* IteratorBindingInitialization_BindingElement(BindingElement as any, iteratorRecord, environment));
    }
  }

  if (BindingRestElement) {
    return Q(yield* IteratorBindingInitialization_BindingRestElement(BindingRestElement, iteratorRecord, environment));
  }
  return NormalCompletion(undefined);
}
