Here is the refactored package structure. The code is designed to maintain the original functionality while adhering to the Single Responsibility Principle.

**Note on Circular Imports:** To keep `Job` in `core.py` and `PriorityQueue` in `priority.py` while allowing them to interact, standard Python module loading is used. This is safe as long as imports occur at the module level.

### 1. `queue/__init__.py`
This file serves as the public API entry point. It aggregates the necessary classes and functions from the submodules.

```python
"""
Job Queue Package.
Provides job management, priority handling, and retry logic.
"""

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

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

### 2. `queue/core.py`
Contains the data model (`Job`) and the high-level queue management logic (`JobQueue`).

```python
"""
Core module containing Job dataclass and basic Queue management.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Optional
from .priority import PriorityQueue


@dataclass(order=True)
class Job:
    """
    Represents a unit of work to be processed.
    Priority is used for ordering (lower value = higher priority).
    """
    priority: int
    created_at: datetime = field(default_factory=datetime.now)
    job_id: Optional[str] = None
    payload: Any = None
    status: str = "pending"

    def __post_init__(self):
        if self.job_id is None:
            self.job_id = f"job-{id(self)}"


class JobQueue:
    """
    Manages a collection of Jobs using an internal Priority Queue.
    """
    def __init__(self):
        self._queue = PriorityQueue()

    def add(self, job: Job) -> None:
        """Add a job to the queue."""
        self._queue.push(job)

    def get_next(self) -> Optional[Job]:
        """Retrieve the highest priority job without removing it."""
        return self._queue.peek()

    def pop(self) -> Optional[Job]:
        """Remove and return the highest priority job."""
        return self._queue.pop()

    def remove(self, job_id: str) -> bool:
        """Remove a specific job by ID."""
        return self._queue.remove(job_id)

    def size(self) -> int:
        """Return the number of jobs in the queue."""
        return self._queue.count()
```

### 3. `queue/priority.py`
Handles the specific logic for ordering jobs using a heap structure.

```python
"""
Module for Priority Queue implementation.
"""
import heapq
from typing import List, Optional
from .core import Job


class PriorityQueue:
    """
    A heap-based priority queue for Job instances.
    """
    def __init__(self):
        self._heap: List[Job] = []
        self._job_ids = {}  # Map job_id to heap index for efficient removal

    def push(self, job: Job) -> None:
        """Push a job onto the heap."""
        heapq.heappush(self._heap, job)
        self._job_ids[job.job_id] = len(self._heap) - 1

    def pop(self) -> Optional[Job]:
        """Pop the highest priority job."""
        if not self._heap:
            return None
        job = heapq.heappop(self._heap)
        # Clean up index map
        self._job_ids.pop(job.job_id, None)
        return job

    def peek(self) -> Optional[Job]:
        """Peek at the highest priority job."""
        if not self._heap:
            return None
        return self._heap[0]

    def remove(self, job_id: str) -> bool:
        """
        Remove a job by ID.
        Note: In a real heap, this is O(n) unless using a more complex structure.
        Here we mark it as removed and lazily ignore it on pop for simplicity.
        """
        if job_id not in self._job_ids:
            return False
        
        # Lazy removal strategy: we just mark it in a set of 'removed_ids' 
        # or rebuild the heap. For this module, we'll rebuild for correctness.
        # In a high-performance scenario, a 'lazy removal' dict is better.
        
        # Simple implementation: Filter heap
        self._heap = [j for j in self._heap if j.job_id != job_id]
        self._job_ids = {j.job_id: i for i, j in enumerate(self._heap)}
        heapq.heapify(self._heap)
        return True

    @property
    def count(self) -> int:
        return len(self._heap)
```

### 4. `queue/retry.py`
Handles the logic for retrying failed jobs with exponential backoff.

```python
"""
Module for Retry logic with exponential backoff.
"""
import time
import random
from typing import Callable, Any, Optional
from .core import Job


def process_with_retry(
    job: Job,
    executor: Callable[[Job], Any],
    max_retries: int = 3,
    base_delay: float = 1.0,
) -> Any:
    """
    Executes a job with retry logic.
    
    Args:
        job: The Job object to process.
        executor: A callable that accepts a Job and returns a result.
        max_retries: Maximum number of retry attempts.
        base_delay: Base delay in seconds for exponential backoff.
        
    Returns:
        The result of the executor.
        
    Raises:
        Exception: If max retries are exceeded.
    """
    last_exception = None
    
    for attempt in range(max_retries + 1):
        try:
            return executor(job)
        except Exception as e:
            last_exception = e
            if attempt < max_retries:
                # Exponential backoff with jitter
                delay = (base_delay * (2 ** attempt)) + random.uniform(0, 1)
                time.sleep(delay)
    
    raise last_exception
```