#include "cell.h"
#include "prosperon.h"
#include "HandmadeMath.h"
#include "stb_ds.h"

#include <string.h>

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MSDF_IMPLEMENTATION
#include "msdf.h"

#define STB_TRUETYPE_IMPLEMENTATION
#define STB_TRUETYPE_NO_STDIO
#include "stb_truetype.h"

#include "stb_ds.h"

#include "HandmadeMath.h"

typedef enum {
  LEFT,
  RIGHT,
  CENTER,
  JUSTIFY
} TEXT_ALIGN;

typedef enum {
  WORD,
  CHARACTER
} TEXT_BREAK;

typedef enum {
  FONT_MODE_BITMAP = 0,
  FONT_MODE_SDF = 1,
  FONT_MODE_MSDF = 2
} FONT_MODE;

struct text_char {
  rect pos;
  rect uv;
  HMM_Vec4 color;
};

struct shader;
struct window;

struct character {
  float advance;
  rect quad;
  rect uv;
  float xoff;
  float yoff;
  float width;
  float height;
};

// text data
struct sFont {
  uint32_t height; /* em_px: glyph size in atlas pixels */
  float ascent; // pixels
  float descent; // pixels
  float linegap; //pixels
  float line_height; // pixels
  struct character Characters[256];
  unsigned char *pixels; // RGBA8 pixel data (RGB for MSDF)
  int atlas_size; // width and height of atlas (square)
  int mode; // FONT_MODE_BITMAP, FONT_MODE_SDF, or FONT_MODE_MSDF
  float range_px; // SDF/MSDF distance range in atlas pixels
  float sharpness; // render-time sharpness multiplier (default 1.0)
};

typedef struct sFont font;
typedef struct Character glyph;


typedef struct { const char *start, *end; float width; } line_t;
typedef struct { line_t *lines; float max_width; } layout_lines;

struct sFont *use_font;

void font_free(JSRuntime *rt, font *f)
{
  if (f->pixels) free(f->pixels);
  free(f);
}

// MakeFontSDF: Create SDF font with explicit parameters
// em_px: glyph size in atlas (like 64, 96, 128)
// range_px: distance field range in atlas pixels (typical: 6-16 for SDF, 2-8 for MSDF)
// padding_px: padding around glyphs (should be >= range_px + 2)
struct sFont *MakeFontSDF(void *ttf_buffer, size_t len, int em_px, float range_px, int padding_px) {
  if (!ttf_buffer)
    return NULL;
    
  int packsize = 1024;

  struct sFont *newfont = calloc(1, sizeof(struct sFont));
  newfont->height = em_px;
  newfont->mode = FONT_MODE_SDF;
  newfont->range_px = range_px;
  newfont->sharpness = 1.0f;
  newfont->atlas_size = packsize;

  unsigned char *bitmap = calloc(1, packsize * packsize);
  
  stbtt_fontinfo fontinfo;
  if (!stbtt_InitFont(&fontinfo, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0))) {
    free(newfont);
    free(bitmap);
    return NULL;
  }

  int ascent, descent, linegap;
  stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &linegap);
  float scale = stbtt_ScaleForPixelHeight(&fontinfo, em_px);
  newfont->ascent = ascent * scale;
  newfont->descent = descent * scale;
  newfont->linegap = linegap * scale;
  newfont->line_height = (newfont->ascent - newfont->descent) + newfont->linegap;

  // Manual SDF packing
  int x = 0;
  int y = 0;
  int row_height = 0;
  int pad = padding_px;
  float onedge_value = 128.0f;
  // pixel_dist_scale controls how distance maps to pixel values
  // Higher = sharper edges but less range for effects
  // Formula: encoded = 0.5 + dist / (2 * range_px)
  // So pixel_dist_scale = 128 / range_px gives us proper encoding
  float pixel_dist_scale = 128.0f / range_px;

  for (unsigned char c = 32; c < 127; c++) {
    int g = stbtt_FindGlyphIndex(&fontinfo, c);
    
    int width, height, xoff, yoff;
    unsigned char *sdf = stbtt_GetGlyphSDF(&fontinfo, scale, g, pad, (unsigned char)onedge_value, pixel_dist_scale, &width, &height, &xoff, &yoff);
    
    if (!sdf) {
      int advance, lsb;
      stbtt_GetGlyphHMetrics(&fontinfo, g, &advance, &lsb);
      newfont->Characters[c].advance = advance * scale;
      continue;
    }

    if (x + width + 1 > packsize) {
      x = 0;
      y += row_height + 1;
      row_height = 0;
    }
    
    if (y + height + 1 > packsize) {
      free(sdf);
      continue;
    }

    for (int sy = 0; sy < height; sy++) {
      for (int sx = 0; sx < width; sx++) {
        bitmap[(y + sy) * packsize + (x + sx)] = sdf[sy * width + sx];
      }
    }
    
    free(sdf);

    rect uv;
    uv.x = (float)x / packsize;
    uv.y = (float)(y + height) / packsize;
    uv.w = (float)width / packsize;
    uv.h = -(float)height / packsize;
    newfont->Characters[c].uv = uv;

    rect quad;
    quad.x = (float)xoff;
    quad.y = (float)(-yoff - height);
    quad.w = (float)width;
    quad.h = (float)height;
    newfont->Characters[c].quad = quad;
    
    int advance, lsb;
    stbtt_GetGlyphHMetrics(&fontinfo, g, &advance, &lsb);
    newfont->Characters[c].advance = advance * scale;

    x += width + 1;
    if (height > row_height) row_height = height;
  }

  // Convert to RGBA8
  newfont->pixels = malloc(packsize * packsize * 4);
  for (int i = 0; i < packsize; i++) {
    for (int j = 0; j < packsize; j++) {
      int idx = (i * packsize + j) * 4;
      newfont->pixels[idx + 0] = 255;
      newfont->pixels[idx + 1] = 255;
      newfont->pixels[idx + 2] = 255;
      newfont->pixels[idx + 3] = bitmap[i * packsize + j];
    }
  }
  
  free(bitmap);
  return newfont;
}

// MakeFontMSDF: Create MSDF font with explicit parameters
struct sFont *MakeFontMSDF(void *ttf_buffer, size_t len, int em_px, float range_px, int padding_px) {
  if (!ttf_buffer)
    return NULL;
    
  int packsize = 1024;

  struct sFont *newfont = calloc(1, sizeof(struct sFont));
  newfont->height = em_px;
  newfont->mode = FONT_MODE_MSDF;
  newfont->range_px = range_px;
  newfont->sharpness = 1.0f;
  newfont->atlas_size = packsize;

  // MSDF uses RGB channels, so we need float RGB buffer then convert
  unsigned char *bitmap_rgb = calloc(1, packsize * packsize * 3);
  
  stbtt_fontinfo fontinfo;
  if (!stbtt_InitFont(&fontinfo, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0))) {
    free(newfont);
    free(bitmap_rgb);
    return NULL;
  }

  int ascent, descent, linegap;
  stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &linegap);
  float scale = stbtt_ScaleForPixelHeight(&fontinfo, em_px);
  newfont->ascent = ascent * scale;
  newfont->descent = descent * scale;
  newfont->linegap = linegap * scale;
  newfont->line_height = (newfont->ascent - newfont->descent) + newfont->linegap;

  int x = 0;
  int y = 0;
  int row_height = 0;
  int border = padding_px;

  for (unsigned char c = 32; c < 127; c++) {
    int g = stbtt_FindGlyphIndex(&fontinfo, c);
    
    msdf_Result result = {0};
    int ok = msdf_genGlyph(&result, &fontinfo, g, border, scale, range_px, NULL);
    
    if (!ok || !result.rgb) {
      int advance, lsb;
      stbtt_GetGlyphHMetrics(&fontinfo, g, &advance, &lsb);
      newfont->Characters[c].advance = advance * scale;
      continue;
    }

    int width = result.width;
    int height = result.height;
  
    if (x + width + 1 > packsize) { 
      x = 0;
      y += row_height + 1;
      row_height = 0;
    }
    
    if (y + height + 1 > packsize) {
      free(result.rgb);
      continue;
    }

    // Copy MSDF RGB data to atlas (convert float to uint8)
    for (int sy = 0; sy < height; sy++) {
      for (int sx = 0; sx < width; sx++) {
        int src_idx = 3 * (sy * width + sx);
        int dst_idx = 3 * ((y + sy) * packsize + (x + sx));
        // Clamp float [0,1] to [0,255]
        float r = result.rgb[src_idx + 0];
        float g = result.rgb[src_idx + 1];
        float b = result.rgb[src_idx + 2];
        bitmap_rgb[dst_idx + 0] = (unsigned char)(fminf(fmaxf(r * 255.0f, 0.0f), 255.0f));
        bitmap_rgb[dst_idx + 1] = (unsigned char)(fminf(fmaxf(g * 255.0f, 0.0f), 255.0f));
        bitmap_rgb[dst_idx + 2] = (unsigned char)(fminf(fmaxf(b * 255.0f, 0.0f), 255.0f));
      }
    }
    
    free(result.rgb);

    // Get glyph box for positioning
    int ix0, iy0, ix1, iy1;
    stbtt_GetGlyphBox(&fontinfo, g, &ix0, &iy0, &ix1, &iy1);
    
    rect uv;
    uv.x = (float)x / packsize;
    uv.y = (float)(y + height) / packsize;
    uv.w = (float)width / packsize;
    uv.h = -(float)height / packsize;
    newfont->Characters[c].uv = uv;

    // Calculate quad position
    // MSDF result includes border, so we need to account for it
    float xoff = (ix0 * scale) - border;
    float yoff = (iy0 * scale) - border;
    
    rect quad;
    quad.x = xoff;
    quad.y = yoff;
    quad.w = (float)width;
    quad.h = (float)height;
    newfont->Characters[c].quad = quad;
    
    int advance, lsb;
    stbtt_GetGlyphHMetrics(&fontinfo, g, &advance, &lsb);
    newfont->Characters[c].advance = advance * scale;

    x += width + 1;
    if (height > row_height) row_height = height;
  }

  // Convert RGB to RGBA8 (alpha = 255 for MSDF, color channels hold distance)
  newfont->pixels = malloc(packsize * packsize * 4);
  for (int i = 0; i < packsize; i++) {
    for (int j = 0; j < packsize; j++) {
      int src_idx = 3 * (i * packsize + j);
      int dst_idx = (i * packsize + j) * 4;
      newfont->pixels[dst_idx + 0] = bitmap_rgb[src_idx + 0];
      newfont->pixels[dst_idx + 1] = bitmap_rgb[src_idx + 1];
      newfont->pixels[dst_idx + 2] = bitmap_rgb[src_idx + 2];
      newfont->pixels[dst_idx + 3] = 255;
    }
  }
  
  free(bitmap_rgb);
  return newfont;
}

// Legacy MakeFont for backward compatibility
struct sFont *MakeFont(void *ttf_buffer, size_t len, int height, int is_sdf) {
  if (!ttf_buffer)
    return NULL;
  
  // For SDF mode, use sensible defaults
  if (is_sdf) {
    // Default: em_px=height, range_px=12, padding_px=14
    return MakeFontSDF(ttf_buffer, len, height, 12.0f, 14);
  }
    
  int packsize = 1024;

  struct sFont *newfont = calloc(1, sizeof(struct sFont));
  newfont->height = height;
  newfont->mode = FONT_MODE_BITMAP;
  newfont->range_px = 0;
  newfont->sharpness = 1.0f;
  newfont->atlas_size = packsize;

  unsigned char *bitmap = calloc(1, packsize * packsize);
  
  stbtt_fontinfo fontinfo;
  if (!stbtt_InitFont(&fontinfo, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0))) {
    free(newfont);
    free(bitmap);
    return NULL;
  }

  int ascent, descent, linegap;
  stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &linegap);
  float scale = stbtt_ScaleForPixelHeight(&fontinfo, height);
  newfont->ascent = ascent * scale;
  newfont->descent = descent * scale; /* descent is negative */
  newfont->linegap = linegap * scale;
  newfont->line_height = (newfont->ascent - newfont->descent) + newfont->linegap;

  // Bitmap-only path
  {
    // Original Bitmap packing
    stbtt_packedchar glyphs[95];
    stbtt_pack_context pc;
    int pad = 2;

    stbtt_PackBegin(&pc, bitmap, packsize, packsize, 0, pad, NULL);
    stbtt_PackFontRange(&pc, ttf_buffer, 0, height, 32, 95, glyphs);
    stbtt_PackEnd(&pc);
    
    for (unsigned char c = 32; c < 127; c++) {
      stbtt_packedchar glyph = glyphs[c - 32];

      rect uv;
      uv.x = (glyph.x0) / (float)packsize;
      uv.w = (glyph.x1-glyph.x0) / (float)packsize;
      uv.y = (glyph.y1) / (float)packsize;
      uv.h = (glyph.y0-glyph.y1) / (float)packsize;
      newfont->Characters[c].uv = uv;

      rect quad;
      quad.x = glyph.xoff;
      quad.y = -glyph.yoff2;  // Top of glyph relative to baseline?
      // Wait, original code had: quad.y = -glyph.yoff2; 
      // But PackFontRange yoff2 is usually bottom?
      // Inspecting original code:
      // quad.y = -glyph.yoff2;  // This looks like coordinate system flipping?
      // height = glyph.yoff2 - glyph.yoff;
      // yoff is top, yoff2 is bottom.
      
      quad.x = glyph.xoff;
      quad.y = glyph.yoff; // Keep standard top-down for consistency with SDF path?
      // Revert to original behavior for bitmap path to be safe
      quad.y = -glyph.yoff2; 
      quad.w = glyph.xoff2 - glyph.xoff;
      quad.h = glyph.yoff2 - glyph.yoff;
      
      newfont->Characters[c].quad = quad;
      newfont->Characters[c].advance = glyph.xadvance;
    }
  }

  // Common pixel expansion
  newfont->pixels = malloc(packsize * packsize * 4); // RGBA8
  for (int i = 0; i < packsize; i++) {
    for (int j = 0; j < packsize; j++) {
      int idx = (i * packsize + j) * 4;
      newfont->pixels[idx + 0] = 255; // R
      newfont->pixels[idx + 1] = 255; // G
      newfont->pixels[idx + 2] = 255; // B
      newfont->pixels[idx + 3] = bitmap[i * packsize + j]; // A
    }
  }
  
  free(bitmap);
  return newfont;
}

// Create SDF font with custom parameters (exposed to JS)
struct sFont *MakeFontSDFParams(void *ttf_buffer, size_t len, int em_px, float range_px, int padding_px, float sharpness) {
  struct sFont *f = MakeFontSDF(ttf_buffer, len, em_px, range_px, padding_px);
  if (f) f->sharpness = sharpness;
  return f;
}

// Create MSDF font with custom parameters (exposed to JS)
struct sFont *MakeFontMSDFParams(void *ttf_buffer, size_t len, int em_px, float range_px, int padding_px, float sharpness) {
  struct sFont *f = MakeFontMSDF(ttf_buffer, len, em_px, range_px, padding_px);
  if (f) f->sharpness = sharpness;
  return f;
}

layout_lines layout_text_lines(const char *text, font *f,
                               float letter_spacing,
                               float wrap,
                               TEXT_BREAK break_at)
{
  layout_lines out = {0};
  int break_at_word = (break_at == WORD);

  const char *line_start = text;
  const char *word_start = text;
  float line_width = 0;
  float word_width = 0;

  float max_width = 0;

  for (const char *p = text; *p; p++) {
    if (*p == '\n') {
      line_t L = { line_start, p, line_width };
      arrput(out.lines, L);
      if (line_width > max_width) max_width = line_width;

      line_start = p + 1;
      word_start = p + 1;
      line_width = 0;
      word_width = 0;
      continue;
    }

    unsigned char ch = (unsigned char)*p;
    float char_w = f->Characters[ch].advance + letter_spacing;

    if (wrap > 0 && line_width + char_w > wrap) {
      const char *break_pt = p;
      float break_w = line_width;

      if (break_at_word && *p != ' ' && word_width > 0 && word_start > line_start) {
        break_pt = word_start;
        break_w = line_width - word_width;
      }

      line_t L = { line_start, break_pt, break_w };
      arrput(out.lines, L);
      if (break_w > max_width) max_width = break_w;

      line_start = break_pt;
      if (break_at_word && *line_start == ' ') line_start++;

      p = line_start - 1;
      word_start = line_start;
      line_width = 0;
      word_width = 0;
      continue;
    }

    line_width += char_w;

    if (break_at_word) {
      if (*p == ' ') {
        word_width = 0;
        word_start = p + 1;
      } else {
        word_width += char_w;
      }
    }
  }

  if (line_start < text + strlen(text)) {
    line_t L = { line_start, text + strlen(text), line_width };
    arrput(out.lines, L);
    if (line_width > max_width) max_width = line_width;
  }

  out.max_width = max_width;
  return out;
}

void sdrawCharacter(struct text_vert **buffer, stbtt_packedchar c, HMM_Vec2 cursor, float scale, struct rgba color) {
  struct text_vert vert;

//  vert.pos.x = cursor.X + c.leftbearing;
//  vert.pos.y = cursor.Y + c.topbearing;
//  vert.wh = c.size;

//  if (vert.pos.x > frame.l || vert.pos.y > frame.t || (vert.pos.y + vert.wh.y) < frame.b || (vert.pos.x + vert.wh.x) < frame.l) return;

//  vert.uv.x = c.rect.x;
//  vert.uv.y = c.rect.y;
//  vert.st.x = c.rect.w;
//  vert.st.y = c.rect.h;
  rgba2floats(vert.color.e, color);

  arrput(*buffer, vert);
}

void draw_char_verts(struct text_vert **buffer, struct character c, HMM_Vec2 cursor, colorf color)
{
  // packedchar has 
  // Adds four verts: bottom left, bottom right, top left, top right
  text_vert bl;
  bl.pos.x = cursor.X + c.quad.x;
  bl.pos.y = cursor.Y + c.quad.y;
  bl.uv.x = c.uv.x;
  bl.uv.y = c.uv.y;
  bl.color = color;
  arrput(*buffer, bl);

  
  text_vert br = bl;
  br.pos.x += c.quad.w;
  br.uv.x += c.uv.w;
  arrput(*buffer, br);
  
  text_vert ul = bl;
  ul.pos.y += c.quad.h;
  ul.uv.y += c.uv.h;
  arrput(*buffer, ul);

  text_vert ur = ul;
  ur.pos.x = br.pos.x;
  ur.uv.x = br.uv.x;
  arrput(*buffer, ur);
}

const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
{
  struct rgba d;
  if (!color) color = &d;
  if (*c != '\033') return c;
  c++;
  if (*c != '[') return c;
  c++;
  if (*c == '0') {
    *color = defc;
    c++;
    return c;
  }
  else if (!strncmp(c, "38;2;", 5)) {
    c += 5;
    *color = (struct rgba){0,0,0,255};
    color->r = atoi(c);
    c = strchr(c, ';')+1;
    color->g = atoi(c);
    c = strchr(c,';')+1;
    color->b = atoi(c);
    c = strchr(c,';')+1;
    return c;
  }
  return c;
}

// text is a string, font f, wrap is how long a line is before wrapping. -1 to not wrap
HMM_Vec2 measure_text(const char *text, font *f, float letter_spacing, float wrap, TEXT_BREAK break_at, TEXT_ALIGN align)
{
  layout_lines lay = layout_text_lines(text, f, letter_spacing, wrap, break_at);

  float lh = f->line_height;
  int line_count = arrlen(lay.lines);
  float height = line_count > 0 ? line_count * lh : 0;

  float width = lay.max_width;
  if (wrap > 0 && align != LEFT && align != JUSTIFY) {
    // For RIGHT or CENTER we might shift lines visually, but width is still max(line.width, wrap ? min(wrap, ...) ).
    // Usually you'd report max(wrap, width) or just wrap, depending on how you want containers to size.
  }

  HMM_Vec2 dim = { width, height };

  arrfree(lay.lines);
  return dim;
}

/* pos given in screen coordinates */
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color,
                             float wrap, TEXT_BREAK break_at, TEXT_ALIGN align,
                             float letter_spacing)
{
  text_vert *buffer = NULL;
  float lh = f->line_height;

  layout_lines lay = layout_text_lines(text, f, letter_spacing, wrap, break_at);
  line_t *lines = lay.lines;

  HMM_Vec2 cursor = pos;

  for (int i = 0; i < arrlen(lines); i++) {
    line_t *L = &lines[i];
    float start_x = pos.x;
    float extra_space = 0;
    int space_count = 0;

    if (wrap > 0) {
      switch (align) {
        case RIGHT:  start_x = pos.x + wrap - L->width; break;
        case CENTER: start_x = pos.x + (wrap - L->width) * 0.5f; break;
        case JUSTIFY:
          if (i < arrlen(lines) - 1 && *(L->end) != '\n') {
            for (const char *p = L->start; p < L->end; p++) if (*p == ' ') space_count++;
            if (space_count > 0) extra_space = (wrap - L->width) / space_count;
          }
          break;
        case LEFT: default: break;
      }
    }

    cursor.x = start_x;

    const char *p = L->start;
    while (p < L->end) {
      unsigned char ch = (unsigned char)*p;
      struct character g = f->Characters[ch];

      if (!isspace(*p)) draw_char_verts(&buffer, g, cursor, color);

      cursor.x += g.advance;
      /* add letter spacing unless this is last char of the line */
      if (p + 1 < L->end) cursor.x += letter_spacing;
      if (align == JUSTIFY && *p == ' ') cursor.x += extra_space;

      p++;
    }

    cursor.x = pos.x;
    cursor.y -= lh;
  }

  arrfree(lines);
  return buffer;
}


// QuickJS class for font
QJSCLASS(font,)

// Helper to attach texture and mode info to font JS object
static void attach_font_texture(JSContext *js, JSValue ret, font *f) {
  if (f->pixels) {
    JSValue texData = JS_NewObject(js);
    JS_SetPropertyStr(js, texData, "width", JS_NewInt32(js, f->atlas_size));
    JS_SetPropertyStr(js, texData, "height", JS_NewInt32(js, f->atlas_size));
    JS_SetPropertyStr(js, texData, "format", JS_NewString(js, "rgba8"));
    
    size_t byte_size = f->atlas_size * f->atlas_size * 4;
    JS_SetPropertyStr(js, texData, "pixels", js_new_blob_stoned_copy(js, f->pixels, byte_size));
      
    JS_SetPropertyStr(js, ret, "texture", texData);
  }
  
  // Add mode string
  const char *mode_str = "bitmap";
  if (f->mode == FONT_MODE_SDF) mode_str = "sdf";
  else if (f->mode == FONT_MODE_MSDF) mode_str = "msdf";
  JS_SetPropertyStr(js, ret, "mode", JS_NewString(js, mode_str));
  
  // Add range_px and sharpness for SDF/MSDF fonts
  JS_SetPropertyStr(js, ret, "range_px", JS_NewFloat64(js, f->range_px));
  JS_SetPropertyStr(js, ret, "sharpness", JS_NewFloat64(js, f->sharpness));
}

// Font constructor (legacy: data, height, is_sdf)
JSC_CCALL(staef_font_new,
  size_t len;
  void *data = js_get_blob_data(js, &len, argv[0]);
  if (data == (void*)-1) return JS_EXCEPTION;
  if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
  
  double height = js2number(js, argv[1]);
  int is_sdf = 0;
  if (argc > 2) is_sdf = JS_ToBool(js, argv[2]);

  font *f = MakeFont(data, len, (int)height, is_sdf);
  if (!f) return JS_ThrowReferenceError(js, "could not create font");
  
  ret = font2js(js, f);
  attach_font_texture(js, ret, f);
)

// SDF font constructor: sdf_font(data, em_px, range_px, padding_px, sharpness)
JSC_CCALL(staef_sdf_font_new,
  size_t len;
  void *data = js_get_blob_data(js, &len, argv[0]);
  if (data == (void*)-1) return JS_EXCEPTION;
  if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
  
  int em_px = argc > 1 ? (int)js2number(js, argv[1]) : 64;
  float range_px = argc > 2 ? (float)js2number(js, argv[2]) : 12.0f;
  int padding_px = argc > 3 ? (int)js2number(js, argv[3]) : 14;
  float sharpness = argc > 4 ? (float)js2number(js, argv[4]) : 1.0f;

  font *f = MakeFontSDFParams(data, len, em_px, range_px, padding_px, sharpness);
  if (!f) return JS_ThrowReferenceError(js, "could not create SDF font");
  
  ret = font2js(js, f);
  attach_font_texture(js, ret, f);
)

// MSDF font constructor: msdf_font(data, em_px, range_px, padding_px, sharpness)
JSC_CCALL(staef_msdf_font_new,
  size_t len;
  void *data = js_get_blob_data(js, &len, argv[0]);
  if (data == (void*)-1) return JS_EXCEPTION;
  if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
  
  int em_px = argc > 1 ? (int)js2number(js, argv[1]) : 64;
  float range_px = argc > 2 ? (float)js2number(js, argv[2]) : 4.0f;
  int padding_px = argc > 3 ? (int)js2number(js, argv[3]) : 6;
  float sharpness = argc > 4 ? (float)js2number(js, argv[4]) : 1.0f;

  font *f = MakeFontMSDFParams(data, len, em_px, range_px, padding_px, sharpness);
  if (!f) return JS_ThrowReferenceError(js, "could not create MSDF font");
  
  ret = font2js(js, f);
  attach_font_texture(js, ret, f);
)

// Calculate text size
JSC_CCALL(staef_font_text_size,
  font *f = js2font(js, self);
  const char *str = JS_ToCString(js, argv[0]);
  float letterSpacing = argc > 1 ? js2number(js, argv[1]) : 0;
  float wrap = argc > 2 ? js2number(js, argv[2]) : -1;
  const char *breakat = argc > 3 ? JS_ToCString(js, argv[3]) : NULL;
  int breakAt = (breakat && strcmp(breakat, "character") == 0) ? CHARACTER : WORD;
  const char *align = argc > 4 ? JS_ToCString(js, argv[4]) : NULL;
  int alignAt = (align && strcmp(align, "right") == 0) ? RIGHT : (align && strcmp(align, "center") == 0) ? CENTER : (align && strcmp(align, "justify") == 0) ? JUSTIFY : LEFT;
  ret = vec22js(js, measure_text(str, f, letterSpacing, wrap, breakAt, alignAt));
  JS_FreeCString(js, str);
  if (breakat) JS_FreeCString(js, breakat);
  if (align) JS_FreeCString(js, align);
)

// Generate text buffer (mesh data)
JSC_CCALL(staef_font_make_text_buffer,
  font *f = js2font(js, self);
  const char *s = JS_ToCString(js, argv[0]);
  rect rectpos = js2rect(js, argv[1]);
  colorf c = js2color(js, argv[2]);
  float wrap = argc > 3 ? js2number(js, argv[3]) : -1;
  const char *breakat = argc > 4 ? JS_ToCString(js, argv[4]) : NULL;
  int breakAt = (breakat && strcmp(breakat, "character") == 0) ? CHARACTER : WORD;
  const char *align = argc > 5 ? JS_ToCString(js, argv[5]) : NULL;
  int alignAt = (align && strcmp(align, "right") == 0) ? RIGHT : (align && strcmp(align, "center") == 0) ? CENTER : (align && strcmp(align, "justify") == 0) ? JUSTIFY : LEFT;
  
  HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
  text_vert *buffer = renderText(s, startpos, f, c, wrap, breakAt, alignAt, 0);
  ret = quads_to_mesh(js, buffer);
  JS_FreeCString(js, s);
  if (breakat) JS_FreeCString(js, breakat);
  if (align) JS_FreeCString(js, align);
  arrfree(buffer);
)

// Font property getters/setters
JSC_GETSET(font, linegap, number)
JSC_GETSET(font, ascent, number)
JSC_GETSET(font, descent, number)
JSC_GETSET(font, line_height, number)
JSC_GETSET(font, height, number)
JSC_GETSET(font, range_px, number)
JSC_GETSET(font, sharpness, number)
JSC_GETSET(font, mode, number)

// Font methods
static const JSCFunctionListEntry js_font_funcs[] = {
  MIST_FUNC_DEF(staef_font, text_size, 5),
  MIST_FUNC_DEF(staef_font, make_text_buffer, 6),
  CGETSET_ADD(font, linegap),
  CGETSET_ADD(font, ascent),
  CGETSET_ADD(font, descent),
  CGETSET_ADD(font, line_height),
  CGETSET_ADD(font, height),
  CGETSET_ADD(font, range_px),
  CGETSET_ADD(font, sharpness),
  CGETSET_ADD(font, mode),
};

// Font constructor function
static JSValue js_font_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_staef_font_new(ctx, JS_NULL, argc, argv);
}

// SDF font constructor function
static JSValue js_sdf_font_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_staef_sdf_font_new(ctx, JS_NULL, argc, argv);
}

// MSDF font constructor function
static JSValue js_msdf_font_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_staef_msdf_font_new(ctx, JS_NULL, argc, argv);
}

// Initialize the staef module
CELL_USE_INIT(
  JSValue mod = JS_NewObject(js);
  JSValue proto;
  
  // Initialize font class
  JS_NewClassID(&js_font_id);
  JS_NewClass(JS_GetRuntime(js), js_font_id, &js_font_class);
  proto = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, proto, js_font_funcs, countof(js_font_funcs));
  JS_SetClassProto(js, js_font_id, proto);
  
  // Create font constructor (legacy)
  JSValue font_ctor = JS_NewCFunction2(js, js_font_constructor, "font", 2, JS_CFUNC_generic, 0);
  JS_SetPropertyStr(js, mod, "font", font_ctor);
  
  // Create SDF font constructor: sdf_font(data, em_px, range_px, padding_px, sharpness)
  JSValue sdf_font_ctor = JS_NewCFunction2(js, js_sdf_font_constructor, "sdf_font", 5, JS_CFUNC_generic, 0);
  JS_SetPropertyStr(js, mod, "sdf_font", sdf_font_ctor);
  
  // Create MSDF font constructor: msdf_font(data, em_px, range_px, padding_px, sharpness)
  JSValue msdf_font_ctor = JS_NewCFunction2(js, js_msdf_font_constructor, "msdf_font", 5, JS_CFUNC_generic, 0);
  JS_SetPropertyStr(js, mod, "msdf_font", msdf_font_ctor);
  
  return mod;
)
