// ========================================================================
// PART 5: PLAYDATE BACKEND IMPLEMENTATION (Level 3)
// ========================================================================
// Same interface as SDL3, completely different implementation.
// Shows where features degrade gracefully.

function PlaydateBackend(pd) {
  this.pd = pd  // Playdate API pointer
  this.target_pool = {}
  this.current_target = null
  this.current_camera = null
}

PlaydateBackend.prototype.get_capabilities = function() {
  return {
    supports_shaders: false,  // NO SHADERS
    supports_render_targets: true,  // LCDBitmap*
    supports_blend_modes: true,  // Limited (copy, inverted, XOR, etc)
    supports_stencil: false,
    max_texture_size: 400,  // Screen size basically
    native_mask_support: true,  // setMask() is native!
    native_effects: ['dither', 'invert']
  }
}

// ========================================================================
// RENDER TARGET MANAGEMENT
// ========================================================================

PlaydateBackend.prototype.get_or_create_target = function(width, height, key) {
  var pool_key = `${width}x${height}`
  
  if (!this.target_pool[pool_key])
    this.target_pool[pool_key] = []
  
  // Reuse from pool
  arrfor(this.target_pool[pool_key], function(target) {
    if (!target.in_use) {
      target.in_use = true
      // Clear bitmap for reuse
      this.pd.graphics.pushContext(target.bitmap)
      this.pd.graphics.clear(this.pd.graphics.kColorClear)
      this.pd.graphics.popContext()
      return target
    }
  })
  
  // Create new LCDBitmap
  var bitmap = this.pd.graphics.newBitmap(width, height, this.pd.graphics.kColorClear)
  
  var target = {
    bitmap: bitmap,
    width: width,
    height: height,
    in_use: true,
    key: key
  }
  
  push(this.target_pool[pool_key], target)
  return target
}

PlaydateBackend.prototype.release_all_targets = function() {
  arrfor(array(this.target_pool), function(key) {
    arrfor(this.target_pool[key], function(target) {
      target.in_use = false
    })
  })
}

// ========================================================================
// COMMAND EXECUTION
// ========================================================================

PlaydateBackend.prototype.execute = function(commands) {
  // No command buffer concept - execute immediately
  arrfor(commands, function(cmd) {
    this.execute_command(cmd)
  })
  
  this.release_all_targets()
}

PlaydateBackend.prototype.execute_command = function(cmd) {
  if (cmd.cmd == 'begin_render') {
    this.cmd_begin_render(cmd)
  } else if (cmd.cmd == 'end_render') {
    this.cmd_end_render()
  } else if (cmd.cmd == 'set_camera') {
    this.cmd_set_camera(cmd)
  } else if (cmd.cmd == 'draw_batch') {
    this.cmd_draw_batch(cmd)
  } else if (cmd.cmd == 'shader_pass') {
    this.cmd_shader_pass(cmd)  // DEGRADES
  } else if (cmd.cmd == 'apply_mask') {
    this.cmd_apply_mask(cmd)  // NATIVE!
  } else if (cmd.cmd == 'composite') {
    this.cmd_composite(cmd)
  } else if (cmd.cmd == 'blit') {
    this.cmd_blit(cmd)
  } else if (cmd.cmd == 'clear') {
    this.cmd_clear(cmd)
  } else if (cmd.cmd == 'present') {
    // Nothing to do, display updates automatically
  } else {
    log.console(`Unknown command: ${cmd.cmd}`)
  }
}

PlaydateBackend.prototype.cmd_begin_render = function(cmd) {
  var target = cmd.target
  var clear = cmd.clear
  var pattern = null

  if (target == 'screen') {
    // Render to screen framebuffer
    this.current_target = 'screen'
    // Playdate: screen is always the current context unless you push
  } else {
    // Render to bitmap
    this.current_target = target
    this.pd.graphics.pushContext(target.bitmap)

    if (clear) {
      // Clear with color (Playdate is 1-bit, so color becomes pattern/dither)
      pattern = this.color_to_dither_pattern(clear)
      this.pd.graphics.setDitherPattern(pattern)
      this.pd.graphics.fillRect(0, 0, target.width, target.height)
    }
  }
}

PlaydateBackend.prototype.cmd_end_render = function() {
  if (this.current_target != 'screen') {
    this.pd.graphics.popContext()
  }
  this.current_target = null
}

PlaydateBackend.prototype.cmd_set_camera = function(cmd) {
  this.current_camera = cmd.camera
  
  // Playdate doesn't have camera uniforms, we transform coordinates manually
}

PlaydateBackend.prototype.cmd_draw_batch = function(cmd) {
  var geometry = cmd.geometry
  var material = cmd.material || {}

  // Get image (Playdate uses LCDBitmap for textures)
  var image = this.get_image(material.texture || 'white')
  if (!image) return

  // Set draw mode based on material
  var draw_mode = this.material_to_draw_mode(material)
  this.pd.graphics.setImageDrawMode(draw_mode)

  // Draw each sprite in the batch
  var i = 0
  var vert_idx = 0
  var v = null
  var screen_pos = null
  var v2 = null
  var width = 0
  var height = 0
  var scale = 0
  var alpha_pattern = null
  var scaled = null
  for (i = 0; i < length(geometry.indices); i += 6) {
    // Each sprite is 2 triangles = 6 indices = 4 vertices
    vert_idx = geometry.indices[i]
    v = geometry.vertices[vert_idx]

    // Transform by camera
    screen_pos = this.world_to_screen(v.pos, this.current_camera)

    // Get sprite size from vertices
    v2 = geometry.vertices[vert_idx + 2]
    width = v2.pos[0] - v.pos[0]
    height = v2.pos[1] - v.pos[1]

    // Transform size by camera zoom
    scale = this.get_camera_scale(this.current_camera)
    width *= scale
    height *= scale

    // Apply vertex color as dither pattern (best we can do on 1-bit)
    if (v.color.a < 1.0) {
      alpha_pattern = this.alpha_to_dither_pattern(v.color.a)
      this.pd.graphics.setDitherPattern(alpha_pattern)
    }

    // Draw image
    if (width != image.width || height != image.height) {
      // Need scaling
      scaled = image.scaledImage(width / image.width, height / image.height)
      this.pd.graphics.drawBitmap(scaled, screen_pos[0], screen_pos[1])
    } else {
      this.pd.graphics.drawBitmap(image, screen_pos[0], screen_pos[1])
    }
  }
}

PlaydateBackend.prototype.cmd_shader_pass = function(cmd) {
  // NO SHADERS ON PLAYDATE
  // Degrade gracefully based on shader type

  var shader = cmd.shader
  var input = cmd.input
  var params = cmd.params

  if (shader == 'threshold') {
    // Threshold: Just copy input (or could dither based on threshold)
    log.console('Threshold shader not supported on Playdate, copying')
    this.copy_bitmap(input.bitmap, this.current_target.bitmap)
  }
  else if (shader == 'gaussian_blur') {
    // Blur: Box blur in CPU (slow but possible)
    log.console('Blur shader using CPU fallback')
    this.cpu_box_blur(input.bitmap, this.current_target.bitmap, params.radius || 5)
  }
  else if (shader == 'add_textures') {
    // Additive blend: Use XOR draw mode (not perfect but interesting)
    log.console('Additive blend approximated with XOR')
    this.pd.graphics.setImageDrawMode(this.pd.graphics.kDrawModeXOR)
    this.pd.graphics.drawBitmap(input.bitmap, 0, 0)
  }
  else if (shader == 'crt_filter') {
    // CRT: Not possible, just copy
    log.console('CRT filter not supported on Playdate, copying')
    this.copy_bitmap(input.bitmap, this.current_target.bitmap)
  }
  else {
    // Unknown shader: copy
    log.console(`Shader ${shader} not supported on Playdate, copying`)
    this.copy_bitmap(input.bitmap, this.current_target.bitmap)
  }
}

PlaydateBackend.prototype.cmd_apply_mask = function(cmd) {
  // PLAYDATE HAS NATIVE MASK SUPPORT!
  var content = cmd.content_texture.bitmap
  var mask = cmd.mask_texture.bitmap
  var invert = cmd.invert
  var inverted = null

  if (invert) {
    // Invert mask first
    inverted = this.pd.graphics.newBitmap(mask.width, mask.height)
    this.pd.graphics.pushContext(inverted)
    this.pd.graphics.setImageDrawMode(this.pd.graphics.kDrawModeInverted)
    this.pd.graphics.drawBitmap(mask, 0, 0)
    this.pd.graphics.popContext()
    mask = inverted
  }

  // Set mask on content bitmap
  content.setMask(mask)

  // Draw masked content to current target
  this.pd.graphics.drawBitmap(content, 0, 0)

  // Clear mask (don't leave it set)
  content.setMask(null)
}

PlaydateBackend.prototype.cmd_composite = function(cmd) {
  var base = cmd.base_texture.bitmap
  var overlay = cmd.overlay_texture.bitmap
  var mode = cmd.mode
  
  // Draw base
  this.pd.graphics.drawBitmap(base, 0, 0)
  
  // Draw overlay with appropriate mode
  var draw_mode = this.composite_mode_to_draw_mode(mode)
  this.pd.graphics.setImageDrawMode(draw_mode)
  this.pd.graphics.drawBitmap(overlay, 0, 0)
  
  // Reset draw mode
  this.pd.graphics.setImageDrawMode(this.pd.graphics.kDrawModeCopy)
}

PlaydateBackend.prototype.cmd_blit = function(cmd) {
  var bitmap = cmd.texture.bitmap
  var dst_rect = cmd.dst_rect
  var filter = cmd.filter

  // Scale bitmap to fit dst_rect
  var scale_x = dst_rect.width / bitmap.width
  var scale_y = dst_rect.height / bitmap.height
  var scaled = null

  if (scale_x != 1.0 || scale_y != 1.0) {
    // Playdate only supports nearest-neighbor scaling
    scaled = bitmap.scaledImage(scale_x, scale_y)
    this.pd.graphics.drawBitmap(scaled, dst_rect.x, dst_rect.y)
  } else {
    this.pd.graphics.drawBitmap(bitmap, dst_rect.x, dst_rect.y)
  }
}

PlaydateBackend.prototype.cmd_clear = function(cmd) {
  var color = cmd.color
  var pattern = this.color_to_dither_pattern(color)
  this.pd.graphics.setDitherPattern(pattern)
  this.pd.graphics.fillRect(0, 0, 400, 240) // screen size
}

// ========================================================================
// HELPER FUNCTIONS
// ========================================================================

PlaydateBackend.prototype.world_to_screen = function(world_pos, camera) {
  if (!camera) return world_pos
  
  // Simple orthographic transform
  var cam_x = camera.pos[0]
  var cam_y = camera.pos[1]
  var scale = this.get_camera_scale(camera)
  
  var screen_x = (world_pos[0] - cam_x) * scale + 200 // center X
  var screen_y = (world_pos[1] - cam_y) * scale + 120 // center Y
  
  return [screen_x, screen_y]
}

PlaydateBackend.prototype.get_camera_scale = function(camera) {
  if (!camera) return 1
  // Scale based on camera width vs screen width
  return 400 / camera.width
}

PlaydateBackend.prototype.material_to_draw_mode = function(material) {
  var blend = material.blend || 'alpha'
  
  // Map blend modes to Playdate draw modes
  var mode_map = {
    'alpha': this.pd.graphics.kDrawModeCopy,
    'add': this.pd.graphics.kDrawModeXOR,  // approximation
    'multiply': this.pd.graphics.kDrawModeNXOR  // approximation
  }
  
  return mode_map[blend] || this.pd.graphics.kDrawModeCopy
}

PlaydateBackend.prototype.composite_mode_to_draw_mode = function(mode) {
  var mode_map = {
    'over': this.pd.graphics.kDrawModeCopy,
    'add': this.pd.graphics.kDrawModeXOR,
    'multiply': this.pd.graphics.kDrawModeNXOR
  }
  return mode_map[mode] || this.pd.graphics.kDrawModeCopy
}

PlaydateBackend.prototype.color_to_dither_pattern = function(color) {
  // Convert RGBA color to 8x8 dither pattern
  // Grayscale value determines density
  var gray = color.r * 0.299 + color.g * 0.587 + color.b * 0.114
  return this.gray_to_dither_pattern(gray)
}

PlaydateBackend.prototype.gray_to_dither_pattern = function(gray) {
  // Bayer matrix dither patterns for different gray levels
  var patterns = [
    // 0% (black)
    [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    // ~25%
    [0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00],
    // ~50%
    [0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA],
    // ~75%
    [0x77, 0xFF, 0xDD, 0xFF, 0x77, 0xFF, 0xDD, 0xFF],
    // 100% (white)
    [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
  ]
  
  var index = floor(gray * (length(patterns) - 1))
  return patterns[index]
}

PlaydateBackend.prototype.alpha_to_dither_pattern = function(alpha) {
  return this.gray_to_dither_pattern(alpha)
}

PlaydateBackend.prototype.copy_bitmap = function(src, dst) {
  this.pd.graphics.pushContext(dst)
  this.pd.graphics.drawBitmap(src, 0, 0)
  this.pd.graphics.popContext()
}

PlaydateBackend.prototype.get_image = function(name) {
  // Look up image from asset system
  // Return LCDBitmap*
  return this.pd.graphics.imagetable(name)[1]
}

// ========================================================================
// CPU FALLBACKS FOR MISSING GPU FEATURES
// ========================================================================

PlaydateBackend.prototype.cpu_box_blur = function(src, dst, radius) {
  // Simple box blur implementation in CPU
  // This is SLOW but correct

  var width = src.width
  var height = src.height

  // Get pixel data (Playdate API for accessing bitmap pixels)
  var src_data = src.getData()
  var dst_data = dst.getData()
  var y = 0
  var x = 0
  var sum = 0
  var count = 0
  var dy = 0
  var dx = 0
  var sx = 0
  var sy = 0

  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      sum = 0
      count = 0

      // Sample neighborhood
      for (dy = -radius; dy <= radius; dy++) {
        for (dx = -radius; dx <= radius; dx++) {
          sx = x + dx
          sy = y + dy

          if (sx >= 0 && sx < width && sy >= 0 && sy < height) {
            sum += src_data[sy * width + sx]
            count++
          }
        }
      }

      dst_data[y * width + x] = sum / count
    }
  }

  dst.setData(dst_data)
}

