/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { BlockId, HIRFunction, PrunedScopeTerminal } from "../HIR";
import { assertExhaustive, retainWhere } from "../Utils/utils";

/**
 * Prunes any reactive scopes that are within a loop (for, while, etc). We don't yet
 * support memoization within loops because this would require an extra layer of reconciliation
 * (plus a way to identify values across runs, similar to how we use `key` in JSX for lists).
 * Eventually we may integrate more deeply into the runtime so that we can do a single level
 * of reconciliation, but for now we've found it's sufficient to memoize *around* the loop.
 */
export function flattenReactiveLoopsHIR(fn: HIRFunction): void {
  const activeLoops = Array<BlockId>();
  for (const [, block] of fn.body.blocks) {
    retainWhere(activeLoops, (id) => id !== block.id);
    const { terminal } = block;
    switch (terminal.kind) {
      case "do-while":
      case "for":
      case "for-in":
      case "for-of":
      case "while": {
        activeLoops.push(terminal.fallthrough);
        break;
      }
      case "scope": {
        if (activeLoops.length !== 0) {
          block.terminal = {
            kind: "pruned-scope",
            block: terminal.block,
            fallthrough: terminal.fallthrough,
            id: terminal.id,
            loc: terminal.loc,
            scope: terminal.scope,
          } as PrunedScopeTerminal;
        }
        break;
      }
      case "branch":
      case "goto":
      case "if":
      case "label":
      case "logical":
      case "maybe-throw":
      case "optional":
      case "pruned-scope":
      case "return":
      case "sequence":
      case "switch":
      case "ternary":
      case "throw":
      case "try":
      case "unreachable":
      case "unsupported": {
        break;
      }
      default: {
        assertExhaustive(
          terminal,
          `Unexpected terminal kind \`${(terminal as any).kind}\``
        );
      }
    }
  }
}