<reasoning_content>
*   Goal: Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Requirements:
        1.  `Job`: Dataclass (`id`, `data`, `priority`, `retries`).
        2.  `RetryPolicy`: Class for retry-with-backoff logic. `run` method returns `(success, attempts_made)`. No actual sleeping.
        3.  `JobQueue`: Class to manage jobs. `add_job`, `process_job` (using `RetryPolicy`), `get_next_job`.
    *   Constraints: Use `dataclass`, `Callable`, `Tuple`, `Optional` (or `|` if Python 3.10+), only standard library. No explanations.

    *   `Job`:
        ```python
        from dataclasses import dataclass
        from typing import Dict

        @dataclass
        class Job:
            id: str
            data: Dict
            priority: int = 0
            retries: int = 0
        ```

    *   `RetryPolicy`:
        ```python
        from typing import Callable, Tuple

        class RetryPolicy:
            def __init__(self, max_attempts: int = 4):
                self.max_attempts = max_attempts

            def run(self, fn: Callable[[dict], any], data: dict) -> Tuple[bool, int]:
                attempts = 0
                while attempts < self.max_attempts:
                    try:
                        fn(data)
                        return True, attempts + 1
                    except Exception:
                        attempts += 1
                return False, attempts
        ```

    *   `JobQueue`:
        *   Storage: Need to handle priority and FIFO. A list of `Job` objects sorted by priority (descending) and insertion order. Or a `heapq` structure.
        *   Wait, "FIFO order within the same priority" means if priority is same, the one added first comes first.
        *   Standard heap usage: `(priority, insertion_index, job)`. High priority usually means higher number? Or lower? Usually, higher number = higher priority.
        *   Let's use a list and `sort` or a `heapq`. Since `get_next_job` needs to return the *highest* priority, and we need FIFO for same priority, `heapq` with a counter is the most efficient way.

        ```python
        import heapq
        from typing import Callable, Tuple, Optional

        class JobQueue:
            def __init__(self):
                self.jobs = [] # Elements: (-priority, entry_count, Job)
                self.counter = 0
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                job = Job(id=job_id, data=data, priority=priority)
                # Use negative priority for max-heap behavior with heapq (min-heap)
                heapq.heappush(self.jobs, (-priority, self.counter, job))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable[[dict], any]) -> bool:
                # Requirement: must use RetryPolicy.
                # But how do we find the job? The method signature takes job_id.
                # I need to find the job with job_id.
                # Wait, if I just search the list, I lose the heap order?
                # Or do I find it, process it, and leave it or remove it?
                # Usually "process_job" implies completing it.
                # However, the signature doesn't say "remove".
                # Let's look at the signatures provided:
                # add_job(id, data, priority)
                # process_job(job_id, processor) -> bool
                # get_next_job() -> (id, data) | None

                # If I search the list for job_id:
                for i, item in enumerate(self.jobs):
                    _, _, job = item
                    if job.id == job_id:
                        success, attempts = self.retry_policy.run(processor, job.data)
                        return success
                return False

            def get_next_job(self) -> Optional[Tuple[str, dict]] | None:
                if not self.jobs:
                    return None
                # peek or pop? "return the highest-priority job". Usually means pop.
                # If it's a queue, we pop.
                neg_prio, count, job = heapq.heappop(self.jobs)
                return (job.id, job.data)
        ```

    *   `Job`: `id`, `data`, `priority`, `retries`. Correct.
    *   `RetryPolicy`: `max_attempts`, `run` returns `(bool, int)`. No sleep. Correct.
    *   `JobQueue`: `add_job`, `process_job` (uses `RetryPolicy`), `get_next_job` (FIFO for same priority).
    *   "FIFO order within same priority": `heapq` with an incrementing counter satisfies this.

    *   Wait, the `RetryPolicy.run` signature is `fn(data)`. The requirement says `fn: Callable`.
    *   `JobQueue.process_job(job_id, processor)`: `processor` is `Callable`.
    *   Type hinting for `Tuple`, `Optional`, `Callable`, `Dict`.

    One detail: `process_job` takes `job_id`. If I pop it in `get_next_job`, `process_job` wouldn't find it unless it stays in the list.
    But usually, in a queue system:
    1. `get_next_job` retrieves the next available.
    2. `process_job` is called on a specific ID.
    
    If `get_next_job` pops, `process_job` won't see it.
    If `process_job` is meant to be called on a job *already* retrieved?
    The instructions say `process_job` must use `RetryPolicy`.
    If `process_job` is called by the user on a specific ID, I should probably search the internal storage.
    
    Let's re-read: `process_job(self, job_id: str, processor: Callable) -> bool`.
    This implies the job is still "in" the queue or system.
    
    Let's refine `JobQueue`:
    - `self.jobs` = a list of `Job` objects? No, `heapq` is better for priority.
    - If I use `heapq`, and `process_job` needs to find a specific ID, I have to iterate over the heap.
    
    Actually, most job queues keep jobs in a list until they are finished.
    
    Wait, let's look at `get_next_job`. It returns `(id, data)`.
    Usually, you get it, then you process it. If `process_job` takes `job_id`, it implies the queue knows which one is being processed or still exists.
    
    To maintain FIFO for same priority:
    `heapq.heappush(self.jobs, (-priority, self.counter, job))`
    
    To handle `process_job(job_id)`:
    I will iterate through `self.jobs` to find the one matching `job_id`.
    
    One more thing: Should `process_job` remove the job if successful?
    The prompt doesn't specify removal. I'll stick to the requested signatures exactly.

    *   `from dataclasses import dataclass`
    *   `from typing import Callable, Dict, Tuple, Optional`
    *   `import heapq`

    Check `RetryPolicy.run`: `run(self, fn: Callable, data) -> tuple[bool, int]`
    The requirement says `fn(data)`.

    Check `JobQueue.get_next_job`: `tuple[str, dict] | None`
    This uses the `|` syntax which is Python 3.10+. I will use that or `Optional`. The prompt uses `|` in the signature description, so I'll stick to that or standard `Optional` if needed. Actually, the prompt says `tuple[...] | None`, so I'll use that.

    *Self-Correction on `JobQueue.process_job`*: 
    If `process_job` is supposed to actually "process" it, and `get_next_job` "returns" it, is it possible `get_next_job` doesn't pop? 
    "return the highest-priority job's (id, data)". Usually "return" without "remove" might imply peek, but "queue" usually implies pop. However, if I pop it, `process_job` (which takes `job_id`) won't find it.
    I will implement `get_next_job` as a pop operation, but if I do that, `process_job` must be able to find it. This is a contradiction unless `process_job` is called *before* `get_next_job` or the user keeps the ID.
    Actually, the most logical flow for a "Job Queue" is:
    1. `add_job`
    2. `get_next_job` -> gives you the job.
    3. You call `process_job` on that ID.
    
    If `get_next_job` pops, the job is no longer in the list.
    If `process_job` is to be called on a job *not* in the list, I'd need a way to track "active" jobs.
    
    Alternative: `get_next_job` is a peek? No, "queue" usually implies consumption.
    
    Let's look at the wording again: `get_next_job` returns the highest priority. `process_job` takes `job_id`.
    If I don't pop in `get_next_job`, I can't easily track "next".
    If I do pop, `process_job` fails to find it.
    
    Maybe `process_job` should also remove the job if successful?
    
    Actually, there's a simpler interpretation: The user might call `process_job` on any job that is still "in" the queue (maybe they want to skip some).
    
    Wait, if `get_next_job` pops the job, then the user has the `id`. But if `process_job` searches the queue, it won't find it.
    
    Let's assume `process_job` is intended to be used on jobs still inside the queue's storage. In that case, `get_next_job` should probably not pop? No, that's not a queue.
    
    Let's try a hybrid: `JobQueue` maintains a list of jobs. `get_next_job` finds the best one, removes it from the list, and returns it. `process_job` searches the list for an ID. 
    If the user calls `process_job` on a job that was already "gotten", `process_job` returns `False` (since it's not in the queue). This is technically correct behavior for a queue.

    Wait! I'll just implement `process_job` to look into the internal list. If the user calls `process_job` on an ID they already "got" via `get_next_job`, it's not in the list anymore, so it returns `False`. That's fine.

    Actually, I'll make `process_job` remove the job if it finds it and succeeds? No, don't overengineer. Stick to the instructions.

    Refined `JobQueue`:
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = [] # heap
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            heapq.heappush(self.jobs, (-priority, self.counter, job))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            for i, (_, _, job) in enumerate(self.jobs):
                if job.id == job_id:
                    success, _ = self.retry_policy.run(processor, job.data)
                    return success
            return False

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            _, _, job = heapq.heappop(self.jobs)
            return (job.id, job.data)
    ```
    
    One edge case: `RetryPolicy.run` says `tuple[bool, int]`.
    The `retries` field in `Job` is not explicitly used by `RetryPolicy` (since the logic is encapsulated in `RetryPolicy` and `RetryPolicy` has its own `max_attempts`). I'll leave the `retries` field in the dataclass as requested but not use it for the `RetryPolicy` logic, as the logic is encapsulated there.

    Wait, the prompt says: `RetryPolicy` encapsulates retry-with-backoff:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    It doesn't say it uses the `Job` object, just the `data` dict.

    Let's double check `Job` fields: `id`, `data`, `priority`, `retries`. Yes.

    Final check on `RetryPolicy`: `attempts_made`.
    If `max_attempts` is 4:
    Attempt 1: fail
    Attempt 2: fail
    Attempt 3: fail
    Attempt 4: fail
    Return `(False, 4)`.
    
    If Attempt 1: success
    Return `(True, 1)`.

    ```python
    def run(self, fn: Callable, data: dict) -> tuple[bool, int]:
        attempts = 0
        while attempts < self.max_attempts:
            attempts += 1
            try:
                fn(data)
                return True, attempts
            except Exception:
                pass
        return False, attempts
    ```
    
    Wait, the prompt says "retries on any exception until success or `max_attempts` reached".
    If `max_attempts` is 1, it tries once.
    If it fails, it returns `(False, 1)`.
    This logic works.

    One last detail: `JobQueue.get_next_job` return type `tuple[str, dict] | None`.
    I will use `from __future__ import annotations` to support the `|` syntax or just use standard `Optional` to be safe, but since the prompt used `|`, I'll assume Python 3.10+.

    Actually, I'll just write the code.

    One detail: `RetryPolicy` should probably handle the `retries` count from `Job`?
    The prompt says: `RetryPolicy` encapsulates retry-with-backoff... `run(self, fn: Callable, data)`. It doesn't take the `Job` object. It only takes `fn` and `data`. So it doesn't care about the `retries` field in the `Job` dataclass. The `retries` field is likely there for other purposes or just part of the requirement.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List, Optional
    import heapq

    @dataclass
    class Job:
        id: str
        data: Dict
        priority: int = 0
        retries: int = 0

    class RetryPolicy:
        def __init__(self, max_attempts: int = 4):
            self.max_attempts = max_attempts

        def run(self, fn: Callable, data: dict) -> Tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts + 1
                except Exception:
                    attempts += 1
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs: List[Tuple[int, int, Job]] = []
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            # Max-heap behavior using negative priority
            heapq.heappush(self.jobs, (-priority, self.counter, job))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            for _, _, job in self.jobs:
                if job.id == job_id:
                    success, _ = self.retry_policy.run(processor, job.data)
                    return success
            return False

        def get_next_job(self) -> Tuple[str, dict] | None:
            if not self.jobs:
                return None
            _, _, job = heapq.heappop(self.jobs)
            return (job.id, job.data)
    ```

    Wait, `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    My implementation works.
    Wait, `RetryPolicy` signature: `run(self, fn: Callable, data)`. My code: `run(self, fn: Callable, data: dict)`. The prompt says `data`, not `data: dict`, but usually type hints are fine. I'll remove the `: dict` from the `run` signature if I want to be strictly literal to the prompt, but `data: dict` is safer for types. The prompt says `data: dict` in `add_job` but just `data` in `run`. I'll follow the prompt's types exactly.

    Prompt check:
    `run(self, fn: Callable, data) -> tuple[bool, int]` -> no `: dict` for `data`.
    `add_job(self, job_id: str, data: dict, priority: int = 0) -> None` -> `data: dict`.

    One more thing: `JobQueue` uses `RetryPolicy`.
    `process_job` must use `RetryPolicy`.
    I have a `self.retry_policy = RetryPolicy()` in `__init__`.

    Is `retries` in `Job` supposed to be updated?
    The `RetryPolicy` doesn't have access to the `Job` object, so it can't update `job.retries`.
    However, the prompt doesn't ask to update it, just to define the class with the field.

    One final detail: "FIFO order within the same priority".
    `heapq` with `(-priority, counter, job)` ensures that:
    1. Lower `-priority` (higher priority) comes first.
    2. If `-priority` is same, lower `counter` (earlier insertion) comes first.
    This perfectly satisfies FIFO for same priority.

    Wait, the prompt uses `tuple[bool, int]`. This is Python 3.9+ syntax. I will use that.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List
    import heapq

    @dataclass
    class Job:
        id: str
        data: Dict
    ...
    ```
    Wait, `Job` fields are `id`, `data`, `priority`, `retries`.
    `priority` default is 0. `retries` default is 0.

    Wait, I should make sure `RetryPolicy` is correctly defined.
    `__init__(self, max_attempts: int = 4)`
    `run(self, fn: Callable, data) -> tuple[bool, int]`

    One detail: `processor` in `process_job`.
    The signature is `processor: Callable`.
    I will use `Callable`.

    Code structure check:
    - `Job` dataclass? Yes.
    - `RetryPolicy` class? Yes.
    - `JobQueue` class? Yes.
    - Standard library only? Yes.
    - No explanations? Yes.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List
    import heapq

    @dataclass
    class Job:
        id: str
        data: Dict
        priority: int = 0
        retries: int = 0

    class RetryPolicy:
        def __init__(self, max_attempts: int = 4):
            self.max_attempts = max_attempts

        def run(self, fn: Callable, data) -> tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts + 1
                except Exception:
                    attempts += 1
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs: List[Tuple[int, int, Job]] = []
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            heapq.heappush(self.jobs, (-priority, self.counter, job))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            for _, _, job in self.jobs:
                if job.id == job_id:
                    success, _ = self.retry_policy.run(processor, job.data)
                    return success
            return False

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            _, _, job = heapq.heappop(self.jobs)
            return (job.id, job.data)

    ```
    Wait, I should check if I need `Optional`. The prompt used `| None`. So I don't need `Optional`.
    Also `Dict` vs `dict`. Python 3.9+ allows `dict[str, Any]`. But since the prompt used `dict` in `data: dict`, I will use `dict`.

    Final review of `RetryPolicy.run`:
    "retries on any exception until success or max_attempts reached"
    If `max_attempts` is 4:
    - Attempt 1: Exception -> `attempts` becomes 1.
    - Attempt 2: Exception -> `attempts` becomes 2.
    - Attempt 3: Exception -> `attempts` becomes 3.
    - Attempt 4: Exception -> `attempts` becomes 4.
    - Loop ends (4 < 4 is false).
    - Returns `(False, 4)`.
    Correct.
    If Attempt 1: Success -> returns `(True, 1)`.
    Correct.

    Wait, `JobQueue.get_next_job` return type: `tuple[str, dict] | None`.
    My code: `return (job.id, job.data)`. Correct.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Dict, Tuple, List
import heapq

@dataclass
class Job:
    id: str
    data: Dict
    priority: int = 0
    retries: int = 0

class RetryPolicy:
    def __init__(self, max_attempts: int = 4):
        self.max_attempts = max_attempts

    def run(self, fn: Callable, data) -> tuple[bool, int]:
        attempts = 0
        while attempts < self.max_attempts:
            try:
                fn(data)
                return True, attempts + 1
            except Exception:
                attempts += 1
        return False, attempts

class JobQueue:
    def __init__(self):
        self.jobs: List[Tuple[int, int, Job]] = []
        self.counter = 0
        self.retry_policy = RetryPolicy()

    def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
        job = Job(id=job_id, data=data, priority=priority)
        # Use negative priority for max-heap behavior with heapq (min-heap)
        # counter ensures FIFO order for identical priorities
        heapq.heappush(self.jobs, (-priority, self.counter, job))
        self.counter += 1

    def process_job(self, job_id: str, processor: Callable) -> bool:
        for _, _, job in self.jobs:
            if job.id == job_id:
                success, _ = self.retry_policy.run(processor, job.data)
                return success
        return False

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.jobs:
            return None
        _, _, job = heapq.heappop(self.jobs)
        return (job.id, job.data)
```
</content>