# [DEF:TaskLoggerModule:Module]
# @SEMANTICS: task, logger, context, plugin, attribution
# @PURPOSE: Provides a dedicated logger for tasks with automatic source attribution.
# @LAYER: Core
# @RELATION: DEPENDS_ON -> TaskManager, CALLS -> TaskManager._add_log
# @TIER: CRITICAL
# @INVARIANT: Each TaskLogger instance is bound to a specific task_id and default source.

# [SECTION: IMPORTS]
from typing import Dict, Any, Optional, Callable
# [/SECTION]

# [DEF:TaskLogger:Class]
# @SEMANTICS: logger, task, source, attribution
# @PURPOSE: A wrapper around TaskManager._add_log that carries task_id and source context.
# @TIER: CRITICAL
# @INVARIANT: All log calls include the task_id and source.
# @UX_STATE: Idle -> Logging -> (system records log)
#
# @TEST_CONTRACT: TaskLoggerModel ->
# {
#   required_fields: {task_id: str, add_log_fn: Callable},
#   optional_fields: {source: str},
#   invariants: [
#       "All specific log methods (info, error) delegate to _log",
#       "with_source creates a new logger with the same task_id"
#   ]
# }
# @TEST_FIXTURE: valid_task_logger -> {"task_id": "test_123", "add_log_fn": lambda *args: None, "source": "test_plugin"}
# @TEST_EDGE: missing_task_id -> raises TypeError
# @TEST_EDGE: invalid_add_log_fn -> raises TypeError
# @TEST_INVARIANT: consistent_delegation -> verifies: [valid_task_logger]
class TaskLogger:
    """
    A dedicated logger for tasks that automatically tags logs with source attribution.
    
    Usage:
        logger = TaskLogger(task_id="abc123", add_log_fn=task_manager._add_log, source="plugin")
        logger.info("Starting backup process")
        logger.error("Failed to connect", metadata={"error_code": 500})
        
        # Create sub-logger with different source
        api_logger = logger.with_source("superset_api")
        api_logger.info("Fetching dashboards")
    """
    
    # [DEF:__init__:Function]
    # @PURPOSE: Initialize the TaskLogger with task context.
    # @PRE: add_log_fn is a callable that accepts (task_id, level, message, context, source, metadata).
    # @POST: TaskLogger is ready to log messages.
    # @PARAM: task_id (str) - The ID of the task.
    # @PARAM: add_log_fn (Callable) - Function to add log to TaskManager.
    # @PARAM: source (str) - Default source for logs (default: "plugin").
    def __init__(
        self, 
        task_id: str, 
        add_log_fn: Callable, 
        source: str = "plugin"
    ):
        self._task_id = task_id
        self._add_log = add_log_fn
        self._default_source = source
    # [/DEF:__init__:Function]
    
    # [DEF:with_source:Function]
    # @PURPOSE: Create a sub-logger with a different default source.
    # @PRE: source is a non-empty string.
    # @POST: Returns new TaskLogger with the same task_id but different source.
    # @PARAM: source (str) - New default source.
    # @RETURN: TaskLogger - New logger instance.
    def with_source(self, source: str) -> "TaskLogger":
        """Create a sub-logger with a different source context."""
        return TaskLogger(
            task_id=self._task_id,
            add_log_fn=self._add_log,
            source=source
        )
    # [/DEF:with_source:Function]
    
    # [DEF:_log:Function]
    # @PURPOSE: Internal method to log a message at a given level.
    # @PRE: level is a valid log level string.
    # @POST: Log entry added via add_log_fn.
    # @PARAM: level (str) - Log level (DEBUG, INFO, WARNING, ERROR).
    # @PARAM: message (str) - Log message.
    # @PARAM: source (Optional[str]) - Override source for this log entry.
    # @PARAM: metadata (Optional[Dict]) - Additional structured data.
    # @UX_STATE: Logging -> (writing internal log)
    def _log(
        self, 
        level: str, 
        message: str, 
        source: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        """Internal logging method."""
        self._add_log(
            task_id=self._task_id,
            level=level,
            message=message,
            source=source or self._default_source,
            metadata=metadata
        )
    # [/DEF:_log:Function]
    
    # [DEF:debug:Function]
    # @PURPOSE: Log a DEBUG level message.
    # @PRE: message is a string.
    # @POST: Log entry added via internally with DEBUG level.
    # @PARAM: message (str) - Log message.
    # @PARAM: source (Optional[str]) - Override source.
    # @PARAM: metadata (Optional[Dict]) - Additional data.
    def debug(
        self, 
        message: str, 
        source: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        self._log("DEBUG", message, source, metadata)
    # [/DEF:debug:Function]
    
    # [DEF:info:Function]
    # @PURPOSE: Log an INFO level message.
    # @PRE: message is a string.
    # @POST: Log entry added internally with INFO level.
    # @PARAM: message (str) - Log message.
    # @PARAM: source (Optional[str]) - Override source.
    # @PARAM: metadata (Optional[Dict]) - Additional data.
    def info(
        self, 
        message: str, 
        source: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        self._log("INFO", message, source, metadata)
    # [/DEF:info:Function]
    
    # [DEF:warning:Function]
    # @PURPOSE: Log a WARNING level message.
    # @PRE: message is a string.
    # @POST: Log entry added internally with WARNING level.
    # @PARAM: message (str) - Log message.
    # @PARAM: source (Optional[str]) - Override source.
    # @PARAM: metadata (Optional[Dict]) - Additional data.
    def warning(
        self, 
        message: str, 
        source: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        self._log("WARNING", message, source, metadata)
    # [/DEF:warning:Function]
    
    # [DEF:error:Function]
    # @PURPOSE: Log an ERROR level message.
    # @PRE: message is a string.
    # @POST: Log entry added internally with ERROR level.
    # @PARAM: message (str) - Log message.
    # @PARAM: source (Optional[str]) - Override source.
    # @PARAM: metadata (Optional[Dict]) - Additional data.
    def error(
        self, 
        message: str, 
        source: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> None:
        self._log("ERROR", message, source, metadata)
    # [/DEF:error:Function]
    
    # [DEF:progress:Function]
    # @PURPOSE: Log a progress update with percentage.
    # @PRE: percent is between 0 and 100.
    # @POST: Log entry with progress metadata added.
    # @PARAM: message (str) - Progress message.
    # @PARAM: percent (float) - Progress percentage (0-100).
    # @PARAM: source (Optional[str]) - Override source.
    def progress(
        self, 
        message: str, 
        percent: float,
        source: Optional[str] = None
    ) -> None:
        """Log a progress update with percentage."""
        metadata = {"progress": min(100, max(0, percent))}
        self._log("INFO", message, source, metadata)
    # [/DEF:progress:Function]

# [/DEF:TaskLogger:Class]

# [/DEF:TaskLoggerModule:Module]
