<reasoning_content>
The user wants a Python implementation of a priority-based job scheduling queue.
Requirements:
1. `add_job` method accepts job_id, job_data, and optional priority (default=0).
2. Higher priority values are processed first (2 > 1 > 0).
3. FIFO for same priority.
4. `get_next_job()` returns the highest priority job as a tuple `(str, dict)` or `None`.
5. Priority levels: 0 (Normal), 1 (High), 2 (Critical).
6. Output only Python code.

I'll use `heapq` for the priority queue. Since `heapq` is a min-heap, I need to negate the priority to get higher priorities first.
To maintain FIFO for same priorities, I'll add a counter as a tie-breaker.

Let's design the class `JobQueue`:
- Initialize with an empty list for the heap and a counter.
- `add_job(job_id: str, job_data: dict, priority: int = 0)`: push `(-priority, counter, job_id, job_data)` onto the heap, increment counter.
- `get_next_job() -> tuple[str, dict] | None`: pop from heap if not empty, return `(job_id, job_data)`, else return `None`.

Wait, the signature in the example is `add_job("normal", {"type": "normal"}, priority=0)`. So `job_id` and `job_data` are positional, `priority` is keyword.
I'll use `heapq` module.

Let's write it out carefully.

```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:
        # Negate priority for max-heap behavior
        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)
```

Check requirements:
1. `add_job` accepts optional priority parameter (default=0) -> Yes.
2. Higher priority jobs processed before lower -> Yes, `-priority` ensures higher priority comes first in min-heap.
3. Same priority maintains FIFO -> Yes, counter ensures insertion order for same priorities.
4. `get_next_job() -> tuple[str, dict] | None` -> Yes.
5. Priority levels mentioned in prompt are just documentation, not strictly enforced, but default is 0. I'll stick to the implementation.
6. Output only Python code.

One minor point: type hints in Python 3.9+ support `tuple[str, dict]`, but to be safe across versions, I can use `Tuple[str, dict]` from typing or just leave it as is since the prompt uses `tuple[str, dict]`. I'll stick to the prompt's style.

Let's verify the example:
```python
queue = JobQueue()
queue.add_job("normal", {"type": "normal"}, priority=0)
queue.add_job("critical", {"type": "critical"}, priority=2)
queue.add_job("high", {"type": "high"}, priority=1)

job = queue.get_next_job()  # Returns critical job first
```
Heap state after adds:
1. `(-0, 0, "normal", {...})`
2. `(-2, 1, "critical", {...})`
3. `(-1, 2, "high", {...})`
Heapify/pop order: `(-2, 1, ...)` comes first. Correct.

What if `get_next_job()` is called on empty queue? Returns `None`. Correct.

Should I include `from typing import Tuple, Optional`? The prompt uses `tuple[str, dict] | None`, which is Python 3.10+ syntax. I'll just use it as is, or stick to standard imports. It's fine.

One thing: `heapq` compares tuples element by element. `(-priority, counter, ...)` works perfectly.

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 not self._heap:
            return None
        _, _, job_id, job_data = heapq.heappop(self._heap)
        return job_id, job_data
```

Wait, should I handle the case where `job_data` might be modified later? The prompt says `get_next_job() -> tuple[str, dict] | None`, returning the tuple. It's fine.

Check if any edge cases: The prompt says "Write only the Python code, no explanations."
I will output exactly the code.

One detail: `tuple[str, dict] | None` is valid in Python 3.10+. I'll keep it.
I'll add `from typing import Optional, Tuple` just in case, but it's not strictly necessary. I'll stick to the prompt's syntax.

Let's double check the `add_job` signature in the prompt: `add_job("normal", {"type": "normal"}, priority=0)`. So `job_id` is first, `job_data` second. My code matches.

All good.
I will produce the code now.
One minor adjustment: I'll make sure type hints are compatible or just use the prompt's style.
I'll output exactly the class.
Done. 
Let's generate. 
Wait, should I include `import heapq`? Yes.
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 not self._heap:
            return None
        _, _, job_id, job_data = heapq.heappop(self._heap)
        return job_id, job_data
```
Looks solid. Matches all requirements. No extra text. Proceeds. 
Self-Correction/Verification during thought:
- Does `heapq` guarantee stability for equal priorities? Yes, because of the counter.
- Is the return type exactly `tuple[str, dict] | None`? Yes.
- Does it handle the example correctly? Yes.
- Should I add type hints for `add_job`? The prompt doesn't specify, but it's good practice. I'll keep them simple.
- The prompt says "Write only the Python code, no explanations." I will strictly follow that.

All set. Output matches the code block.✅
Proceeds. 
Output Generation. 
```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 not self._heap:
            return None
        _, _, job_id, job_data = heapq.heappop(self._heap)
        return job_id, job_data
```
Done. 
Wait, should I wrap it in a single block? Yes.
Is there any requirement about thread safety? No.
Is there any requirement about removing jobs? No.
The implementation is minimal and correct.
I will output exactly this. 
One thing: `tuple[str, dict] | None` syntax requires Python 3.10+. If the environment is older, it might fail. But the prompt uses it, so it's fine. I'll keep it.
All good. 
Output matches. 
Proceeds. 
[Output Generation] -> *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) -> 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 not self._heap:
            return None
        _, _, job_id, job_data = heapq.heappop(self._heap)
        return job_id, job_data
```
</content>