#!.venv/bin/python
"""Khadhroony SRLPv4 main script"""

import argparse
import base64
import gettext
import json
import locale
import os
import platform
import portalocker
import random
import re
import rc_doc
import rc_icons
import rc_images
import sys
import yaml

from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timezone
from loguru import logger
from PySide6 import __version__ as pyside6version
from PySide6.QtCore import (
    QAbstractTableModel,
    QEasingCurve,
    QEvent,
    QMargins,
    QModelIndex,
    QObject,
    QPropertyAnimation,
    Qt,
    QTimer,
    Signal
)
from PySide6.QtGui import (
    QAction,
    QColor,
    QCursor,
    QIcon,
    QKeyEvent,
    QLinearGradient,
    QPainter,
    QPixmap,
    QFont
)
from PySide6.QtPdf import QPdfDocument
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import (
    QApplication,
    QDialog,
    QDialogButtonBox,
    QFileDialog,
    QFormLayout,
    QGraphicsOpacityEffect,
    QGridLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QMenu,
    QMessageBox,
    QSplashScreen,
    QPushButton,
    QSystemTrayIcon,
    QTableView,
    QTabWidget,
    QToolBar,
    QVBoxLayout,
    QWidget,
    QAbstractItemView,
    QHeaderView
)
from solders.keypair import Keypair
from typing import Any, IO

APP_NAME: str = "KhadhroonySRLPv4"
"""Application Name"""
APP_ABOUT_NAME: str = "Khadhroony Sol RLPv4 Trading App"
"""Application Full Name"""
APP_FULL_NAME: str = "Khadhroony Solana Raydium Liquidity Pool v4 Trading App"
"""Application Full Name"""
APP_DESC: str = "Khadhroony Solana Raydium Liquidity Pool v4 Trading Application"
"""Application Version"""
APP_VERSION_INFO = ('0', '0', '2')
APP_VERSION = '.'.join(APP_VERSION_INFO)
"""Application Name + Version"""
APP_NAME_VERSION = "Khadhroony Solana Raydium Liquidity Pool v4 Trading Application @ " + APP_VERSION
"""Application Langs"""
APP_LANGS = ["en", "fr", "ar"]
APP_RTL_LANGS = [
    "ae",  # avestique
    "aeb",  # arabe tunisien
    "aec",  # arabe saïdi
    "ar",  # arabe
    "arb",  # arabe standard moderne
    "arc",  # araméen
    "arq",  # arabe algérien
    "ary",  # arabe marocain
    "arz",  # arabe égyptien
    "ayl",  # arabe libyen
    "ckb",  # sorani
    "dv",  # divéhi (maldivien)
    "fa",  # persan
    "glk",  # guilaki
    "he",  # hébreu
    "khw",  # khowar
    "mzn",  # mazandarani
    "ota",  # turc ottoman
    "otk",  # vieux turc
    "pnb",  # pendjabi occidental
    "ps",  # pachto
    "syr",  # syriaque
    "tmr",  # judéo-araméen babylonien
    "ug",  # ouïghour
    "ur"  # ourdou
]
"""Application Salt for wallet encrypting"""
APP_SALT = APP_NAME + " @ " + APP_VERSION_INFO[0] + "." + APP_VERSION_INFO[1]

YAML_CONFIG_FILE = "KhadhroonySRLPv4.yaml"

DEFAULT_YAML_CONFIG = {
    "lastFile": "",
    "lastFiles": [],
    "defaultLang": "en",
    "lastWidth": 1024,
    "lastHeight": 768,
    "lastMaximized": True
}


WALLET_FILE_EXT = "kew"
WALLET_FILE_DESC = "Khadhroony Encrypted Wallet"
WALLETS_FOLDER = "./wallets"

LOCALES_DIR = os.path.join(os.path.dirname(__file__), "locale")
_ = gettext.gettext
is_rtl = False


def load_yaml_app_config():
    """Load the configuration file or create a default one."""
    if not os.path.exists(YAML_CONFIG_FILE):
        logger.debug(f"{YAML_CONFIG_FILE} not found, creating new one")
        with open(YAML_CONFIG_FILE, "w") as f:
            yaml.dump(DEFAULT_YAML_CONFIG, f)
        return DEFAULT_YAML_CONFIG
    with open(YAML_CONFIG_FILE, "r") as f:
        data = yaml.safe_load(f)
        logger.debug(f"{YAML_CONFIG_FILE} safe loaded")
    # Validation et complétion des données
    for key, val in DEFAULT_YAML_CONFIG.items():
        if key not in data:
            logger.debug(f"{key} not found")
            data[key] = val
        elif not isinstance(data[key], type(val)):
            logger.debug(f"{key} wrong type : wanted {type(val)}, found {type(data[key])}")
            data[key] = val
    return data


def save_yaml_app_config(cfg):
    """Save the configuration to the file."""
    with open(YAML_CONFIG_FILE, "w") as f:
        yaml.dump(cfg, f)


def create_wallets_folder():
    """Vérifie si le répertoire wallets existe et le créé dans le cas contraire."""
    if not os.path.exists(WALLETS_FOLDER):
        os.makedirs(WALLETS_FOLDER)


def derive_key(password: str, salt: bytes) -> bytes:
    """
    Derive a symmetric key from the given password and salt.
    :param password: The password to derive the key from.
    :param salt: A random salt for the key derivation.
    :return: The derived key.
    """
    kdf = PBKDF2HMAC(
        algorithm = hashes.SHA256(),
        length = 32,
        salt = salt,
        iterations = 100_000,
        backend = default_backend(),
    )
    return base64.urlsafe_b64encode(kdf.derive(password.encode()))


def encrypt_string(password: str, salt: bytes, plaintext: str) -> str:
    """
    Encrypts a string using a password.
    :param password: The password to derive the encryption key.
    :param salt: A salt for the key derivation.
    :param plaintext: The string to encrypt.
    :return: A tuple containing the salt and the encrypted string.
    """
    key = derive_key(password, salt)  # Derive the encryption key
    fernet = Fernet(key)
    encrypted_text = fernet.encrypt(plaintext.encode())
    return encrypted_text.decode()


def center_on_screen(widget: QWidget):
    """Centre la fenêtre sur le moniteur actif (celui où le curseur est présent)."""
    screen = QApplication.screenAt(QCursor().pos())  # Obtenir le moniteur actif
    if not screen:
        screen = QApplication.primaryScreen()  # Par défaut, utiliser l'écran principal

    screen_geometry = screen.availableGeometry()  # Dimensions de l'écran
    widget_geometry = widget.frameGeometry()
    # Calcul des coordonnées pour centrer la fenêtre
    x = screen_geometry.x() + (screen_geometry.width() - widget_geometry.width()) // 2
    y = screen_geometry.y() + (screen_geometry.height() - widget_geometry.height()) // 2
    widget.move(x, y)


def decrypt_string(password: str, salt: bytes, encrypted_text: bytes | str) -> str:
    """
    Decrypts an encrypted string using the given password and salt.
    :param password: The password to derive the decryption key.
    :param salt: The salt used during encryption.
    :param encrypted_text: The encrypted string to decrypt.
    :return: The decrypted string.
    """
    enc_text = encrypted_text
    if isinstance(encrypted_text, str):
        enc_text = encrypted_text.encode()
    key = derive_key(password, salt)  # Derive the decryption key
    fernet = Fernet(key)
    return fernet.decrypt(enc_text).decode()


def get_system_language() -> str:
    """Get the system language."""
    global is_rtl
    # sys_locale = locale.getdefaultlocale()[0]
    sys_locale = locale.getlocale()[0]
    default_lang = 'en'
    if sys_locale:
        lang_split = sys_locale.split("_")[0]  # Extract language code
        if lang_split in APP_LANGS:
            if lang_split in APP_RTL_LANGS:
                is_rtl = True
            else:
                is_rtl = False
            return lang_split
    is_rtl = False
    return default_lang


def get_lang_or_fallback(lang: str) -> str:
    global is_rtl
    if lang in APP_LANGS:
        if lang in APP_RTL_LANGS:
            is_rtl = True
        else:
            is_rtl = False
        return lang
    return get_system_language()


def set_app_lang_code(lang_code):
    """Set the application language."""
    global _
    global is_rtl
    try:
        lang = gettext.translation("app", localedir = LOCALES_DIR, languages = [lang_code])
        lang.install()
        _ = lang.gettext
        if lang_code in APP_RTL_LANGS:
            is_rtl = True
        else:
            is_rtl = False
    except FileNotFoundError:
        # Fallback to default language (English)
        gettext.install("app", localedir = LOCALES_DIR, names = ["en"])
        _ = gettext.gettext
        is_rtl = False


def get_qt_dir() -> Qt.LayoutDirection:
    global is_rtl
    return Qt.LayoutDirection.RightToLeft if is_rtl else Qt.LayoutDirection.LeftToRight


def get_qt_align_center() -> Qt.AlignmentFlag:
    return Qt.AlignmentFlag.AlignCenter


def get_qt_align_vchl(rtl:bool = False) -> Qt.AlignmentFlag:
    if rtl:
        return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight
    else:
        return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft


def get_qt_align_vchr(rtl:bool = False) -> Qt.AlignmentFlag:
    if rtl:
        return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft
    else:
        return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight


def get_qt_align_hcenter() -> Qt.AlignmentFlag:
    return Qt.AlignmentFlag.AlignHCenter


def get_qt_align_hl(rtl:bool = False) -> Qt.AlignmentFlag:
    if rtl:
        return Qt.AlignmentFlag.AlignRight
    else:
        return Qt.AlignmentFlag.AlignLeft


def get_qt_align_hr(rtl:bool = False) -> Qt.AlignmentFlag:
    if rtl:
        return Qt.AlignmentFlag.AlignLeft
    else:
        return Qt.AlignmentFlag.AlignRight

class LockedFile:
    """
    Une classe pour gérer les fichiers avec verrouillage exclusif.
    Utilise portalocker pour éviter les accès simultanés.
    """

    def __init__(self, file_path, mode = "r"):
        """
        Initialise un Verrou.
        
        :param file_path: Chemin vers le fichier à verrouiller.
        :param mode: Mode d'ouverture du fichier ('r', 'w', 'a', etc.).
        """
        self._file = None
        self._filePath = file_path
        self._mode = mode

    def __enter__(self) -> IO[Any]:
        """
        Ouvre et verrouille le fichier.
        """
        self._file = open(file = self._filePath, mode = self._mode)
        # Application d'un verrou exclusif
        portalocker.lock(self._file, portalocker.LOCK_EX)
        return self._file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Libère le verrou et ferme le fichier.
        """
        try:
            portalocker.unlock(self._file)
        finally:
            self._file.close()


class SplashScreen(QSplashScreen):
    """SplashScreen class."""

    def __init__(self, fade_in_duration = 1500, display_duration = 2000, fade_out_duration = 1500):
        super().__init__(QPixmap(":imgSplash"))
        # logger.info(f"SplashScreen {Consts.APP_FULL_NAME} ({Consts.APP_VERSION})")
        self.setWindowFlag(Qt.WindowType.FramelessWindowHint)  # Supprimer les boutons de fermeture
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        # Appliquer l'effet d'opacité pour gérer les transitions
        self._opacityEffect = QGraphicsOpacityEffect(self)
        self.setGraphicsEffect(self._opacityEffect)
        # Animation Fade-in
        self._fadeIn = QPropertyAnimation(self._opacityEffect, b"opacity")
        self._fadeIn.setDuration(fade_in_duration)
        self._fadeIn.setStartValue(0)
        self._fadeIn.setEndValue(1)
        self._fadeIn.setEasingCurve(QEasingCurve.Type.InOutQuad)
        # Animation Fade-out
        self.fadeOut = QPropertyAnimation(self._opacityEffect, b"opacity")
        self.fadeOut.setDuration(fade_out_duration)
        self.fadeOut.setStartValue(1)
        self.fadeOut.setEndValue(0)
        self.fadeOut.setEasingCurve(QEasingCurve.Type.InOutQuad)
        # Planification de l'animation fade-out après la durée
        QTimer.singleShot(display_duration, self.start_fade_out)
        # Lancer l'animation fade-in
        self._fadeIn.start()

    def start_fade_out(self):
        """Lancer l'animation fade-out et fermer le splash screen à la fin."""
        self.fadeOut.start()
        self.fadeOut.finished.connect(self.close)

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)


class AboutWindow(QWidget):

    def __init__(self, bg_txt:str, parent = None):
        super().__init__(parent)
        self.setWindowTitle(_("About %(app)s") % {'app': APP_NAME_VERSION})
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setFixedSize(900, 600)
        self.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint)
        self._bg_Qpix = QPixmap(":imgAbout")
        # Texte centré placé avant le bouton de fermeture pour ne pas empêcher le click de celui-là
        self._about_Qlbl = QLabel(self)
        self._about_Qlbl.setText(bg_txt)
        self._about_Qlbl.setAlignment(Qt.AlignmentFlag.AlignCenter)  # Centrer le texte
        self._about_Qlbl.setStyleSheet("font-size: 20px; color: white;")  # Style du texte
        self._about_Qlbl.setWordWrap(True)  # Activer le retour à la ligne
        self._about_Qlbl.setGeometry(0, 0, self.width(), self.height())  # Occuper toute la fenêtre
        # Bouton de fermeture de la fenetre
        self._exit_Qpbtn = QPushButton(self)
        self._exit_Qpbtn.setIcon(QIcon(":icoQuit"))
        self._exit_Qpbtn.setFixedSize(24, 24)
        self._exit_Qpbtn.setGeometry(self.width() - 44, 20, 24, 24)  # Position et taille
        self._exit_Qpbtn.clicked.connect(self.close)
        self._exit_Qpbtn.move(self.width() - self._exit_Qpbtn.width() - 20, 20)

        # self.outsideClickFilter = OutsideCloseClickFilter(self)
        # QApplication.instance().installEventFilter(self.outsideClickFilter)

    def closeEvent(self, event) -> None:
        # QApplication.instance().removeEventFilter(self.outsideClickFilter)
        super().closeEvent(event)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
        # Redimensionner l'image pour qu'elle corresponde à la taille de la fenêtre
        scaled_image = self._bg_Qpix.scaled(
            self.size(),
            Qt.AspectRatioMode.IgnoreAspectRatio,
            Qt.TransformationMode.SmoothTransformation,
        )
        painter.drawPixmap(0, 0, scaled_image)
        painter.setOpacity(0.7)
        # Ajouter un effet de dégradé
        gradient = QLinearGradient(0, 0, 0, self.height())
        gradient.setColorAt(0, QColor(0, 0, 0, 0))  # Transparent en haut
        gradient.setColorAt(1, QColor(0, 0, 0, 128))  # Sombre en bas
        painter.fillRect(self.rect(), gradient)

    def event(self, event):
        """Surveiller les événements de la fenêtre."""
        if event.type() == QEvent.Type.WindowDeactivate:
            self.close()
        return super().event(event)

    def keyPressEvent(self, event: QKeyEvent):
        """Gérer les raccourcis clavier pour fermer la fenêtre."""
        if event.key() in {Qt.Key.Key_Escape, Qt.Key.Key_Space, Qt.Key.Key_Enter, Qt.Key.Key_Return}:
            print("Fenêtre fermée avec une touche simple.")
            self.close()
        elif event.key() == Qt.Key.Key_F4 and event.modifiers() & Qt.KeyboardModifier.ControlModifier:
            print("Fenêtre fermée avec Ctrl+F4.")
            self.close()
        else:
            # Si une autre touche est pressée, laisser le comportement par défaut
            super().keyPressEvent(event)


class HelpWindow(QWidget):

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, pdf_doc:str, parent = None):
        super().__init__(parent)
        self.setWindowTitle(_("Help - %(app)s") % {'app': APP_NAME_VERSION})
        # self.setWindowIcon(QIcon(":/res/images/favicon.ico"))
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setFixedSize(900, 600)
        self.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.MSWindowsFixedSizeDialogHint)
        # Créer le document PDF et le visualiseur PDF
        self._pdfDoc = QPdfDocument(self)
        self._pdfView = QPdfView()
        self._pdfView.setDocument(self._pdfDoc)
        # Configurer le widget central pour afficher le PDF
        self._main_Qvlo = QVBoxLayout()
        self.setLayout(self._main_Qvlo)
        self._main_Qvlo.setSpacing(0)
        self._main_Qvlo.setContentsMargins(QMargins(0, 0, 0, 0))
        self._main_Qvlo.addWidget(self._pdfView)
        # self._pdfDoc.load(":/res/help.pdf")
        self._pdfDoc.load(pdf_doc)
        if self._pdfDoc.status() != QPdfDocument.Status.Ready:
            self.close()
        else:
            # Afficher la première page du PDF
            self._pdfView.setPageMode(QPdfView.PageMode.MultiPage)
            self._pdfView.setZoomMode(QPdfView.ZoomMode.FitToWidth)
            self._pdfView.setPageSpacing(0)
            self._pdfView.setDocumentMargins(QMargins(0, 0, 0, 0))


class LogEmitter(QObject):
    """
    Objet émettant un signal pour envoyer les logs vers l'interface graphique.
    """
    newLogSignal = Signal(dict)  # Le log est transmis sous forme de dictionnaire


class LogHandler:
    """
    Gestionnaire personnalisé pour diriger les logs de loguru vers un QTableView.
    """

    def __init__(self, emitter: LogEmitter):
        self._logEmitter = emitter

    def __call__(self, message: str):
        """
        Appelé par loguru pour chaque message de log.
        """
        parsed_log = self.parse_log_message(message)
        if parsed_log:
            self._logEmitter.newLogSignal.emit(parsed_log)

    @staticmethod
    def parse_log_message(message: str):
        """
        Parse un message de log loguru au format attendu (timestamp, LEVEL, message).
        """
        parts = message.split(" -|- ", 2)  # On attend le format "{time}  -|-  {level}  -|-  {message}"
        if len(parts) == 3:
            return {
                "timestamp": parts[0],  # Timestamp
                "level": parts[1],  # Niveau du log
                "message": parts[2]  # Message
            }
        return None


class LogsTableModel(QAbstractTableModel):
    """
    Modèle pour gérer l'affichage des logs dans un QTableView.
    """

    def __init__(self, display_lines: int = 100):
        super().__init__()
        self._dataList = []  # Stocke les logs
        self._maxLines = display_lines  # Nombre maximum de lignes
        self._headers = [_("Timestamp"), _("Level"), _("Message")]
        self._sortColumn = 0
        self._sortOrder = Qt.SortOrder.AscendingOrder
        self.sort(self._sortColumn, self._sortOrder)

    def rowCount(self, parent = QModelIndex()):
        return len(self._dataList)

    def columnCount(self, parent = QModelIndex()):
        return len(self._headers)

    def set_headers(self, new_headers: list):
        self._headers = new_headers
        self.headerDataChanged.emit(Qt.Orientation.Horizontal, 0, len(self._headers) - 1)
        self.layoutChanged.emit()

    def headerData(self, section:int, orientation:Qt.Orientation, role:Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
        """
        Définit les en-têtes des colonnes.
        """
        if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
            return self._headers[section]

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
        if not index.isValid():
            return None
        elif role == Qt.ItemDataRole.DisplayRole:
            log = self._dataList[index.row()]
            if index.column() == 0:  # Timestamp
                return log["timestamp"]
            elif index.column() == 1:  # Level
                return log["level"]
            elif index.column() == 2:  # Message
                return log["message"]
        elif role == Qt.ItemDataRole.BackgroundRole and index.column() == 1:  # Level
            log = self._dataList[index.row()]
            if log["level"] == 'DEBUG':
                return QColor(Qt.GlobalColor.cyan)
            elif log["level"] == 'INFO':
                return QColor(Qt.GlobalColor.white)
            elif log["level"] == 'SUCCESS':
                return QColor(Qt.GlobalColor.green)
            elif log["level"] == 'WARNING':
                return QColor(Qt.GlobalColor.yellow)
            elif log["level"] == 'ERROR':
                return QColor(Qt.GlobalColor.red)
            elif log["level"] == 'CRITICAL':
                return QColor(Qt.GlobalColor.darkRed)
        elif role == Qt.ItemDataRole.ForegroundRole and index.column() == 1:  # Level
            log = self._dataList[index.row()]
            if log["level"] == 'DEBUG':
                return QColor(Qt.GlobalColor.darkGray)
            elif log["level"] == 'INFO':
                return QColor(Qt.GlobalColor.black)
            elif log["level"] == 'SUCCESS':
                return QColor(Qt.GlobalColor.darkGray)
            elif log["level"] == 'WARNING':
                return QColor(Qt.GlobalColor.lightGray)
            elif log["level"] == 'ERROR':
                return QColor(Qt.GlobalColor.darkGray)
            elif log["level"] == 'CRITICAL':
                return QColor(Qt.GlobalColor.lightGray)
        elif role == Qt.ItemDataRole.FontRole:
            if index.column() == 1:  # Level
                # log = self._dataList[index.row()]
                font = QFont("Helvetica")
                font.setBold(True)
                return font
            return None
        elif role == Qt.ItemDataRole.TextAlignmentRole:
            global is_rtl
            if index.column() == 0:
                return get_qt_align_vchr()
            elif index.column() == 1:
                return get_qt_align_center()
            elif index.column() == 2:
                return get_qt_align_vchl()
        return None

    def sort(self, column: int, order: Qt.SortOrder = Qt.SortOrder.DescendingOrder):
        self._sortColumn = column
        self._sortOrder = order
        # Sorting logic
        if column == 0:  # Sorting by "timestamp"
            self._dataList.sort(key = lambda log: log["timestamp"], reverse = (order == Qt.SortOrder.AscendingOrder))
        elif column == 1:  # Sorting by "level"
            self._dataList.sort(key = lambda log: log["level"], reverse = (order == Qt.SortOrder.AscendingOrder))
        elif column == 2:  # Sorting by "message"
            self._dataList.sort(key = lambda log: log["message"], reverse = (order == Qt.SortOrder.AscendingOrder))
        # Notify view that data has changed after sorting
        self.layoutChanged.emit()

    def add_data(self, log: dict):
        """
        Ajoute un log au modèle tout en respectant la limite maximale des lignes.
        """
        self._dataList.append(log)
        while len(self._dataList) > self._maxLines:
            self._dataList.pop(0)  # Supprime les anciennes lignes
        self.sort(self._sortColumn, self._sortOrder)
        self.layoutChanged.emit()

    def set_data(self, data: list):
        self._dataList = data
        self.sort(self._sortColumn, self._sortOrder)
        self.layoutChanged.emit()


class LogViewerWidget(QWidget):
    """
    Widget principal contenant un QTableView pour afficher les logs.
    """

    def __init__(self, max_display_lines: int = 100, parent = None):
        super().__init__(parent)
        # Layout principal
        self._main_Qvlo = QVBoxLayout(self)
        self.setLayout(self._main_Qvlo)
        self._main_Qvlo.setContentsMargins(0, 0, 0, 0)
        # TableView
        self._logs_Qtvmb = QTableView(self)
        self._main_Qvlo.addWidget(self._logs_Qtvmb)
        # Model
        self._logs_lQtm = LogsTableModel(max_display_lines)
        self._logs_Qtvmb.setModel(self._logs_lQtm)
        # selection de ligne entiere
        self._logs_Qtvmb.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        # désactiver tentative d'edition
        self._logs_Qtvmb.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)  # Lecture seule
        # alterner les couleurs
        self._logs_Qtvmb.setAlternatingRowColors(True)
        # remplir  le background
        self._logs_Qtvmb.setAutoFillBackground(True)
        # activer le trie
        self._logs_Qtvmb.setSortingEnabled(True)
        # Configuration des tailles des colonnes
        self._logs_Qtvmb.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)  # Première colonne
        self._logs_Qtvmb.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)  # Deuxième colonne
        self._logs_Qtvmb.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)  # Troisième colonne
        self._logs_Qtvmb.setTextElideMode(Qt.TextElideMode.ElideNone)
        self._logs_Qtvmb.setWordWrap(False)
        # Émetteur de signaux pour les logs
        self._logEmitter = LogEmitter()
        self._logEmitter.newLogSignal.connect(self.add_log)  # Connexion du signal
        # Ajouter un gestionnaire personnalisé pour loguru
        log_handler = LogHandler(self._logEmitter)
        # logger.remove()  # Supprime les sorties par défaut
        logger.add(log_handler, format = "{time:YYYY-MM-DD HH:mm:ss.SSS} -|- {level} -|- {message}", level = "INFO")
        # update display
        self.update_display()

    def add_log(self, log: dict):
        """
        Ajoute un log au modèle et effectue le défilement automatique.
        """
        self._logs_lQtm.add_data(log)

    def update_display(self):
        self._logs_lQtm.set_headers([_("Timestamp"), _("Level"), _("Message")])
        self.setLayoutDirection(get_qt_dir())


class Wallet:
    """Class Wallet can raise ValueError """
    WrongWalletName = 1
    """'walletName' wrong type (must not be empty string)"""
    WrongWalletId = 2
    """'walletId' wrong type (must not be empty string)"""
    WrongWalletPass = 3
    """'walletPass' wrong type (must not be empty string)"""
    WrongWalletType = 4
    """'walletType' must be equal string 'solana'"""
    JSONDecodeError = 5
    """Cannot decode Json"""

    def __init__(self,
            wallet_name: str,
            wallet_pub_key: str,
            wallet_priv_key: str,
            dt_crea: datetime = None,
            dt_update: datetime = None,
            main_sol_balance: float = 0,
            main_tokens: list = None,
            dev_sol_balance: float = 0,
            dev_tokens: list = None,
            wallet_type: str = 'solana'):
        # Vérification des paramètres obligatoires
        if not isinstance(wallet_name, str) or not wallet_name.strip():
            raise ValueError(Wallet.WrongWalletName)
        if not isinstance(wallet_pub_key, str) or not wallet_pub_key.strip():
            raise ValueError(Wallet.WrongWalletId)
        if not isinstance(wallet_priv_key, str) or not wallet_priv_key.strip():
            raise ValueError(Wallet.WrongWalletPass)
        if wallet_type != "solana":
            raise ValueError(Wallet.WrongWalletType)
        # Attribuer les valeurs obligatoires
        self.walletType = wallet_type
        self.walletName = wallet_name
        self.walletPubKey = wallet_pub_key
        self.walletPrivKey = wallet_priv_key
        # Gérer les valeurs par défaut des dates
        self.dtCrea = dt_crea if isinstance(dt_crea, datetime) else datetime.now(timezone.utc)
        self.dtUpdate = dt_update if isinstance(dt_update, datetime) else datetime.now(timezone.utc)
        # Gérer les balances et les mettre à zéro si elles sont négatives
        self.mainSolBalance = max(main_sol_balance, 0)
        self.devSolBalance = max(dev_sol_balance, 0)
        # Initialiser les listes de tokens si elles ne sont pas fournies
        self.mainTokens = main_tokens if main_tokens is not None else []
        self.devTokens = dev_tokens if dev_tokens is not None else []

    def to_json(self) -> str:
        """Convertir l'instance en JSON"""
        return json.dumps({
            "walletType": self.walletType,
            "walletName": self.walletName,
            "walletPubKey": self.walletPubKey,
            "walletPrivKey": self.walletPrivKey,
            "dtCrea": self.dtCrea.isoformat(),
            "dtUpdate": self.dtUpdate.isoformat(),
            "mainSolBalance": self.mainSolBalance,
            "mainTokens": self.mainTokens,
            "devSolBalance": self.devSolBalance,
            "devTokens": self.devTokens
        })

    @staticmethod
    def parse_datetime(dt_str):
        """Parse une chaîne de date en datetime, retourne l'heure actuelle en UTC en cas d'erreur."""
        if isinstance(dt_str, str):
            try:
                return datetime.fromisoformat(dt_str)
            except ValueError:
                logger.warning("Format de date invalide, remplacement par l'heure actuelle.")
        return datetime.now(timezone.utc)

    @staticmethod
    def from_json(json_string: str):
        """Créer une instance Wallet à partir d'une chaîne JSON"""
        try:
            data = json.loads(json_string)
            # Vérification des paramètres obligatoires
            if 'walletName' not in data or not isinstance(data["walletName"], str) or not data["walletName"].strip():
                raise ValueError(Wallet.WrongWalletName)
            if 'walletPubKey' not in data or not isinstance(data["walletPubKey"], str) or not data["walletPubKey"].strip():
                raise ValueError(Wallet.WrongWalletId)
            if 'walletPrivKey' not in data or not isinstance(data["walletPrivKey"], str) or not data["walletPrivKey"].strip():
                raise ValueError(Wallet.WrongWalletPass)
            if 'walletType' not in data or data["walletType"] != "solana":
                raise ValueError(Wallet.WrongWalletType)
            wallet_name = data["walletName"]
            wallet_pub_key = data["walletPubKey"]
            wallet_priv_key = data["walletPrivKey"]
            wallet_type = data["walletType"]
            # Récupérer et valider les dates
            dt_crea = Wallet.parse_datetime(data.get("dtCrea"))
            dt_update = Wallet.parse_datetime(data.get("dtUpdate"))
            # Récupérer et valider les balances, les réinitialiser à zéro si elles sont négatives
            main_sol_balance = max(data.get("mainSolBalance", 0), 0)
            dev_sol_balance = max(data.get("devSolBalance", 0), 0)
            # Récupérer les listes de tokens, initialiser à liste vide si absentes
            main_tokens = data.get("mainTokens", [])
            dev_tokens = data.get("devTokens", [])
            # Créer et retourner l'instance Wallet
            return Wallet(wallet_name, wallet_pub_key, wallet_priv_key, dt_crea, dt_update, main_sol_balance, main_tokens, dev_sol_balance, dev_tokens, wallet_type)
        except json.JSONDecodeError:
            raise ValueError(Wallet.JSONDecodeError)


class WalletTokensTableModel(QAbstractTableModel):

    def __init__(self, parent = None):
        super().__init__(parent)
        self._headers = [_("Token Id"), _("Buy Price"), _("Buy Time"), _("Action")]
        self._dataList = []

    def set_headers(self, new_headers: list):
        self._headers = new_headers
        self.headerDataChanged.emit(Qt.Orientation.Horizontal, 0, 3)
        self.layoutChanged.emit()

    def rowCount(self, parent = QModelIndex()):
        return len(self._dataList)

    def columnCount(self, parent = QModelIndex()):
        return len(self._headers)

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
        if not index.isValid():
            return None
        elif role == Qt.ItemDataRole.DisplayRole:
            token = self._dataList[index.row()]
            if index.column() == 0:  # Timestamp
                return token["token_id"]
            elif index.column() == 1:  # Level
                return token["buy_price"]
            elif index.column() == 2:  # Message
                return token["buy_time"]
            elif index.column() == 3:  # Action
                return ""
        elif role == Qt.ItemDataRole.TextAlignmentRole:
            global is_rtl
            if index.column() == 0:
                return get_qt_align_vchr()
            elif index.column() == 1:
                return get_qt_align_center()
            elif index.column() == 2:
                return get_qt_align_vchl(not is_rtl)
        elif role == Qt.ItemDataRole.DecorationRole and index.column() == 0:
            return QIcon(":icoWallet")

        return None

    def headerData(self, section, orientation, role = Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
            return self._headers[section]
        return None

    def set_data(self, data:list):
        self._dataList = data
        self.layoutChanged.emit()


class WalletWidget(QWidget):

    def __init__(self, parent = None):
        super().__init__(parent)
        self.setWindowIcon(QIcon(":icoWallet"))
        self.wallet = None
        self.fileKey = None
        self.filePath = None
        self._main_Qglo = QGridLayout(self)
        self.setLayout(self._main_Qglo)
        # Line 1: Wallet Name
        self._walletName_Qlbl = QLabel()
        self.walletName_Qled = QLineEdit()
        self._saveWallet_Qpbtn = QPushButton()
        self._init_line1()
        # Line 2: Wallet PubKey
        self._walletPubKey_Qlbl = QLabel()
        self.walletPubKey_Qled = QLineEdit()
        self._copyToClipboard_Qpbtn = QPushButton()
        self._init_line2()
        # Line 3: Balances
        self._realBalance_Qlbl = QLabel()
        self.realBalance_Qled = QLineEdit()
        self._demoBalance_Qlbl = QLabel()
        self.demoBalance_Qled = QLineEdit()
        self._init_line3()
        # Line 4: Tokens Label
        self._tokens_Qlbl = QLabel()
        self._init_line4()
        # # Line 5: TableView
        self._tokens_qtbvmb = QTableView(self)
        self._tokens_wtQtm = WalletTokensTableModel(self)
        self._init_tokens_qtbvmb()
        self.update_display()
        self.populate_table()

    def set_wallet(self, wallet: Wallet, file_pass: str, file_path:str):
        self.save_wallet()
        self.fileKey = file_pass
        self.filePath = file_path
        self.wallet = wallet
        self.update_wallet_display()

    def update_wallet_display(self):
        if self.wallet is not None:
            self.walletPubKey_Qled.setText(self.wallet.walletPubKey)
            self.walletName_Qled.setText(self.wallet.walletName)
            self.realBalance_Qled.setText(f"{self.wallet.mainSolBalance:.9f}")
            self.demoBalance_Qled.setText(f"{self.wallet.devSolBalance:.9f}")
        else:
            self.walletPubKey_Qled.setText("")
            self.walletName_Qled.setText("")
            self.realBalance_Qled.setText(f"{0:.9f}")
            self.demoBalance_Qled.setText(f"{0:.9f}")

    def save_wallet(self):
        if self.wallet is not None and self.fileKey is not None and self.filePath is not None:
            encrypted_data = encrypt_string(self.fileKey, APP_SALT.encode(), self.wallet.to_json())
            with LockedFile(self.filePath, "w+b") as file:
                file.seek(0)
                file.truncate()
                file.write(encrypted_data.encode())

    def _init_line1(self):
        self.walletName_Qled.setAlignment(get_qt_align_center())
        self._saveWallet_Qpbtn.setIcon(QIcon(":icoSave"))
        self._saveWallet_Qpbtn.setMaximumWidth(50)
        self._saveWallet_Qpbtn.clicked.connect(self.save_wallet)
        self._main_Qglo.addWidget(self._walletName_Qlbl, 0, 0, 1, 2)
        self._main_Qglo.addWidget(self.walletName_Qled, 0, 2, 1, 9)
        self._main_Qglo.addWidget(self._saveWallet_Qpbtn, 0, 11, 1, 1)

    def _init_line2(self):
        self.walletPubKey_Qled.setAlignment(get_qt_align_center())
        self.walletPubKey_Qled.setText("XXXXXXXXXXXX")
        self.walletPubKey_Qled.setReadOnly(True)
        self._copyToClipboard_Qpbtn.setIcon(QIcon(":icoCopy"))
        self._copyToClipboard_Qpbtn.setMaximumWidth(50)
        self._copyToClipboard_Qpbtn.clicked.connect(self.copy_wallet_priv_key_to_clipboard)
        self._main_Qglo.addWidget(self._walletPubKey_Qlbl, 1, 0, 1, 2)
        self._main_Qglo.addWidget(self.walletPubKey_Qled, 1, 2, 1, 9)
        self._main_Qglo.addWidget(self._copyToClipboard_Qpbtn, 1, 11, 1, 1)

    def _init_line3(self):
        self.realBalance_Qled.setAlignment(get_qt_align_vchr())
        self.realBalance_Qled.setText("10.000000000")
        # self.realBalance_Qled.setMaximumWidth(300)
        self.realBalance_Qled.setReadOnly(True)
        self.demoBalance_Qled.setAlignment(get_qt_align_vchr())
        self.demoBalance_Qled.setText("10.000000000")
        # self.demoBalance_Qled.setMaximumWidth(300)
        self.demoBalance_Qled.setReadOnly(True)
        self._main_Qglo.addWidget(self._realBalance_Qlbl, 2, 0, 1, 2)
        self._main_Qglo.addWidget(self.realBalance_Qled, 2, 2, 1, 3)
        self._main_Qglo.addWidget(self._demoBalance_Qlbl, 2, 7, 1, 2)
        self._main_Qglo.addWidget(self.demoBalance_Qled, 2, 9, 1, 3)

    def _init_line4(self):
        self._tokens_Qlbl.setAlignment(get_qt_align_center())
        self._main_Qglo.addWidget(self._tokens_Qlbl, 3, 0, 1, 12)

    def _init_tokens_qtbvmb(self):
        self._tokens_qtbvmb.setModel(self._tokens_wtQtm)
        # selection de ligne entiere
        self._tokens_qtbvmb.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        # desactiver tentative d'edition
        self._tokens_qtbvmb.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)  # Lecture seule
        # alterner les couleurs
        self._tokens_qtbvmb.setAlternatingRowColors(True)
        # remplir  le background
        self._tokens_qtbvmb.setAutoFillBackground(True)
        # activer le trie
        self._tokens_qtbvmb.setSortingEnabled(True)

        # Configuration des tailles des colonnes
        self._tokens_qtbvmb.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)  # Première colonne
        self._tokens_qtbvmb.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)  # Deuxième colonne
        self._tokens_qtbvmb.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Interactive)  # Troisième colonne
        self._tokens_qtbvmb.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)  # Quatrième colonne
        self._tokens_qtbvmb.setTextElideMode(Qt.TextElideMode.ElideNone)
        self._tokens_qtbvmb.setWordWrap(False)
        self._main_Qglo.addWidget(self._tokens_qtbvmb, 4, 0, 1, 12)

    def update_display(self):
        global is_rtl
        self.setLayoutDirection(get_qt_dir())
        self._walletName_Qlbl.setText(_("Wallet Name:"))
        self._walletName_Qlbl.setAlignment(get_qt_align_vchr(is_rtl))
        self._walletPubKey_Qlbl.setText(_("Wallet PubKey:"))
        self._walletPubKey_Qlbl.setAlignment(get_qt_align_vchr(is_rtl))
        self._realBalance_Qlbl.setText(_("Balance Real:"))
        self._realBalance_Qlbl.setAlignment(get_qt_align_vchr(is_rtl))
        self._demoBalance_Qlbl.setText(_("Balance Demo:"))
        self._demoBalance_Qlbl.setAlignment(get_qt_align_vchr(is_rtl))
        self._tokens_Qlbl.setText(_("My Tokens:"))
        self._tokens_wtQtm.set_headers([_("Token Id"), _("Buy Price"), _("Buy Time"), _("Action")])

    def populate_table(self):
        data = []
        for x in range(10):
            token_id = ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ', k = 3))
            buy_price = f"{random.uniform(0.000000001, 1.000000000):.9f}"
            buy_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
            action = _("Sell")  # Placeholder for button-like behavior

            token = {
                "token_id": token_id,  # token_id
                "buy_price": buy_price,  # Niveau du log
                "buy_time": buy_time,  # Message
                "action": action  # Message
            }

            data.append(token)

        self._tokens_wtQtm.set_data(data)

    def copy_wallet_priv_key_to_clipboard(self):
        """Copier le texte de QLineEdit dans le presse-papiers."""
        clipboard = QApplication.clipboard()
        clipboard.setText(self.walletPubKey_Qled.text())
        logger.info(_("walletId copied to clipboard"))


class SimpleMsgBox(QMessageBox):
    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, window_title: str, window_text: str, ico: QMessageBox.Icon, parent = None):
        super().__init__(parent)
        self.setLayoutDirection(get_qt_dir())
        self.setOption(QMessageBox.Option.DontUseNativeDialog, True)
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setWindowTitle(window_title)
        self.setText(window_text)
        self.setIcon(ico)


class NewWalletNameAndPassDialog(QDialog):

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, parent = None):
        super().__init__(parent)
        self.setLayoutDirection(get_qt_dir())
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setWindowTitle(_("Wallet Name and EncryptPass"))
        main_qflo = QFormLayout()
        self.setLayout(main_qflo)
        self._walletName_Qle = QLineEdit(self)
        self._walletEncyptPass_Qle = QLineEdit(self)
        self._walletEncyptPass_Qle.setEchoMode(QLineEdit.EchoMode.Password)
        main_qflo.addRow(_("Wallet Name:"), self._walletName_Qle)
        main_qflo.addRow(_("Wallet EncryptPass:"), self._walletEncyptPass_Qle)

        validate_qpbtn = QPushButton(_("Validate"))
        cancel_qpbtn = QPushButton(_("Cancel"))

        button_qdbbox = QDialogButtonBox(self)
        button_qdbbox.addButton(validate_qpbtn, QDialogButtonBox.ButtonRole.AcceptRole)
        button_qdbbox.addButton(cancel_qpbtn, QDialogButtonBox.ButtonRole.RejectRole)

        # Connecter les signaux des boutons
        validate_qpbtn.clicked.connect(self.validate_and_accept)
        cancel_qpbtn.clicked.connect(self.reject)

        main_qflo.addWidget(button_qdbbox)

        self.walletName = None
        self.walletEncyptPass = None

    def validate_and_accept(self):
        # Validation du champ 1
        if len(self._walletName_Qle.text().strip()) < 2:
            msg_qmbox = SimpleMsgBox(_("Error"), _("The Wallet Name must have a minimum of 2 characters."), QMessageBox.Icon.Warning, self)
            msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
            msg_qmbox.exec()
            return
        # Validation du champ 2 (mot de passe)
        if not re.match(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$", self._walletEncyptPass_Qle.text()):
            msg_qmbox = SimpleMsgBox(_("Error"), _("The Wallet EncryptPass must have a minimum of 1 lower case character, 1 upper case character, 1 numeric character, 1 special character (@$!%*?&), and must have a minimum of 8 characters length."), QMessageBox.Icon.Warning, self)
            msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
            msg_qmbox.exec()
            return
        self.walletName = self._walletName_Qle.text().strip()
        self.walletEncyptPass = self._walletEncyptPass_Qle.text()
        # Si les deux champs sont valides, on accepte la saisie
        self.accept()


class NewWalletFileDialog(QFileDialog):

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, parent = None):
        super().__init__(parent)
        self.setOption(QFileDialog.Option.DontUseNativeDialog, True)  # Empêche d'écraser un fichier existant
        self.setWindowTitle(_("Save Content to New Wallet File"))
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)  # Mode "Enregistrer sous"
        self.setFileMode(QFileDialog.FileMode.AnyFile)  # Permet de saisir un nouveau fichier
        self.setOption(QFileDialog.Option.DontConfirmOverwrite, True)
        self.setLabelText(QFileDialog.DialogLabel.LookIn, _("Wallets location:"))
        self.setLabelText(QFileDialog.DialogLabel.FileName, _("File name:"))
        self.setLabelText(QFileDialog.DialogLabel.FileType, "File type:")
        self.setLabelText(QFileDialog.DialogLabel.Accept, _("Validate"))
        self.setLabelText(QFileDialog.DialogLabel.Reject, _("Cancel"))
        self.setDirectory(WALLETS_FOLDER)
        self.setDefaultSuffix(WALLET_FILE_EXT)
        self.setNameFilter(_("%(desc)s (*.%(ext)s)") % {"desc": WALLET_FILE_DESC, "ext": WALLET_FILE_EXT})
        # self.setMinimumSize(dialog.width(), dialog.height())


class LoadWalletPassDialog(QDialog):

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, file_name: str, parent = None):
        super().__init__(parent)
        self.setLayoutDirection(get_qt_dir())
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setWindowTitle(_("Wallet EncryptPass for file '%(fileName)s'") % {"fileName": file_name})
        main_qflo = QFormLayout()
        self.setLayout(main_qflo)
        self._walletEncyptPass_Qle = QLineEdit(self)
        self._walletEncyptPass_Qle.setEchoMode(QLineEdit.EchoMode.Password)
        main_qflo.addRow(_("Wallet EncryptPass:"), self._walletEncyptPass_Qle)

        validate_qpbtn = QPushButton(_("Validate"))
        cancel_qpbtn = QPushButton(_("Cancel"))
        button_qdbbox = QDialogButtonBox(self)
        button_qdbbox.addButton(validate_qpbtn, QDialogButtonBox.ButtonRole.AcceptRole)
        button_qdbbox.addButton(cancel_qpbtn, QDialogButtonBox.ButtonRole.RejectRole)
        # Connecter les signaux des boutons
        validate_qpbtn.clicked.connect(self.validate_and_accept)
        cancel_qpbtn.clicked.connect(self.reject)
        main_qflo.addWidget(button_qdbbox)
        self.walletEncyptPass = None

    def validate_and_accept(self):
        # Validation du champ 1
        if len(self._walletEncyptPass_Qle.text().strip()) < 1:
            msg_qmbox = SimpleMsgBox(_("Error"), _("You must enter a valid Password."), QMessageBox.Icon.Warning, self)
            msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
            msg_qmbox.exec()
            return
        self.walletEncyptPass = self._walletEncyptPass_Qle.text()
        # Si les deux champs sont valides, on accepte la saisie
        self.accept()


class LoadWalletFileDialog(QFileDialog):

    def showEvent(self, event) -> None:
        super().showEvent(event)
        center_on_screen(self)

    def __init__(self, parent = None):
        super().__init__(parent)
        self.setOption(QFileDialog.Option.DontUseNativeDialog, True)  # Empêche d'écraser un fichier existant
        self.setWindowTitle(_("Load Wallet File"))
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)  # Mode "Ouvrir sous"
        self.setFileMode(QFileDialog.FileMode.ExistingFile)  # Permet de saisir un fichier existant
        self.setDirectory(WALLETS_FOLDER)
        self.setDefaultSuffix(WALLET_FILE_EXT)
        self.setLabelText(QFileDialog.DialogLabel.LookIn, _("Wallets location:"))
        self.setLabelText(QFileDialog.DialogLabel.FileName, _("File name:"))
        self.setLabelText(QFileDialog.DialogLabel.FileType, "File type:")
        self.setLabelText(QFileDialog.DialogLabel.Accept, _("Validate"))
        self.setLabelText(QFileDialog.DialogLabel.Reject, _("Cancel"))
        self.setNameFilter(_("%(desc)s (*.%(ext)s)") % {"desc": WALLET_FILE_DESC, "ext": WALLET_FILE_EXT})
        # self.setMinimumSize(dialog.width(), dialog.height())


class MainWindow(QMainWindow):

    def closeEvent(self, event: QEvent):
        """Intercepter la fermeture pour masquer la fenêtre au lieu de quitter."""
        self.hide()
        event.ignore()

    def showEvent(self, event) -> None:
        self.showApp_Qact.setDisabled(True)
        self.hideApp_Qact.setDisabled(False)
        super().showEvent(event)
        if self.mustCenter:
            self.mustCenter = False
            center_on_screen(self)

    def hideEvent(self, event) -> None:
        self.showApp_Qact.setDisabled(False)
        self.hideApp_Qact.setDisabled(True)
        self.mustCenter = True
        super().hideEvent(event)

    def __init__(self, cfg:dict = DEFAULT_YAML_CONFIG):
        super().__init__()
        self.config = cfg
        self.currentLang = get_lang_or_fallback(self.config['defaultLang'])
        self.setWindowIcon(QIcon(":imgIcon"))
        self.setWindowTitle(APP_NAME_VERSION)
        self.setMinimumSize(1024, 768)
        self.resize(max(self.config.get('lastWidth'), 1024), max(self.config.get('lastHeight'), 768))
        self.mustCenter = True
        # Main Menubar
        self.main_Qmnub = self.menuBar()
        # File Menu
        self.file_Qmnu = QMenu()
        self.newWallet_Qact = QAction(self)
        self.newWallet_Qact.setIcon(QIcon(":icoWalletNew"))
        self.openWallet_Qact = QAction(self)
        self.openWallet_Qact.setIcon(QIcon(":icoWalletOpen"))
        self.filesHistory_Qmnu = QMenu()
        self.filesHistory_Qmnu.setIcon(QIcon(":icoLastFiles"))
        self.noFileHistory_Qact = QAction(self)
        self.noFileHistory_Qact.setIcon(QIcon(":icoNoHistory"))
        self.noFileHistory_Qact.setDisabled(True)
        self.hideApp_Qact = QAction(self)
        self.hideApp_Qact.setIcon(QIcon(":icoTrayHide"))
        self.closeApp_Qact = QAction(self)
        self.closeApp_Qact.setIcon(QIcon(":icoQuit"))
        self.forceCloseApp_Qact = QAction(self)
        self.forceCloseApp_Qact.setIcon(QIcon(":icoForceQuit"))
        # finalize initialisation of File Menu
        self._init_file_qmnu()
        # Langs Menu
        self.langs_Qmnu = QMenu()
        self.changeLangEn_Qact = QAction(QIcon(":icoLang"), "English", self)
        self.changeLangFr_Qact = QAction(QIcon(":icoLang"), "Français", self)
        self.changeLangAr_Qact = QAction(QIcon(":icoLang"), "العربية", self)
        self.changeLangDe_Qact = QAction(QIcon(":icoLang"), "Deutsch", self)
        self.changeLangIt_Qact = QAction(QIcon(":icoLang"), "Italiano", self)
        self.changeLangEs_Qact = QAction(QIcon(":icoLang"), "Espagnol", self)
        # finalize initialisation of Langs Menu
        self._init_langs_qmnu()
        # Help Menu
        self.help_Qmnu = QMenu()
        self.help_Qact = QAction(self)
        self.help_Qact.setIcon(QIcon(":icoHelp"))
        self.about_Qact = QAction(self)
        self.about_Qact.setIcon(QIcon(":icoAbout"))
        self._init_help_qmnu()
        # set texts and titles for Main Menubar
        # Main Toolbar
        self.main_Qtb = QToolBar()
        self._init_main_toolbar()
        # Initialisation de l'icône du System Tray
        self.tray_Qsti = QSystemTrayIcon(QIcon(":imgIcon"), self)
        # self.trayIcon.setToolTip(f"{APP_FULL_NAME} ({APP_VERSION})") # don't work on linux ???
        # Menu du trayIcon
        self.tray_Qmnu = QMenu()
        self.showApp_Qact = QAction(self)
        self._init_tray_qmnu()
        # Central widget: QTabWidget
        self.main_Qtabw = QTabWidget(self)
        self.setCentralWidget(self.main_Qtabw)
        self.token_qw = QWidget()
        self.main_Qtabw.addTab(self.token_qw, QIcon(":icoTokens"), _("Tokens:"))
        self.token_qw.setLayout(QVBoxLayout())
        self.token_qw.layout().addWidget(QTableView())
        self.wallet_Qw = WalletWidget(self)
        self.main_Qtabw.addTab(self.wallet_Qw, QIcon(":icoWallet"), _("Wallet: %(walletName)s") % {"walletName": ""})
        self.logViewer_Qw = LogViewerWidget(1000, self)
        self.main_Qtabw.addTab(self.logViewer_Qw, QIcon(":icoLogs"), _("Logs:"))
        self.update_display()
        logger.success(_("Init of %(appName)s") % {"appName": APP_ABOUT_NAME})

    def first_load(self):
        # logger.debug(type(config))
        configLastMaximized = self.config.get('lastMaximized', True)
        if configLastMaximized:
            self.showMaximized()
        else:
            self.show()
        lastFile = self.config["lastFile"]
        if lastFile is None or lastFile == "":
            self._init_new_or_load_wallet()
        elif not os.path.exists(lastFile):
            msg_qmbox = SimpleMsgBox(_("Error"), _("The File '%(fileName)s' does not exists. Please choose a new file.") % {"fileName": lastFile}, QMessageBox.Icon.Warning, self)
            msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
            msg_qmbox.exec()
            self._init_new_or_load_wallet()
        else:
            self._ask_load_wallet_pass(lastFile, True)

    def _quit_app_func(self):
        msg_qmbox = SimpleMsgBox(_("Do you really want to Quit?"), _("If you accept, all the files will be saved\nand the application will be closed."), QMessageBox.Icon.Question, self)
        yes_qpbtn = msg_qmbox.addButton(_("Yes"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.addButton(_("No"), QMessageBox.ButtonRole.ActionRole)  # no_qpbtn  = msg_qmbox.addButton(_("No"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.exec()
        if msg_qmbox.clickedButton() == yes_qpbtn:
            self.config['lastMaximized'] = self.isMaximized()
            # if self.isMaximized():
            #    self.showNormal()
            # TODO: save size of non maximized window
            self.config['lastHeight'] = self.height()
            self.config['lastWidth'] = self.width()
            # saving self.currentLang in config memory
            self.config['defaultLang'] = self.currentLang
            # save memory config to file
            save_yaml_app_config(self.config)

            self.wallet_Qw.save_wallet()
            # close instance
            QApplication.instance().quit()

    def _force_quit_app_func(self):
        msg_qmbox = SimpleMsgBox(_("Do you really want to Quit?"), _("If you accept, nothing will be saved\nand the application will be closed."), QMessageBox.Icon.Critical, self)
        yes_qpbtn = msg_qmbox.addButton(_("Yes"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.addButton(_("No"), QMessageBox.ButtonRole.ActionRole)  # no_qpbtn = msg_qmbox.addButton(_("No"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.exec()
        if msg_qmbox.clickedButton() == yes_qpbtn:
            QApplication.instance().quit()

    def _change_lang(self, lang_code, lang_name):

        self.currentLang = get_lang_or_fallback(lang_code)
        set_app_lang_code(self.currentLang)
        logger.info(_("Switching to language %(lang)s.") % {"lang": lang_name})
        self.update_display()



    def _init_tray_qmnu(self):
        self.tray_Qsti.setContextMenu(self.tray_Qmnu)
        self.tray_Qmnu.addAction(self.hideApp_Qact)
        self.showApp_Qact.setIcon(QIcon(":icoTrayShow"))
        self.showApp_Qact.setDisabled(True)
        self.showApp_Qact.triggered.connect(self.show)
        self.tray_Qmnu.addAction(self.showApp_Qact)
        self.tray_Qmnu.addAction(self.closeApp_Qact)
        self.tray_Qmnu.addAction(self.forceCloseApp_Qact)

        self.tray_Qsti.show()

    def _show_about_window(self):
        """Affiche une boîte de dialogue 'À propos'."""
        bg_txt = f"<h3>{APP_FULL_NAME}: {APP_VERSION}<br>PYTHON: {platform.python_version()}<br>Qt: {pyside6version}<br>OS: {platform.system()}</h3>"
        about_window = AboutWindow(bg_txt, self)
        about_window.show()

    def _show_help_window(self):
        """Affiche une boîte de dialogue 'Aide'."""
        help_window = HelpWindow(":pdfHelp", self)
        help_window.show()

    def _init_main_toolbar(self):
        # Création de la barre d'outils
        self.addToolBar(self.main_Qtb)
        self.main_Qtb.setMovable(False)
        favicon_toolbar_qlbl = QLabel()
        favicon_toolbar_qlbl.setPixmap(QPixmap(":imgFavicon").scaled(24, 24, Qt.AspectRatioMode.KeepAspectRatio))
        self.main_Qtb.addWidget(favicon_toolbar_qlbl)
        self.main_Qtb.addSeparator()
        self.main_Qtb.addAction(self.openWallet_Qact)
        self.main_Qtb.addAction(self.newWallet_Qact)
        self.main_Qtb.addSeparator()
        # self.toolbarPcp.addAction(self.actionOpenWsMainNetConnexion)
    
    def _init_new_or_load_wallet(self):
        msg_qmbox = SimpleMsgBox(_("Action Required"), _("To use the application, you have to load a wallet or to create a new one."), QMessageBox.Icon.Critical, self)
        load_qpbtn = msg_qmbox.addButton(_("Load Wallet"), QMessageBox.ButtonRole.ActionRole)
        create_qpbtn = msg_qmbox.addButton(_("New Wallet"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.addButton(_("Quit"), QMessageBox.ButtonRole.RejectRole)  # quit_qpbtn  = msg_qmbox.addButton(_("No"), QMessageBox.ButtonRole.ActionRole)
        msg_qmbox.exec()
        if msg_qmbox.clickedButton() == load_qpbtn:
            create_wallets_folder()
            self._ask_load_wallet_filepath(True)
        elif msg_qmbox.clickedButton() == create_qpbtn:
            create_wallets_folder()
            self._ask_new_wallet_name_and_pass(True)
        else:
            self.close()
            QApplication.instance().quit()
        return

    def _create_new_wallet(self):
        create_wallets_folder()
        self._ask_new_wallet_name_and_pass()

    def _ask_new_wallet_name_and_pass(self, return_to_prev: bool = False):
        wallet_dialog = NewWalletNameAndPassDialog(self)
        if wallet_dialog.exec():
            new_wallet_name = wallet_dialog.walletName
            new_wallet_pass = wallet_dialog.walletEncyptPass
            self._ask_new_wallet_filepath(new_wallet_name, new_wallet_pass, return_to_prev)
        elif return_to_prev:
            self._init_new_or_load_wallet()

    def _ask_new_wallet_filepath(self, new_wallet_name: str, new_wallet_pass: str, return_to_prev: bool = False):
        saved_file = False
        while True:
            saved_file = False
            # Ouvre le QFileDialog pour choisir un emplacement et un nom de fichier
            dialog = NewWalletFileDialog(self)
            if dialog.exec():
                file_path = dialog.selectedFiles()[0]
                # Ajoute l'extension .kew si elle est manquante
                if not file_path.endswith("." + WALLET_FILE_EXT):
                    file_path += "." + WALLET_FILE_EXT
                file_name = os.path.basename(file_path)
                # Vérifie si le fichier existe déjà
                if os.path.exists(file_path):
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The File '%(fileName)s' already exists. Please choose a new file.") % {"fileName": file_name}, QMessageBox.Icon.Warning, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    continue  # Relance le dialogue pour forcer un nouveau fichier
                # Enregistre le contenu dans le nouveau fichier
                try:
                    keypair = Keypair()
                    wallet = Wallet(wallet_name = new_wallet_name, wallet_pub_key = str(keypair.pubkey()), wallet_priv_key = str(keypair.secret()))
                    encrypted_data = encrypt_string(new_wallet_pass, APP_SALT.encode(), wallet.to_json())
                    with LockedFile(file_path, "w+b") as file:
                        file.write(encrypted_data.encode())
                    file_path = os.path.realpath(file_path)
                    if file_path not in self.config["lastFiles"]:
                        self.config["lastFiles"].append(file_path)
                        while len(self.config["lastFiles"]) > 5:
                            self.config["lastFiles"].pop(0)
                        self._refresh_files_history_qmnu()
                    self.config["lastFile"] = file_path
                    self.wallet_Qw.set_wallet(wallet, new_wallet_pass, file_path)
                    self.main_Qtabw.setTabText(1, _("Wallet: %(walletName)s") % {"walletName": wallet.walletName})
                    saved_file = True
                    break  # Quitte la boucle après avoir sauvegardé
                except Exception as e:
                    logger.error(e)
                    msg_qmbox = SimpleMsgBox(_("Error"), _("An error occurred while saving the file '%(fileName)s'.") % {"fileName": file_name}, QMessageBox.Icon.Warning, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    break  # Quitte la boucle en cas d'erreur
            else:
                break  # L'utilisateur a annulé.
        if saved_file:
            return
        if return_to_prev:
            self._ask_new_wallet_name_and_pass(return_to_prev)

    def _load_wallet(self):
        self._ask_load_wallet_filepath()

    def _ask_load_wallet_filepath(self, return_to_prev: bool = False):
        loaded_file = False
        file_path = None
        while True:
            dialog = LoadWalletFileDialog(self)
            if dialog.exec():
                file_path = dialog.selectedFiles()[0]
                file_name = os.path.basename(file_path)
                if not os.path.exists(file_path):
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The File '%(fileName)s' does not exists. Please choose another file.") % {"fileName": file_name}, QMessageBox.Icon.Warning, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                elif self.config["lastFile"] is None or file_path != self.config["lastFile"] or return_to_prev:
                    loaded_file = True
                else:
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The File '%(fileName)s' is already loaded. Please choose another file.") % {"fileName": file_name}, QMessageBox.Icon.Warning, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    continue  # Relance le dialogue pour forcer un nouveau fichier
                if loaded_file:
                    break
                 # Relance le dialogue pour forcer un nouveau fichier
                continue
            else:
                break  # L'utilisateur a annulé.
        if loaded_file:
            self._ask_load_wallet_pass(file_path, return_to_prev)
            return
        if return_to_prev:
            self._init_new_or_load_wallet()

    def _ask_load_wallet_pass(self, file_path: str, return_to_prev: bool = False):
        loaded_file = False
        wallet = None
        wallet_pass = None
        while not loaded_file:
            file_name = os.path.basename(file_path)
            wallet_dialog = LoadWalletPassDialog(file_name, self)
            if wallet_dialog.exec():
                wallet_pass = wallet_dialog.walletEncyptPass
                try:
                    with LockedFile(file_path, "rb") as file:
                        enc_data = file.read()
                    wallet = Wallet.from_json(decrypt_string(wallet_pass, APP_SALT.encode(), enc_data.decode()))
                    loaded_file = True
                    break
                except portalocker.exceptions.LockException as e:
                    logger.error(_("LockException for Wallet file %(fileName)s %(exp)s") % {"fileName": file_path, "exp": e})
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The File '%(fileName)s' seems to be locked. Please choose another file.") % {"fileName": file_name}, QMessageBox.Icon.Critical, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    loaded_file = False
                    break
                except InvalidToken as e:
                    logger.error(_("Wrong password for Wallet file %(fileName)s %(exp)s") % {"fileName": file_path, "exp": e})
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The password you provided for file '%(fileName)s' is incorrect. Please try again.") % {"fileName": file_name}, QMessageBox.Icon.Critical, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    loaded_file = False
                    continue
                except json.JSONDecodeError as e:
                    logger.error(_("Cannot decode json in Wallet file %(fileName)s %(exp)s") % {"fileName": file_path, "exp": e})
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The file '%(fileName)s' seems to be corrupted. Please choose another file.") % {"fileName": file_name}, QMessageBox.Icon.Critical, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    loaded_file = False
                    break
                except ValueError as e:
                    logger.error(_("Wrong values in %(fileName)s %(exp)s") % {"fileName": file_path, "exp": e})
                    msg_qmbox = SimpleMsgBox(_("Error"), _("The file '%(fileName)s' seems to have wrong values. Please choose another file.") % {"fileName": file_name}, QMessageBox.Icon.Critical, self)
                    msg_qmbox.addButton(_("Ok"), QMessageBox.ButtonRole.ActionRole)
                    msg_qmbox.exec()
                    loaded_file = False
                    break
            else:
                break
        if loaded_file:
            file_path = os.path.realpath(file_path)
            if file_path not in self.config["lastFiles"]:
                self.config["lastFiles"].append(file_path)
                while len(self.config["lastFiles"]) > 5:
                    self.config["lastFiles"].pop(0)
                self._refresh_files_history_qmnu()
            self.config["lastFile"] = file_path
            self.wallet_Qw.set_wallet(wallet, wallet_pass, file_path)
            self.main_Qtabw.setTabText(1, _("Wallet: %(walletName)s") % {"walletName": wallet.walletName})
            return
        if return_to_prev:
            self._ask_load_wallet_filepath(return_to_prev)

    def _init_file_qmnu(self):
        """Initialize File Menu."""
        # add File Menu to Main MenuBar
        self.main_Qmnub.addMenu(self.file_Qmnu)
        # add action "New Wallet" to File Menu
        self.file_Qmnu.addAction(self.newWallet_Qact)
        # set action target
        self.newWallet_Qact.triggered.connect(self._create_new_wallet)
        # add action "Open Wallet" to File Menu
        self.file_Qmnu.addAction(self.openWallet_Qact)
        # set action target
        self.openWallet_Qact.triggered.connect(self._load_wallet)
        self.file_Qmnu.addSeparator()
        # add files
        self.file_Qmnu.addMenu(self.filesHistory_Qmnu)
        self._refresh_files_history_qmnu()
        self.file_Qmnu.addSeparator()
        # add action "Hide application" to File Menu
        self.file_Qmnu.addAction(self.hideApp_Qact)
        # set action target
        self.hideApp_Qact.triggered.connect(self.hide)
        # add action "Close application" to File Menu
        self.file_Qmnu.addAction(self.closeApp_Qact)
        # set action target
        self.closeApp_Qact.triggered.connect(self._quit_app_func)
        # add action "Close without saving" to File Menu
        self.file_Qmnu.addAction(self.forceCloseApp_Qact)
        # set action target
        self.forceCloseApp_Qact.triggered.connect(self._force_quit_app_func)

    def _refresh_files_history_qmnu(self):
        self.filesHistory_Qmnu.clear()
        for file_path in self.config.get('lastFiles', []):
            if not os.path.exists(file_path):
                self.config['lastFiles'].remove(file_path)
            else:
                file_name = os.path.basename(file_path)
                file_open_qact = QAction(QIcon(":icoWallet"), file_name, self)
                self.filesHistory_Qmnu.addAction(file_open_qact)
                # TODO: Add trigger connect
        else:
            self.filesHistory_Qmnu.addAction(self.noFileHistory_Qact)

    def _init_langs_qmnu(self):
        """Initialize Langs Menu."""
        self.main_Qmnub.addMenu(self.langs_Qmnu)
        #
        self.langs_Qmnu.addAction(self.changeLangEn_Qact)
        self.changeLangEn_Qact.triggered.connect(lambda: self._change_lang("en", self.changeLangEn_Qact.text()))
        #
        self.langs_Qmnu.addAction(self.changeLangFr_Qact)
        self.changeLangFr_Qact.triggered.connect(lambda: self._change_lang("fr", self.changeLangFr_Qact.text()))
        #
        self.langs_Qmnu.addAction(self.changeLangAr_Qact)
        self.changeLangAr_Qact.triggered.connect(lambda: self._change_lang("ar", self.changeLangAr_Qact.text()))
        # self.changeLangAr_Qact.setDisabled(True)
        #
        self.langs_Qmnu.addAction(self.changeLangDe_Qact)
        self.changeLangDe_Qact.triggered.connect(lambda: self._change_lang("de", self.changeLangDe_Qact.text()))
        self.changeLangDe_Qact.setDisabled(True)
        #
        self.langs_Qmnu.addAction(self.changeLangIt_Qact)
        self.changeLangIt_Qact.triggered.connect(lambda: self._change_lang("it", self.changeLangIt_Qact.text()))
        self.changeLangIt_Qact.setDisabled(True)
        #
        self.langs_Qmnu.addAction(self.changeLangEs_Qact)
        self.changeLangEs_Qact.triggered.connect(lambda: self._change_lang("es", self.changeLangEs_Qact.text()))
        self.changeLangEs_Qact.setDisabled(True)

    def _init_help_qmnu(self):
        """Initialize Help menu."""
        # add Help Menu to Main MenuBar
        self.main_Qmnub.addMenu(self.help_Qmnu)
        # add action "Help Content" to Help Menu
        self.help_Qmnu.addAction(self.help_Qact)
        # set action target
        self.help_Qact.triggered.connect(self._show_help_window)
        # add action "About Khadhroony SRLPv4" to Help Menu
        self.help_Qmnu.addAction(self.about_Qact)
        # set action target
        self.about_Qact.triggered.connect(self._show_about_window)

    def update_display(self):
        """refresh translations of texts in main menubar when language change."""
        self.file_Qmnu.setTitle(_("File"))
        self.newWallet_Qact.setText(_("New Wallet"))
        self.openWallet_Qact.setText(_("Open Wallet"))
        self.filesHistory_Qmnu.setTitle(_("Last Files"))
        self.noFileHistory_Qact.setText(_("No file found"))
        self.hideApp_Qact.setText(_("Hide application"))
        self.closeApp_Qact.setText(_("Close application"))
        self.forceCloseApp_Qact.setText(_("Close without saving"))
        self.langs_Qmnu.setTitle(_("Languages"))
        self.help_Qmnu.setTitle(_("Help"))
        self.help_Qact.setText(_("Help Content"))
        self.about_Qact.setText(_("About %(app)s") % {"app":APP_NAME})
        self.showApp_Qact.setText(_("Show application"))
        self.main_Qtabw.setTabText(0, _("Tokens:"))
        if self.wallet_Qw.wallet is None:
            self.main_Qtabw.setTabText(1, _("Wallet: %(walletName)s") % {"walletName": ""})
        else:
            self.main_Qtabw.setTabText(1, _("Wallet: %(walletName)s") % {"walletName": self.wallet_Qw.wallet.walletName})
        self.main_Qtabw.setTabText(2, _("Log Display:"))
        self.wallet_Qw.update_display()
        self.logViewer_Qw.update_display()
        self.setLayoutDirection(get_qt_dir())


if __name__ == "__main__":
    logger.remove()  # Supprime les sorties par défaut
    logger.add(sys.stderr, level = 'TRACE', format = '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{line: <4}</cyan> - <level>{message}</level>', filter = None, colorize = None, serialize = False, backtrace = True, diagnose = True, enqueue = False, context = None, catch = True)
    logger.add("logs/KhadhroonyRaydium4.{time}.log", format = "{time:YYYY-MM-DD HH:mm:ss.SSS} - {level} - {line: <4}: {message}", level = "DEBUG")
    app_lang = get_lang_or_fallback(get_system_language())
    set_app_lang_code(app_lang)
    gettext.bindtextdomain('argparse', 'locale')
    gettext.textdomain('argparse')
    parser = argparse.ArgumentParser(
        description = "===~-<=<~~>   No Khadhrawy   <$<~>$>   No Hamroony   <~~>=>-~===\n\n" + APP_DESC + "\n" + _("Launch without options to show main window"),
        formatter_class = argparse.RawTextHelpFormatter,
        epilog = "===~-<=<~~>   No Khadhrawy   <$<~>$>   No Hamroony   <~~>=>-~==="
        )
    # parser.add_argument("-v", "--version", action="store_true", help="show version and exit.")
    parser.add_argument('-v', '--version', action = 'version', version = "{prog} (v. {version})".format(prog = APP_NAME, version = APP_VERSION), help = _("show program version and exit"))
    args = parser.parse_args()
    # creation du répertoire de wallets
    create_wallets_folder()
    # chargement configuration de base / historique / langue / display etc ...
    config = load_yaml_app_config()
    configLocale = config["defaultLang"]
    app_lang = get_lang_or_fallback(configLocale)
    set_app_lang_code(app_lang)
    config['defaultLang'] = app_lang
    app = QApplication(sys.argv)
    # chargement du SplashScreen
    splash_screen = SplashScreen()
    splash_screen.show()
    # Planifier l'affichage de la fenêtre principale après le splash screen
    main_window = MainWindow(config)
    splash_screen.fadeOut.finished.connect(main_window.first_load)

    sys.exit(app.exec())
