/*
 * Copyright 2026 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_ir_js_utils_h
#define wasm_ir_js_utils_h

#include "ir/intrinsics.h"
#include "wasm-type.h"
#include "wasm.h"

namespace wasm::JSUtils {

// Whether this is a descriptor struct type whose first field is immutable and a
// subtype of externref.
inline bool hasPossibleJSPrototypeField(HeapType type) {
  if (!type.getDescribedType()) {
    return false;
  }
  assert(type.isStruct());
  const auto& fields = type.getStruct().fields;
  if (fields.empty()) {
    return false;
  }
  if (fields[0].mutable_ == Mutable) {
    return false;
  }
  if (!fields[0].type.isRef()) {
    return false;
  }
  return fields[0].type.getHeapType().isMaybeShared(HeapType::ext);
}

// Calls flowIn and flowOut on all types that may flow in from or out to JS.
template<typename In, typename Out>
void iterJSInterface(Module& wasm, In flowIn, Out flowOut) {
  // @binaryen.js.called functions are called from JS. Their parameters flow
  // in from JS and their results flow back out.
  for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
    auto* func = wasm.getFunction(f);
    for (auto type : func->getParams()) {
      flowIn(type);
    }
    for (auto type : func->getResults()) {
      flowOut(type);
    }
  }

  for (auto& ex : wasm.exports) {
    switch (ex->kind) {
      case ExternalKindImpl::Function: {
        // Exported functions are also called from JS. Their parameters flow
        // in from JS and their result flow back out.
        auto* func = wasm.getFunction(*ex->getInternalName());
        for (auto type : func->getParams()) {
          flowIn(type);
        }
        for (auto type : func->getResults()) {
          flowOut(type);
        }
        break;
      }
      case ExternalKindImpl::Table: {
        // Exported tables let values flow in and out.
        auto* table = wasm.getTable(*ex->getInternalName());
        flowOut(table->type);
        flowIn(table->type);
        break;
      }
      case ExternalKindImpl::Global: {
        // Exported globals let values flow out. Iff they are mutable, they
        // also let values flow back in.
        auto* global = wasm.getGlobal(*ex->getInternalName());
        flowOut(global->type);
        if (global->mutable_) {
          flowIn(global->type);
        }
        break;
      }
      case ExternalKindImpl::Memory:
      case ExternalKindImpl::Tag:
      case ExternalKindImpl::Invalid:
        break;
    }
  }
  for (auto& func : wasm.functions) {
    // Imported functions are the opposite of exported functions. Their
    // parameters flow out and their results flow in.
    if (func->imported()) {
      for (auto type : func->getParams()) {
        flowOut(type);
      }
      for (auto type : func->getResults()) {
        flowIn(type);
      }
    }
  }
  for (auto& table : wasm.tables) {
    // Imported tables, like exported tables, let values flow in and out.
    if (table->imported()) {
      flowOut(table->type);
      flowIn(table->type);
    }
  }
  for (auto& global : wasm.globals) {
    // Imported mutable globals let values flow in and out. Imported immutable
    // globals imply that values will flow in.
    if (global->imported()) {
      flowIn(global->type);
      if (global->mutable_) {
        flowOut(global->type);
      }
    }
  }
}

} // namespace wasm::JSUtils

#endif // wasm_ir_js_utils_h
