import { ObjectTree } from "@weborigami/async-tree";
import assert from "node:assert";
import { describe, test } from "node:test";
import * as ops from "../../src/runtime/ops.js";

import evaluate from "../../src/runtime/evaluate.js";

describe("evaluate", () => {
  test("can retrieve values from scope", async () => {
    const code = createCode([ops.scope, "message"]);
    const parent = new ObjectTree({
      message: "Hello",
    });
    const tree = new ObjectTree({});
    tree.parent = parent;
    const result = await evaluate.call(tree, code);
    assert.equal(result, "Hello");
  });

  test("can invoke functions in scope", async () => {
    // Match the array representation of code generated by the parser.
    const code = createCode([
      [ops.scope, "greet"],
      [ops.scope, "name"],
    ]);

    const tree = new ObjectTree({
      async greet(name) {
        return `Hello ${name}`;
      },
      name: "world",
    });

    const result = await evaluate.call(tree, code);
    assert.equal(result, "Hello world");
  });

  test("passes context to invoked functions", async () => {
    const code = createCode([ops.scope, "fn"]);
    const tree = new ObjectTree({
      async fn() {
        assert.equal(this, tree);
      },
    });
    await evaluate.call(tree, code);
  });

  test("evaluates a function with fixed number of arguments", async () => {
    const fn = (x, y) => ({
      c: `${x}${y}c`,
    });
    const code = createCode([ops.traverse, fn, "a", "b", "c"]);
    assert.equal(await evaluate.call(null, code), "abc");
  });

  test("if object in function position isn't a function, can unpack it", async () => {
    const fn = (...args) => args.join(",");
    const packed = new String();
    /** @type {any} */ (packed).unpack = async () => fn;
    const code = createCode([packed, "a", "b", "c"]);
    const result = await evaluate.call(null, code);
    assert.equal(result, "a,b,c");
  });

  test("by defalut sets the parent of a returned tree to the current tree", async () => {
    const fn = () => new ObjectTree({});
    const code = createCode([fn]);
    const tree = new ObjectTree({});
    const result = await evaluate.call(tree, code);
    assert.equal(result.parent, tree);
  });
});

/**
 * @returns {import("../../index.ts").Code}
 */
function createCode(array) {
  const code = array;
  /** @type {any} */ (code).location = {
    source: {
      text: "",
    },
  };
  return code;
}
