// clay2.cm - Revised UI layout engine emitting flat drawables
//
// Changes from clay.cm:
// - No __proto__, uses meme/merge
// - Emits flat list of drawables for film2d
// - Supports scissor clipping
//
// Now returns [drawable, drawable, ...] instead of {type:'group', ...}

var layout = use('layout')
var graphics = use('graphics')

var clay = {}

// Layout context
var lay_ctx = layout.make_context()

// Base configuration for UI elements
var base_config = {
  font: null,
  background_image: null,
  slice: 0,
  font_path: 'fonts/dos', // Default font
  font_size: 16,
  color: {r:1, g:1, b:1, a:1},
  spacing: 0,
  padding: 0,
  margin: 0,
  offset: {x:0, y:0},
  size: null,
  background_color: null,
  clipped: false,
  text_break: 'word',
  text_align: 'left',
  max_size: null,
  contain: 0,
  behave: 0
}

function normalize_color(c, fallback) {
  var fb = fallback || {r:1, g:1, b:1, a:1}
  if (!c) return {r:fb.r, g:fb.g, b:fb.b, a:fb.a}
  return {
    r: c.r != null ? c.r : fb.r,
    g: c.g != null ? c.g : fb.g,
    b: c.b != null ? c.b : fb.b,
    a: c.a != null ? c.a : fb.a
  }
}

function normalize_spacing(s) {
  if (is_number(s)) return {l:s, r:s, t:s, b:s}
  if (is_array(s)) {
    if (length(s) == 2) return {l:s[0], r:s[0], t:s[1], b:s[1]}
    if (length(s) == 4) return {l:s[0], r:s[1], t:s[2], b:s[3]}
  }
  if (is_object(s)) return {l:s.l||0, r:s.r||0, t:s.t||0, b:s.b||0}
  return {l:0, r:0, t:0, b:0}
}

// Tree building state
var root_item = null
var tree_root = null
var config_stack = []

// Rewriting state management for cleaner recursion
var tree_stack = []

clay.layout = function(fn, size) {
  lay_ctx.reset()
  var root_id = lay_ctx.item()
  
  lay_ctx.set_size(root_id, size)
  lay_ctx.set_contain(root_id, layout.contain.row)
  
  var root_node = {
    id: root_id,
    config: meme(base_config, {size: size}),
    children: []
  }
  
  tree_stack = [root_node]
  
  fn() // User builds tree
  
  lay_ctx.run()
  
  // Post-layout: build flat drawable list
  return build_drawables(root_node, size.height)
}

function build_drawables(node, root_height, parent, parent_scissor) {
  var p_abs_x = (parent && parent.x) || 0
  var p_abs_y = (parent && parent.y) || 0
  var p_layer = (parent && parent.layer) || 0

  var rect = lay_ctx.get_rect(node.id)
  
  // Calculate absolute world Y for this node (bottom-up layout to top-down render)
  var abs_y = root_height - (rect.y + rect.height)
  var abs_x = rect.x
  
  // IMPORTANT: The offset in config is applied VISUALLY.
  var vis_x = abs_x + node.config.offset.x
  var vis_y = abs_y + node.config.offset.y
  
  var drawables = []
  
  // Scissor
  var current_scissor = parent_scissor
  var sx = vis_x
  var sy = vis_y
  var sw = rect.width
  var sh = rect.height
  var clip_right = null
  var clip_bottom = null
  if (node.config.clipped) {
    // Intersect with parent
    if (parent_scissor) {
      sx = max(sx, parent_scissor.x)
      sy = max(sy, parent_scissor.y)
      clip_right = min(vis_x + sw, parent_scissor.x + parent_scissor.width)
      clip_bottom = min(vis_y + sh, parent_scissor.y + parent_scissor.height)
      sw = max(0, clip_right - sx)
      sh = max(0, clip_bottom - sy)
    }

    current_scissor = {x: sx, y: sy, width: sw, height: sh}
  }
  
  // Background
  if (node.config.background_image) {
    if (node.config.slice) {
      push(drawables, {
        type: 'sprite',
        image: node.config.background_image,
        pos: {x: vis_x, y: vis_y},
        width: rect.width,
        height: rect.height,
        slice: node.config.slice,
        color: node.config.background_color || {r:1, g:1, b:1, a:1},
        layer: p_layer - 0.1, // slightly behind content
        scissor: current_scissor
      })
    } else {
      push(drawables, {
        type: 'sprite',
        image: node.config.background_image,
        pos: {x: vis_x, y: vis_y},
        width: rect.width,
        height: rect.height,
        color: node.config.background_color || {r:1, g:1, b:1, a:1},
        layer: p_layer - 0.1,
        scissor: current_scissor
      })
    }
  } else if (node.config.background_color) {
     push(drawables, {
       type: 'rect',
       pos: {x: vis_x, y: vis_y},
       width: rect.width,
       height: rect.height,
       color: node.config.background_color,
       layer: p_layer - 0.1,
       scissor: current_scissor
     })
  }
  
  // Content (Image/Text)
  if (node.config.image) {
    push(drawables, {
      type: 'sprite',
      image: node.config.image,
      pos: {x: vis_x, y: vis_y},
      width: rect.width,
      height: rect.height, 
      color: node.config.color,
      layer: p_layer,
      scissor: current_scissor
    })
  }
  
  if (node.config.text) {
    push(drawables, {
      type: 'text',
      text: node.config.text,
      font: node.config.font_path,
      size: node.config.font_size, 
      color: node.config.color,
      pos: {x: vis_x, y: vis_y + rect.height}, // Baseline adjustment
      anchor_y: 1.0, // Text usually draws from baseline up or top down? 
                     // film2d text uses top-left by default unless anchor set.
                     // Original clay put it at `y + rect.height`.
                     // Let's assume origin top-left, so we might need anchor adjustment or just position.
                     // If frame is top-down (0 at top), `abs_y` is top.
                     // `rect.y` in layout is bottom-up? "rect.y is from bottom" says original comment.
                     // `abs_y = root_height - (rect.y + rect.height)` -> Top edge of element.
                     // Text usually wants baseline.
                     // If we put it at `vis_y + rect.height`, that's bottom of element.
      layer: p_layer,
      scissor: current_scissor
    })
  }

  // Children
  arrfor(node.children, function(child) {
    drawables = array(drawables, build_drawables(child, root_height, {x: vis_x, y: vis_y, layer: p_layer + 0.01}, current_scissor))
  })
  
  return drawables
}


// --- Item Creation Helpers ---

function process_configs(configs) {
  var cfg = meme(base_config, configs)

  cfg.color = normalize_color(cfg.color, base_config.color)
  if (cfg.background_color) cfg.background_color = normalize_color(cfg.background_color, {r:1,g:1,b:1,a:1})

  if (!cfg.offset) cfg.offset = {x:0, y:0}
  else cfg.offset = {x: cfg.offset.x || 0, y: cfg.offset.y || 0}

  return cfg
}

function push_node(configs, contain_mode) {
  var config = process_configs(configs)
  if (contain_mode != null) config.contain = contain_mode
  
  var item = lay_ctx.item()
  
  // Apply layout props
  lay_ctx.set_margins(item, normalize_spacing(config.margin))
  lay_ctx.set_contain(item, config.contain)
  lay_ctx.set_behave(item, config.behave)
  
  var s = config.size
  if (s) {
    if (is_array(s)) s = {width: s[0], height: s[1]}
    lay_ctx.set_size(item, s)
  }
  
  var node = {
    id: item,
    config: config,
    children: []
  }
  
  // Add to parent
  var parent = tree_stack[length(tree_stack)-1]
  push(parent.children, node)
  lay_ctx.insert(parent.id, item)
  
  push(tree_stack, node)
  return node
}

function pop_node() {
  pop(tree_stack)
}

// Generic container
clay.container = function(configs, fn) {
  var _fn = is_function(configs) ? configs : fn
  var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])

  push_node(_configs, null)
  if (_fn) _fn()
  pop_node()
}

// Stacks
clay.vstack = function(configs, fn) {
  var _fn = is_function(configs) ? configs : fn
  var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])

  var c = layout.contain.column

  push_node(_configs, c)
  if (_fn) _fn()
  pop_node()
}

clay.hstack = function(configs, fn) {
  var _fn = is_function(configs) ? configs : fn
  var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])

  var c = layout.contain.row
  push_node(_configs, c)
  if (_fn) _fn()
  pop_node()
}

clay.zstack = function(configs, fn) {
  var _fn = is_function(configs) ? configs : fn
  var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])

  var c = layout.contain.layout

  push_node(_configs, c)
  if (_fn) _fn()
  pop_node()
}

// Leaf nodes
clay.image = function(path, configs) {
  var img = graphics.texture(path)
  var c = [{image: path}]
  var final_config = process_configs(configs)
  if (!final_config.size && !final_config.behave)
     c.size = {width: img.width, height: img.height}

  var _configs = is_array(configs) ? configs : [configs]

  push_node(array(c, _configs), null)
  pop_node()
}

clay.text = function(str, configs) {
  var c = [{text: str}]
  var final_config = process_configs(configs)
  if (!final_config.size && !final_config.behave) {
    c.size = {width: 100, height: 20}
  }

  var _configs = is_array(configs) ? configs : [configs]

  push_node(array(c, _configs), null)
  pop_node()
}

clay.rectangle = function(configs) {
  var _configs = is_array(configs) ? configs : [configs]
  push_node(_configs, null)
  pop_node()
}

clay.button = function(str, action, configs) {
  var btn_config = [{
    padding: 10,
    background_color: {r:0.3, g:0.3, b:0.4, a:1}
  }]

  var _configs = is_array(configs) ? configs : [configs]

  clay.zstack(array(btn_config, _configs), function() {
    clay.text(str, {color: {r:1,g:1,b:1,a:1}})
  })
}

// Constants
clay.behave = layout.behave
clay.contain = layout.contain

return clay
