class SideBar(ctk.CTkFrame):
    """Class for the navigation sidebar."""
    def __init__(
        self,
        master: ctk.CTkFrame,
        app_state: AppState,
        run_state: RunState,
        navigate: Callable[[Literal[-1, 1], int, AppState], None],
        on_save: Callable[[AppState], None],
        on_exit: Callable[[], NoReturn],
        **kwargs,
    ):
        """Constructor."""
        super().__init__(master, **kwargs)
        self.state = app_state
        self.run = run_state
        self.navigate = navigate
        self.save = on_save
        self.exit = on_exit
        self.sidebar_frame = ctk.CTkFrame(master=master, width=140, corner_radius=0)
        self.sidebar_frame.grid(row=0, column=1, sticky="nsew")
        self.sidebar_frame.grid_rowconfigure(7, weight=1)
        padding = self.state["padding"]
        half_padding = padding // 2
        self.navigate_label = ctk.CTkLabel(
            master=self.sidebar_frame,
            text="Navigate",
            font=(self.state["font"], 16, "bold"),
        )
        self.navigate_label.grid(
            row=0, column=0, padx=padding, pady=(padding, half_padding)
        )
        self.prev_button = ctk.CTkButton(
            master=self.sidebar_frame,
            text="Previous",
            command=self._previous,
            state=("normal" if self.run["run_index"] > 0 else "disabled"),
        )
        self.prev_button.grid(row=1, column=0, padx=padding, pady=half_padding)
        self.next_button = ctk.CTkButton(
            master=self.sidebar_frame,
            text="Next",
            command=self._next,
            state=(
                "normal"
                if self.run["run_index"] < self.run["total_runs"] - 1
                else "disabled"
            ),
        )
        self.next_button.grid(row=2, column=0, padx=padding, pady=half_padding)
        self.run_counter_label = ctk.CTkLabel(
            master=self.sidebar_frame,
            text=f"Run: {self.run['run_index'] + 1} / {self.run['total_runs']}",
            font=(self.state["font"], 16, "bold"),
        )
        self.run_counter_label.grid(row=3, column=0, padx=padding, pady=half_padding)
        self.already_evaluated_label = ctk.CTkLabel(
            master=self.sidebar_frame,
            text="Already Evaluated" if self.run["already_evaluated"] else "",
            font=(self.state["font"], 16, "bold"),
            text_color="red",
        )
        self.already_evaluated_label.grid(
            row=4, column=0, padx=padding, pady=half_padding
        )
        self.save_button = ctk.CTkButton(
            master=self.sidebar_frame, text="Save", command=self._save
        )
        self.save_button.grid(row=5, column=0, padx=padding, pady=half_padding)
        self.exit_button = ctk.CTkButton(
            master=self.sidebar_frame, text="Exit", command=self._exit
        )
        self.exit_button.grid(row=6, column=0, padx=padding, pady=half_padding)
        self.appearance_label = ctk.CTkLabel(
            self.sidebar_frame,
            text="Appearance",
            font=(self.state["font"], 16, "bold"),
        )
        self.appearance_label.grid(
            row=8, column=0, padx=padding, pady=(padding, half_padding)
        )
        self.appearance_option_menu = ctk.CTkOptionMenu(
            self.sidebar_frame,
            values=["System", "Light", "Dark"],
            command=self._change_appearance_mode,
        )
        self.appearance_option_menu.grid(
            row=9, column=0, padx=padding, pady=half_padding
        )
        self.scaling_label = ctk.CTkLabel(
            master=self.sidebar_frame,
            text="UI Scaling",
            font=(self.state["font"], 16, "bold"),
        )
        self.scaling_label.grid(row=10, column=0, padx=padding, pady=half_padding)
        self.scaling_option_menu = ctk.CTkOptionMenu(
            master=self.sidebar_frame,
            values=["70%", "80%", "90%", "100%", "110%", "120%", "130%"],
            command=self._change_scaling_value,
        )
        self.scaling_option_menu.grid(
            row=11, column=0, padx=padding, pady=(half_padding, padding)
        )
    def update_state(self, run_state: RunState) -> None:
        """Updates the run state for consistency."""
        self.run = run_state
        self.run_counter_label.configure(
            text=f"Run: {self.run['run_index'] + 1} / {self.run['total_runs']}"
        )
        self.already_evaluated_label.configure(
            text="Already evaluated" if self.run["already_evaluated"] else ""
        )
    def _previous(self) -> None:
        """Callback for the previous button press."""
        new_run_index = self.run["run_index"] - 1
        if new_run_index == 0:
            self.prev_button.configure(state="disabled")
        else:
            self.prev_button.configure(state="normal")
        self.next_button.configure(state="normal")
        self.navigate(-1, new_run_index, self.state)
    def _next(self) -> None:
        """Callback for the next button press."""
        new_run_index = self.run["run_index"] + 1
        if new_run_index >= self.run["total_runs"] - 1:
            self.next_button.configure(state="disabled")
        else:
            self.next_button.configure(state="normal")
        self.prev_button.configure(state="normal")
        self.navigate(1, new_run_index, self.state)
    def _change_appearance_mode(self, new_appearance_mode: str) -> None:
        """Changes the UI color mode."""
        ctk.set_appearance_mode(new_appearance_mode)
    def _change_scaling_value(self, new_scaling: str) -> None:
        """Changes the UI scaling."""
        new_scaling_val = int(new_scaling.replace("%", "")) / 100
        ctk.set_widget_scaling(new_scaling_val)
    def _save(self) -> None:
        """Calls the save state function."""
        self.save(self.state)
    def _exit(self) -> NoReturn:
        """Calls the exit function."""
        self.save(self.state)
        self.exit()