<reasoning_content>
Here's a thinking process:

1.  **Understand User Requirements:**
   - Modify `add_job` to accept an optional `priority` parameter (default=0)
   - Higher priority jobs processed first
   - Same priority jobs maintain FIFO order
   - Add `get_next_job() -> tuple[str, dict] | None` to get the highest priority job
   - Priority levels: 0 (Normal), 1 (High), 2 (Critical)
   - Example provided shows usage
   - Output only Python code, no explanations

2.  **Identify Key Data Structures:**
   - Need a queue that supports priority-based retrieval while maintaining FIFO for same-priority items.
   - Options:
     - `heapq` (min-heap): Can store tuples like `(-priority, sequence_number, job_id, job_data)` to handle higher priority first and FIFO for ties.
     - Separate queues per priority level: `collections.deque` for each priority. Since priorities are fixed (0, 1, 2), this is efficient and simple.
     - `queue.PriorityQueue`: Standard library, but might have overhead and doesn't strictly guarantee FIFO for equal priorities unless we add a counter. Also, it's thread-safe which might be overkill but fine.
   - Given the fixed priority levels (0, 1, 2), using a list of deques or a dict of deques is very straightforward and efficient. Alternatively, a heap with a counter is also standard.
   - Let's go with a heap approach using `heapq` with a counter for FIFO, as it's more general and commonly expected for priority queues. But wait, the requirement says priority levels 0, 1, 2. A heap with `(-priority, counter, job_id, job_data)` works perfectly.
   - Actually, using a list of deques for each priority level might be simpler and more efficient for fixed priorities. Let's stick to `heapq` for generality, or just use a simple list of deques. The problem doesn't specify thread safety, so I'll keep it simple.
   - Let's use `heapq` with a monotonic counter to ensure FIFO for same priority. Tuple: `(-priority, counter, job_id, job_data)`.
   - Wait, the return type for `get_next_job()` is `tuple[str, dict] | None`. So it returns `(job_id, job_data)`.
   - I need to implement a `JobQueue` class.

3.  **Design the Class:**
   ```python
   import heapq
   from collections import deque
   from typing import Optional

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0):
           # Higher priority first -> negate priority for min-heap
           heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
           self._counter += 1

       def get_next_job(self) -> Optional[tuple[str, dict]]:
           if not self._queue:
               return None
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return (job_id, job_data)
   ```

4.  **Check Requirements:**
   - Modify `add_job` to accept optional priority (default=0) -> Yes
   - Higher priority processed first -> Yes (negated priority in min-heap)
   - Same priority maintains FIFO -> Yes (counter ensures insertion order)
   - Add `get_next_job() -> tuple[str, dict] | None` -> Yes
   - Priority levels 0, 1, 2 -> Handled naturally
   - Example matches -> Yes
   - Only Python code, no explanations -> Yes

   Wait, should I validate priority levels? The requirement says "Priority levels: 0: Normal, 1: High, 2: Critical". It doesn't strictly say to enforce them, but it's good practice. I'll skip validation unless necessary, as it's not explicitly required. The example just uses them.

   Let's double-check the return type annotation: `tuple[str, dict] | None`. In Python 3.10+, this is valid. I'll use `typing.Optional` or just the modern syntax. The prompt uses `tuple[str, dict] | None`, so I'll stick with that.

   One thing: `heapq` is standard. I'll import it.
   Should I use `collections.deque` per priority instead? It's actually simpler and doesn't require a counter. But `heapq` is fine. I'll stick with `heapq` as it's the standard priority queue implementation in Python.

   Let's refine:
   ```python
   import heapq
   from typing import Optional

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
           self._counter += 1

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._queue:
               return None
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return job_id, job_data
   ```

   Wait, the prompt says "Modify `add_job`", implying there might be an existing class, but since I'm writing from scratch, I'll provide the full class.
   The type hint `tuple[str, dict] | None` is Python 3.10+. I'll keep it as requested.
   I should also consider if they want priority validation. Not strictly required.
   Let's test mentally:
   - add("normal", {}, 0) -> pushes (0, 0, "normal", {})
   - add("critical", {}, 2) -> pushes (-2, 1, "critical", {})
   - add("high", {}, 1) -> pushes (-1, 2, "high", {})
   Heap order: (-2, 1, ...), (-1, 2, ...), (0, 0, ...) -> correct, critical first, then high, then normal.
   Same priority: counter ensures FIFO.
   Returns `(job_id, job_data)` or `None`.
   Matches requirements.

   One minor point: The prompt says `get_next_job() -> tuple[str, dict] | None`. I'll use exactly that.
   I'll add `from __future__ import annotations` if needed, but not necessary. Python 3.10+ supports it natively.
   I'll keep it simple.

   Code structure:
   ```python
   import heapq

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
           self._counter += 1

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._queue:
               return None
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return job_id, job_data
   ```

   Wait, should I handle priority validation? The prompt lists priority levels but doesn't explicitly ask to enforce them. I'll leave it as is.
   Could also use a list of deques for O(1) pops and simpler logic:
   ```python
   from collections import deque
   from typing import Optional

   class JobQueue:
       def __init__(self):
           self._queues = {0: deque(), 1: deque(), 2: deque()}

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           if priority in self._queues:
               self._queues[priority].append((job_id, job_data))

       def get_next_job(self) -> tuple[str, dict] | None:
           for p in (2, 1, 0):
               if self._queues[p]:
                   return self._queues[p].popleft()
           return None
   ```
   This is actually cleaner and explicitly handles the fixed priority levels mentioned. It's O(1) for add and get (since only 3 levels). It also naturally maintains FIFO per priority. I think this is better given the explicit priority levels. But the heap approach is more general. I'll stick with heap as it's the standard "priority queue" pattern, but the deque approach is very Pythonic for fixed priorities. I'll go with heap to be safe, as it's more robust if priorities change later, and matches typical interview expectations. Actually, both are fine. I'll use `heapq` as it's standard.

   Wait, the prompt says "Modify `add_job` to accept an optional priority parameter". This implies I should just provide the class. I'll provide the complete class.

   Let's verify the exact signature requested:
   `add_job` should accept `job_id`, `job_data`, and `priority`.
   `get_next_job() -> tuple[str, dict] | None`

   I'll write it out carefully.

   One thing: `tuple[str, dict] | None` syntax requires Python 3.10+. I'll assume that's fine.
   I'll add type hints for completeness.

   Final check of the code:
   ```python
   import heapq
   from typing import Optional

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
           self._counter += 1

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._queue:
               return None
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return job_id, job_data
   ```

   This meets all requirements. I'll output exactly this.
   One minor adjustment: I'll remove `Optional` import since I'm using `| None` syntax, which is standard in 3.10+. I'll keep it clean.
   Actually, to be safe across versions, I could use `typing.Optional`, but the prompt explicitly uses `tuple[str, dict] | None`, so I'll stick with that.

   All good. Output matches request.✅
   I will generate only the code.✅
   Proceed. 
   Self-Correction/Refinement during thought: 
   Should I validate priority? The prompt says "Priority levels: 0: Normal, 1: High, 2: Critical". It's just informational. I won't add validation unless necessary.
   The heap approach is solid.
   I'll output the code now. 
   One thing: `get_next_job` returns `tuple[str, dict] | None`. In Python, returning `job_id, job_data` automatically creates a tuple. So `return job_id, job_data` is correct.
   All set. 
   Output matches exactly. 
   [Done] 
   Generating response. 
   ```python
   import heapq

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
           self._counter += 1

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._queue:
               return None
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return job_id, job_data
   ```
   This looks correct and complete. Matches all constraints. No extra text. Ready. 
   Output matches response.✅
   [Final Check]
   - `add_job` accepts optional priority (default=0) ✅
   - Higher priority first ✅ (negated in heap)
   - Same priority FIFO ✅ (counter)
   - `get_next_job()` returns correct type ✅
   - Only Python code ✅
   All good. 
   Proceeds. 
   [Output Generation] -> *Self-Correction/Note during generation prep*: I'll make sure the code block is properly formatted. The prompt says "Write only the Python code, no explanations." I will strictly follow that.
   Code is ready.✅
   Output matches exactly. 
   [Done]

</reasoning_content>

<content>
```python
import heapq

class JobQueue:
    def __init__(self):
        self._queue = []
        self._counter = 0

    def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
        heapq.heappush(self._queue, (-priority, self._counter, job_id, job_data))
        self._counter += 1

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