#    VenviPy - A Virtual Environment Manager for Python.
#    Copyright (C) 2021 - Youssef Serestou - sinusphi.sq@gmail.com
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License or any
#    later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    A copy of the GNU General Public License version 3 named LICENSE is
#    in the root directory of VenviPy.
#    If not, see <https://www.gnu.org/licenses/licenses.en.html#GPL>.

# -*- coding: utf-8 -*-
"""
This module contains the tables.
"""
import os
import shlex
import subprocess
import tempfile
from pathlib import Path
import shutil
import logging
from functools import partial

from PyQt6.QtGui import QIcon, QCursor, QAction
from PyQt6.QtCore import pyqtSignal, QThread, QTimer
from PyQt6.QtWidgets import (
    QStyle,
    QTableView,
    QMenu,
    QMessageBox,
    QFileDialog,
    QInputDialog
)

import get_data
import creator
from dialogs import ConsoleDialog, ProgBarDialog
from creator import InstallWorker
from manage_pip import PipManager
from platforms import get_platform


logger = logging.getLogger(__name__)



class BaseTable(QTableView):
    """The base table for all tables.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.delete_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_TrashIcon)
        )
        self.info_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogInfoView)
        )

    def get_selected_item(self):
        """
        Return `str` from `name` column of the selected row.
        """
        listed_items = self.selectionModel().selectedRows()
        for index in listed_items:
            selected_item = index.data()
            return selected_item


    def get_comment(self):
        """
        Return `str` from `comment` column of the selected row.
        """
        index = self.currentIndex()
        row_index = self.selectionModel().selectedRows()
        row_comment = index.sibling(row_index[0].row(), 4).data()
        return row_comment



class VenvTable(BaseTable):
    """List the virtual environments found.
    """
    started = pyqtSignal()
    finished = pyqtSignal()
    text_changed = pyqtSignal(str)
    refresh = pyqtSignal()
    start_installer = pyqtSignal()
    start_pkg_manager = pyqtSignal()


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.drive_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_DriveHDIcon)
        )
        self.delete_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_TrashIcon)
        )
        self.save_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton)
        )
        self.folder_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_DirOpenIcon)
        )
        self.info_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogInfoView)
        )
        self.reload_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_BrowserReload)
        )
        self.desk_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_DesktopIcon)
        )
        self.computer_icon = QIcon(
            self.style().standardIcon(QStyle.StandardPixmap.SP_ComputerIcon)
        )

        self.progress_bar = ProgBarDialog()
        self.console = ConsoleDialog()
        self.thread = QThread(self)
        self.m_install_worker = InstallWorker()

        # thread
        self.thread.start()
        self.m_install_worker.moveToThread(self.thread)

        self.m_install_worker.started.connect(self.console.exec)
        self.m_install_worker.text_changed.connect(
            self.console.update_status
        )

        # perform a proper stop using quit() and wait()
        self.thread.finished.connect(self.thread.quit)
        self.thread.finished.connect(self.thread.wait)


    def contextMenuEvent(self, event):

        idx = self.indexAt(event.pos())
        if not idx.isValid():
            return

        self.selectRow(idx.row())

        #]===================================================================[#
        #] ACTIONS [#========================================================[#
        #]===================================================================[#

        upgrade_pip_action = QAction(
            self.reload_icon,
            "&Upgrade Pip to latest",
            self,
            statusTip="Upgrade Pip to the latest version"
        )
        upgrade_pip_action.triggered.connect(
            lambda: self.upgrade_pip(event, pkg="pip")
        )

        install_wheel_action = QAction(
            self.desk_icon,
            "Install &Wheel",
            self,
            statusTip="Install wheel package"
        )
        install_wheel_action.triggered.connect(
            lambda: self.install_wheel(event, pkg="wheel")
        )

        manage_packages_action = QAction(
            "&Manage installed packages",
            self,
            statusTip="Manage installed packages"
        )
        manage_packages_action.triggered.connect(
            lambda: self.manage_packages(event)
        )

        install_packages_action = QAction(
            "Install &packages from PyPI",
            self,
            statusTip="Install packages from PyPI"
        )
        install_packages_action.triggered.connect(
            lambda: self.install_pypi_packages(event)
        )

        install_requires_action = QAction(
            "Install from a &requirements file",
            self,
            statusTip="Install packages from requirements"
        )
        install_requires_action.triggered.connect(
            lambda: self.install_requires(event)
        )

        install_local_action = QAction(
            "Install from a &local project directory",
            self,
            statusTip="Install from a local stored project directory"
        )
        install_local_action.triggered.connect(
            lambda: self.install_local(event)
        )

        install_vcs_action = QAction(
            "Install from a &VCS project url",
            self,
            statusTip="Install from a VCS url"
        )
        install_vcs_action.triggered.connect(
            lambda: self.install_vcs(event)
        )

        save_requires_action = QAction(
            self.save_icon,
            "Save &requirements",
            self,
            statusTip="Generate a requirements file"
        )
        save_requires_action.triggered.connect(
            lambda: self.save_requires(event)
        )

        comment_add_action = QAction(
            self.info_icon,
            "&Add or modify description",
            self,
            statusTip="Add or modify description"
        )
        comment_add_action.triggered.connect(
            lambda: self.comment_add(event)
        )

        comment_remove_action = QAction(
            self.info_icon,
            "&Remove description",
            self,
            statusTip="Remove description"
        )
        comment_remove_action.triggered.connect(
            lambda: self.comment_remove(event)
        )

        list_packages_action = QAction(
            "&List installed packages",
            self,
            statusTip="List installed packages"
        )
        list_packages_action.triggered.connect(
            lambda: self.list_packages(event, style=1)
        )

        list_freeze_action = QAction(
            "Show &freeze output",
            self,
            statusTip="List the output of 'pip freeze'"
        )
        list_freeze_action.triggered.connect(
            lambda: self.freeze_packages(event, style=2)
        )

        list_deptree_action = QAction(
            "Display &dependency tree",
            self,
            statusTip="List dependencies with 'pipdeptree'"
        )
        list_deptree_action.triggered.connect(
            lambda: self.deptree_packages(event, style=3)
        )

        open_venv_dir_action = QAction(
            self.folder_icon,
            "&Open containing folder",
            self,
            statusTip="Open the folder containing the virtual environment"
        )
        open_venv_dir_action.triggered.connect(
            lambda: self.open_venv_dir(event)
        )

        open_terminal_action = QAction(
            self.desk_icon,
            "Open &terminal (activated)",
            self,
            statusTip="Open a terminal with this environment activated"
        )
        open_terminal_action.triggered.connect(lambda: self.open_terminal(event))

        delete_venv_action = QAction(
            self.delete_icon,
            "Delete &environment",
            self,
            statusTip="Delete environment"
        )
        delete_venv_action.triggered.connect(
            lambda: self.delete_venv(event)
        )

        #]===================================================================[#
        #] MENUS [#==========================================================[#
        #]===================================================================[#

        context_menu = QMenu(self)

        details_sub_menu = QMenu("Det&ails", self, icon=self.info_icon)
        manage_sub_menu  = QMenu("&Manage", self, icon=self.computer_icon)
        comment_sub_menu = QMenu("&Description", self, icon=self.info_icon)

        context_menu.addAction(upgrade_pip_action)
        context_menu.addAction(install_wheel_action)

        context_menu.addMenu(manage_sub_menu)
        manage_sub_menu.addAction(install_packages_action)
        manage_sub_menu.addAction(install_requires_action)
        manage_sub_menu.addAction(install_local_action)
        manage_sub_menu.addAction(install_vcs_action)

        context_menu.addMenu(details_sub_menu)
        details_sub_menu.addAction(list_packages_action)
        details_sub_menu.addAction(list_freeze_action)

        context_menu.addMenu(comment_sub_menu)
        comment_sub_menu.addAction(comment_add_action)
        comment_sub_menu.addAction(comment_remove_action)

        context_menu.addAction(save_requires_action)
        context_menu.addAction(open_venv_dir_action)
        context_menu.addAction(open_terminal_action)
        context_menu.addAction(delete_venv_action)

        context_menu.exec(event.globalPos())


    def valid_version(self, venv_path):
        """Test wether the Python version required is installed.
        """
        cfg_file = os.path.join(venv_path, "pyvenv.cfg")
        is_installed = get_data.get_config(cfg_file, "installed")
        version = get_data.get_config(cfg_file, "version")
        py_path = get_data.get_config(cfg_file, "py_path")
        msg_txt = (
            f"This environment requires {version} \n"
            f"from {py_path} which is \nnot installed.\n"
        )

        if is_installed == "no":
            msg_box = QMessageBox(
                QMessageBox.Icon.Critical,
                "Error",
                msg_txt,
                QMessageBox.StandardButton.Ok,
                self
            )
            msg_box.exec()
            return False
        return True


    def venv_exists(self, path):
        """
        Test wether the directory of the selected environment exists.
        """
        if os.path.exists(path):
            return True

        msg_box = QMessageBox(
            QMessageBox.Icon.Critical,
            "Error",
            "Selected environment could not be found.",
            QMessageBox.StandardButton.Ok,
            self
        )
        msg_box.exec()

        # refresh venv table
        self.refresh.emit()
        return False


    def has_pip(self, venv_dir, venv_name):
        platform = get_platform()
        venv_path = Path(venv_dir) / venv_name

        if not self.venv_exists(venv_path):
            return False

        if not self.valid_version(venv_path):
            return False

        venv_python = platform.venv_python_path(venv_path)
        if not venv_python.exists():
            QMessageBox.information(self, "Info", "Python binary not found in this environment.")
            return False

        try:
            result = subprocess.run(
                [str(venv_python), "-m", "pip", "--version"],
                capture_output=True,
                check=False,          # <-- wichtig
                text=True,
                encoding="utf-8",
                errors="replace",
            )

        except OSError as e:
            QMessageBox.information(self, "Info", f"Could not run Python:\n{e}")
            return False

        if result.returncode == 0:
            return True

        details = (result.stderr or result.stdout or "").strip()
        logger.debug(f"pip --version failed (rc={result.returncode}):\n{details}")
        QMessageBox.information(self, "Info", "This environment has no Pip installed.")
        return False


    def upgrade_pip(self, event, pkg):
        """Run `pip install --upgrade pip` command.
        """
        self.install_sys_pkg(pkg)


    def install_wheel(self, event, pkg):
        """Run `pip install --upgrade wheel` command.
        """
        self.install_sys_pkg(pkg)


    def install_sys_pkg(self, pkg):
        """Run `pip install --upgrade <pkg>` command.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()

        if self.has_pip(active_dir, venv):
            self.console.setWindowTitle(f"Installing latest {pkg}")
            logger.debug(f"Installing latest version of {pkg}...")

            self.manager = PipManager(active_dir, venv)
            self.manager.started.connect(self.console.exec)
            self.manager.text_changed.connect(self.console.update_status)

            self.manager.run_pip(creator.cmds[0], [creator.opts[0], f"{pkg}"])

            # clear the content on window close
            if self.console.close:
                self.console.console_window.clear()



    def manage_packages(self, event):
        """Manage installed packages of the selected environment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_path = Path(active_dir) / venv

        if self.has_pip(active_dir, venv):
            with open(get_data.ACTIVE_VENV, "w", encoding="utf-8") as f:
                f.write(str(venv_path))

            self.start_pkg_manager.emit()


    def install_pypi_packages(self, event):
        """
        Install packages from [PyPI](https://pypi.org)
        into the selected environment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_path = Path(active_dir) / venv

        if self.has_pip(active_dir, venv):
            with open(get_data.ACTIVE_VENV, "w", encoding="utf-8") as f:
                f.write(str(venv_path))

            self.start_installer.emit()


    def install_requires(self, event):
        """
        Install packages from a requirements file into the
        selected environment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        platform = get_platform()
        venv_bin = platform.venv_python_path(Path(active_dir) / venv)

        if self.has_pip(active_dir, venv):
            file_name = QFileDialog.getOpenFileName(
                self,
                "Specify a requirements file"
            )
            file_path = file_name[0]

            if file_path != "":
                creator.fix_requirements(file_path)
                logger.debug("Installing from requirements...")
                cmd = [
                    str(venv_bin),
                    "-m",
                    "pip",
                    *shlex.split(creator.cmds[0]),
                    creator.opts[1],
                    file_path,
                ]
                self.console.setWindowTitle(
                    "Installing from requirements file"
                )
                wrapper = partial(self.m_install_worker.run_process, cmd)
                QTimer.singleShot(0, wrapper)

                # clear the content on window close
                if self.console.close:
                    self.console.console_window.clear()


    def install_local(self, event):
        """Install from a local project directory.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()

        if self.has_pip(active_dir, venv):
            project_dir = QFileDialog.getExistingDirectory(
                self,
                "Select project directory"
            )
            project_name = os.path.basename(project_dir)

            if project_dir != "":
                if get_data.is_writable(project_dir):
                    self.console.setWindowTitle(f"Installing {project_name}")
                    logger.debug("Installing from local project directory...")

                    self.manager = PipManager(active_dir, venv)
                    self.manager.started.connect(self.console.exec)
                    self.manager.text_changed.connect(self.console.update_status)
                    self.manager.run_pip(
                        creator.cmds[0], [creator.opts[3], project_dir]
                    )

                    # clear the content on window close
                    if self.console.close:
                        self.console.console_window.clear()

                else:
                    msg_txt = (
                        "Filesystem not writable.\n\n"
                        "Unable to create the '.egg-info'      \n"
                        "folder inside the project directory.      \n\n"
                    )
                    QMessageBox.warning(self, "Abort", msg_txt)


    def install_vcs(self, event):
        """Install from VCS.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        platform = get_platform()
        venv_bin = platform.venv_python_path(Path(active_dir) / venv)

        if self.has_pip(active_dir, venv):
            url, ok = QInputDialog.getText(
                self,
                "Specify project",
                "Enter link to repository:" + " " * 80
            )

            if url != "":
                self.vcs_project_url = url[:-4] if url.endswith(".git") else url
                self.vcs_project_name = os.path.basename(self.vcs_project_url)
                formatted_project_url = (
                    f"git+{self.vcs_project_url}#egg={self.vcs_project_name}"
                )
                cmd = [
                    str(venv_bin),
                    "-m",
                    "pip",
                    *shlex.split(creator.cmds[0]),
                    formatted_project_url,
                ]
                self.console.setWindowTitle(
                    f"Installing {self.vcs_project_name}"
                )
                wrapper = partial(self.m_install_worker.run_process, cmd)
                QTimer.singleShot(0, wrapper)

                # clear the content on window close
                if self.console.close:
                    self.console.console_window.clear()


    def save_requires(self, event):
        """
        Pip freeze a requirements of the selected environment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_dir = Path(active_dir) / venv

        if self.has_pip(active_dir, venv):
            save_file = QFileDialog.getSaveFileName(
                self,
                "Save requirements",
                directory=str(venv_dir / "requirements.txt")
            )
            save_path = save_file[0]

            if not save_path:
                return

            platform = get_platform()
            venv_python = platform.venv_python_path(venv_dir)
            try:
                with open(save_path, "w", encoding="utf-8") as f:
                    subprocess.run([
                        str(venv_python),
                        "-m",
                        "pip",
                        "freeze"],
                        stdout=f,
                        check=False,
                    )
            except subprocess.CalledProcessError as e:
                logger.debug(f"Failed to save requirements: {e}")

            logger.debug(f"Saved '{save_path}'")
            message_txt = f"Saved requirements in \n{save_path}"
            QMessageBox.information(self, "Saved", message_txt)

            # comment the 'pkg_resources==0.0.0' entry
            # DEPRECATED: now handled in manage_pip.py
            #creator.fix_requirements(save_path)


    def list_packages(self, event, style):
        """
        Open console dialog and list the installed packages. The argument
        `style` controls which style the output should have: `style=1` for
        `pip list`, `style=2` for `pip freeze` and style=3 for a dependency
        output via `pipdeptree`.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()

        if self.has_pip(active_dir, venv):
            self.console.setWindowTitle(f"Packages in: {venv}")

            self.manager = PipManager(active_dir, venv)
            self.manager.started.connect(self.console.exec)
            self.manager.text_changed.connect(self.console.update_status)

            self.manager.run_pip(creator.cmds[style])

            # clear the content on window close
            if self.console.close:
                self.console.console_window.clear()


    def freeze_packages(self, event, style):
        """Print `pip freeze` output to console window.
        """
        self.list_packages(event, style)


    def deptree_packages(self, event, style):
        """
        Test if `pipdeptree` is installed and ask user wether to
        install it if it's not. Then call `self.list_packages()`
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        platform = get_platform()
        venv_python = platform.venv_python_path(Path(active_dir) / venv)
        pipdeptree_result = subprocess.run(
            [str(venv_python), "-m", "pipdeptree", "--version"],
            capture_output=True,
            check=False,
            text=True,
            encoding="utf-8",
            errors="replace",
        )
        has_pipdeptree = pipdeptree_result.returncode == 0
        message_txt = (
            "This requires pipdeptree             \nto be installed.\n\n"
            "Do you want to install it?\n"
        )

        if has_pipdeptree:
            self.list_packages(event, style)
        else:
            if self.has_pip(active_dir, venv):
                msg_box_confirm = QMessageBox.question(
                    self,
                    "Confirm",
                    message_txt,
                    QMessageBox.StandardButton.Yes
                    | QMessageBox.StandardButton.Cancel
                )
                if msg_box_confirm == QMessageBox.StandardButton.Yes:
                    self.progress_bar.setWindowTitle("Installing")
                    self.progress_bar.status_label.setText(
                        "Installing pipdeptree..."
                    )
                    logger.debug("Installing pipdeptree...")

                    self.manager = PipManager(active_dir, venv)
                    self.manager.run_pip(
                        creator.cmds[0], [creator.opts[0], "pipdeptree"]
                    )
                    self.manager.started.connect(self.progress_bar.exec)
                    self.manager.finished.connect(self.progress_bar.close)
                    self.manager.process_stop()
                    self.list_packages(event, style)


    def comment_add(self, event):
        """Add / modify a comment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        comment_old = self.get_comment()
        venvipy_cfg = os.path.join(active_dir, venv, "venvipy.cfg")

        if not os.path.exists(venvipy_cfg):
            comment_new, ok = QInputDialog.getText(
                self,
                "Enter description",
                "\nEnter a description:" + " " * 70
            )
            if len(comment_new) > 0:
                # save a new description
                creator.save_comment(venvipy_cfg, comment_new)
                logger.debug("Description saved")

        else:
            comment_new, ok = QInputDialog.getText(
                self,
                "Modify description",
                "\nModify your description:" + " " * 70,
                text=comment_old
            )
            if len(comment_new) > 0:
                # modify an existing description
                creator.save_comment(venvipy_cfg, comment_new)
                logger.debug("Description updated")
            else:
                # keep the old description
                creator.save_comment(venvipy_cfg, comment_old)

        # refresh venv table
        self.refresh.emit()


    def comment_remove(self, event):
        """Remove comment.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venvipy_cfg = os.path.join(active_dir, venv, "venvipy.cfg")

        if os.path.exists(venvipy_cfg):
            msg_box_warning = QMessageBox.warning(
                self,
                "Remove comment",
                "Delete this comment.        \n"
                "Are you sure?\n",
                QMessageBox.StandardButton.Yes
                | QMessageBox.StandardButton.Cancel
            )
            if msg_box_warning == QMessageBox.StandardButton.Yes:
                os.remove(venvipy_cfg)
                logger.debug(f"Successfully deleted '{venvipy_cfg}'")
                self.refresh.emit()


    def open_venv_dir(self, event):
        """Open the selected venv directory.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_dir = Path(active_dir) / venv

        if venv_dir.is_dir():
            platform = get_platform()
            if platform.is_windows():
                os.startfile(str(venv_dir))
            else:
                subprocess.run(["xdg-open", str(venv_dir)], check=False)

 
    def open_terminal(self, event):
        """Open a terminal with the selected environment activated.

        Note: there is no single "default terminal" API on Linux, so this is
        a best-effort launcher for common terminal emulators.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_dir = Path(active_dir) / venv

        if not self.venv_exists(str(venv_dir)):
            return
        
        if not self.valid_version(str(venv_dir)):
            return

        platform = get_platform()

        if platform.is_windows():
            try:
                platform.open_activated_terminal(
                    venv_dir=venv_dir,
                    workdir=Path.home(),
                    env=env,
                    banner=True,
                )
            except Exception as e:
                QMessageBox.information(
                    self, "Info", f"Failed to open terminal: {e}"
                )
            return

        activate = platform.activate_script_path(venv_dir)

        if not activate.exists():
            QMessageBox.information(
                self, "Info", "Activate script not found in this environment."
            )
            return

        workdir = Path.home()

        # real rcfile -> works reliably from Python
        fd, rc_path = tempfile.mkstemp(prefix="venvipy-", suffix=".bashrc")
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(f"source {shlex.quote(str(activate))}\n")
            f.write(f"cd {shlex.quote(str(workdir))}\n\n")
            f.write("# quick self-checks\n")
            f.write("echo Active environment: \"$VIRTUAL_ENV\"\n")
            f.write("echo Python interpreter: \"$(command -v python)\"\n")
            f.write("echo ' '\n")
        os.chmod(rc_path, 0o600)

        env = os.environ.copy()

        # remove inherited venv (the one VenviPy was launched with)
        dev_venv = env.pop("VIRTUAL_ENV", None)
        for k in ("VIRTUAL_ENV_PROMPT", "_OLD_VIRTUAL_PATH", "_OLD_VIRTUAL_PYTHONHOME", "PYTHONHOME"):
            env.pop(k, None)

        # optional: conda leftovers
        for k in ("CONDA_PREFIX", "CONDA_DEFAULT_ENV", "CONDA_SHLVL"):
            env.pop(k, None)

        # also remove the dev venv bin prefix from PATH (otherwise python/pip may still resolve to dev)
        if dev_venv:
            dev_bin = str(Path(dev_venv) / platform.venv_bin_dir_name())
            parts = env.get("PATH", "").split(os.pathsep)
            env["PATH"] = os.pathsep.join(
                p for p in parts
                if Path(p).expanduser().resolve() != Path(dev_bin).expanduser().resolve()
            )

        shell = env.get("SHELL", "/bin/bash")
        shell_name = Path(shell).name

        if shell_name == "zsh" and shutil.which("zsh"):
            zd = tempfile.mkdtemp(prefix="venvipy-zsh-")
            zrc = Path(zd) / ".zshrc"

            zrc.write_text(
                (
                    '[[ -f ~/.zshrc ]] && source ~/.zshrc\n'
                    f"source {shlex.quote(str(activate))}\n"
                    f"cd {shlex.quote(str(workdir))}\n"
                    'echo VIRTUAL_ENV="$VIRTUAL_ENV"\n'
                    "which python\n"
                ),
                encoding="utf-8",
            )
            env["ZDOTDIR"] = zd
            shell_cmd = ["zsh", "-i"]

        else:
            # fallback: your existing bash --rcfile method
            shell_cmd = ["bash", "--rcfile", rc_path, "-i"]

        candidates = [
            ["xfce4-terminal", "-x", *shell_cmd],
            ["gnome-terminal", "--", *shell_cmd],
            ["konsole", "-e", *shell_cmd],
            ["mate-terminal", "--", *shell_cmd],
            ["x-terminal-emulator", "-e", *shell_cmd],
            ["qterminal", "-e", *shell_cmd],
            ["terminator", "-x", *shell_cmd],
            ["alacritty", "-e", *shell_cmd],
            ["kitty", *shell_cmd],
            ["xterm", "-e", *shell_cmd],
        ]

        last_err = None

        for cmd in candidates:
            if not shutil.which(cmd[0]):
                continue

            try:
                subprocess.Popen(cmd, start_new_session=True, env=env)
                return

            except Exception as e:
                last_err = e

        if last_err:
            logger.debug(f"Terminal launch failed: {last_err}")

        QMessageBox.information(
            self,
            "Info",
            "No supported terminal emulator found. Install one and try again."
        )


    def delete_venv(self, event):
        """
        Delete the selected virtual environment by clicking
        delete from the context menu in venv table.
        """
        active_dir = get_data.get_active_dir_str()
        venv = self.get_selected_item()
        venv_path = os.path.join(active_dir, venv)

        if self.venv_exists(venv_path):
            msg_box_critical = QMessageBox.critical(
                self,
                "Delete venv",
                f"Delete '{venv}'?           \n"
                "Are you sure? This can't be undone.\n",
                QMessageBox.StandardButton.Yes
                | QMessageBox.StandardButton.Cancel
            )
            if msg_box_critical == QMessageBox.StandardButton.Yes:
                shutil.rmtree(venv_path)
                logger.debug(f"Successfully deleted '{venv_path}'")
                self.refresh.emit()



class InterpreterTable(BaseTable):
    """
    List the Python installs found.
    """
    drop_item = pyqtSignal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


    def contextMenuEvent(self, event):
        idx = self.indexAt(event.pos())
        if not idx.isValid():
            return

        self.selectRow(idx.row())

        context_menu = QMenu(self)

        remove_py_action = QAction(
            self.delete_icon,
            "&Remove from list",
            self,
            statusTip="Remove this item from the table"
        )
        remove_py_action.triggered.connect(lambda: QTimer.singleShot(0, lambda: self.remove_python(event)))
        context_menu.addAction(remove_py_action)

        context_menu.exec(event.globalPos())


    def remove_python(self, event):
        """Remove a Python version from the table.
        """
        item = self.get_selected_item()

        msg_box_warning = QMessageBox.warning(
            self,
            "Confirm",
            "Remove this item from list.       \n"
            "Are you sure?\n",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel
        )
        if msg_box_warning == QMessageBox.StandardButton.Yes:
            with open(get_data.DB_FILE, "r", encoding="utf-8") as f:
                lines = f.readlines()
            with open(get_data.DB_FILE, "w", encoding="utf-8") as f:
                for line in lines:
                    if item not in line:
                        f.write(line)

            logger.debug(f"Removed '{item}' from database")
            self.drop_item.emit()
