This refactoring separates concerns into distinct modules, making the code easier to test, maintain, and scale.

Here is the content for each file in the `queue/` package.

---

### 📁 `queue/core.py`

This module handles the fundamental definitions of a job and the main queue structure.

```python
# queue/core.py

from dataclasses import dataclass, field
from typing import Callable, Any, Optional
from queue.priority import PriorityQueue

@dataclass(order=True)
class Job:
    """
    Represents a unit of work to be executed.
    The 'priority' field is used by the PriorityQueue.
    """
    priority: int = field(compare=True)
    job_id: str = field(compare=False)
    task: Callable[..., Any] = field(compare=False)
    args: tuple = field(default_factory=tuple, compare=False)
    max_retries: int = field(default=3, compare=False)
    current_attempts: int = field(default=0, compare=False)

    def __post_init__(self):
        # Ensure priority is an integer for comparison
        if not isinstance(self.priority, int):
            raise TypeError("Job priority must be an integer.")


class JobQueue:
    """
    The main queue manager that holds and processes jobs.
    It relies on PriorityQueue for ordering.
    """
    def __init__(self):
        # The PriorityQueue handles the heap structure
        self._queue = PriorityQueue()
        print("JobQueue initialized.")

    def add_job(self, job: Job):
        """Adds a job to the queue."""
        self._queue.put(job)
        print(f"Job '{job.job_id}' added with priority {job.priority}.")

    def get_next_job(self) -> Optional[Job]:
        """Retrieves and removes the highest priority job."""
        return self._queue.get()

    def is_empty(self) -> bool:
        """Checks if the queue is empty."""
        return self._queue.empty()
```

### 📁 `queue/priority.py`

This module encapsulates the priority queue logic, abstracting away the underlying data structure (like `heapq`).

```python
# queue/priority.py

import heapq
from typing import List, Any

class PriorityQueue:
    """
    A standard min-heap implementation for managing jobs based on priority.
    """
    def __init__(self):
        # The heap structure stores tuples: (priority, item)
        self._heap: List[Any] = []

    def put(self, item: Any):
        """Adds an item to the queue, maintaining the heap property."""
        # Assuming the item (Job) is comparable based on its priority attribute
        heapq.heappush(self._heap, item)

    def get(self) -> Any:
        """Removes and returns the smallest item (highest priority job)."""
        if not self._heap:
            raise IndexError("get from an empty priority queue")
        return heapq.heappop(self._heap)

    def empty(self) -> bool:
        """Checks if the queue is empty."""
        return not bool(self._heap)
```

### 📁 `queue/retry.py`

This module handles the business logic for executing tasks, including failure detection and exponential backoff retries.

```python
# queue/retry.py

import time
import random
from queue.core import Job
from typing import Callable, Any

def process_with_retry(job: Job, executor: Callable[..., Any]) -> bool:
    """
    Executes a job with exponential backoff retry logic.

    Args:
        job: The Job object to execute.
        executor: The function that actually runs the task logic.

    Returns:
        True if the job succeeded, False otherwise.
    """
    while job.current_attempts <= job.max_retries:
        job.current_attempts += 1
        print(f"\n--- Attempt {job.current_attempts}/{job.max_retries + 1} for Job '{job.job_id}' ---")
        
        try:
            # Execute the actual task
            result = executor(*job.args)
            print(f"Job '{job.job_id}' succeeded. Result: {result}")
            return True  # Success
        
        except Exception as e:
            print(f"Job '{job.job_id}' failed on attempt {job.current_attempts}: {e}")

            if job.current_attempts > job.max_retries:
                print(f"Job '{job.job_id}' failed permanently after {job.max_retries} retries.")
                return False # Permanent failure

            # Exponential Backoff Calculation: 2^n + jitter
            # n = current attempt number
            backoff_time = (2 ** job.current_attempts) + random.uniform(0, 1)
            print(f"Waiting for {backoff_time:.2f} seconds before retrying...")
            time.sleep(backoff_time)
            
            # If we are retrying, the job object is modified (current_attempts incremented)
            # and the loop continues.
            
    return False
```

### 📁 `queue/__init__.py`

This file serves as the public API for the package, hiding the internal structure and providing a clean interface.

```python
# queue/__init__.py

from .core import Job, JobQueue
from .retry import process_with_retry

# Define what is exposed when 'from queue import *' is used
__all__ = ['Job', 'JobQueue', 'process_with_retry']
```

***

### Usage Example (Demonstration)

To show how the refactored package is used, here is a simple script outside the `queue` directory:

```python
# main.py

import random
from queue import Job, JobQueue, process_with_retry

# --- Task Definitions ---

# A task that always succeeds
def successful_task(data):
    print(f"Executing successful task with data: {data}")
    return f"Processed {data}"

# A task that fails a specific number of times before succeeding
class FlakyTask:
    def __init__(self):
        self.fail_count = 0
    
    def execute(self, data):
        self.fail_count += 1
        if self.fail_count <= 2:
            print(f"--- Flaky Task failed (Attempt {self.fail_count}) ---")
            raise ConnectionError("Database connection lost.")
        print(f"--- Flaky Task succeeded on attempt {self.fail_count} ---")
        return "Success after retries"

# --- Setup and Execution ---

def run_queue_simulation():
    queue_manager = JobQueue()
    flaky_task_instance = FlakyTask()

    # 1. Create a successful job
    job_success = Job(
        priority=1, 
        job_id="A001_Success", 
        task=successful_task, 
        args=("Report 1",),
        max_retries=2
    )
    queue_manager.add_job(job_success)

    # 2. Create a flaky job (will succeed after retries)
    job_flaky = Job(
        priority=2, 
        job_id="B002_Flaky", 
        task=flaky_task_instance.execute, 
        args=("Critical Data",),
        max_retries=5
    )
    queue_manager.add_job(job_flaky)

    # 3. Create a job that fails permanently
    job_fail = Job(
        priority=3, 
        job_id="C003_Fail", 
        task=lambda: 1/0, # Division by zero
        args=(),
        max_retries=2
    )
    queue_manager.add_job(job_fail)

    # --- Processing Loop ---
    print("\n================ STARTING PROCESSING =================\n")
    
    while not queue_manager.is_empty():
        job = queue_manager.get_next_job()
        
        # Use the centralized retry logic
        success = process_with_retry(job, job.task)
        
        if not success:
            print(f"\n[SYSTEM ALERT] Job {job.job_id} was permanently dropped.")

if __name__ == "__main__":
    run_queue_simulation()
```