import sys
import time
import select
import os
from cyd import CYD
from st7789 import FONT
import framebuf
import gc
import re
from libpidos import LibPiDOS

# --- SPLASH SCREEN ---
SPLASH = """
  ____  _       _____   ____   _____ 
 |  _ \(_)     |  __ \ / __ \ / ____|
 | |_) |_ _____| |  | | |  | | (___  
 |  __/| |_____| |  | | |  | |\___ \ 
 | |   | |     | |__| | |__| |____) |
 |_|   |_|     |_____/ \____/|_____/ 
 ------------------------------------
 Pi-DOS v1.5 Turbo - Matrix Edition
"""

# Couleurs de base
MATRIX_GREEN = CYD.color565(0, 255, 0)
BLACK = CYD.BLACK

# --- CACHE DE CARACTÈRES TURBO ---
# On pré-génère le rendu binaire de chaque caractère pour éviter de boucler
# 128 octets par caractère (8x8 pixels * 2 octets)
CHAR_CACHE = {}
CHAR_FBUFS = {}
FG_BYTES = MATRIX_GREEN.to_bytes(2, 'big')
BG_BYTES = BLACK.to_bytes(2, 'big')

# --- PALETTE ANSI ---
ANSI_COLORS = {
    '30': CYD.BLACK,   # Noir
    '31': CYD.RED,     # Rouge
    '32': CYD.GREEN,   # Vert
    '33': CYD.YELLOW,  # Jaune
    '34': CYD.BLUE,    # Bleu
    '35': CYD.MAGENTA, # Magenta
    '36': CYD.CYAN,    # Cyan
    '37': CYD.WHITE,   # Blanc
    '0':  MATRIX_GREEN # Reset vers Matrix Green
}

print("Initialisation du cache Turbo...")
for char, bitmap in FONT.items():
    buf = bytearray(8 * 8 * 2)
    for r in range(8):
        byte = bitmap[r]
        for c in range(8):
            idx = (r * 8 + c) * 2
            if (byte >> (7 - c)) & 1:
                buf[idx], buf[idx+1] = FG_BYTES[0], FG_BYTES[1]
            else:
                buf[idx], buf[idx+1] = BG_BYTES[0], BG_BYTES[1]
    CHAR_CACHE[char] = buf
    CHAR_FBUFS[char] = framebuf.FrameBuffer(buf, 8, 8, framebuf.RGB565)

# Palette ANSI (Supprimée pour RAM)

class Console:
    def __init__(self, cyd, cols=40, rows=25):
        self.cyd = cyd
        self.cols = cols
        self.rows = rows
        self.char_w = 8
        self.char_h = 8
        self.cursor_x = 0
        self.cursor_y = 0
        self.fg_color = MATRIX_GREEN
        self.bg_color = BLACK
        self._ansi_buf = ""
        self._in_ansi = False
        self.lib = LibPiDOS(cyd)
        self.show_status = True
        
        import gc
        gc.collect()
        
        try:
            # Mode Ultra-Turbo : un buffer par ligne
            # On tente d'allouer AVANT le reste pour maximiser les chances
            self.line_buffers = [bytearray(320 * 8 * 2) for _ in range(rows)]
            self.BLACK_LINE = bytearray(320 * 8 * 2)
            self.use_line_buffers = True
            print("Mode Ultra-Turbo active ({:.1f} Ko RAM)".format((rows+1)*320*16/1024))
        except MemoryError:
            gc.collect()
            self.line_buf = bytearray(320 * 8 * 2)
            self.line_fbuf = framebuf.FrameBuffer(self.line_buf, 320, 8, framebuf.RGB565)
            self.use_line_buffers = False
            print("Mode Turbo Standard active (Faible RAM)")

        # Buffer de texte (liste de listes)
        self.buffer = [[' ' for _ in range(cols)] for _ in range(rows)]
        
        self.cyd.clear(BLACK)
        self.show_cursor()

    # Suppression de _get_char_data devenu inutile

    def _update_line_buffer(self, char, x, y):
        if not self.use_line_buffers: return
        char_data = CHAR_CACHE.get(char, CHAR_CACHE['?'])
        lbuf = self.line_buffers[y]
        col_offset = x * 16
        # Copie directe et déroulée pour performances maximales
        lbuf[col_offset : col_offset+16] = char_data[0:16]
        lbuf[col_offset + 640 : col_offset+656] = char_data[16:32]
        lbuf[col_offset + 1280 : col_offset+1296] = char_data[32:48]
        lbuf[col_offset + 1920 : col_offset+1936] = char_data[48:64]
        lbuf[col_offset + 2560 : col_offset+2576] = char_data[64:80]
        lbuf[col_offset + 3200 : col_offset+3216] = char_data[80:96]
        lbuf[col_offset + 3840 : col_offset+3856] = char_data[96:112]
        lbuf[col_offset + 4480 : col_offset+4496] = char_data[112:128]

    def show_cursor(self):
        # Curseur BLOC (très rétro)
        self.cyd.display.fill_hrect(self.cursor_x * self.char_w, self.cursor_y * self.char_h, self.char_w, self.char_h, self.fg_color)

    def hide_cursor(self):
        # On restaure le caractère sous le curseur
        char = self.buffer[self.cursor_y][self.cursor_x]
        char_data = CHAR_CACHE.get(char, CHAR_CACHE['?'])
        self.cyd.display.blit(self.cursor_x * self.char_w, self.cursor_y * self.char_h, 8, 8, char_data)

    def draw_line_turbo(self, row_idx):
        """Dessine une ligne via framebuf blit (Très rapide)."""
        text_line = self.buffer[row_idx]
        self.line_fbuf.fill(0)
        for col_idx in range(self.cols):
            char = text_line[col_idx]
            if char != ' ':
                self.line_fbuf.blit(CHAR_FBUFS.get(char, CHAR_FBUFS['?']), col_idx * 8, 0)
        self.cyd.display.blit(0, row_idx * 8, 320, 8, self.line_buf)

    def scroll(self):
        # Décalage rapide des listes texte
        self.buffer.pop(0)
        self.buffer.append([' ' for _ in range(self.cols)])
        
        if self.use_line_buffers:
            # Mode Ultra-Turbo : on recycle le buffer mémoire sans recalculer !
            first = self.line_buffers.pop(0)
            first[:] = self.BLACK_LINE
            self.line_buffers.append(first)
            
            # Envoi direct (très rapide)
            for y in range(self.rows):
                self.cyd.display.blit(0, y * 8, 320, 8, self.line_buffers[y])
        else:
            # Fallback
            empty_start = -1
            for y in range(self.rows):
                line = self.buffer[y]
                # Optimisation : on groupe les lignes vides
                is_empty = all(c == ' ' for c in line)
                if is_empty:
                    if empty_start == -1: empty_start = y
                else:
                    if empty_start != -1:
                        self.cyd.display.fill_hrect(0, empty_start * 8, 320, (y - empty_start) * 8, BLACK)
                        empty_start = -1
                    self.draw_line_turbo(y)
            if empty_start != -1:
                self.cyd.display.fill_hrect(0, empty_start * 8, 320, (self.rows - empty_start) * 8, BLACK)

    def putchar(self, char):
        self.hide_cursor()
        if char == '\n' or char == '\r':
            self.cursor_x = 0
            self.cursor_y += 1
        elif char == '\b' or ord(char) == 127:
            if self.cursor_x > 0:
                self.cursor_x -= 1
                self.buffer[self.cursor_y][self.cursor_x] = ' '
                self._update_line_buffer(' ', self.cursor_x, self.cursor_y)
                self.cyd.display.fill_hrect(self.cursor_x * self.char_w, self.cursor_y * self.char_h, self.char_w, self.char_h, BLACK)
        else:
            if self.cursor_x < self.cols and self.cursor_y < self.rows:
                self.buffer[self.cursor_y][self.cursor_x] = char
                self._update_line_buffer(char, self.cursor_x, self.cursor_y)
                char_data = CHAR_CACHE.get(char, CHAR_CACHE['?'])
                self.cyd.display.blit(self.cursor_x * self.char_w, self.cursor_y * self.char_h, 8, 8, char_data)
                self.cursor_x += 1
            
            if self.cursor_x >= self.cols:
                self.cursor_x = 0
                self.cursor_y += 1
        
        if self.cursor_y >= self.rows:
            self.scroll()
            self.cursor_y = self.rows - 1
        self.show_cursor()


    def write(self, text):
        """Affiche de longs textes de manière optimisée avec support ANSI."""
        self.hide_cursor()
        lines_changed = set()
        
        i = 0
        while i < len(text):
            char = text[i]
            
            # --- PARSEUR ANSI TRÈS BASIQUE (\x1b[31m) ---
            if char == '\x1b':
                self._in_ansi = True
                self._ansi_buf = ""
                i += 1
                continue
            
            if self._in_ansi:
                if char == 'm':
                    # Fin de séquence
                    code = self._ansi_buf.replace('[', '')
                    if code in ANSI_COLORS:
                        self.fg_color = ANSI_COLORS[code]
                        # Régénérer le cache de couleurs si besoin ? 
                        # Pour la RAM, on va juste changer la couleur de tracé direct
                    self._in_ansi = False
                else:
                    self._ansi_buf += char
                i += 1
                continue
            
            if char == '\n' or char == '\r':
                self.cursor_x = 0
                self.cursor_y += 1
            elif char == '\b' or ord(char) == 127:
                if self.cursor_x > 0:
                    self.cursor_x -= 1
                    self.buffer[self.cursor_y][self.cursor_x] = ' '
                    self._update_line_buffer(' ', self.cursor_x, self.cursor_y)
                    lines_changed.add(self.cursor_y)
            else:
                if self.cursor_x < self.cols and self.cursor_y < self.rows:
                    self.buffer[self.cursor_y][self.cursor_x] = char
                    self._update_line_buffer(char, self.cursor_x, self.cursor_y)
                    lines_changed.add(self.cursor_y)
                    self.cursor_x += 1
                
                if self.cursor_x >= self.cols:
                    self.cursor_x = 0
                    self.cursor_y += 1
            
            if self.cursor_y >= self.rows:
                self.scroll()
                self.cursor_y = self.rows - 1
                lines_changed.clear()
            
            i += 1
        
        # Rend les lignes modifiées
        if self.use_line_buffers:
            for y in sorted(lines_changed):
                self.cyd.display.blit(0, y * 8, 320, 8, self.line_buffers[y])
        else:
            for y in sorted(lines_changed):
                self.draw_line_turbo(y)
        
        self.show_cursor()
        if self.show_status:
            self.lib.draw_status_bar(y=228, fg=CYD.GRAY, bg=CYD.BLACK)

    def clear(self):
        self.cyd.clear(BLACK)
        self.buffer = [[' ' for _ in range(self.cols)] for _ in range(self.rows)]
        if self.use_line_buffers:
            for y in range(self.rows):
                self.line_buffers[y][:] = self.BLACK_LINE
        self.cursor_x = 0
        self.cursor_y = 0
        self.show_cursor()

    def print(self, *args, sep=' ', end='\n'):
        text = sep.join(str(a) for a in args) + end
        self.write(text)

    def input(self, prompt=''):
        self.write(str(prompt))
        buf = ""
        while True:
            r, _, _ = select.select([sys.stdin], [], [], 0.05)
            if r:
                char = sys.stdin.read(1)
                if char in ('\r', '\n'):
                    self.write('\n')
                    return buf
                elif char == '\b' or ord(char) == 127:
                    if len(buf) > 0:
                        buf = buf[:-1]
                        self.write('\b')
                else:
                    buf += char
                    self.write(char)

class Shell:
    def __init__(self, console):
        self.console = console
        self.input_buf = ""
        self.history = []
        self.history_idx = -1
        self.esc_state = 0
        self.env = {
            "PROMPT": "$P$G",
            "PATH": "C:\\;C:\\BIN;A:\\"
        }
        self.cur_drive = "C"
        self.print_prompt()

    def get_prompt(self):
        cwd = os.getcwd()
        if cwd.startswith("/sd"):
            p = "A:" + cwd[3:].replace('/', '\\')
        else:
            p = "C:" + cwd.replace('/', '\\')
        if not p.endswith('\\'): p += '\\'
        return p + ">"

    def print_prompt(self):
        self.console.write(self.get_prompt())

    def resolve_path(self, path):
        if not path: return os.getcwd()
        path = path.replace('\\', '/')
        if path.upper().startswith("A:"):
            p = path[2:]
            if not p.startswith('/'): p = '/' + p
            return "/sd" + p if p != "/" else "/sd"
        if path.upper().startswith("C:"):
            p = path[2:]
            if not p.startswith('/'): p = '/' + p
            return p if p else "/"
        
        # Chemin relatif ou absolu sans lettre
        if path.startswith("/"):
            cwd = os.getcwd()
            if cwd.startswith("/sd"): return "/sd" + path
            return path
            
        return path # Relatif

    def process_char(self, char):
        # Gestion des séquences d'échappement (Flèches, etc.)
        if self.esc_state == 0:
            if char == '\x1b':
                self.esc_state = 1
                return
        elif self.esc_state == 1:
            if char == '[':
                self.esc_state = 2
                return
            elif char == 'O':
                self.esc_state = 3
                return
            else:
                self.esc_state = 0
        elif self.esc_state == 2 or self.esc_state == 3:
            self.esc_state = 0
            if char == 'A': # HAUT
                self.navigate_history(-1)
                return
            elif char == 'B': # BAS
                self.navigate_history(1)
                return
            return

        if char == '\r' or char == '\n':
            self.console.putchar('\n')
            cmd = self.input_buf.strip()
            if cmd:
                if not self.history or self.history[-1] != cmd:
                    self.history.append(cmd)
                    if len(self.history) > 20: self.history.pop(0)
            self.history_idx = -1
            self.execute(self.input_buf)
            self.input_buf = ""
            self.print_prompt()
        elif char == '\t':
            self.tab_complete()
        elif char == '\b' or ord(char) == 127:
            if len(self.input_buf) > 0:
                self.input_buf = self.input_buf[:-1]
                self.console.putchar('\b')
        else:
            self.input_buf += char
            self.console.putchar(char)

    def navigate_history(self, delta):
        if not self.history: return
        
        # Inversons pour que HAUT aille vers le plus vieux
        if delta < 0: # HAUT
            if self.history_idx == -1: 
                self.history_idx = len(self.history) - 1
            else: 
                self.history_idx = max(0, self.history_idx - 1)
        else: # BAS
            if self.history_idx == -1:
                return # Déjà sur la ligne vide
            elif self.history_idx == len(self.history) - 1:
                self.history_idx = -1
            else:
                self.history_idx += 1
        
        # Effacer ligne actuelle
        for _ in range(len(self.input_buf)):
            self.console.putchar('\b')
            
        if self.history_idx == -1:
            self.input_buf = ""
        else:
            self.input_buf = self.history[self.history_idx]
        
        self.console.write(self.input_buf)

    def tab_complete(self):
        parts = self.input_buf.split()
        if not parts: return
        
        to_complete = parts[-1]
        path = "."
        prefix = to_complete
        
        if '/' in to_complete or '\\' in to_complete:
            return
            
        try:
            files = os.listdir(path)
            matches = [f for f in files if f.lower().startswith(prefix.lower())]
            
            if len(matches) == 1:
                rem = matches[0][len(prefix):]
                self.input_buf += rem
                self.console.write(rem)
        except: pass

    def execute(self, line):
        line = line.strip()
        if not line: return
        
        # --- REDIRECTION > ---
        redirect_file = None
        if '>' in line:
            line, redirect_file = line.split('>', 1)
            line = line.strip()
            redirect_file = self.resolve_path(redirect_file.strip())

        # Sauvegarde du stdout original si besoin (MicroPython ne supporte pas facilement le dup2)
        # On va juste intercepter les print() de l'execute
        original_write = self.console.write
        if redirect_file:
            try:
                f_out = open(redirect_file, "w")
                def redirect_write(text):
                    f_out.write(text)
                self.console.write = redirect_write
            except:
                self.console.write("Erreur redirection.\n")
                redirect_file = None

        try:
            self._do_execute(line)
        finally:
            if redirect_file:
                f_out.close()
                self.console.write = original_write

    def _do_execute(self, line):
        parts = line.split()
        if not parts: return
        cmd = parts[0].lower()
        args = parts[1:]

        if cmd.endswith(".bat"):
            self.execute_batch(self.resolve_path(line))
            return

        if len(line) == 2 and line[1] == ':':
            drive = line[0].upper()
            if drive == 'A':
                try: 
                    os.chdir("/sd")
                    self.cur_drive = "A"
                except: self.console.write("Lecteur A: non pret.\n")
            elif drive == 'C':
                try: 
                    os.chdir("/")
                    self.cur_drive = "C"
                except: pass
            return

        # Support cd.. et cd\ sans espace
        if line.lower().startswith("cd.."):
            cmd = "cd"
            args = [".."]
        elif line.lower().startswith("cd\\"):
            cmd = "cd"
            args = ["/"]
        else:
            parts = line.split()
            cmd = parts[0].lower()
            args = parts[1:]

        if cmd == "help":
            self.console.write("Commandes: DIR, TYPE, CLS, VER, ECHO, DATE, MEM, DEL, MD, RD, CD, COPY\n")
            self.console.write("Plus: WIFI, SOUND, INFO, EDIT, SET, WGET, REBOOT\n")
        elif cmd == "cls":
            self.console.clear()
        elif cmd == "ver":
            self.console.write("Pi-DOS Turbo v1.5 (Matrix Edition)\n")
        elif cmd == "set":
            if not args:
                for k, v in self.env.items():
                    self.console.write("{}={}\n".format(k, v))
            else:
                s = " ".join(args)
                if '=' in s:
                    k, v = s.split('=', 1)
                    self.env[k.upper()] = v
                else:
                    self.console.write(self.env.get(args[0].upper(), "") + "\n")
        elif cmd == "info":
            import machine
            import gc
            self.console.write("\n--- SYSTEM INFO ---\n")
            self.console.write("CPU Freq: {} MHz\n".format(machine.freq() // 1000000))
            self.console.write("Mem Free: {} KB\n".format(gc.mem_free() // 1024))
            self.console.write("WiFi Status: {}\n".format("Connecte" if self.console.cyd.wlan.isconnected() else "Deconnecte"))
            if self.console.cyd.wlan.isconnected():
                self.console.write("IP Address: {}\n".format(self.console.cyd.wlan.ifconfig()[0]))
            self.console.write("Uptime: {} s\n\n".format(time.ticks_ms() // 1000))
        elif cmd == "sound":
            if args:
                try: self.console.cyd.beep(int(args[0]), int(args[1]) if len(args) > 1 else 100)
                except: pass
            else:
                self.console.cyd.play_click()
        elif cmd == "wifi":
            if not args:
                self.console.write("Syntaxe: WIFI SCAN | WIFI CONNECT <ssid> <pass>\n")
            elif args[0].lower() == "scan":
                self.console.write("Scanning...\n")
                nets = self.console.cyd.scan_wifi()
                for ssid, rssi in nets:
                    self.console.write("{:<20} ({:>3} dBm)\n".format(ssid, rssi))
            elif args[0].lower() == "connect" and len(args) >= 3:
                self.console.write("Connecting to {}...\n".format(args[1]))
                res = self.console.cyd.connect_wifi(args[1], args[2])
                if res: self.console.write("Connecte! IP: {}\n".format(res[0]))
                else: self.console.write("Echec de connexion.\n")
        elif cmd == "wget":
            if not args:
                self.console.write("Syntaxe: WGET <url> [filename]\n")
            else:
                try:
                    import urequests
                    url = args[0]
                    fname = args[1] if len(args) > 1 else url.split('/')[-1]
                    self.console.write("Telechargement de {}...\n".format(url))
                    r = urequests.get(url)
                    with open(fname, "wb") as f:
                        f.write(r.content)
                    r.close()
                    self.console.write("Termine: {} ({} octets)\n".format(fname, len(r.content)))
                except Exception as e:
                    self.console.write("Erreur: {}\n".format(e))
        elif cmd == "reboot":
            import machine
            machine.reset()
        elif cmd == "edit":
            if not args:
                self.console.write("Syntaxe: EDIT <fichier>\n")
            else:
                self.execute("edlin.py " + args[0])
        elif cmd == "echo":
            self.console.write(" ".join(args) + "\n")
        elif cmd == "date":
            t = time.localtime()
            self.console.write("La date actuelle est : {:02d}-{:02d}-{:04d}\n".format(t[2], t[1], t[0]))
            self.console.write("L'heure actuelle est : {:02d}:{:02d}:{:02d}\n".format(t[3], t[4], t[5]))
        elif cmd == "mem":
            import gc
            gc.collect()
            free = gc.mem_free()
            alloc = gc.mem_alloc()
            total = free + alloc
            self.console.write("\n")
            self.console.write("  {:>8} octets de memoire totale\n".format(total))
            self.console.write("  {:>8} octets utilises\n".format(alloc))
            self.console.write("  {:>8} octets libres\n\n".format(free))
        elif cmd == "del":
            if not args:
                self.console.write("Syntaxe incorrecte.\n")
            else:
                path = self.resolve_path(args[0])
                if '/' in path:
                    base, pattern_str = path.rsplit('/', 1)
                    base = base or "/"
                else:
                    base = os.getcwd()
                    pattern_str = path
                
                pattern = "^" + pattern_str.replace('.', '\\.').replace('*', '.*').replace('?', '.') + "$"
                try:
                    files = os.listdir(base)
                    count = 0
                    for f in files:
                        if re.match(pattern, f):
                            try:
                                full = base + "/" + f if not base.endswith('/') else base + f
                                os.remove(full)
                                count += 1
                            except: pass
                    self.console.write("{} fichier(s) supprime(s).\n".format(count))
                except:
                    self.console.write("Fichier introuvable.\n")
        elif cmd == "copy":
            if len(args) < 2:
                self.console.write("Syntaxe incorrecte.\n")
            else:
                try:
                    src_path = self.resolve_path(args[0])
                    dst_path = self.resolve_path(args[1])
                    
                    if '/' in src_path:
                        base, pattern_str = src_path.rsplit('/', 1)
                        base = base or "/"
                    else:
                        base = os.getcwd()
                        pattern_str = src_path
                    
                    pattern = "^" + pattern_str.replace('.', '\\.').replace('*', '.*').replace('?', '.') + "$"
                    files = os.listdir(base)
                    count = 0
                    for f in files:
                        if re.match(pattern, f):
                            source = base + "/" + f if not base.endswith('/') else base + f
                            target = dst_path
                            try:
                                if os.stat(dst_path)[0] & 0x4000:
                                    target = dst_path + '/' + f if not dst_path.endswith('/') else dst_path + f
                            except: pass
                            
                            with open(source, "rb") as fsrc:
                                with open(target, "wb") as fdst:
                                    while True:
                                        buf = fsrc.read(512)
                                        if not buf: break
                                        fdst.write(buf)
                            count += 1
                    self.console.write("{} fichier(s) copie(s).\n".format(count))
                except Exception as e:
                    self.console.write("Erreur de copie: {}\n".format(e))
        elif cmd == "md":
            if not args:
                self.console.write("Syntaxe incorrecte.\n")
            else:
                try: os.mkdir(self.resolve_path(args[0]))
                except: self.console.write("Erreur creation.\n")
        elif cmd == "rd":
            if not args:
                self.console.write("Syntaxe incorrecte.\n")
            else:
                try: os.rmdir(self.resolve_path(args[0]))
                except: self.console.write("Erreur suppression.\n")
        elif cmd == "cd":
            if not args:
                self.console.write(self.get_prompt() + "\n")
            else:
                dest = self.resolve_path(args[0])
                try: os.chdir(dest)
                except: self.console.write("Chemin introuvable.\n")
        elif cmd == "dir":
            cwd = os.getcwd()
            if cwd.startswith("/sd"):
                v_cwd = "A:" + cwd[3:].replace('/', '\\')
                self.console.write("\n Vol A: sans nom\n")
            else:
                v_cwd = "C:" + cwd.replace('/', '\\')
                self.console.write("\n Vol C: sans nom\n")
            
            if not v_cwd.endswith('\\'): v_cwd += '\\'
            self.console.write(" Num de serie: 1234-5678\n")
            self.console.write("\n Rep de {}\n\n".format(v_cwd))
            
            arg_path = self.resolve_path(args[0]) if args else os.getcwd()
            
            # Gestion des wildcards dans DIR
            if args and ('*' in args[0] or '?' in args[0]):
                if '/' in arg_path:
                    base, pattern_str = arg_path.rsplit('/', 1)
                    base = base or "/"
                else:
                    base = os.getcwd()
                    pattern_str = arg_path
                pattern = "^" + pattern_str.replace('.', '\\.').replace('*', '.*').replace('?', '.') + "$"
            else:
                base = arg_path
                try:
                    if os.stat(base)[0] & 0x4000:
                        pattern = None # Lister tout le dossier
                    else:
                        # C'est un fichier unique
                        pattern = "^" + base.rsplit('/', 1)[-1].replace('.', '\\.') + "$"
                        base = base.rsplit('/', 1)[0] or "/"
                except:
                    pattern = None
            
            try:
                files = os.listdir(base)
                file_count = 0
                dir_count = 0
                total_size = 0
                for f in files:
                    if pattern and not re.match(pattern, f): continue
                    # Cacher le dossier 'sd' si on est à la racine de C:
                    if f == "sd" and base == "/": continue
                    
                    try:
                        full_f = base + "/" + f if not base.endswith('/') else base + f
                        stat = os.stat(full_f)
                        mode = stat[0]
                        size = stat[6]
                        try:
                            mtime = stat[8]
                            t = time.localtime(mtime)
                            date_str = "{:02d}-{:02d}-{:02d}".format(t[2], t[1], t[0]%100)
                            time_str = "{:02d}:{:02d}".format(t[3], t[4])
                        except:
                            date_str = "01-01-80"
                            time_str = "12:00"
                        
                        is_dir = mode & 0x4000
                        name_part = f[:12]
                        if is_dir:
                            size_part = "<DIR>"
                            dir_count += 1
                        else:
                            size_part = str(size)
                            file_count += 1
                            total_size += size
                        self.console.write("{:<12} {:>7} {} {}\n".format(name_part, size_part, date_str, time_str))
                    except: pass
                
                self.console.write("{:>6} fichier(s) {:>14} oct.\n".format(file_count, total_size))
                try:
                    vfs = os.statvfs(base)
                    free = vfs[0] * vfs[3]
                    self.console.write("{:>6} rep(s) {:>18} libres\n".format(dir_count, free))
                except:
                    self.console.write("{:>14} rep(s)\n".format(dir_count))
            except:
                self.console.write("Erreur lecture.\n")
        elif cmd == "type":
            if not args:
                self.console.write("Syntaxe incorrecte.\n")
            else:
                try:
                    path = self.resolve_path(args[0])
                    with open(path, "r") as f:
                        for l in f:
                            self.console.write(l.replace('\n', '\r\n'))
                except:
                    self.console.write("Fichier introuvable.\n")
        else:
            args_path = [os.getcwd(), "/sd", "/sd/bin", "/bin"]
            found = None
            if args and (args[0].upper().startswith("A:") or args[0].upper().startswith("C:")):
                p = self.resolve_path(args[0])
                try:
                    os.stat(p)
                    found = p
                except: pass
            else:
                for p in args_path:
                    try:
                        full = p + "/" + cmd if not p.endswith("/") else p + cmd
                        os.stat(full)
                        found = full
                        break
                    except: pass
                if not found:
                    for p in args_path:
                        try:
                            full = p + "/" + cmd + ".py" if not p.endswith("/") else p + cmd + ".py"
                            os.stat(full)
                            found = full
                            break
                        except: pass
            
            if found:
                try:
                    with open(found, "r") as f:
                        script_content = f.read()
                    namespace = {
                        'console': self.console, 
                        'cyd': self.console.cyd, 
                        'sys_argv': [found] + args,
                        'print': self.console.print,
                        'input': self.console.input,
                        'os': os,
                        'time': time,
                        'shell': self,
                        'sys': sys
                    }
                    exec(script_content, namespace)
                except Exception as e:
                    self.console.write("Erreur script: {}\n".format(e))
            else:
                self.console.write("Commande inconnue.\n")

    def execute_batch(self, path):
        try:
            with open(path, "r") as f:
                for line in f:
                    l = line.strip()
                    if l and not l.startswith("REM"):
                        self.console.write("> " + l + "\n")
                        self.execute(l)
        except Exception as e:
            self.console.write("Erreur BAT: {}\n".format(e))

    def run(self):
        self.console.clear()
        self.console.write(SPLASH)
        self.console.write("Flash: 4MB, RAM: 320KB, SD: Detectee\n")
        
        # Exécution de AUTOEXEC.BAT si présent
        try:
            os.stat("/autoexec.bat")
            self.console.write("\nExecution de AUTOEXEC.BAT...\n")
            with open("/autoexec.bat", "r") as f:
                for line in f:
                    l = line.strip()
                    if l and not l.startswith("REM"):
                        self.execute(l)
                        self.console.write("\n")
        except:
            self.console.write("Tapez HELP pour la liste des commandes.\n")
        
        self.print_prompt()

def main():
    cyd = CYD()
    cyd.mount_sd() # Montage automatique de la SD sur /sd
    console = Console(cyd)
    shell = Shell(console)
    shell.run()
    while True:
        r, _, _ = select.select([sys.stdin], [], [], 0)
        if r:
            char = sys.stdin.read(1)
            if char: shell.process_char(char)
        else:
            time.sleep_ms(2)

if __name__ == "__main__":
    main()
