<reasoning_content>
The user wants a Python implementation of a priority-based job scheduling queue.
Key requirements:
1. `add_job(self, job_id: str, job_data: dict, priority: int = 0)` method.
2. Higher priority jobs processed first.
3. FIFO for same priority.
4. `get_next_job(self) -> tuple[str, dict] | None` method.
5. Priority levels: 0 (Normal), 1 (High), 2 (Critical).
6. Output only Python code.

Implementation details:
- I need a data structure that supports efficient insertion and extraction based on priority while maintaining order for equal priorities.
- A heap (priority queue) is suitable. In Python, `heapq` module provides a min-heap.
- Since higher priority should be processed first, and `heapq` pops the smallest value, I should negate the priority or use a custom comparator. Negating priority is easier: priority 2 becomes -2, priority 1 becomes -1, priority 0 becomes 0. So -2 < -1 < 0, meaning priority 2 comes out first.
- To maintain FIFO order for same priorities, I need a counter or timestamp that increases with each insertion. This counter will be the second element in the heap tuple.
- Heap tuple structure: `(-priority, counter, job_id, job_data)`
- The `counter` ensures that if priorities are equal, the one with the smaller counter (earlier insertion) comes out first.
- I'll wrap this in a class `JobQueue`.

Let's draft the class:

```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):
        # Negate priority so higher priority values come out first in a min-heap
        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 requirement says `get_next_job() -> tuple[str, dict] | None`.
The example shows:
```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
```

Let's verify the logic:
1. `add_job("normal", ..., 0)` -> heap: `[(0, 0, "normal", {...})]`, counter=1
2. `add_job("critical", ..., 2)` -> heap: `[(-2, 1, "critical", {...}), (0, 0, "normal", {...})]`, counter=2
3. `add_job("high", ..., 1)` -> heap: `[(-2, 1, "critical", {...}), (0, 0, "normal", {...}), (-1, 2, "high", {...})]`
   Wait, heap invariant: `(-2, 1)` is smallest. `(-1, 2)` is next. `(0, 0)` is largest.
   So `heappop` will return `(-2, 1, "critical", ...)`. Correct.
   Next pop: `(-1, 2, "high", ...)`. Correct.
   Next pop: `(0, 0, "normal", ...)`. Correct.

FIFO order for same priority:
If I add two jobs with priority 0:
1. `add_job("A", ..., 0)` -> `(0, 0, "A", ...)`
2. `add_job("B", ..., 0)` -> `(0, 1, "B", ...)`
Pop order: `(0, 0, "A", ...)` then `(0, 1, "B", ...)`. Correct.

Type hints: `tuple[str, dict] | None` is valid in Python 3.10+. I should use standard typing if compatibility is a concern, but the prompt uses `tuple[str, dict] | None`, so I'll stick to that or use `Optional[Tuple[str, dict]]`. I'll use `from typing import Optional, Tuple` to be safe, or just follow the prompt's syntax. The prompt uses `tuple[str, dict] | None`, which is Python 3.10+ syntax. I'll assume modern Python is fine.

Let's refine the code to match exactly.

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

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) -> Optional[Tuple[str, dict]]:
        if not self._queue:
            return None
        _, _, job_id, job_data = heapq.heappop(self._queue)
        return job_id, job_data
```

Wait, the prompt says `get_next_job() -> tuple[str, dict] | None`. I'll use that exact signature if possible, or stick to standard typing. I'll just use `from typing import Optional, Tuple` and `Optional[Tuple[str, dict]]` or just `tuple[str, dict] | None` assuming Python 3.10+. I'll use `tuple[str, dict] | None` as requested.

One minor point: `job_data` is a dict. In the heap, it's stored as is. That's fine.
The prompt asks for "Write only the Python code, no explanations."

I will produce the code block.

Check edge cases:
- Empty queue: `get_next_job` returns `None`. Handled.
- Priority levels: 0, 1, 2. Handled by negation.
- FIFO: Handled by counter.
- Type hints: I'll use `from typing import Optional, Tuple` to be safe across versions, or just use the prompt's syntax. I'll stick to standard `typing` module for broader compatibility, but the prompt explicitly uses `tuple[str, dict] | None`. I'll use that.

Actually, `heapq` compares tuples element by element. `(-priority, counter, job_id, job_data)` works. But if priorities and counters are equal (impossible due to counter increment), it would compare `job_id` (string) which is fine.

One thing: `job_data` is a dict. Comparing dicts raises TypeError in Python. But since `counter` is unique, it will never reach comparing `job_id` or `job_data`. So it's safe.

Let's write it out carefully.

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

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, `tuple[str, dict] | None` syntax requires Python 3.10+. I'll assume it's acceptable since the prompt uses it. To be strictly safe, I could use `Optional[Tuple[str, dict]]`, but I'll stick to the prompt's style.

Actually, the prompt says:
`get_next_job() -> tuple[str, dict] | None`
I will use exactly that.

One detail: `job_data` in the heap might cause issues if somehow two items have same priority and counter (impossible here), but if they did, comparing dicts would crash. Since counter is strictly increasing, it's fine.

I'll provide the code now.
Check requirements:
1. Modify `add_job` to accept optional priority (default=0) -> Done.
2. Higher priority processed first -> Done (negated priority in min-heap).
3. Same priority maintains FIFO -> Done (counter ensures order).
4. `get_next_job()` returns tuple or None -> Done.
5. Priority levels 0, 1, 2 -> Handled naturally by integer comparison.
6. Only Python code -> Done.

Final code:
```python
import heapq
from typing import Optional, Tuple

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, `tuple[str, dict] | None` might cause a syntax error in Python < 3.10. I'll use `Optional[Tuple[str, dict]]` to be safe, or just keep the prompt's syntax. The prompt uses it, so I'll keep it. Actually, many online judges/support environments are 3.8 or 3.9. I'll stick to `from typing import Optional, Tuple` and `Optional[Tuple[str, dict]]` to be robust, but I'll match the prompt's signature style if possible. I'll just use `tuple[str, dict] | None` as requested. It's fine.

One minor adjustment: I'll add type hints to `add_job` as well for completeness, though not strictly required. The prompt says "Modify `add_job` to accept...".

I'll output exactly the class implementation.

Let's verify one more time.
`heapq` is a min-heap.
`-priority`: 2 -> -2, 1 -> -1, 0 -> 0.
Min-heap pops smallest: -2 comes first. Correct.
`counter`: increases. For same `-priority`, smaller `counter` comes first. Correct.
`get_next_job` returns `(job_id, job_data)` or `None`. Correct.

Code looks solid.
I will produce it now.
One thing: `from typing import Optional, Tuple` is not strictly needed if I use `|` syntax, but I'll include it for safety or just use `|`. I'll stick to `|` as in prompt.
Actually, to avoid any version issues, I'll use `from typing import Optional, Tuple` and `Optional[Tuple[str, dict]]`. It's standard. But the prompt explicitly wrote `tuple[str, dict] | None`. I'll follow the prompt.

Final check of the code:
```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 matches all requirements. I'll output this.
One detail: `dict` and `str` in type hints might need `from __future__ import annotations` in Python 3.9 or earlier, but it's fine for 3.10+. I'll assume modern Python.
All good.
Output matches request.✅
Proceeds. 
Self-Correction/Refinement during thought: 
Wait, `heapq` compares tuples. If `job_data` contains unorderable types, it won't matter because `counter` breaks ties. But if `counter` somehow didn't break ties, it would fail. It's safe.
I'll stick to the code.
Output matches. 
Proceeds. 
[Output Generation] -> *Produces code block*
```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
```
Looks good. I will present it. 
Note: I'll remove the `from typing import ...` to keep it clean, as `tuple` and `dict` are built-ins in modern Python, and `|` is supported in 3.10+. If compatibility is a concern, I could use `Optional[Tuple[str, dict]]`, but the prompt uses `|`. I'll stick to prompt.
Done. 
Output matches response. 
Proceeds. 
[Final Check]
Requirements:
1. `add_job` accepts optional priority (default=0) -> Yes.
2. Higher priority first -> Yes, `-priority` in min-heap.
3. FIFO for same priority -> Yes, `_counter` ensures order.
4. `get_next_job() -> tuple[str, dict] | None` -> Yes.
5. Priority levels 0, 1, 2 -> Handled naturally.
6. Only Python code -> Yes.

All good.✅
Output Generation. 
(Self-Correction/Note during generation prep)
I should make sure the type hint `tuple[str, dict] | None` works in the environment. It's fine.
I will output exactly the code.
Done. 
[Output] -> *Proceeds*

</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>