<reasoning_content>
Here's a thinking process:

1.  **Understand User Requirements:**
   - Implement a priority-based job scheduling queue in Python.
   - 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) - though the exact levels aren't strictly enforced by code, just the ordering.
   - Return type for `get_next_job` is `tuple[str, dict] | None`.
   - Write 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.
     - Multiple queues per priority level (e.g., dict of deque).
     - `collections.deque` with sorting (inefficient).
   - `heapq` is standard for priority queues. To maintain FIFO for same priority, we can use a tuple `(priority, counter, job_id, job_data)` where counter increments for each job added. Since `heapq` is a min-heap, we need to invert priority or use negative priority if higher number means higher priority. The requirement says "Higher priority jobs should be processed before lower priority jobs", so priority 2 > 1 > 0. We can store `(-priority, counter, job_id, job_data)` or just use `(-priority, counter, ...)` in the heap.
   - Alternatively, use a dictionary of deques: `self.queues = {0: deque(), 1: deque(), 2: deque()}`. Then `get_next_job()` checks from highest priority down to 0. This is simpler and guarantees FIFO per priority without needing a counter. But the requirement says "priority parameter (default=0)" and doesn't restrict to just 0,1,2. It's safer to handle arbitrary priorities with heapq.
   - Let's stick with `heapq` + counter for robustness.

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

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

       def add_job(self, job_id: str, job_data: dict, priority: int = 0):
           # Use negative priority so higher priority comes first in min-heap
           heapq.heappush(self._heap, (-priority, self._counter, job_id, job_data))
           self._counter += 1

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

4.  **Check Requirements:**
   - `add_job` accepts optional priority (default=0) -> Yes.
   - Higher priority processed first -> Yes, using `-priority` in min-heap.
   - Same priority maintains FIFO -> Yes, using `_counter` which increments.
   - `get_next_job() -> tuple[str, dict] | None` -> Yes.
   - Example matches -> Yes.
   - Only Python code, no explanations -> Yes.

   Wait, the return type hint in the prompt is `tuple[str, dict] | None`. I should use that exact syntax or compatible one. Python 3.10+ supports `tuple[str, dict] | None`. I'll stick to that.

   Let's verify the `heapq` behavior:
   - `heappush` with `(-priority, counter, job_id, job_data)`
   - If priority is 2, stored as -2. If priority is 0, stored as 0. Min-heap pops smallest first, so -2 pops before -1 before 0. Correct.
   - Counter ensures FIFO for same priority. Correct.
   - `get_next_job` returns `(job_id, job_data)` or `None`. Correct.

   One minor thing: The prompt says `get_next_job() -> tuple[str, dict] | None`. I'll add type hints accordingly.
   Also, should I keep the job in the queue if `get_next_job` just "gets" it? The name `get_next_job` usually implies popping/removing it, similar to `heappop`. The example shows `queue.get_next_job()  # Returns critical job first`, which implies it's consumed. I'll assume it removes it. If it's just peeking, it would be `peek_next_job`. I'll stick with pop.

   Let's write it out carefully.

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

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

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

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

   Wait, `tuple[str, dict]` syntax requires Python 3.9+. It's fine. I'll use it as requested.
   Should I import `Optional`? Not strictly needed if using `| None`.
   I'll keep it clean.

   Let's double check the exact signature requested:
   `get_next_job() -> tuple[str, dict] | None`
   `add_job` signature isn't strictly specified but should accept `job_id`, `job_data`, `priority=0`. The example shows `queue.add_job("normal", {"type": "normal"}, priority=0)`. So `add_job(self, job_id: str, job_data: dict, priority: int = 0)`.

   One thing: `heapq` stores tuples. When popping, we get `(-priority, counter, job_id, job_data)`. We return `(job_id, job_data)`. That's correct.

   Is there any edge case? What if `job_data` contains uncomparable types? `heapq` compares tuples element by element. If priorities and counters are equal (impossible due to counter), it would compare `job_id` (str) which is fine. So no issue.

   The code looks solid. I'll output exactly that.
   I'll make sure to include necessary imports and class definition.
   No extra text.
   Ready. 
   One minor adjustment: The prompt says `tuple[str, dict] | None`. I'll use exactly that.
   I'll write it out. 
   All good. 
   Output matches response. 
   Proceeds. 
   [Self-Correction/Verification during thought]
   - Check if `get_next_job` should remove or just peek. Usually "get" implies removal in queue contexts. I'll stick with `heappop`.
   - The example shows `queue.get_next_job()  # Returns critical job first`. It doesn't show subsequent calls, but standard queue behavior is pop.
   - Type hints: `tuple[str, dict] | None` is valid in Python 3.10+. I'll use it.
   - I'll add `from typing import Optional` just in case, but it's not needed with `|`. I'll stick to modern syntax.
   - 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):
             heapq.heappush(self._heap, (-priority, self._counter, job_id, job_data))
             self._counter += 1

         def get_next_job(self) -> tuple[str, dict] | None:
             if not self._heap:
                 return None
             _, _, job_id, job_data = heapq.heappop(self._heap)
             return job_id, job_data
     ```
   This matches all requirements. Output matches exactly. No extra text. Done. 
   One thing: The prompt says `add_job` should accept an optional priority parameter. It doesn't specify other parameters, but the example shows `queue.add_job("normal", {"type": "normal"}, priority=0)`. So `job_id` and `job_data` are implied. I'll keep them.
   All good.✅
   Output generation. 
   [Output Generation] -> *Proceeds*
   *(Self-Correction/Refinement during thought)*: 
   Wait, should I use `collections.deque` per priority level? It's simpler and avoids heap overhead, but heap is standard for priority queues. The counter method is robust. I'll stick with heap.
   Another option: `self._queue = {}` with deques. But heap is fine.
   I'll output the heap version. It's concise and correct.
   Final check of the prompt: "Write only the Python code, no explanations."
   I will output exactly the code block.
   Done. 
   [Output] -> *Proceeds*

</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):
        heapq.heappush(self._heap, (-priority, self._counter, job_id, job_data))
        self._counter += 1

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