import { Closure, Value, Env, EnvFrame } from 'src/lang/eval/value';
import { exprToString, productPatternToString } from './Expr';

export function Val(prop: { value: Value }) {
  return (
    <code>{ valueToString(prop.value) }</code>
  );
}

export function valueToString(val: Value): string {
  switch (val.tag) {
    case "number": return val.value.toString();
    case "string": return `"${val.value}"`;
    case "tag":    return `#${val.tag_name}`;
    case "tagged": return `#${val.tag_name} ${valueToString(val.value)}`;
    case "tuple":  return `(${val.values.map(valueToString).join(", ")})`;
    case "record": {
      const entries = Array.from(val.fields.entries())
        .map(([k, v]) => `${k} = ${valueToString(v)}`)
        .join(", ");
      return `{ ${entries} }`;
    }
    case "closure": return closureToString(val.closure);
  }
}

function closureToString(c: Closure): string {
  const params = c.parameters.map(productPatternToString).join(", ");
  const envStr = envToString(c.env);
  // We represent the closure as the code + a summary of its captured scope
  return `fn { ${params} . ${exprToString(c.body)} } [captured: ${envStr}]`;
}

function envToString(env: Env): string {
  if (env.tag === "nil") return "∅";
  
  const frames: string[] = [];
  let current: Env = env;
  
  while (current.tag === "frame") {
    frames.push(frameToString(current.frame));
    current = current.parent;
  }
  
  // Shows stack from inner-most to outer-most
  return frames.join(" ⮕ ");
}

function frameToString(frame: EnvFrame): string {
  const entries = Array.from(frame.entries());
  if (entries.length === 0) return "{}";
  
  const formattedEntries = entries.map(([name, val]) => {
    return `${name} = ${valueToString(val)}`;
  });

  return `{ ${formattedEntries.join(", ")} }`;
}
