<reasoning_content>
Thinking Process:

1.  **Analyze the Request:**
    *   Goal: Implement priority-based job scheduling for a queue in Python.
    *   Requirements:
        1.  `add_job` method needs an optional `priority` parameter (default 0).
        2.  Higher priority jobs processed before lower priority jobs (Note: The example shows `priority=2` as Critical and `priority=1` as High, implying higher number = higher priority).
        3.  FIFO order for same priority.
        4.  `get_next_job()` method returning a tuple `(str, dict) | None`.
    *   Priority Levels:
        *   0: Normal
        *   1: High
        *   2: Critical
    *   Output: Python code only, no explanations.

2.  **Determine Data Structure:**
    *   Since it's a priority queue, `heapq` (min-heap) is a common choice.
    *   However, the requirement says "Higher priority jobs should be processed before lower priority jobs" and the example shows `priority=2` (Critical) coming before `priority=0` (Normal).
    *   Python's `heapq` is a min-heap. To make higher numbers pop first, we can negate the priority or use a max-heap simulation.
    *   Alternatively, since we need FIFO stability for the same priority, simply sorting or using a heap with a tuple `(priority, insertion_index, job_data)` works well.
    *   Wait, the example says:
        *   `priority=0` -> Normal
        *   `priority=1` -> High
        *   `priority=2` -> Critical
        *   `get_next_job()` returns `critical` (priority 2) first.
    *   So, Priority 2 > Priority 1 > Priority 0.
    *   To use `heapq` (min-heap), we can store `(-priority, insertion_order, job)`. This ensures higher priority (larger number) becomes smaller negative number, so it pops first.
    *   Wait, if I use `(-priority, insertion_order)`, then:
        *   Priority 2 -> -2
        *   Priority 1 -> -1
        *   Priority 0 -> 0
        *   Min-heap pops -2 first. Correct.
    *   To ensure FIFO for same priority, we need a counter/timestamp to break ties.

3.  **Drafting the Class:**
    *   Class name: `JobQueue` (implied by example).
    *   `__init__`: Initialize heap and counter.
    *   `add_job`: Accept `job_id` (string), `job_data` (dict), `priority` (int). Push to heap.
    *   `get_next_job`: Pop from heap, return tuple. Handle empty queue.

4.  **Refining the Heap Logic:**
    *   Python's `heapq` compares tuples element by element.
    *   Item to push: `(-priority, counter, job_id, job_data)`.
    *   Counter starts at 0 and increments.
    *   If priorities equal, lower counter (earlier insertion) comes first.
    *   `get_next_job` returns `job_id, job_data`.

5.  **Addressing Specifics:**
    *   `add_job` signature: `def add_job(self, job_id: str, job_data: dict, priority: int = 0):`
    *   `get_next_job` signature: `def get_next_job(self) -> tuple[str, dict] | None:`
    *   Imports: `import heapq`.

6.  **Code Construction:**

   ```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):
           # Higher priority number means higher priority (2 > 1 > 0)
           # Negate priority for min-heap behavior (lower value pops first)
           item = (-priority, self.counter, job_id, job_data)
           heapq.heappush(self.heap, item)
           self.counter += 1

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

7.  **Review against Constraints:**
    *   `add_job` accepts optional priority (default 0)? Yes.
    *   Higher priority processed before lower? Yes (using negation).
    *   FIFO for same priority? Yes (using counter).
    *   `get_next_job` returns `tuple[str, dict] | None`? Yes.
    *   Example logic check:
        *   add normal (0) -> heap: [(0, 0, "normal", ...)]
        *   add critical (2) -> heap: [(-2, 1, "critical", ...), (0, 0, "normal", ...)] (heapified)
        *   add high (1) -> heap: [(-2, 1, "critical", ...), (-1, 2, "high", ...), (0, 0, "normal", ...)]
        *   get_next -> pops (-2, ...) -> critical. Correct.

8.  **Final Polish:**
    *   Ensure type hints match the prompt's request style (`tuple[str, dict] | None`).
    *   Make sure no extra text is included.

    Wait, one detail in the example: `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    The prompt asks for `add_job` to accept `job_id` and `job_data` based on the example usage, but the requirement text just says "Modify `add_job` to accept an optional priority parameter".
    Looking at the example: `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    So the first arg is likely the job ID/Name, second is the data dict.
    I will follow the example signature.

    Wait, `get_next_job` returns `tuple[str, dict]`.
    In my draft: `return (job_id, job_data)`.
    This matches.

    One minor thing: `typing` imports. Standard library `heapq` is needed.
    I should use standard type hints compatible with Python 3.9+ (using `|` for union) or `typing`. The prompt uses `tuple[str, dict] | None`, which suggests Python 3.10+ syntax. I will use that style.

    Let's double check the priority logic.
    Requirement: "Higher priority jobs should be processed before lower priority jobs".
    Example: Priority 2 (Critical) processed before Priority 0 (Normal).
    So 2 > 1 > 0.
    My logic: `(-priority, counter)`.
    Heapq pops smallest.
    -2 < -1 < 0.
    So Priority 2 is popped first. Correct.

    FIFO: `counter` ensures stability.

    Code looks solid.

    One thing: The prompt says "Write only the Python code, no explanations."
    I will output just the code block.

    Wait, I should check if the class name is specified. The example uses `queue = JobQueue()`. So class name must be `JobQueue`.

    Final check on imports. `heapq` is standard.

    One detail on type hinting: `tuple[str, dict] | None`. In older Python versions `|` isn't supported for types without `from __future__ import annotations` or `typing.Union`. However, since the prompt uses `|`, I will use `|` in the return type hint to match the requested style, assuming a modern Python environment. I will also import `heapq`.

    Wait, `dict` is a builtin, `str` is a builtin.
    I should ensure the code is valid.

    Let's write it.

```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):
        # Negate priority so higher priority numbers pop first (min-heap)
        # Counter ensures FIFO order for same priority
        item = (-priority, self.counter, job_id, job_data)
        heapq.heappush(self.heap, item)
        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, I need to make sure the return type annotation syntax is valid in the context. `tuple[str, dict] | None` is valid in Python 3.10+. I will use it as requested.

    One check: `job_data` type hint in `add_job`. The example passes `{"type": "normal"}`. So `dict` is correct.
    The prompt asks for `get_next_job() -> tuple[str, dict] | None`.
    I will stick to that.

    Is there any ambiguity on `add_job` arguments?
    Example: `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    So `job_id` is first, `job_data` is second.
    My code reflects this.

    Final check on constraints:
    1. `add_job` accepts optional priority (default 0). Done.
    2. Higher priority processed before lower. Done (via negation).
    3. Same priority FIFO. Done (via counter).
    4. `get_next_job` returns tuple or None. Done.

    Ready to generate.

    Wait, I should check if `tuple` needs to be imported from `typing` in older versions, but since I'm using `|` syntax, I assume Python 3.10+ runtime where `tuple` is a generic type natively. However, to be safe and strictly compliant with standard Python typing practices often expected in these tasks, I might just use the provided syntax in the prompt. The prompt uses `tuple[str, dict] | None` in the requirement section. I will use that exact signature.

    Wait, one detail: `job_data` in the example is `{"type": "normal"}`.
    The return type is `tuple[str, dict]`.
    The `add_job` signature in the example is `add_job("normal", {"type": "normal"}, priority=0)`.
    So `job_id` is the first arg, `job_data` is the second.
    My implementation matches.

    One more check: The prompt says "Write only the Python code".
    I will provide the code block.

    Wait, I should consider if `typing` is needed for `dict` in type hints if running on older Python, but usually `dict` works fine as a generic in recent versions. I will keep it simple.

    Wait, I need to make sure I don't import `Optional` if I use `| None`.
    I will use `heapq` and the class definition.

    One small edge case: `job_data` could be anything, but the requirement says `dict`.
    The `get_next_job` return type hint is `tuple[str, dict] | None`.
    I will ensure the return matches.

    Okay, writing the 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):
        # Higher priority (larger number) should come first.
        # heapq is a min-heap, so we negate priority.
        # Counter ensures FIFO for same priority.
        item = (-priority, self._counter, job_id, job_data)
        heapq.heappush(self._heap, item)
        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 good. I will remove the `_` prefix on attributes for simplicity unless preferred, but `_` is fine. I'll stick to simple names `heap` and `counter` to match the simplicity of the request.

    Actually, looking at the example: `queue = JobQueue()`.
    I will make sure the class name is `JobQueue`.

    Final check on priority logic.
    Priority 2 (Critical) -> -2.
    Priority 1 (High) -> -1.
    Priority 0 (Normal) -> 0.
    Heap pops smallest. -2 < -1 < 0.
    So 2 pops before 1 before 0.
    This matches "Higher priority jobs should be processed before lower priority jobs" given the example mapping.

    Code is ready.

</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):
        # Negate priority so higher priority numbers pop first (min-heap behavior)
        # Counter ensures FIFO order for jobs with the same priority
        item = (-priority, self._counter, job_id, job_data)
        heapq.heappush(self._heap, item)
        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>