# -*- coding: utf-8 -*-
'''
@File    : progress_monitor.py
@Time    : 2025/12/27 00:10:07
@Author  : wty-yy, Gemini3 Pro
@Version : 1.0
@Blog    : https://wty-yy.github.io/
@Desc    : Centralized progress monitoring with Dynamic Slot Management
'''
from tqdm import tqdm
import multiprocessing
from typing import Dict
from threading import Thread
from dataclasses import dataclass
import heapq

class ProgressTypes:
    INIT = 'init'       # Init progress (set total, desc)
    UPDATE = 'update'   # Update progress value (set value)
    DESC = 'desc'       # Update description text only (set desc)
    RESET = 'reset'     # Reset progress bar (set total, desc)
    FINISH = 'finish'   # Mark completion (set desc)
    ERROR = 'error'     # Mark error (set desc)

@dataclass
class ProgressData:
    progress_queue: multiprocessing.Queue
    task_id: int = 0
    msg_prefix: str = ''

def report_progress(progress_data: ProgressData, msg_type, value=None, desc=None, total=None):
    """
    Assistant function to report progress to the main process.
    Args:
        queue: multiprocessing.Queue
        task_id: int, line number of the task
        msg_type: ProgressTypes
        value: int, value to update (for UPDATE type)
        desc: str, description text (for DESC, INIT, FINISH types)
        total: int, total value (for INIT, RESET types)
    """
    if progress_data is None:
        return
    queue = progress_data.progress_queue
    task_id = progress_data.task_id
    msg_prefix = progress_data.msg_prefix
    if desc is not None:
        desc = msg_prefix + desc
    queue.put((task_id, msg_type, {'value': value, 'desc': desc, 'total': total}))

class ProgressMonitor:
    def __init__(self, total_rows):
        self.total_rows = total_rows
        self.active_bars: Dict[int, Dict] = {} 
        self.free_slots = []
        self.max_slot_used = 0 
        
    def _get_free_slot(self):
        """ Get a free screen display position """
        if self.free_slots:
            return heapq.heappop(self.free_slots)
        else:
            self.max_slot_used += 1
            return self.max_slot_used

    def _release_slot(self, slot):
        """ Release a screen display position """
        heapq.heappush(self.free_slots, slot)

    def listener_loop(self, queue):
        """
        Listener thread in the main process
        """
        # Create the main progress bar (Position 0)
        main_bar = tqdm(
            total=self.total_rows, 
            position=0, 
            desc="🚀 Total Progress", 
            bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [Done: {n_fmt}] [{elapsed}<{remaining}]",
            leave=True,
            smoothing=0.1  # Optional: set smoothing factor (0-1) to prevent drastic ETA fluctuations due to slow tasks
        )
        
        # Number of tasks still pending
        pending_tasks = self.total_rows
        
        while pending_tasks > 0:
            record = queue.get()
            if record is None: break
            
            task_id, msg_type, data = record
            
            # --- Dynamically create/get sub progress bars ---
            if task_id not in self.active_bars:
                # Only create bar when message received to avoid Pending screen flicker
                slot = self._get_free_slot()
                # leave=False is key: after task completion, clear the line for new task reuse
                bar = tqdm(
                    total=100,
                    position=slot,
                    desc=f"Task {task_id} Starting...",
                    bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]",
                    leave=False 
                )
                self.active_bars[task_id] = {'bar': bar, 'slot': slot}
            
            bar_info = self.active_bars[task_id]
            bar = bar_info['bar']
            
            # --- Handle messages ---
            try:
                if msg_type == ProgressTypes.INIT:
                    total = data.get('total', 100)
                    desc = data.get('desc', '')
                    bar.reset(total=total)
                    bar.set_description(desc)
                    bar.refresh()
                    
                elif msg_type == ProgressTypes.UPDATE:
                    val = data.get('value', 1)
                    bar.update(val)
                    
                elif msg_type == ProgressTypes.DESC:
                    desc = data.get('desc')
                    if desc: bar.set_description(desc)
                        
                elif msg_type == ProgressTypes.RESET:
                    total = data.get('total', 100)
                    desc = data.get('desc', '')
                    bar.reset(total=total)
                    bar.set_description(desc)
                    bar.refresh()
                    
                elif msg_type == ProgressTypes.FINISH or msg_type == ProgressTypes.ERROR:
                    # Update sub progress bar status
                    desc = data.get('desc')
                    if desc: bar.set_description(desc)
                    bar.refresh()
                    
                    # Close sub progress bar and release slot
                    bar.close() # 因为 leave=False，这行会从屏幕消失
                    slot = bar_info['slot']
                    self._release_slot(slot)
                    del self.active_bars[task_id]
                    
                    # Update the main progress bar
                    main_bar.update(1)
                    main_bar.refresh()
                    
                    pending_tasks -= 1

            except Exception as e:
                pass # Prevent printing errors from disrupting layout

        # Close all remaining bars (theoretically should be empty except main_bar)
        main_bar.close()
        for info in self.active_bars.values():
            info['bar'].close()

def start_progress_monitor_thread(total_rows):
    """
    Create and start a ProgressMonitor thread. Queue is returned for reporting progress.
    Args:
        total_rows: int, number of tasks to monitor
    Returns:
        queue: multiprocessing.Queue
        monitor_thread: threading.Thread
    """
    queue = multiprocessing.Manager().Queue()
    monitor = ProgressMonitor(total_rows)
    monitor_thread = Thread(target=monitor.listener_loop, args=(queue,))
    monitor_thread.start()
    return queue, monitor_thread
