import { Decimal } from "./decimal.js";
import { Option } from "effect";
function serializeValue(value) {
  if (value === null) return "null";
  if (typeof value === "boolean") return String(value);
  if (typeof value === "string") return serializeString(value);
  if (value instanceof Decimal) return value.toString();
  if (value instanceof Uint8Array) return serializeBytes(value);
  if (Option.isOption(value)) {
    if (Option.isSome(value)) return `optional.of(${serializeValue(value.value)})`;
    return "optional.none()";
  }
  if (typeof value === "number" || typeof value === "bigint") return String(value);
  if (typeof value === "object") {
    const keys = Object.keys(value);
    if (keys.every((k) => /^\d+$/.test(k))) {
      const bytes = new Uint8Array(keys.length);
      const obj = value;
      for (let i = 0; i < keys.length; i++) bytes[i] = obj[i];
      return serializeBytes(bytes);
    }
  }
  return String(value);
}
export function serialize(ast) {
  const { op, args } = ast;
  switch (op) {
    case "value":
      return serializeValue(args);
    case "id":
      return args;
    case "||":
    case "&&":
    case "==":
    case "!=":
    case "<":
    case "<=":
    case ">":
    case ">=":
    case "in":
    case "+":
    case "-":
    case "*":
    case "/":
    case "%": {
      const binArgs = args;
      return `${wrap(binArgs[0], op)} ${op} ${wrap(binArgs[1], op)}`;
    }
    case "!_":
      return `!${wrap(args, op)}`;
    case "-_": {
      const uArg = args;
      return ["+", "-", "*", "/", "%"].includes(uArg.op) ? `-(${serialize(uArg)})` : `-${serialize(uArg)}`;
    }
    case ".": {
      const dotArgs = args;
      return `${wrap(dotArgs[0], op)}.${dotArgs[1]}`;
    }
    case ".?": {
      const dotqArgs = args;
      return `${wrap(dotqArgs[0], op)}.?${dotqArgs[1]}`;
    }
    case "[]": {
      const idxArgs = args;
      return `${wrap(idxArgs[0], op)}[${serialize(idxArgs[1])}]`;
    }
    case "[?]": {
      const idxqArgs = args;
      return `${wrap(idxqArgs[0], op)}[?${serialize(idxqArgs[1])}]`;
    }
    case "call": {
      const callArgs = args;
      return `${callArgs[0]}(${callArgs[1].map(serialize).join(", ")})`;
    }
    case "rcall": {
      const rcallArgs = args;
      return `${wrap(rcallArgs[1], op)}.${rcallArgs[0]}(${rcallArgs[2].map(serialize).join(", ")})`;
    }
    case "list":
      return `[${args.map(serialize).join(", ")}]`;
    case "map":
      return `{${args.map(
        (e) => e.length === 1 || e[0].op === "spread" ? serialize(e[0]) : `${serialize(e[0])}: ${serialize(e[1])}`
      ).join(", ")}}`;
    case "spread":
      return `...${serialize(args)}`;
    case "?:": {
      const ternArgs = args;
      return `${wrap(ternArgs[0], op)} ? ${wrap(ternArgs[1], op)} : ${serialize(ternArgs[2])}`;
    }
    default:
      throw new Error(`Unknown AST operation: ${op}`);
  }
}
function serializeString(str) {
  const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\f/g, "\\f").replaceAll("\b", "\\b").replace(/\v/g, "\\v");
  let result = "";
  for (let i = 0; i < escaped.length; i++) {
    const code = escaped.charCodeAt(i);
    if (code < 32 || code > 126) {
      result += code <= 65535 ? `\\u${code.toString(16).padStart(4, "0")}` : `\\U${code.toString(16).padStart(8, "0")}`;
    } else {
      result += escaped[i];
    }
  }
  return `"${result}"`;
}
function serializeBytes(bytes) {
  let result = 'b"';
  for (const byte of bytes) {
    if (byte === 92) result += "\\\\";
    else if (byte === 34) result += '\\"';
    else if (byte === 10) result += "\\n";
    else if (byte === 13) result += "\\r";
    else if (byte === 9) result += "\\t";
    else if (byte >= 32 && byte <= 126) result += String.fromCharCode(byte);
    else result += `\\x${byte.toString(16).padStart(2, "0")}`;
  }
  return `${result}"`;
}
const PRECEDENCE = {
  "?:": 1,
  "||": 2,
  "&&": 3,
  "==": 4,
  "!=": 4,
  "<": 5,
  "<=": 5,
  ">": 5,
  ">=": 5,
  "in": 5,
  "+": 6,
  "-": 6,
  "-_": 6,
  "*": 7,
  "/": 7,
  "%": 7,
  "!_": 8,
  ".": 9,
  ".?": 9,
  "[]": 9,
  "[?]": 9,
  "call": 9,
  "rcall": 9
};
function needsParentheses(ast, parentOp) {
  const childOp = ast.op;
  const parentPrec = PRECEDENCE[parentOp] || 0;
  const childPrec = PRECEDENCE[childOp] || 0;
  if (childOp === "value" || childOp === "id" || childOp === "call" || childOp === "rcall" || childOp === "list" || childOp === "map") {
    return false;
  }
  if ((parentOp === "*" || parentOp === "/" || parentOp === "%") && childOp === "-_") return false;
  if (parentOp === "*" && childOp === "*" && ast.args[0]?.op === "-_") return false;
  if ((childOp === "." || childOp === "[]" || childOp === ".?" || childOp === "[?]") && (parentOp === "." || parentOp === "[]" || parentOp === ".?" || parentOp === "[?]" || parentOp === "rcall"))
    return false;
  if (parentOp === "?:") return childOp === "?:";
  if (parentOp === "!_" || parentOp === "-_") return childPrec < parentPrec;
  if (parentOp === "/" && (childOp === "*" || childOp === "+" || childOp === "-")) return true;
  if (childOp === "/" && parentOp !== void 0) return true;
  if ((parentOp === "*" || parentOp === "/") && ["+", "-", "*", "/"].includes(childOp)) {
    return true;
  }
  if (childPrec < parentPrec) return true;
  if (childPrec === parentPrec && (parentOp === "/" || parentOp === "%") && (childOp === "/" || childOp === "%"))
    return true;
  return false;
}
function wrap(ast, parentOp) {
  return needsParentheses(ast, parentOp) ? `(${serialize(ast)})` : serialize(ast);
}
export default serialize;
