<reasoning_content>
Thinking Process:

1.  **Analyze the Request:**
    *   Goal: Implement priority-based job scheduling for a queue.
    *   Requirements:
        1.  Modify `add_job` to accept an optional `priority` parameter (default=0).
        2.  Higher priority jobs processed before lower priority jobs.
        3.  Jobs with the same priority maintain FIFO order.
        4.  Add `get_next_job() -> tuple[str, dict] | None` to retrieve the highest priority job.
    *   Priority Levels: 0 (Normal), 1 (High), 2 (Critical). Note: The example shows `priority=2` for critical and `priority=1` for high. This implies higher integer = higher priority.
    *   Output: Only Python code, no explanations.

2.  **Determine Data Structure:**
    *   A standard `queue.Queue` or `list` won't work directly if we need to maintain order based on priority while preserving FIFO within the same priority.
    *   Option 1: `heapq`. This is the standard way to implement a priority queue.
        *   Store tuples: `(priority, insertion_order, job_id, job_data)`.
        *   Since Python's `heapq` is a min-heap, we want higher priority (integer) to come first.
        *   If higher integer means higher priority (2 > 1 > 0), a min-heap would put 0 at the top. We need to negate priority or invert the comparison.
        *   Wait, the requirement says "Higher priority jobs should be processed before lower priority jobs". Example: Critical (2) before High (1) before Normal (0).
        *   So, 2 > 1 > 0.
        *   To use `heapq` (min-heap), we store `(-priority, insertion_order, job_id, job_data)`.
        *   This ensures -2 < -1 < 0, so Critical (-2) comes before High (-1) before Normal (0).
        *   To maintain FIFO for same priority, we need a counter (sequence number) to break ties.
    *   Option 2: Sorted List / Bucket Queue. Since priority levels are limited (0, 1, 2), we could use buckets. However, `heapq` is more generic and robust if priorities change later. Given the prompt implies a general implementation, `heapq` is the standard approach for "Priority Queue".
    *   Let's check the constraint: "Jobs with the same priority should maintain FIFO order".
    *   Using `heapq` with a global counter ensures stability.

3.  **Drafting the Code:**
    *   Class name: `JobQueue` (implied by the example).
    *   `__init__`: Initialize the heap and a counter.
    *   `add_job(job_id, job_data, priority=0)`:
        *   Increment counter.
        *   Push `(-priority, counter, job_id, job_data)` to heap.
    *   `get_next_job()`:
        *   Pop from heap.
        *   Return `(job_id, job_data)`.
        *   Handle empty queue (return `None`).

4.  **Refining the Logic:**
    *   The example shows `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    *   Arguments: `job_id` (str), `job_data` (dict), `priority` (int).
    *   Return type of `get_next_job`: `tuple[str, dict] | None`.
    *   Priority logic: 2 (Critical) > 1 (High) > 0 (Normal).
    *   Heap logic: Min-heap. To make 2 pop before 0, store `-2` vs `-0`. `-2 < 0`. Correct.
    *   Tie-breaking: Counter ensures FIFO for same priority.

5.  **Writing the Code:**

```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) -> None:
        # Higher priority number means higher priority (2 > 1 > 0)
        # heapq is a min-heap, so we negate priority to process higher values first
        entry = (-priority, self.counter, job_id, job_data)
        heapq.heappush(self.heap, entry)
        self.counter += 1

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

6.  **Review against Constraints:**
    *   `add_job` accepts optional priority (default=0)? Yes.
    *   Higher priority processed before lower? Yes (negated priority in min-heap).
    *   Same priority FIFO? Yes (counter ensures stable sort).
    *   `get_next_job` returns `tuple[str, dict] | None`? Yes.
    *   Example usage matches? Yes.

7.  **Final Polish:**
    *   Ensure imports are included.
    *   Ensure type hints match the requirement description (`tuple[str, dict] | None`).
    *   Keep it concise.

    Wait, `priority` in the example:
    `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 store `(-priority, counter, ...)`, then:
    Normal: (0, counter, ...)
    Critical: (-2, counter, ...)
    High: (-1, counter, ...)

    Heap order:
    -2 (Critical) < -1 (High) < 0 (Normal).
    So Critical pops first. Correct.

    One minor detail: The prompt asks for `get_next_job() -> tuple[str, dict] | None`.
    In Python 3.9+, `tuple[str, dict]` is valid. In older versions, `Tuple[str, dict]` from typing is needed. I will use standard typing for compatibility or just the built-in generic syntax if assuming modern Python. The prompt uses `-> tuple[str, dict] | None`, which suggests Python 3.10+ syntax (PEP 604). I will stick to the requested signature style.

    Also, make sure to import `heapq`.

    Let's double check the `add_job` signature based on the example:
    `queue.add_job("normal", {"type": "normal"}, priority=0)`
    Args: `job_id`, `job_data`, `priority`.

    Code looks solid.

```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) -> None:
        # Negate priority so higher priority values come first in min-heap
        # Counter ensures FIFO for same priority
        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)
```
    I will remove the `_` prefix on attributes just to keep it simple, or keep them for encapsulation. It doesn't matter much. I'll stick to simple attribute names.
    Also, ensure `Tuple` vs `tuple`. The prompt uses `tuple[str, dict]`. I will use that.

    One check: `priority` default is 0.
    The prompt says: "Higher priority jobs should be processed before lower priority jobs".
    Example: 2 (Critical) > 1 (High) > 0 (Normal).
    So `priority=2` should come before `priority=0`.
    My logic: `(-2, ...)` vs `(0, ...)`. `-2 < 0`. Min-heap pops smaller first. So `-2` pops first. Correct.

    Final check on imports. `heapq` is needed. `typing` is good practice but maybe not strictly required if using built-in generics in 3.9+. However, `Optional` might need importing if used in type hints, though the prompt uses `| None` syntax which is built-in. I will import `heapq`.

    Wait, the prompt asks to "Write only the Python code". I should provide the class definition.

    One detail: `job_data` in the example is `{"type": "normal"}`. The type hint says `dict`.
    `get_next_job` return type hint: `tuple[str, dict] | None`.

    Okay, ready to generate.

```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) -> None:
        # Higher priority number means higher priority (e.g., 2 > 1 > 0).
        # Using min-heap, so we negate priority to make higher values pop first.
        # Counter ensures FIFO order for jobs with the same priority.
        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, `Optional` is not used in the signature `tuple[str, dict] | None`. I can remove `Optional` import to be cleaner, but it's harmless. I'll remove it to match the prompt's hint style exactly.
    Actually, `tuple[str, dict]` is valid in Python 3.9+.
    I will remove `typing` imports if not strictly needed, but `heapq` is essential.
    Let's keep it minimal.

    Revised 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 good.

    Wait, one edge case: `job_data` is `dict`. In the example `{"type": "normal"}`.
    The return type hint `tuple[str, dict] | None` matches.
    The signature `add_job` matches the example call `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    Wait, in the example `queue.add_job("normal", {"type": "normal"}, priority=0)`, the second arg is a dict.
    My signature `def add_job(self, job_id: str, job_data: dict, priority: int = 0)` matches.
    Wait, `job_id` is `"normal"`. `job_data` is `{"type": "normal"}`.
    Yes.

    Final check on constraints:
    1. Modify `add_job` to accept optional priority (default=0). Checked.
    2. Higher priority processed before lower. Checked (negated priority).
    3. Same priority FIFO. Checked (counter).
    4. Add `get_next_job`. Checked.
    5. Only Python code. Checked.

    One tiny nuance: `job_data` in `add_job` vs `job_id` in `add_job`.
    Example: `queue.add_job("normal", {"type": "normal"}, priority=0)`
    First arg: "normal" (job_id)
    Second arg: {"type": "normal"} (job_data)
    Third arg: priority=0
    My code: `def add_job(self, job_id: str, job_data: dict, priority: int = 0)`
    This matches perfectly.

    Wait, I should check if `job_data` is actually a dict in the example. `{"type": "normal"}` is a dict.
    The return type of `get_next_job` is `tuple[str, dict]`.
    So `job_id` is str, `job_data` is dict.
    My return `(job_id, job_data)` matches.

    One thing: The prompt says "Write only the Python code". I should avoid markdown blocks if possible, but usually, code blocks are expected for code. I will wrap it in markdown code block for clarity as is standard for "Write code" tasks, but strictly speaking "only the code" might mean raw text. However, usually, it means "no conversational text". I will output the code block.

    Wait, I'll remove the `typing` import since I'm using the new syntax `tuple[...] | None` which implies Python 3.9+, where `tuple` is generic. I don't need `from typing import ...`.
    I will keep `heapq`.

    One more check: `priority` default is 0.
    Requirement: "Higher priority jobs should be processed before lower priority jobs".
    Example: 2 (Critical) > 1 (High) > 0 (Normal).
    So 2 is higher than 0.
    In a min-heap, smallest value pops first.
    If I push `(-2, ...)` and `(0, ...)`, `-2 < 0`. `-2` pops first.
    So Critical pops before Normal.
    This satisfies "Higher priority jobs (2) processed before lower (0)".
    Logic holds.

    Final check on code structure.
    Class `JobQueue`.
    Methods `add_job`, `get_next_job`.
    Imports `heapq`.
    No extra text.

    Wait, I should make sure I don't use `typing.Optional` if I use `| None`.
    I won't import `Optional`.

    One detail: The prompt example `queue.add_job("normal", {"type": "normal"}, priority=0)`.
    This implies `job_id` is the first arg, `job_data` is the second.
    My code: `def add_job(self, job_id: str, job_data: dict, priority: int = 0)`.
    Correct.

    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) -> None:
        # Negate priority so higher values (e.g., 2) become smaller negative numbers (-2)
        # ensuring they pop first from the min-heap.
        # Counter ensures stable ordering (FIFO) for jobs with the same priority.
        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>