<reasoning_content>
Thinking Process:

1.  **Understand the Goal**: The user wants a Python implementation of a priority-based job scheduling queue.
2.  **Analyze Requirements**:
    *   Class name seems to be `JobQueue` based on the example.
    *   Method `add_job(job_id: str, job_data: dict, priority: int = 0)`: Adds a job with a priority.
    *   Method `get_next_job() -> tuple[str, dict] | None`: Retrieves the highest priority job.
    *   Priority Logic: Higher number = higher priority. 2 (Critical) > 1 (High) > 0 (Normal).
    *   Tie-breaking: FIFO (First In, First Out) for same priority.
    *   Output: Return `job_id` and `job_data` as a tuple, or `None` if empty.
    *   Constraint: Write *only* the Python code.

3.  **Data Structure Selection**:
    *   Need to store jobs.
    *   Need to efficiently retrieve the max priority item.
    *   Need to maintain order for equal priorities.
    *   *Option A: List + Sort/Scan*. $O(N)$ add, $O(N)$ get. Simple but inefficient for large queues.
    *   *Option B: Heap (Priority Queue)*. `heapq` in Python is a min-heap.
        *   To use for max-heap (higher priority first), negate the priority.
        *   To handle FIFO for same priority, use a counter (timestamp/index) as the second element in the tuple. Since `heapq` compares tuples element by element, if priorities are equal, it compares the counter. To ensure FIFO (earlier jobs come out first), the counter should increase with time.
        *   Wait, `heapq` pops the *smallest* item.
        *   Tuple structure: `(negative_priority, insertion_counter, job_id, job_data)`.
        *   If priority is 2 (Critical), tuple starts with -2.
        *   If priority is 0 (Normal), tuple starts with 0.
        *   -2 < 0, so Critical comes out first. Correct.
        *   If priorities are same (e.g., both 0), it compares `insertion_counter`. Smaller counter (earlier addition) comes out first. Correct.
    *   *Option C: Sorted List (bisect)*. $O(N)$ insert, $O(1)$ pop.
    *   Given the constraints and typical interview-style coding tasks, a Heap is the standard efficient solution ($O(\log N)$). However, for a simple script, a list might be acceptable, but Heap is "correct" engineering. Let's stick to the Heap approach or a simple list if the prompt implies simplicity. The prompt doesn't specify performance constraints, but "Implement priority-based job scheduling" usually implies using a Priority Queue data structure.
    *   Actually, since I need to write *only* the code, I should make it robust.

4.  **Drafting the Code (Heap approach)**:

   ```python
   import heapq

   class JobQueue:
       def __init__(self):
           self.queue = []
           self.counter = 0  # For FIFO tie-breaking

       def add_job(self, job_id: str, job_data: dict, priority: int = 0):
           # heapq is a min-heap. To get highest priority first, we negate priority.
           # Tuple comparison compares first element, then second.
           # (-priority, counter) ensures higher priority comes first, then lower counter (FIFO).
           entry = (-priority, self.counter, job_id, job_data)
           self.counter += 1
           heapq.heappush(self.queue, entry)

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self.queue:
               return None
           
           # Pop the smallest item (which is highest priority due to negation)
           _, _, job_id, job_data = heapq.heappop(self.queue)
           return (job_id, job_data)
   ```

5.  **Refining Requirements Check**:
    *   `add_job` accepts `priority` default 0? Yes.
    *   Higher priority processed before lower? Yes (negated priority).
    *   Same priority FIFO? Yes (counter).
    *   `get_next_job` returns `tuple[str, dict] | None`? Yes.
    *   Example usage matches?
        *   `add_job("normal", ..., 0)` -> push `(0, 0, "normal", ...)`
        *   `add_job("critical", ..., 2)` -> push `(-2, 1, "critical", ...)`
        *   `add_job("high", ..., 1)` -> push `(-1, 2, "high", ...)`
        *   Heap contents: `[(-2, 1, "critical", ...), (-1, 2, "high", ...), (0, 0, "normal", ...)]` (order in list might vary, but heap invariant holds).
        *   `heappop` returns `(-2, 1, "critical", ...)`.
        *   Returns `("critical", {...})`. Correct.

6.  **Alternative (List approach)**:
    If imports are discouraged or simplicity is preferred, a list with sorting works too, but $O(N)$ is worse. Usually, standard library `heapq` is acceptable. The prompt doesn't forbid imports.

    Let's check if the prompt implies a specific class structure. The example uses `queue = JobQueue()`. So I need to define the class.

    Let's verify the type hint syntax. `tuple[str, dict] | None` is Python 3.10+ syntax. If the environment is older, it might need `typing` imports. However, modern Python usually accepts this. To be safe and compatible, I could use `Optional` and `Tuple` from `typing`, or just stick to the user's syntax if they provided it. The user provided `tuple[str, dict] | None` in the prompt, so I will use that.

7.  **Final Code Structure**:

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

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

       def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
           # Use negative priority for max-heap behavior (higher number = higher priority)
           # Use counter to maintain FIFO order for same priority
           entry = (-priority, self._counter, job_id, job_data)
           self._counter += 1
           heapq.heappush(self._heap, entry)

       def get_next_job(self) -> Tuple[str, dict] | None:
           if not self._heap:
               return None
           # Pop returns (-priority, counter, job_id, job_data)
           _, _, job_id, job_data = heapq.heappop(self._heap)
           return (job_id, job_data)
   ```
   Wait, the user asked for `tuple[str, dict] | None`. I used `Tuple` from typing in the thought process but the user used built-in syntax. I will stick to the user's syntax style or just standard valid Python.

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

   If I use the heap logic:
   1. Normal (0): push `(0, 0, "normal", ...)`
   2. Critical (2): push `(-2, 1, "critical", ...)`
   3. High (1): push `(-1, 2, "high", ...)`

   Heap state (conceptually sorted by first element):
   1. `(-2, 1, "critical", ...)` -> Priority 2 (Critical)
   2. `(-1, 2, "high", ...)` -> Priority 1 (High)
   3. `(0, 0, "normal", ...)` -> Priority 0 (Normal)

   Pop 1: Returns Critical. Correct.
   Pop 2: Returns High. Correct.
   Pop 3: Returns Normal. Correct.

   What if two Critical jobs?
   1. Job A (Crit): `(-2, 0, "A", ...)`
   2. Job B (Crit): `(-2, 1, "B", ...)`
   Heap compares -2 (equal), then 0 vs 1. 0 is smaller.
   Pop A. Correct (FIFO).

   The logic holds.

   Is `heapq` allowed? Usually yes. If not, a simple list implementation is:
   ```python
   class JobQueue:
       def __init__(self):
           self.jobs = []
           self.counter = 0

       def add_job(self, job_id: str, job_data: dict, priority: int = 0):
           self.jobs.append((-priority, self.counter, job_id, job_data))
           self.counter += 1
           # Keep sorted? No, that's O(N). Just append and find max on get?
           # Or sort on insert?
           # Let's just sort on insert to keep it simple without imports if needed, 
           # but heap is better. 
           # However, without imports, sorting list is easiest.
           # But heap is standard. I will use heapq. It's standard library.
   ```
   Actually, to be safe against environment restrictions (though unlikely for a generic prompt), a list with `sort` or just finding the max is safer? No, `heapq` is standard. I'll stick to `heapq` or just a sorted list if I want to avoid imports.
   Let's look at the "Write only the Python code" instruction. It doesn't restrict imports.
   However, a pure Python list implementation is often preferred in these snippets if dependencies aren't strictly necessary, but `heapq` is the "correct" data structure.
   Let's write it without imports to be absolutely dependency-free?
   If I don't use `heapq`, I have to manage the order manually.
   List approach:
   `self.jobs` stores `(-priority, counter, job_id, job_data)`.
   `add_job`: append.
   `get_next_job`: find min item (since we negated priority), remove it, return data.
   Finding min is $O(N)$.
   Given the prompt is a simple coding task, $O(N)$ might be acceptable, but $O(\log N)$ is better.
   I will use `heapq` because it's the standard way to implement a priority queue in Python.

   Let's refine the 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:
           # Negate priority to simulate max-heap behavior with min-heap
           # Counter ensures FIFO order for equal priorities
           entry = (-priority, self._counter, job_id, job_data)
           self._counter += 1
           heapq.heappush(self._queue, entry)

       def get_next_job(self) -> tuple[str, dict] | None:
           if not self._queue:
               return None
           # Pop the item with the lowest value (highest priority due to negation)
           _, _, job_id, job_data = heapq.heappop(self._queue)
           return (job_id, job_data)
   ```
   
   Wait, the user example:
   `queue = JobQueue()`
   `queue.add_job("normal", {"type": "normal"}, priority=0)`
   
   If I use the code above, it works perfectly.

   Double check type hints.
   User wrote: `get_next_job() -> tuple[str, dict] | None`
   My code: `get_next_job(self) -> tuple[str, dict] | None`
   This is valid Python 3.10+.
   
   One detail: The user's example passes `priority=0` explicitly. My default is 0. That's fine.
   
   Is there any edge case?
   - Empty queue: returns None. Handled.
   - No jobs: returns None. Handled.
   
   I will provide the solution using `heapq` as it is the most robust standard solution.

   Wait, maybe I shouldn't import inside the block if I can help it? No, imports are fine.
   However, to be extremely safe and concise, a list-based solution is also very short.
   
   List based solution:
   ```python
   class JobQueue:
       def __init__(self):
           self.jobs = []
           self.idx = 0
       
       def add_job(self, job_id, job_data, priority=0):
           self.jobs.append((-priority, self.idx, job_id, job_data))
           self.idx += 1
           
       def get_next_job(self):
           if not self.jobs:
               return None
           # Find index of min item (highest priority)
           # Since we want to remove it, and list pop by index is O(N), 
           # but finding is O(N).
           # Actually, just sorting the list every time is O(N log N) add or O(N log N) get.
           # But finding min is O(N).
           
           # Let's just find the best one.
           best_idx = 0
           for i in range(1, len(self.jobs)):
               if self.jobs[i] < self.jobs[best_idx]:
                   best_idx = i
           
           _, _, job_id, job_data = self.jobs.pop(best_idx)
           return (job_id, job_data)
   ```
   This requires no imports. It is $O(N)$ per operation.
   The Heap solution is $O(\log N)$ per operation.
   Usually, for "Priority Queue", Heap is expected. I'll stick to Heap. It's cleaner code (no loops).

   Wait, if I use `heapq`, I need `import heapq`.
   I will include the import.

   Let's check the constraints again.
   "Write only the Python code, no explanations."
   
   I will output the class definition.

   One minor thing: The user's example output description says `Returns critical job first`.
   My logic:
   Normal (0) -> push (0, 0, ...)
   Critical (2) -> push (-2, 1, ...)
   High (1) -> push (-1, 2, ...)
   Heap: [(-2, 1, ...), (-1, 2, ...), (0, 0, ...)] (roughly)
   Pop -> (-2, 1, ...) -> Critical.
   Matches.

   What if priorities are negative? The prompt implies levels 0, 1, 2. But generally priority queues handle negatives. My logic works for negatives too (e.g. -5 is lower priority than 0? No, -5 < 0, so -(-5) = 5 > 0. Wait.
   If priority is -5 (very low?), my code does `-(-5) = 5`.
   If priority is 0, code does `0`.
   5 > 0. So -5 priority comes out *after* 0 priority?
   Wait. `heappop` returns *smallest* value.
   Value for -5 is 5. Value for 0 is 0.
   0 is smaller. 0 comes out first.
   So 0 priority (Normal) comes out before -5 priority.
   Is -5 lower than 0? Yes.
   So higher priority (0) comes before lower priority (-5).
   Logic holds.
   
   What if priority is 5 (Super Critical)?
   Value is -5.
   Value for 0 is 0.
   -5 < 0.
   -5 comes out first.
   So 5 priority comes before 0 priority.
   Logic holds.

   Final check on imports. `heapq` is standard.
   
   Code looks solid.

```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:
        # Use negative priority to implement max-heap behavior with min-heap
        # Use counter to ensure FIFO order for jobs with the same priority
        entry = (-priority, self._counter, job_id, job_data)
        self._counter += 1
        heapq.heappush(self._heap, entry)

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self._heap:
            return None
        # Pop the entry with the smallest value (highest priority due to negation)
        _, _, job_id, job_data = heapq.heappop(self._heap)
        return (job_id, job_data)
```
   The type hint `tuple[str, dict] | None` requires Python 3.10+. If the user is on 3.9 or lower, this will syntax error.
   However, the user *wrote* `tuple[str, dict] | None` in the requirements. This implies they are using a version that supports it or don't care about strict compatibility. I will follow their syntax.

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

   One optimization: I don't strictly need to store `job_data` inside the tuple if I just return it, but `heappop` returns the whole tuple. It's fine.

   Wait, if I want to be super safe about the "Write only the Python code" and avoiding imports if possible (sometimes these automated graders don't like imports at top level if not requested), I can implement a simple sort.
   But `heapq` is the professional answer. I'll stick with it. If I were to avoid imports:

```python
class JobQueue:
    def __init__(self):
        self._jobs = []
        self._counter = 0

    def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:
        self._jobs.append((-priority, self._counter, job_id, job_data))
        self._counter += 1
        # Keeping it sorted might be expensive O(N), but for a simple queue implementation 
        # without imports, it's a valid approach. 
        # However, finding the best job to pop is O(N) anyway.
        # Let's just append and find max on pop.

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self._jobs:
            return None
        
        # Find the job with the highest priority (smallest negative value)
        # and FIFO (smallest counter)
        # We can just sort or find min.
        # Since N is likely small in these examples, simple scan is fine.
        
        best_idx = 0
        for i in range(1, len(self._jobs)):
            if self._jobs[i] < self._jobs[best_idx]:
                best_idx = i
        
        # Remove and return
        _, _, job_id, job_data = self._jobs.pop(best_idx)
        return (job_id, job_data)
```
   This is dependency-free. But `heapq` is built-in standard library, effectively "part of Python". It is better practice. I will provide the `heapq` version.

   Wait, looking at the prompt's `add_job` signature: `add_job(self, job_id: str, job_data: dict, priority: int = 0)`.
   My code matches.

   I'll provide the `heapq` solution.

   Wait, just to be 100% compliant with "Write only the Python code", I should ensure the indentation and structure are perfect.

   One detail: The prompt example:
   `queue.add_job("normal", {"type": "normal"}, priority=0)`
   My class method signature: `def add_job(self, job_id: str, job_data: dict, priority: int = 0) -> None:`
   Matches.

   Final check on logic:
   Higher priority = processed first.
   Priority 2 (Critical) > Priority 1 (High) > Priority 0 (Normal).
   My code: `entry = (-priority, ...)`
   Priority 2 -> -2.
   Priority 1 -> -1.
   Priority 0 -> 0.
   Heap pops smallest. -2 < -1 < 0.
   So 2 comes out first. Correct.

   FIFO for same priority.
   Priority 0 -> 0.
   Job 1 (time 0): (0, 0, ...)
   Job 2 (time 1): (0, 1, ...)
   Heap compares (0, 0) vs (0, 1). 0 < 1.
   Job 1 comes out first. Correct.

   Result tuple: `tuple[str, dict]`.
   My code returns `(job_id, job_data)`. Correct.

   Return None if empty. Correct.

   I will write the code now.

```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 to use min-heap as max-heap
        # Counter ensures FIFO order for equal priorities
        entry = (-priority, self._counter, job_id, job_data)
        self._counter += 1
        heapq.heappush(self._queue, entry)

    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)
```
   The prompt asks for `get_next_job() -> tuple[str, dict] | None`.
   I will include the type hint.

   One thought: Does the user want the imports? "Write only the Python code". Yes, imports are part of the code.

   I'll output the block.

</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:
        # Use negative priority to simulate max-heap behavior with min-heap
        # Use counter to maintain FIFO order for jobs with the same priority
        entry = (-priority, self._counter, job_id, job_data)
        self._counter += 1
        heapq.heappush(self._queue, entry)

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self._queue:
            return None
        # Pop the entry with the smallest value (highest priority due to negation)
        _, _, job_id, job_data = heapq.heappop(self._queue)
        return (job_id, job_data)
```
</content>