Here is the refactored package structure. This design separates concerns into data (`core`), data structures (`priority`), and business logic (`retry`), while managing Python's circular import requirements safely.

### Directory Structure

```text
queue/
├── __init__.py
├── core.py
├── priority.py
└── retry.py
```

### 1. `queue/priority.py`
Implements the heap-based priority queue. It depends on the `Job` dataclass from `core.py`.

```python
import heapq
from typing import Any, Optional

# Import Job from core. 
# Note: This creates a circular dependency with core.py, 
# but it is safe because Job is defined before JobQueue uses PriorityQueue.
from .core import Job

class PriorityQueue:
    """
    A thread-safe priority queue implementation using a min-heap.
    Jobs with lower priority values are processed first.
    """
    
    def __init__(self):
        self._heap: list[tuple[int, int, Job]] = []
        self._counter = 0  # To maintain FIFO order for same-priority jobs

    def push(self, job: Job) -> None:
        """Add a job to the queue."""
        # Tuple: (priority, insertion_order, job)
        # insertion_order ensures stable sorting (FIFO) for same priority
        heapq.heappush(self._heap, (job.priority, self._counter, job))
        self._counter += 1

    def pop(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        if not self._heap:
            return None
        _, _, job = heapq.heappop(self._heap)
        return job

    def peek(self) -> Optional[Job]:
        """Return the highest priority job without removing it."""
        if not self._heap:
            return None
        return self._heap[0][2]

    def is_empty(self) -> bool:
        """Check if the queue is empty."""
        return len(self._heap) == 0

    def __len__(self) -> int:
        return len(self._heap)
```

### 2. `queue/core.py`
Contains the `Job` dataclass and the `JobQueue` wrapper which handles state and thread safety.

```python
import threading
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, Any

from .priority import PriorityQueue

@dataclass
class Job:
    """
    Represents a unit of work to be processed.
    """
    id: str
    payload: Any
    priority: int = 0
    status: str = "pending"  # pending, running, completed, failed
    created_at: datetime = field(default_factory=datetime.now)
    metadata: dict = field(default_factory=dict)

class JobQueue:
    """
    Thread-safe wrapper around PriorityQueue for managing job lifecycle.
    """
    
    def __init__(self):
        self._queue: PriorityQueue = PriorityQueue()
        self._lock = threading.Lock()
        self._jobs_map: dict[str, Job] = {}  # For tracking job status by ID

    def add_job(self, job: Job) -> None:
        """Add a new job to the queue."""
        with self._lock:
            self._queue.push(job)
            self._jobs_map[job.id] = job

    def get_next_job(self) -> Optional[Job]:
        """Retrieve the next job to process."""
        with self._lock:
            job = self._queue.pop()
            if job:
                job.status = "running"
                self._jobs_map[job.id] = job
            return job

    def complete_job(self, job_id: str) -> bool:
        """Mark a job as completed."""
        with self._lock:
            if job_id in self._jobs_map:
                self._jobs_map[job_id].status = "completed"
                return True
            return False

    def fail_job(self, job_id: str, error: str = "") -> bool:
        """Mark a job as failed."""
        with self._lock:
            if job_id in self._jobs_map:
                self._jobs_map[job_id].status = "failed"
                self._jobs_map[job_id].metadata["error"] = error
                return True
            return False

    def get_job_status(self, job_id: str) -> Optional[str]:
        """Get current status of a specific job."""
        with self._lock:
            job = self._jobs_map.get(job_id)
            return job.status if job else None

    def size(self) -> int:
        """Return the number of pending jobs."""
        return len(self._queue)
```

### 3. `queue/retry.py`
Handles retry logic with exponential backoff. It imports `Job` from `core.py`.

```python
import time
import math
from typing import Callable, Optional
from .core import Job

def process_with_retry(
    job: Job, 
    func: Callable[[Job], Any], 
    max_retries: int = 3, 
    base_delay: float = 1.0,
    backoff_factor: float = 2.0
) -> bool:
    """
    Executes a function on a job with exponential backoff retry logic.
    
    Args:
        job: The job object containing payload and metadata.
        func: The execution function that accepts a Job.
        max_retries: Maximum number of retry attempts.
        base_delay: Initial delay in seconds.
        backoff_factor: Multiplier for delay on each retry.
        
    Returns:
        True if successful, False if max retries reached.
    """
    attempt = 0
    
    while attempt <= max_retries:
        try:
            func(job)
            return True
        except Exception as e:
            attempt += 1
            if attempt > max_retries:
                # Update job status to failed if we exhausted retries
                # (In a real scenario, you might pass the JobQueue here to update status)
                job.status = "failed"
                job.metadata["error"] = str(e)
                return False
            
            # Calculate exponential backoff
            delay = base_delay * (backoff_factor ** (attempt - 1))
            time.sleep(delay)
            
    return False
```

### 4. `queue/__init__.py`
Exposes the clean public API. It imports specific items from submodules.

```python
# queue/__init__.py

# Expose the main public components
from .core import Job, JobQueue
from .retry import process_with_retry

# Optional: Hide internal implementation details
# If users want to use PriorityQueue directly, they can do:
# from queue.priority import PriorityQueue

__all__ = [
    "Job",
    "JobQueue",
    "process_with_retry",
]
```

### Usage Example

Here is how a client would use the refactored package:

```python
from queue import Job, JobQueue, process_with_retry

def my_task(job: Job):
    # Simulate work
    print(f"Processing {job.id}: {job.payload}")
    if "fail" in str(job.payload):
        raise ValueError("Simulated failure")

# Create queue
q = JobQueue()

# Add jobs
q.add_job(Job(id="1", payload="success", priority=1))
q.add_job(Job(id="2", payload="fail", priority=2))

# Process
while not q.size() == 0:
    job = q.get_next_job()
    if job:
        success = process_with_retry(job, my_task)
        if success:
            q.complete_job(job.id)
        else:
            q.fail_job(job.id)
```