import {
  LuaBuiltinFunction,
  LuaRuntimeError,
  LuaTable,
} from "$common/space_lua/runtime.ts";

export const mathApi = new LuaTable({
  // Random number generation
  random: new LuaBuiltinFunction((_sf, m?: number, n?: number) => {
    if (m === undefined && n === undefined) {
      // random() returns [0,1)
      return Math.random();
    } else if (n === undefined) {
      // random(m) returns [1,m]
      return Math.floor(Math.random() * m!) + 1;
    } else {
      // random(m,n) returns [m,n]
      return Math.floor(Math.random() * (n - m! + 1)) + m!;
    }
  }),

  // Basic functions
  abs: new LuaBuiltinFunction((_sf, x: number) => Math.abs(x)),
  ceil: new LuaBuiltinFunction((_sf, x: number) => Math.ceil(x)),
  floor: new LuaBuiltinFunction((_sf, x: number) => Math.floor(x)),
  max: new LuaBuiltinFunction((_sf, ...args: number[]) => Math.max(...args)),
  min: new LuaBuiltinFunction((_sf, ...args: number[]) => Math.min(...args)),

  // Rounding and remainder
  fmod: new LuaBuiltinFunction((_sf, x: number, y: number) => x % y),
  modf: new LuaBuiltinFunction((_sf, x: number) => {
    const int = Math.floor(x);
    const frac = x - int;
    return new LuaTable([int, frac]);
  }),

  // Power and logarithms
  exp: new LuaBuiltinFunction((_sf, x: number) => Math.exp(x)),
  log: new LuaBuiltinFunction((_sf, x: number, base?: number) => {
    if (base === undefined) {
      return Math.log(x);
    }
    return Math.log(x) / Math.log(base);
  }),
  pow: new LuaBuiltinFunction((_sf, x: number, y: number) => Math.pow(x, y)),
  sqrt: new LuaBuiltinFunction((_sf, x: number) => Math.sqrt(x)),

  // Trigonometric functions
  cos: new LuaBuiltinFunction((_sf, x: number) => Math.cos(x)),
  sin: new LuaBuiltinFunction((_sf, x: number) => Math.sin(x)),
  tan: new LuaBuiltinFunction((_sf, x: number) => Math.tan(x)),
  acos: new LuaBuiltinFunction((_sf, x: number) => Math.acos(x)),
  asin: new LuaBuiltinFunction((_sf, x: number) => Math.asin(x)),
  atan: new LuaBuiltinFunction((_sf, y: number, x?: number) => {
    if (x === undefined) {
      return Math.atan(y);
    }
    return Math.atan2(y, x);
  }),

  // Hyperbolic functions
  cosh: new LuaBuiltinFunction((_sf, x: number) => Math.cosh(x)),
  sinh: new LuaBuiltinFunction((_sf, x: number) => Math.sinh(x)),
  tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(x)),

  // Additional utility
  deg: new LuaBuiltinFunction((_sf, x: number) => x * 180 / Math.PI),
  rad: new LuaBuiltinFunction((_sf, x: number) => x * Math.PI / 180),
  ult: new LuaBuiltinFunction((_sf, m: number, n: number) => {
    // Unsigned less than comparison
    return (m >>> 0) < (n >>> 0);
  }),

  // Keep the cosineSimilarity utility function
  cosineSimilarity: new LuaBuiltinFunction(
    (sf, vecA: LuaTable | number[], vecB: LuaTable | number[]) => {
      // Convert LuaTable to number[]
      if (vecA instanceof LuaTable) {
        vecA = vecA.toJSArray();
      }
      if (vecB instanceof LuaTable) {
        vecB = vecB.toJSArray();
      }

      if (vecA.length !== vecB.length) {
        throw new LuaRuntimeError("Vectors must be of the same length", sf);
      }

      let dotProduct = 0;
      let normA = 0;
      let normB = 0;

      for (let i = 0; i < vecA.length; i++) {
        dotProduct += vecA[i] * vecB[i];
        normA += vecA[i] ** 2;
        normB += vecB[i] ** 2;
      }

      return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    },
  ),
});
