import time

from engine.effects.performance import PerformanceMonitor, get_monitor
from engine.effects.registry import EffectRegistry
from engine.effects.types import EffectContext, PartialUpdate


class EffectChain:
    def __init__(
        self, registry: EffectRegistry, monitor: PerformanceMonitor | None = None
    ):
        self._registry = registry
        self._order: list[str] = []
        self._monitor = monitor

    def _get_monitor(self) -> PerformanceMonitor:
        if self._monitor is not None:
            return self._monitor
        return get_monitor()

    def set_order(self, names: list[str]) -> None:
        self._order = list(names)

    def get_order(self) -> list[str]:
        return self._order.copy()

    def add_effect(self, name: str, position: int | None = None) -> bool:
        if name not in self._registry.list_all():
            return False
        if position is None:
            self._order.append(name)
        else:
            self._order.insert(position, name)
        return True

    def remove_effect(self, name: str) -> bool:
        if name in self._order:
            self._order.remove(name)
            return True
        return False

    def reorder(self, new_order: list[str]) -> bool:
        all_plugins = set(self._registry.list_all().keys())
        if not all(name in all_plugins for name in new_order):
            return False
        self._order = list(new_order)
        return True

    def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
        monitor = self._get_monitor()
        frame_number = ctx.frame_number
        monitor.start_frame(frame_number)

        # Get dirty regions from canvas via context (set by CanvasStage)
        dirty_rows = ctx.get_state("canvas.dirty_rows")

        # Create PartialUpdate for effects that support it
        full_buffer = dirty_rows is None or len(dirty_rows) == 0
        partial = PartialUpdate(
            rows=None,
            cols=None,
            dirty=dirty_rows,
            full_buffer=full_buffer,
        )

        frame_start = time.perf_counter()
        result = list(buf)
        for name in self._order:
            plugin = self._registry.get(name)
            if plugin and plugin.config.enabled:
                chars_in = sum(len(line) for line in result)
                effect_start = time.perf_counter()
                try:
                    # Use process_partial if supported, otherwise fall back to process
                    if getattr(plugin, "supports_partial_updates", False):
                        result = plugin.process_partial(result, ctx, partial)
                    else:
                        result = plugin.process(result, ctx)
                except Exception:
                    plugin.config.enabled = False
                elapsed = time.perf_counter() - effect_start
                chars_out = sum(len(line) for line in result)
                monitor.record_effect(name, elapsed * 1000, chars_in, chars_out)

        total_elapsed = time.perf_counter() - frame_start
        monitor.end_frame(frame_number, total_elapsed * 1000)
        return result
