# uf/engine.py
import time
import random
import os
import math
import difflib
import json

# Optional REPL history (Linux/macOS native, Windows needs pyreadline3)
try:
    import readline
except ImportError:
    readline = None

# -----------------------------
# Tokenizer
# -----------------------------
def tokenize(line):
    tokens = []
    current = ""
    in_string = False
    for char in line:
        if char == '"':
            in_string = not in_string
            current += char
        elif char.isspace() and not in_string:
            if current:
                tokens.append(current)
                current = ""
        else:
            current += char
    if current:
        tokens.append(current)
    return tokens

# -----------------------------
# Error System
# -----------------------------
def uf_error(code, message, line=None, hint=None):
    print(f"\n[UF Error {code}]")
    print(message)
    if line is not None:
        print(f"Line: {line}")
    if hint:
        print(f"Hint: {hint}")
    print()

def suggest_command(cmd, command_list):
    matches = difflib.get_close_matches(cmd, command_list, n=1)
    return matches[0] if matches else None

# -----------------------------
# Base Mode Class
# -----------------------------
class BaseMode:
    def __init__(self, variables):
        self.variables = variables

    def show_commands(self):
        raise NotImplementedError

    def execute(self, tokens):
        raise NotImplementedError

    def get_commands(self):
        return []

# -----------------------------
# UF Engine Class
# -----------------------------
class UFEngine:
    def __init__(self):
        self.variables = {}
        self.mode_classes = {}
        self.current_mode = None
        self.mode_name = "uf"
        self.line_number = 0

        # Debug system
        self.debug = False

        # Import system
        self.imported_files = set()
        self.module_paths = [
            os.getcwd(),
            os.path.join(os.getcwd(), "modules"),
            os.path.join(os.getcwd(), "uf_modules"),
            os.path.join(os.getcwd(), "std"),
        ]

    # -----------------------------
    # Mode Registration
    # -----------------------------
    def register_mode(self, name, mode_class):
        self.mode_classes[name.lower()] = mode_class

    def select_mode(self, name=None):
        if name and name.lower() in self.mode_classes:
            self.mode_name = name.lower()
        else:
            while True:
                name = input("Select mode (UF / iUF / nUF / dnUF): ").strip().lower()
                if name in self.mode_classes:
                    self.mode_name = name
                    break

        self.current_mode = self.mode_classes[self.mode_name](self.variables)
        print(f"Loaded mode: {self.mode_name.upper()}")

        if self.mode_name == "uf":
            self.current_mode.show_commands()

    # -----------------------------
    # Import System (.uf files)
    # -----------------------------
    def resolve_module_path(self, filename):
        filename = filename.strip('"').strip("'")

        if not filename.endswith(".uf"):
            filename += ".uf"

        for path in self.module_paths:
            full_path = os.path.join(path, filename)
            if os.path.isfile(full_path):
                return full_path

        if os.path.isfile(filename):
            return filename

        return None

    def handle_import(self, tokens):
        if len(tokens) < 2:
            uf_error("E400", "Import requires a filename", line=self.line_number)
            return True

        module_name = tokens[1]
        module_path = self.resolve_module_path(module_name)

        if not module_path:
            uf_error(
                "E404",
                f"Module not found: {module_name}",
                line=self.line_number,
                hint='Example: import "math.uf"'
            )
            return True

        if module_path in self.imported_files:
            if self.debug:
                print(f"[DEBUG] Module already imported: {module_path}")
            return True

        self.imported_files.add(module_path)
        self.run_file(module_path, is_import=True)
        return True

    # -----------------------------
    # Variable Persistence
    # -----------------------------
    def save_vars(self, filename):
        try:
            with open(filename, "w", encoding="utf-8") as f:
                json.dump(self.variables, f, indent=2)
            print(f"[UF] Variables saved to {filename}")
        except Exception as e:
            uf_error("E501", f"Failed to save variables: {str(e)}")

    def load_vars(self, filename):
        try:
            with open(filename, "r", encoding="utf-8") as f:
                data = json.load(f)
                if isinstance(data, dict):
                    self.variables.update(data)
                    print(f"[UF] Variables loaded from {filename}")
                else:
                    uf_error("E502", "Invalid save file format")
        except FileNotFoundError:
            uf_error("E404", f"Save file not found: {filename}")
        except Exception as e:
            uf_error("E503", f"Failed to load variables: {str(e)}")

    # -----------------------------
    # Core Execution
    # -----------------------------
    def execute_line(self, line):
        tokens = tokenize(line)
        if not tokens:
            return

        self.line_number += 1
        cmd = tokens[0].lower()

        if self.debug:
            print(f"[DEBUG] Line {self.line_number} Tokens: {tokens}")

        # -------- ENGINE BUILT-IN COMMANDS --------
        if cmd == "import":
            if self.handle_import(tokens):
                return

        if cmd == "help":
            if self.current_mode:
                self.current_mode.show_commands()
            return

        if cmd == "clear":
            os.system("cls" if os.name == "nt" else "clear")
            return

        if cmd == "debug":
            if len(tokens) > 1 and tokens[1].lower() == "on":
                self.debug = True
                print("[UF] Debug mode enabled.")
            else:
                self.debug = False
                print("[UF] Debug mode disabled.")
            return

        if cmd == "save":
            if len(tokens) > 1:
                self.save_vars(tokens[1])
            else:
                uf_error("E400", "Usage: save filename.json", line=self.line_number)
            return

        if cmd == "load":
            if len(tokens) > 1:
                self.load_vars(tokens[1])
            else:
                uf_error("E400", "Usage: load filename.json", line=self.line_number)
            return

        if cmd == "vars":
            if not self.variables:
                print("[UF] No variables defined.")
            else:
                print("[UF] Variables:")
                for k, v in self.variables.items():
                    print(f"  {k} = {v}")
            return

        # -------- MODE COMMAND VALIDATION --------
        if hasattr(self.current_mode, "get_commands"):
            commands = self.current_mode.get_commands()
            if commands and cmd not in commands:
                suggestion = suggest_command(cmd, commands)
                uf_error(
                    "E001",
                    f"Unknown command: {cmd}",
                    line=self.line_number,
                    hint=f'Did you mean "{suggestion}"?' if suggestion else "Type 'help' to see available commands."
                )
                return

        # -------- MODE EXECUTION --------
        try:
            self.current_mode.execute(tokens)
        except Exception as e:
            uf_error(
                "E999",
                f"Runtime error: {str(e)}",
                line=self.line_number
            )

    # -----------------------------
    # REPL (with command history)
    # -----------------------------
    def run_repl(self):
        self.line_number = 0
        print("UF Interactive REPL (type 'exit' to quit)")

        history_file = os.path.join(os.path.expanduser("~"), ".uf_history")

        if readline:
            try:
                readline.read_history_file(history_file)
            except FileNotFoundError:
                pass

        while True:
            try:
                line = input(f"{self.mode_name.upper()} > ").strip()
            except EOFError:
                print("\nExiting UF Engine...")
                break

            if line.lower() == "exit":
                print("Exiting UF Engine...")
                break

            if not line:
                continue

            self.execute_line(line)

        if readline:
            try:
                readline.write_history_file(history_file)
            except Exception:
                pass

    # -----------------------------
    # File Execution (supports imports)
    # -----------------------------
    def run_file(self, filename, is_import=False):
        previous_line = self.line_number
        self.line_number = 0

        try:
            with open(filename, "r", encoding="utf-8") as f:
                if is_import:
                    print(f"[UF] Importing module: {os.path.basename(filename)}")

                for raw_line in f:
                    line = raw_line.strip()
                    if not line or line.startswith("#"):
                        continue
                    self.execute_line(line)

        except FileNotFoundError:
            uf_error("E404", f"File not found: {filename}")
        except Exception as e:
            uf_error("E500", f"Execution failed: {str(e)}")

        self.line_number = previous_line

# -----------------------------
# Mode Classes
# -----------------------------
from .modes import UFMode, iUFMode, nUFMode, dnUFMode