<reasoning_content>
Here's a thinking process:

1.  **Understand User Requirements:**
   - Implement priority-based job scheduling for a queue.
   - Modify `add_job` to accept an optional `priority` parameter (default=0).
   - Higher priority jobs should be processed before lower priority jobs.
   - Jobs with the same priority should 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 way to store jobs with priorities while maintaining FIFO for same priority.
   - Options:
     - `heapq` with a counter for FIFO ordering (standard approach for priority queues with stable ordering).
     - Separate lists/deques for each priority level.
     - `collections.deque` per priority level.
   - Given the small number of priority levels (0, 1, 2), using a list of deques or a dictionary mapping priority to deque is efficient and simple.
   - Alternatively, `heapq` with `(-priority, counter, job_id, job_data)` works well and is standard. Let's stick to `heapq` for simplicity and robustness, or use a list of deques for direct priority access. Actually, `heapq` is fine, but since priorities are small integers (0-2), a list of deques might be simpler and faster. Let's use `heapq` as it's the standard Python way for priority queues, but I need to handle the FIFO requirement carefully.
   - Wait, `heapq` with `(-priority, insertion_order, job_id, job_data)` naturally handles both requirements: higher priority first (because of negative), and FIFO for same priority (because of insertion order).
   - Let's implement using `heapq`.

3.  **Design the Class:**
   ```python
   import heapq
   from typing import Optional, Tuple, Dict, Any

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

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           # Higher priority should be processed first.
           # heapq is a min-heap, so we use -priority to make higher priority come first.
           # _counter ensures FIFO for same priority.
           heapq.heappush(self._heap, (-priority, self._counter, job_id, job_data))
           self._counter += 1

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

4.  **Check Requirements:**
   - Modify `add_job` to accept optional priority parameter (default=0) -> Yes.
   - Higher priority jobs processed before lower -> Yes, using `-priority`.
   - Same priority maintains FIFO -> Yes, using `_counter`.
   - Add `get_next_job() -> tuple[str, dict] | None` -> Yes.
   - Priority levels 0, 1, 2 -> Handled naturally by integer comparison.
   - Example matches -> Yes.

   Wait, should I validate priority levels? The prompt says "Priority levels: 0: Normal, 1: High, 2: Critical". It doesn't strictly require validation, but it's good practice. I'll skip strict validation unless necessary, as the example just passes integers. The prompt says "Modify `add_job`...", implying there might be an existing class, but I'll provide the full class implementation.

   Let's refine the type hints to match modern Python (3.10+ uses `|` for union, but I'll stick to `Optional` or `|` as requested). The prompt uses `tuple[str, dict] | None`, so I'll use that.

   One thing: `heapq` stores tuples, and when popping, we get `(-priority, counter, job_id, job_data)`. The return type should be `tuple[str, dict] | None`.

   Let's write it out carefully.

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

   class JobQueue:
       def __init__(self):
           self._heap: list = []
           self._counter: int = 0

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

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

   Wait, should I include type hints exactly as requested? The prompt says `get_next_job() -> tuple[str, dict] | None`. I'll use that exact signature.

   Let's double-check the FIFO requirement. With `(-priority, self._counter, ...)`, if two jobs have the same priority, the one with the smaller `_counter` (added earlier) will be popped first. This correctly maintains FIFO.

   Is there any edge case? What if `job_data` is not comparable? `heapq` compares tuples element by element. If `-priority` and `_counter` are equal (which they won't be because `_counter` increments), it would try to compare `job_id` and `job_data`. Since `_counter` is unique, it will never reach `job_data` comparison. So it's safe.

   Alternatively, I could use a list of deques for priorities 0, 1, 2. That might be simpler and avoid tuple comparison issues entirely. But `heapq` is standard. I'll stick with `heapq`.

   Let's consider if the prompt expects a specific structure. It says "Implement priority-based job scheduling for the queue." and gives an example. I'll provide the complete class.

   One minor thing: The prompt says `tuple[str, dict] | None`. I'll use exactly that.

   Code structure:
   ```python
   import heapq

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

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

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

   Wait, should I validate priority? The prompt lists levels 0, 1, 2. I'll add a simple check or just assume valid input. I'll skip validation to keep it clean, as it's not explicitly required.

   Let's test mentally with the example:
   ```python
   queue = JobQueue()
   queue.add_job("normal", {"type": "normal"}, priority=0)
   # heap: [(-0, 0, "normal", {"type": "normal"})]
   queue.add_job("critical", {"type": "critical"}, priority=2)
   # heap: [(-2, 1, "critical", {"type": "critical"}), (-0, 0, "normal", ...)]
   queue.add_job("high", {"type": "high"}, priority=1)
   # heap: [(-2, 1, "critical", ...), (-0, 0, "normal", ...), (-1, 2, "high", ...)] (after heapify)
   # Actually, heap order: (-2, 1, ...) is smallest, so it's at root.
   job = queue.get_next_job()  # Pops (-2, 1, "critical", ...) -> returns ("critical", {"type": "critical"})
   ```
   Matches example.

   What about same priority FIFO?
   ```python
   queue.add_job("a", {}, priority=1)
   queue.add_job("b", {}, priority=1)
   # heap: [(-1, 0, "a", {}), (-1, 1, "b", {})]
   # get_next_job pops "a" first. Correct.
   ```

   All requirements met.

   I'll format it exactly as requested. No explanations. Just the code.
   I'll add type hints for `add_job` parameters for completeness, but keep it minimal.
   The prompt says "Write only the Python code, no explanations."

   Final check of the code:
   ```python
   import heapq

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

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

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

   This looks solid. I'll output exactly this.
   One thing: `tuple[str, dict] | None` requires Python 3.10+. That's fine.
   I'll make sure imports are included.
   Ready.✅
   Output matches response. 
   Proceeds. 
   Self-Correction/Refinement during thought: 
   Should I use `dataclasses` or just a simple class? Simple class is fine.
   The prompt says "Modify `add_job`...", implying there might be pre-existing code, but since it's not provided, I'll provide the full class implementation.
   I'll stick to the provided code. It's clean and meets all constraints.
   Output matches. 
   Proceeds. 
   [Output Generation] -> *outputs the code*
   Note: I'll make sure the type hints match exactly. `tuple[str, dict] | None` is correct.
   All good. 
   Output matches the final code block. 
   [Done] 
   *Self-Correction/Verification during generation prep*: 
   Wait, `heapq` might compare `job_data` if `_counter` somehow collides, but it won't. Still, to be strictly safe against any weird comparison issues, I could wrap it, but it's fine. Python's `heapq` is standard for this.
   Another approach: `collections.deque` per priority level. But `heapq` is more general and handles arbitrary priorities gracefully. The prompt says priorities 0, 1, 2, but `heapq` works fine.
   I'll stick with `heapq`.
   Code is ready.✅
   Output matches. 
   [Final Check of the Prompt]: "Write only the Python code, no explanations." -> I will strictly follow this.
   Proceeds. 
   [Output Generation] -> *outputs code*
   *(Done.)*

</reasoning_content>

<content>
```python
import heapq

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

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

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