import { parseExpressionString } from "$common/space_lua/parse.ts";
import type { LuaExpression } from "$common/space_lua/ast.ts";
import { evalExpression } from "$common/space_lua/eval.ts";
import {
  LuaBuiltinFunction,
  LuaEnv,
  LuaRuntimeError,
  type LuaStackFrame,
  LuaTable,
  luaToString,
  luaValueToJS,
} from "$common/space_lua/runtime.ts";

/**
 * These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
 */

/**
 * Helper function to create an augmented environment
 */
function createAugmentedEnv(
  sf: LuaStackFrame,
  envAugmentation?: LuaTable,
): LuaEnv {
  const globalEnv = sf.threadLocal.get("_GLOBAL");
  if (!globalEnv) {
    throw new Error("_GLOBAL not defined");
  }
  const env = new LuaEnv(globalEnv);
  if (envAugmentation) {
    for (const key of envAugmentation.keys()) {
      env.setLocal(key, envAugmentation.rawGet(key));
    }
  }
  return env;
}

export const spaceLuaApi = new LuaTable({
  /**
   * Parses a lua expression and returns the parsed expression.
   *
   * @param sf - The current space_lua state.
   * @param luaExpression - The lua expression to parse.
   * @returns The parsed expression.
   */
  parse_expression: new LuaBuiltinFunction(
    (_sf, luaExpression: string) => {
      return parseExpressionString(luaExpression);
    },
  ),
  /**
   * Evaluates a parsed lua expression and returns the result.
   *
   * @param sf - The current space_lua state.
   * @param parsedExpr - The parsed lua expression to evaluate.
   * @param envAugmentation - An optional environment to augment the global environment with.
   * @returns The result of the evaluated expression.
   */
  eval_expression: new LuaBuiltinFunction(
    async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => {
      const env = createAugmentedEnv(sf, envAugmentation);
      return luaValueToJS(await evalExpression(parsedExpr, env, sf));
    },
  ),
  /**
   * Interpolates a string with lua expressions and returns the result.
   *
   * @param sf - The current space_lua state.
   * @param template - The template string to interpolate.
   * @param envAugmentation - An optional environment to augment the global environment with.
   * @returns The interpolated string.
   */
  interpolate: new LuaBuiltinFunction(
    async (sf, template: string, envAugmentation?: LuaTable) => {
      let result = "";
      let currentIndex = 0;

      while (true) {
        const startIndex = template.indexOf("${", currentIndex);
        if (startIndex === -1) {
          result += template.slice(currentIndex);
          break;
        }

        result += template.slice(currentIndex, startIndex);

        // Find matching closing brace by counting nesting
        let nestLevel = 1;
        let endIndex = startIndex + 2;
        while (nestLevel > 0 && endIndex < template.length) {
          if (template[endIndex] === "{") {
            nestLevel++;
          } else if (template[endIndex] === "}") {
            nestLevel--;
          }
          if (nestLevel > 0) {
            endIndex++;
          }
        }

        if (nestLevel > 0) {
          throw new LuaRuntimeError("Unclosed interpolation expression", sf);
        }

        const expr = template.slice(startIndex + 2, endIndex);
        try {
          const parsedExpr = parseExpressionString(expr);
          const env = createAugmentedEnv(sf, envAugmentation);
          const luaResult = luaValueToJS(
            await evalExpression(parsedExpr, env, sf),
          );
          result += luaToString(luaResult);
        } catch (e: any) {
          throw new LuaRuntimeError(
            `Error evaluating "${expr}": ${e.message}`,
            sf,
          );
        }

        currentIndex = endIndex + 1;
      }

      return result;
    },
  ),
});