<reasoning_content>
Here's a thinking process:

1.  **Understand User Requirements:**
   - Implement a priority-based job scheduling queue.
   - `add_job` method: accepts job name, job data (dict), and optional priority (default=0).
   - Higher priority jobs processed first.
   - Same priority jobs maintain FIFO order.
   - `get_next_job()` method: returns a tuple `(str, dict)` or `None` if empty.
   - Priority levels: 0 (Normal), 1 (High), 2 (Critical) - just informational, but higher number = higher priority.
   - Output: Only Python code, no explanations.

2.  **Identify Key Data Structures:**
   - Need a priority queue that supports:
     - Higher priority first
     - FIFO for same priority
     - Efficient insertion and extraction
   - Python's `heapq` module is perfect for this. It's a min-heap, so we can negate priorities to make higher priorities come first.
   - To maintain FIFO for same priorities, we can use a monotonic counter/timestamp as a secondary sort key.
   - Each queue entry will be a tuple: `(-priority, counter, job_name, job_data)`
   - `heapq` will sort by first element (negated priority), then second (counter), naturally maintaining FIFO.

3.  **Design the Class:**
   ```python
   import heapq
   import itertools

   class JobQueue:
       def __init__(self):
           self._queue = []
           self._counter = itertools.count()  # Monotonically increasing counter

       def add_job(self, name: str, data: dict, priority: int = 0):
           # Negate priority so higher priority comes first in min-heap
           entry = (-priority, next(self._counter), name, data)
           heapq.heappush(self._queue, entry)

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

4.  **Check Requirements:**
   - `add_job` accepts optional priority (default=0)? Yes.
   - Higher priority processed first? Yes, due to negation.
   - Same priority maintains FIFO? Yes, due to counter.
   - `get_next_job() -> tuple[str, dict] | None`? Yes.
   - Priority levels 0, 1, 2 mentioned? Just informational, doesn't change implementation.
   - Only Python code? Yes.

   Wait, type hints: `tuple[str, dict] | None` is valid in Python 3.10+. I'll keep it as requested.
   Should I import `itertools`? Yes.
   Should I handle edge cases? The heap handles empty queue check.
   Type hints for `add_job`: `def add_job(self, name: str, data: dict, priority: int = 0) -> None:`
   The prompt says `get_next_job() -> tuple[str, dict] | None`. I'll match that.

   Let's double-check the exact signature requested: `get_next_job() -> tuple[str, dict] | None`
   I'll stick to that.

   One minor thing: `itertools.count()` is efficient and thread-safe for generation (though not the heap itself, but that's fine for this scope). Alternatively, a simple integer counter works too. I'll use a simple integer counter to avoid imports if not strictly necessary, but `itertools` is standard. I'll stick with `itertools.count()` or just `self._counter = 0` and increment. Actually, `itertools.count()` is cleaner. I'll use a simple integer to keep it minimal, or just `self._counter = 0`. Let's use `self._counter = 0` and `self._counter += 1` inside `add_job`. It's simpler.

   Wait, `heapq` with tuples compares element by element. So `(-priority, counter, name, data)` works. But `name` is a string, which is fine. Actually, `data` is a dict, which is not comparable in Python 3. If two entries have the same priority and counter (impossible due to counter), it would try to compare `name` and then `data`. Since `counter` is unique, it will never reach `name` or `data` for comparison. So it's safe. But to be strictly safe and avoid any potential comparison issues if counter somehow duplicates (it won't), I can just use `(-priority, counter, name, data)`. Actually, Python's heapq will compare the third element only if first two are equal. Since counter is unique, it's fine.

   Let's refine:
   ```python
   import heapq
   import itertools

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

       def add_job(self, name: str, data: dict, priority: int = 0) -> None:
           heapq.heappush(self._heap, (-priority, next(self._counter), name, data))

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

   Check type hints: `tuple[str, dict] | None` requires Python 3.10+. The prompt uses it, so it's fine.
   I'll add `from __future__ import annotations` if needed, but not necessary. I'll just write it as requested.

   One thing: `itertools` import should be at the top.
   The prompt says "Write only the Python code, no explanations."
   I'll output exactly the class and necessary imports.

   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 after adds:
   `(-0, 0, "normal", {...})`
   `(-2, 1, "critical", {...})`
   `(-1, 2, "high", {...})`
   Heapify/push maintains min-heap property. `(-2, 1, ...)` is smallest, so it pops first. Correct.
   FIFO for same priority: counter ensures order. Correct.

   All requirements met.
   I'll write the code cleanly.
   One minor point: `tuple[str, dict] | None` is valid in 3.10+. I'll keep it.
   I'll make sure imports are included.
   Ready.✅
   Output matches exactly.✅
   Proceed. 
   Self-Correction/Verification during thought:
   - Should I use `typing.Optional` and `typing.Tuple` for broader compatibility? The prompt uses `tuple[str, dict] | None`, so I'll stick to that.
   - `itertools.count()` is fine.
   - I'll add type hints to `add_job` for completeness, though not strictly required.
   - The code is minimal and correct.

   Final code:
   ```python
   import heapq
   import itertools

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

       def add_job(self, name: str, data: dict, priority: int = 0) -> None:
           heapq.heappush(self._heap, (-priority, next(self._counter), name, data))

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._heap:
               return None
           _, _, name, data = heapq.heappop(self._heap)
           return name, data
   ```
   Looks solid. Matches all constraints. Outputs only code. No extra text.✅
   I will generate this. 
   One thing: `tuple[str, dict] | None` might cause a syntax error in Python < 3.10, but the prompt explicitly uses it, so it's fine. I'll keep it exactly as requested.
   All good. 
   Output matches response. 
   Proceeds. 
   [Done] 
   *Self-Correction/Refinement during generation prep*: 
   Wait, `tuple[str, dict] | None` is fine. I'll just output the code block.
   I'll make sure there's no markdown if the prompt says "Write only the Python code", but usually markdown code blocks are acceptable. I'll just provide the raw code or standard markdown. The prompt says "Write only the Python code, no explanations." I'll output just the code block.
   All set. 
   Output matches the final code.✅
   Proceeds. 
   [Output Generation] -> *Proceeds*
   *(Done.)*

</reasoning_content>

<content>
```python
import heapq
import itertools

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

    def add_job(self, name: str, data: dict, priority: int = 0) -> None:
        heapq.heappush(self._heap, (-priority, next(self._counter), name, data))

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