// effects.cm - Effect Registry with Built-in Effect Recipes
//
// Effects are defined as recipes that produce abstract render passes.
// The compositor uses these recipes to build render plans.
// Backends implement the actual shader logic.

var effects = {}

// Effect registry
var _effects = {}

effects.register = function(name, deff) {
  _effects[name] = deff
}

effects.get = function(name) {
  return _effects[name]
}

effects.list = function() {
  return array(_effects)
}

// Built-in effect: Bloom
effects.register('bloom', {
  type: 'multi_pass',
  requires_target: true,
  params: {
    threshold: {default: 0.8, type: 'float'},
    intensity: {default: 1.0, type: 'float'},
    blur_passes: {default: 3, type: 'int'}
  },
  build_passes: function(input, output, params, ctx) {
    var passes = []
    var size = ctx.target_size

    // Threshold extraction
    var thresh_target = ctx.alloc_target(size.width, size.height, 'bloom_thresh')
    push(passes, {
      type: 'shader',
      shader: 'threshold',
      input: input,
      output: thresh_target,
      uniforms: {
        threshold: params.threshold != null ? params.threshold : 0.8,
        intensity: params.intensity != null ? params.intensity : 1.0
      }
    })

    // Blur ping-pong
    var blur_a = ctx.alloc_target(size.width, size.height, 'bloom_blur_a')
    var blur_b = ctx.alloc_target(size.width, size.height, 'bloom_blur_b')
    var blur_src = thresh_target
    var texel = {x: 1 / size.width, y: 1 / size.height}
    var blur_count = params.blur_passes != null ? params.blur_passes : 3
    var i = 0

    for (i = 0; i < blur_count; i++) {
      push(passes, {
        type: 'shader',
        shader: 'blur',
        input: blur_src,
        output: blur_a,
        uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
      })
      push(passes, {
        type: 'shader',
        shader: 'blur',
        input: blur_a,
        output: blur_b,
        uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
      })
      blur_src = blur_b
    }

    // Additive composite
    push(passes, {
      type: 'composite',
      base: input,
      overlay: blur_src,
      output: output,
      blend: 'add'
    })

    return passes
  }
})

// Built-in effect: Mask
effects.register('mask', {
  type: 'conditional',
  requires_target: true,
  params: {
    source: {required: false},  // Legacy: direct handle reference
    source_id: {required: false}, // New: ID string for film2d.get()
    channel: {default: 'alpha'},
    invert: {default: false},
    soft: {default: false},
    space: {default: 'local'}
  },
  build_passes: function(input, output, params, ctx) {
    var passes = []
    var size = ctx.target_size

    // Check backend capabilities for stencil optimization
    if (!params.soft && ctx.backend && ctx.backend.caps && ctx.backend.caps.has_stencil) {
      // Could use stencil - but for now use texture approach
    }

    if (ctx.backend && ctx.backend.caps && !ctx.backend.caps.has_render_targets) {
      // Can't do masks on this backend - just pass through
      return [{type: 'blit', source: input, dest: output}]
    }

    // Resolve mask source
    var mask_source = params.source
    if (params.source_id && ctx.film2d) {
      mask_source = ctx.film2d.get(params.source_id)
    }

    if (!mask_source) {
      // No mask source - pass through
      return [{type: 'blit', source: input, dest: output}]
    }

    // Render mask source to target
    var mask_target = ctx.alloc_target(size.width, size.height, 'mask_src')
    push(passes, {
      type: 'render_subtree',
      root: mask_source,
      output: mask_target,
      clear: {r: 0, g: 0, b: 0, a: 0},
      space: params.space || 'local'
    })

    // Apply mask shader
    push(passes, {
      type: 'shader',
      shader: 'mask',
      inputs: [input, mask_target],
      output: output,
      uniforms: {
        channel: params.channel == 'alpha' ? 0 : 1,
        invert: params.invert ? 1 : 0
      }
    })

    return passes
  }
})

// Built-in effect: CRT
effects.register('crt', {
  type: 'single_pass',
  requires_target: true,
  params: {
    curvature: {default: 0.1},
    scanline_intensity: {default: 0.3},
    vignette: {default: 0.2}
  },
  build_passes: function(input, output, params, ctx) {
    return [{
      type: 'shader',
      shader: 'crt',
      input: input,
      output: output,
      uniforms: {
        curvature: params.curvature != null ? params.curvature : 0.1,
        scanline_intensity: params.scanline_intensity != null ? params.scanline_intensity : 0.3,
        vignette: params.vignette != null ? params.vignette : 0.2,
        resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
      }
    }]
  }
})

// Built-in effect: Blur
effects.register('blur', {
  type: 'multi_pass',
  requires_target: true,
  params: {
    passes: {default: 2}
  },
  build_passes: function(input, output, params, ctx) {
    var passes = []
    var size = ctx.target_size
    var texel = {x: 1 / size.width, y: 1 / size.height}
    var blur_a = ctx.alloc_target(size.width, size.height, 'blur_a')
    var blur_b = ctx.alloc_target(size.width, size.height, 'blur_b')
    var src = input
    var blur_count = params.passes != null ? params.passes : 2
    var i = 0

    for (i = 0; i < blur_count; i++) {
      push(passes, {
        type: 'shader',
        shader: 'blur',
        input: src,
        output: blur_a,
        uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
      })
      push(passes, {
        type: 'shader',
        shader: 'blur',
        input: blur_a,
        output: blur_b,
        uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
      })
      src = blur_b
    }

    // Final blit to output
    push(passes, {type: 'blit', source: src, dest: output})
    return passes
  }
})

// Built-in effect: Accumulator (motion blur / trails)
effects.register('accumulator', {
  type: 'stateful',
  requires_target: true,
  params: {
    decay: {default: 0.9}
  },
  build_passes: function(input, output, params, ctx) {
    var size = ctx.target_size
    var prev = ctx.get_persistent_target('accum_prev', size.width, size.height)
    var curr = ctx.get_persistent_target('accum_curr', size.width, size.height)

    return [
      {
        type: 'shader',
        shader: 'accumulator',
        inputs: [input, prev],
        output: curr,
        uniforms: {decay: params.decay != null ? params.decay : 0.9}
      },
      {type: 'blit', source: curr, dest: prev},
      {type: 'blit', source: curr, dest: output}
    ]
  }
})

// Built-in effect: Pixelate
effects.register('pixelate', {
  type: 'single_pass',
  requires_target: true,
  params: {
    pixel_size: {default: 4}
  },
  build_passes: function(input, output, params, ctx) {
    return [{
      type: 'shader',
      shader: 'pixelate',
      input: input,
      output: output,
      uniforms: {
        pixel_size: params.pixel_size != null ? params.pixel_size : 4,
        resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
      }
    }]
  }
})

// Built-in effect: Color grading
effects.register('color_grade', {
  type: 'single_pass',
  requires_target: true,
  params: {
    brightness: {default: 0},
    contrast: {default: 1},
    saturation: {default: 1},
    gamma: {default: 1}
  },
  build_passes: function(input, output, params, ctx) {
    return [{
      type: 'shader',
      shader: 'color_grade',
      input: input,
      output: output,
      uniforms: {
        brightness: params.brightness != null ? params.brightness : 0,
        contrast: params.contrast != null ? params.contrast : 1,
        saturation: params.saturation != null ? params.saturation : 1,
        gamma: params.gamma != null ? params.gamma : 1
      }
    }]
  }
})

// Built-in effect: Vignette
effects.register('vignette', {
  type: 'single_pass',
  requires_target: true,
  params: {
    intensity: {default: 0.3},
    softness: {default: 0.5}
  },
  build_passes: function(input, output, params, ctx) {
    return [{
      type: 'shader',
      shader: 'vignette',
      input: input,
      output: output,
      uniforms: {
        intensity: params.intensity != null ? params.intensity : 0.3,
        softness: params.softness != null ? params.softness : 0.5,
        resolution: {width: ctx.target_size.width, height: ctx.target_size.height}
      }
    }]
  }
})

// Built-in effect: Chromatic aberration
effects.register('chromatic', {
  type: 'single_pass',
  requires_target: true,
  params: {
    offset: {default: 0.005}
  },
  build_passes: function(input, output, params, ctx) {
    return [{
      type: 'shader',
      shader: 'chromatic',
      input: input,
      output: output,
      uniforms: {
        offset: params.offset != null ? params.offset : 0.005
      }
    }]
  }
})

// Built-in effect: Outline
effects.register('outline', {
  type: 'single_pass',
  requires_target: true,
  params: {
    color: {default: {r: 0, g: 0, b: 0, a: 1}},
    width: {default: 1}
  },
  build_passes: function(input, output, params, ctx) {
    var c = params.color || {r: 0, g: 0, b: 0, a: 1}
    return [{
      type: 'shader',
      shader: 'outline',
      input: input,
      output: output,
      uniforms: {
        outline_color: c,
        outline_width: params.width != null ? params.width : 1,
        texel_size: {x: 1 / ctx.target_size.width, y: 1 / ctx.target_size.height}
      }
    }]
  }
})

// Helper: Check if an effect requires a render target
effects.requires_target = function(effect_type) {
  var deff = _effects[effect_type]
  return deff ? (deff.requires_target || false) : false
}

// Helper: Get default params for an effect
effects.default_params = function(effect_type) {
  var deff = _effects[effect_type]
  if (!deff || !deff.params) return {}
  
  var defaults = {}
  arrfor(array(deff.params), k => {
    if (deff.params[k].default != null) {
      defaults[k] = deff.params[k].default
    }
  })
  return defaults
}

// Helper: Validate effect params
effects.validate_params = function(effect_type, params) {
  var deff = _effects[effect_type]
  if (!deff || !deff.params) return true
  
  arrfor(array(deff.params), k => {
    if (deff.params[k].required && params[k] == null) {
      return false
    }
  })
  return true
}

return effects
