import tkinter as tk
from tkinter import ttk, messagebox, font
import decimal
import json
import os
import appdirs
import sys
import traceback

# --- Futuristic Theme Colors ---
BG_COLOR = '#212121'; FG_COLOR = '#E0E0E0'; ACCENT_COLOR = '#00BCD4'
ENTRY_BG = '#424242'; BUTTON_BG = '#37474F'; BUTTON_ACTIVE_BG = '#455A64'
ERROR_FG = '#FF5252'; INFO_FG = '#4FC3F7'; QUICK_RATE_FG = '#B0BEC5'
SEPARATOR_COLOR = '#546E7A'; DISABLED_FG = '#757575'
RESULT_ACTION_FG = '#FFEB3B'

# --- Configuration ---
APP_NAME = "AMMSwapApp"
APP_AUTHOR = "User"
STATE_FILENAME = "app_state.json"

# --- Defaults (used if state file is missing/invalid) ---
DEFAULT_TOKEN_A_SYMBOL = "USDT"
DEFAULT_TOKEN_B_SYMBOL = "DZD"
DEFAULT_TOKEN_A_DECIMALS = 4
DEFAULT_TOKEN_B_DECIMALS = 2
DEFAULT_TOKEN_A_RESERVE = "100000"
DEFAULT_TOKEN_B_RESERVE = "26000000"
DEFAULT_FEE = "0.3"
DEFAULT_DIRECTION = "a_to_b" # Use generic direction value
DEFAULT_QUICK_RATES = ['10', '100', '1000'] # Store as strings in config

# --- Globals ---
decimal.getcontext().prec = 50
ZERO_THRESHOLD = decimal.Decimal('1E-9')

# --- Calculation Functions (Remain Generic) ---
def calculate_amount_out(amount_in, reserve_in, reserve_out, fee_percent):
    try:
        amount_in_str = amount_in if amount_in else '0'
        reserve_in_str = reserve_in if reserve_in else '0'
        reserve_out_str = reserve_out if reserve_out else '0'
        fee_percent_str = fee_percent if fee_percent else '0'
        amount_in_dec = decimal.Decimal(amount_in_str)
        reserve_in_dec = decimal.Decimal(reserve_in_str)
        reserve_out_dec = decimal.Decimal(reserve_out_str)
        fee_percent_dec = decimal.Decimal(fee_percent_str)
    except decimal.InvalidOperation: return decimal.Decimal('NaN'), decimal.Decimal('NaN')
    if reserve_in_dec <= 0 or reserve_out_dec <= 0: return decimal.Decimal('NaN'), decimal.Decimal('NaN')
    if amount_in_dec < 0: return decimal.Decimal('0'), decimal.Decimal('0')
    if fee_percent_dec < 0: fee_percent_dec = decimal.Decimal('0')
    if fee_percent_dec >= 100: return decimal.Decimal('NaN'), decimal.Decimal('NaN')
    if amount_in_dec == 0: return decimal.Decimal('0'), decimal.Decimal('0')
    fee = fee_percent_dec / decimal.Decimal('100'); fee_amount = amount_in_dec * fee
    amount_in_with_fee = amount_in_dec * (decimal.Decimal('1') - fee)
    denominator = reserve_in_dec + amount_in_with_fee
    if denominator <= 0: return decimal.Decimal('NaN'), fee_amount
    amount_out = (amount_in_with_fee * reserve_out_dec) / denominator
    if amount_out > reserve_out_dec: return decimal.Decimal('NaN'), fee_amount
    return amount_out, fee_amount

def calculate_amount_in(amount_out_desired, reserve_in, reserve_out, fee_percent):
    try:
        amount_out_desired_str = amount_out_desired if amount_out_desired else '0'
        reserve_in_str = reserve_in if reserve_in else '0'
        reserve_out_str = reserve_out if reserve_out else '0'
        fee_percent_str = fee_percent if fee_percent else '0'
        amount_out_dec = decimal.Decimal(amount_out_desired_str)
        reserve_in_dec = decimal.Decimal(reserve_in_str)
        reserve_out_dec = decimal.Decimal(reserve_out_str)
        fee_percent_dec = decimal.Decimal(fee_percent_str)
    except decimal.InvalidOperation: return decimal.Decimal('NaN'), decimal.Decimal('NaN')
    if reserve_in_dec <= 0 or reserve_out_dec <= 0: return decimal.Decimal('NaN'), decimal.Decimal('NaN')
    if amount_out_dec < 0: return decimal.Decimal('0'), decimal.Decimal('0')
    if fee_percent_dec < 0: fee_percent_dec = decimal.Decimal('0')
    if fee_percent_dec >= 100: return decimal.Decimal('inf'), decimal.Decimal('NaN')
    if amount_out_dec == 0: return decimal.Decimal('0'), decimal.Decimal('0')
    if amount_out_dec >= reserve_out_dec: return decimal.Decimal('inf'), decimal.Decimal('NaN')
    fee = fee_percent_dec / decimal.Decimal('100')
    one_minus_fee = decimal.Decimal('1') - fee
    if one_minus_fee <= 0: return decimal.Decimal('inf'), decimal.Decimal('NaN')
    numerator = amount_out_dec * reserve_in_dec
    denominator_inner = reserve_out_dec - amount_out_dec
    if denominator_inner <= 0: return decimal.Decimal('inf'), decimal.Decimal('NaN')
    amount_in_with_fee = numerator / denominator_inner
    amount_in = amount_in_with_fee / one_minus_fee
    fee_amount = amount_in * fee
    return amount_in, fee_amount

# --- Main Application Class ---
class AMMSwapApp:
    def __init__(self, root):
        self.root = root
        self.root.title("ΛMM Ξxchange v2.7 - Responsive") # Updated Title
        self.root.geometry("650x750") # Start a bit larger, but allow resize
        # Make window resizable
        self.root.minsize(600, 700) # Set a minimum practical size
        # self.root.resizable(True, True) # Default is True
        self.root.configure(bg=BG_COLOR)
        self._updating = False

        # --- State/Config Loading ---
        self.state_file_path = self._get_state_file_path()
        print(f"State file location: {self.state_file_path}")
        initial_state = self._load_state()

        # --- Initialize Variables ---
        self.token_a_symbol = tk.StringVar(value=initial_state.get("token_a_symbol", DEFAULT_TOKEN_A_SYMBOL))
        self.token_b_symbol = tk.StringVar(value=initial_state.get("token_b_symbol", DEFAULT_TOKEN_B_SYMBOL))
        self.token_a_decimals = tk.IntVar(value=initial_state.get("token_a_decimals", DEFAULT_TOKEN_A_DECIMALS))
        self.token_b_decimals = tk.IntVar(value=initial_state.get("token_b_decimals", DEFAULT_TOKEN_B_DECIMALS))
        self.token_a_reserve_var = tk.StringVar(value=initial_state.get("token_a_reserve", DEFAULT_TOKEN_A_RESERVE))
        self.token_b_reserve_var = tk.StringVar(value=initial_state.get("token_b_reserve", DEFAULT_TOKEN_B_RESERVE))
        self.fee_var = tk.StringVar(value=initial_state.get("fee", DEFAULT_FEE))
        self.direction = tk.StringVar(value=initial_state.get("direction", DEFAULT_DIRECTION))
        self.quick_rate_amounts_str = initial_state.get("quick_rate_amounts", DEFAULT_QUICK_RATES)
        self.quick_rate_amounts = self._parse_quick_rates(self.quick_rate_amounts_str)
        self.pay_amount_var = tk.StringVar()
        self.receive_amount_var = tk.StringVar()
        self.target_price_var = tk.StringVar()
        self.target_result_var = tk.StringVar(value="Result will appear here")
        self.label_token_a_reserve = tk.StringVar()
        self.label_token_b_reserve = tk.StringVar()
        self.label_target_price = tk.StringVar()
        self.label_rb_a_to_b = tk.StringVar()
        self.label_rb_b_to_a = tk.StringVar()
        self.label_quick_rate_header_a_to_b = tk.StringVar()
        self.label_quick_rate_header_b_to_a = tk.StringVar()

        # --- Setup ---
        self.configure_styles()
        self.vcmd = (self.root.register(self._validate_decimal_input), '%P', '%W')
        self.setup_ui()
        self._update_ui_dynamic_labels()
        self.add_traces()
        self.root.after(50, lambda: self.handle_update(source='pool'))
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)

    def _parse_quick_rates(self, rate_list_str):
        rates = []
        if isinstance(rate_list_str, list):
            for r_str in rate_list_str:
                try: rates.append(decimal.Decimal(r_str))
                except (decimal.InvalidOperation, TypeError): print(f"Warning: Invalid quick rate amount '{r_str}' in config, skipping.")
        if not rates:
             print("Warning: Using default quick rate amounts.")
             return [decimal.Decimal(r) for r in DEFAULT_QUICK_RATES]
        return rates

    def _validate_decimal_input(self, proposed_value, widget_name):
        if proposed_value == "": return True
        try:
            val = decimal.Decimal(proposed_value)
            if widget_name.endswith("fee_entry"):
                 if val < 0 or val >= 100: return False
            elif widget_name.endswith("token_a_reserve_entry") or widget_name.endswith("token_b_reserve_entry"):
                 if val < 0: return False
            elif widget_name.endswith("target_price_entry"):
                 if val < 0: return False
            elif val < 0: # General non-negative
                 return False
            return True
        except decimal.InvalidOperation: return False

    def _get_state_file_path(self):
        data_dir = appdirs.user_data_dir(APP_NAME, APP_AUTHOR)
        os.makedirs(data_dir, exist_ok=True)
        return os.path.join(data_dir, STATE_FILENAME)

    def _load_state(self):
        defaults = {"token_a_symbol": DEFAULT_TOKEN_A_SYMBOL, "token_b_symbol": DEFAULT_TOKEN_B_SYMBOL, "token_a_decimals": DEFAULT_TOKEN_A_DECIMALS, "token_b_decimals": DEFAULT_TOKEN_B_DECIMALS, "token_a_reserve": DEFAULT_TOKEN_A_RESERVE, "token_b_reserve": DEFAULT_TOKEN_B_RESERVE, "fee": DEFAULT_FEE, "direction": DEFAULT_DIRECTION, "quick_rate_amounts": DEFAULT_QUICK_RATES}
        try:
            if os.path.exists(self.state_file_path):
                with open(self.state_file_path, 'r') as f: state = json.load(f)
                if isinstance(state, dict):
                    print("Loaded previous state.")
                    for key, default_val in defaults.items(): # Ensure all keys exist and validate
                        if key not in state: state[key] = default_val
                    if not isinstance(state.get("token_a_symbol"), str) or not state["token_a_symbol"]: state["token_a_symbol"] = defaults["token_a_symbol"]
                    if not isinstance(state.get("token_b_symbol"), str) or not state["token_b_symbol"]: state["token_b_symbol"] = defaults["token_b_symbol"]
                    if not isinstance(state.get("token_a_decimals"), int) or state["token_a_decimals"] < 0: state["token_a_decimals"] = defaults["token_a_decimals"]
                    if not isinstance(state.get("token_b_decimals"), int) or state["token_b_decimals"] < 0: state["token_b_decimals"] = defaults["token_b_decimals"]
                    if not self._is_valid_decimal_str(state.get("token_a_reserve"), allow_negative=False): state["token_a_reserve"] = defaults["token_a_reserve"]
                    if not self._is_valid_decimal_str(state.get("token_b_reserve"), allow_negative=False): state["token_b_reserve"] = defaults["token_b_reserve"]
                    if not self._is_valid_decimal_str(state.get("fee"), allow_negative=False, max_val=decimal.Decimal(100)): state["fee"] = defaults["fee"]
                    if state.get("direction") not in ["a_to_b", "b_to_a"]: state["direction"] = defaults["direction"]
                    if not isinstance(state.get("quick_rate_amounts"), list): state["quick_rate_amounts"] = defaults["quick_rate_amounts"]
                    return state
                else: print("Warning: State file has invalid format. Using defaults."); return defaults
            else: print("No previous state file found. Using defaults."); return defaults
        except (json.JSONDecodeError, IOError, Exception) as e:
            print(f"Error loading state file: {e}. Using defaults.")
            try: os.rename(self.state_file_path, f"{self.state_file_path}.corrupted")
            except OSError: pass
            return defaults

    def _is_valid_decimal_str(self, value_str, allow_negative=True, max_val=None):
        if not isinstance(value_str, str): return False
        try:
            val = decimal.Decimal(value_str)
            if not allow_negative and val < 0: return False
            if max_val is not None and val >= max_val: return False
            return True
        except decimal.InvalidOperation: return False

    def _save_state(self):
        state = {"token_a_symbol": self.token_a_symbol.get(), "token_b_symbol": self.token_b_symbol.get(), "token_a_decimals": self.token_a_decimals.get(), "token_b_decimals": self.token_b_decimals.get(), "token_a_reserve": self.token_a_reserve_var.get() or "0", "token_b_reserve": self.token_b_reserve_var.get() or "0", "fee": self.fee_var.get() or "0", "direction": self.direction.get(), "quick_rate_amounts": [str(r) for r in self.quick_rate_amounts]}
        try:
            with open(self.state_file_path, 'w') as f: json.dump(state, f, indent=4)
            print("Application state saved.")
        except (IOError, Exception) as e: print(f"Error saving state file: {e}"); messagebox.showwarning("Save Error", f"Could not save application state:\n{e}", parent=self.root)

    def _on_close(self): print("Closing application..."); self._save_state(); self.root.destroy()

    def configure_styles(self):
        style = ttk.Style(); style.theme_use('clam')
        try:
            default_font = font.nametofont("TkDefaultFont").actual()
            self.ui_font=(default_font['family'], 10); self.header_font=(default_font['family'], 11, 'bold'); self.mono_font=('Consolas', 9); self.quick_rate_font=(default_font['family'], 9); self.entry_font=(default_font['family'], 11); self.result_font=(default_font['family'], 10, 'bold')
        except tk.TclError:
             self.ui_font=('Arial', 10); self.header_font=('Arial', 11, 'bold'); self.mono_font=('Courier', 9); self.quick_rate_font=('Arial', 9); self.entry_font=('Arial', 11); self.result_font=('Arial', 10, 'bold')
        style.configure('.', background=BG_COLOR, foreground=FG_COLOR, fieldbackground=ENTRY_BG, insertcolor=FG_COLOR, font=self.ui_font)
        style.configure('TLabel', background=BG_COLOR, foreground=FG_COLOR, font=self.ui_font); style.configure('TFrame', background=BG_COLOR)
        style.configure('QuickRate.TLabel', foreground=QUICK_RATE_FG, font=self.quick_rate_font, background=BG_COLOR)
        style.configure('QuickRateHeader.TLabel', foreground=ACCENT_COLOR, font=self.ui_font, background=BG_COLOR)
        style.configure('Info.TLabel', foreground=INFO_FG, font=self.quick_rate_font, background=BG_COLOR)
        style.configure('Result.TLabel', foreground=RESULT_ACTION_FG, font=self.result_font, background=BG_COLOR, anchor='center', padding=5)
        style.map('TEntry', foreground=[('readonly', DISABLED_FG), ('disabled', DISABLED_FG), ('!disabled', FG_COLOR)], fieldbackground=[('readonly', ENTRY_BG),('focus', ENTRY_BG), ('!focus', ENTRY_BG)], bordercolor=[('focus', ACCENT_COLOR), ('!focus', '#555555')], lightcolor=[('focus', ENTRY_BG)], darkcolor=[('focus', ENTRY_BG)])
        style.configure('TEntry', borderwidth=1, relief='flat', padding=(5, 5), font=self.entry_font)
        style.configure('TButton', background=BUTTON_BG, foreground=FG_COLOR, borderwidth=1, relief='flat', padding=5, font=self.ui_font)
        style.map('TButton', background=[('active', ACCENT_COLOR), ('hover', BUTTON_ACTIVE_BG)], foreground=[('active', BG_COLOR)], relief=[('pressed', 'sunken'), ('!pressed', 'flat')])
        style.configure('TRadiobutton', background=BG_COLOR, foreground=FG_COLOR, indicatorrelief='flat', indicatormargin=-1, font=self.ui_font, padding=5)
        style.map('TRadiobutton', background=[('active', BG_COLOR)], indicatorcolor=[('selected', ACCENT_COLOR), ('!selected', '#555555')], foreground=[('selected', ACCENT_COLOR),('disabled', '#888888')])
        style.configure('TLabelframe', background=BG_COLOR, borderwidth=1, relief="solid", bordercolor="#444444", padding=10)
        style.configure('TLabelframe.Label', background=BG_COLOR, foreground=ACCENT_COLOR, font=self.header_font)
        style.configure('TSeparator', background=SEPARATOR_COLOR)

    def setup_ui(self):
        # Configure root window resizing behavior
        self.root.columnconfigure(0, weight=1) # Make column 0 fill width
        self.root.rowconfigure(0, weight=1)    # Make row 0 fill height

        # Main frame fills the root window
        main_frame = ttk.Frame(self.root, padding="15")
        main_frame.grid(row=0, column=0, sticky="nsew") # Use grid and sticky

        # Configure main_frame's rows to control vertical expansion
        main_frame.columnconfigure(0, weight=1) # Allow content to expand horizontally
        # Give weight to the row containing the result_frame (index 3 assuming frames are packed)
        main_frame.rowconfigure(3, weight=1)

        # --- Pool State Frame ---
        # Pack frames with fill='x' to expand horizontally
        config_frame = ttk.LabelFrame(main_frame, text="Pool State & Quick Rates", padding="15")
        config_frame.grid(row=0, column=0, pady=5, padx=10, sticky="ew") # Use grid, sticky ew
        config_frame.columnconfigure(1, weight=1) # Allow entry column to expand
        self.token_a_reserve_entry = self._create_labeled_entry(config_frame, self.label_token_a_reserve, 0, self.token_a_reserve_var, validate=True, widget_name_suffix="token_a_reserve_entry")
        self.token_b_reserve_entry = self._create_labeled_entry(config_frame, self.label_token_b_reserve, 1, self.token_b_reserve_var, validate=True, widget_name_suffix="token_b_reserve_entry")
        self.fee_entry = self._create_labeled_entry(config_frame, "Swap Fee (%):", 2, self.fee_var, validate=True, widget_name_suffix="fee_entry")
        sep = ttk.Separator(config_frame, orient='horizontal'); sep.grid(row=3, column=0, columnspan=2, pady=(10, 8), sticky='ew')
        ttk.Label(config_frame, text="Quick Rates:", style='QuickRateHeader.TLabel').grid(row=4, column=0, columnspan=2, pady=(0, 5), sticky='w', padx=10)
        self.quick_rate_labels = {'a_to_b': {}, 'b_to_a': {}}
        self._create_quick_rate_labels(config_frame, start_row=5, col=0, is_reversed=False)
        self._create_quick_rate_labels(config_frame, start_row=5, col=1, is_reversed=True)

        # --- Target Price Frame ---
        target_frame = ttk.LabelFrame(main_frame, text="Calculate Swap for Target Price", padding="15")
        target_frame.grid(row=1, column=0, pady=5, padx=10, sticky="ew") # Use grid, sticky ew
        target_frame.columnconfigure(1, weight=1) # Allow entry column to expand
        self.target_price_entry = self._create_labeled_entry(target_frame, self.label_target_price, 0, self.target_price_var, entry_width=20, validate=True, widget_name_suffix="target_price_entry")
        # Center button using grid configure
        target_frame.rowconfigure(1, weight=1) # Give button row weight if needed
        ttk.Button(target_frame, text="Calculate Required Swap", command=self.calculate_swap_for_target_price, width=25).grid(row=1, column=0, columnspan=2, pady=15) # Still centered via columnspan
        self.target_result_label = ttk.Label(target_frame, textvariable=self.target_result_var, style='Result.TLabel'); self.target_result_label.grid(row=2, column=0, columnspan=2, pady=5, sticky="ew")

        # --- Swap Controls Frame ---
        swap_ctrl_frame = ttk.LabelFrame(main_frame, text="Initiate Swap", padding="15")
        swap_ctrl_frame.grid(row=2, column=0, pady=5, padx=10, sticky="ew") # Use grid, sticky ew
        swap_ctrl_frame.columnconfigure(1, weight=1) # Allow entry column to expand
        radio_frame = ttk.Frame(swap_ctrl_frame); radio_frame.grid(row=0, column=0, columnspan=2, pady=(0, 15), sticky="w") # Sticky west for radios
        self.rb_a_to_b = ttk.Radiobutton(radio_frame, textvariable=self.label_rb_a_to_b, variable=self.direction, value="a_to_b"); self.rb_a_to_b.pack(side=tk.LEFT, padx=15)
        self.rb_b_to_a = ttk.Radiobutton(radio_frame, textvariable=self.label_rb_b_to_a, variable=self.direction, value="b_to_a"); self.rb_b_to_a.pack(side=tk.LEFT, padx=15)
        self.pay_entry = self._create_labeled_entry(swap_ctrl_frame, "You Pay:", 1, self.pay_amount_var, validate=True)
        self.receive_entry = self._create_labeled_entry(swap_ctrl_frame, "You Receive:", 2, self.receive_amount_var, validate=True)
        self.info_label = ttk.Label(swap_ctrl_frame, text="", style='Info.TLabel'); self.info_label.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 10), sticky="ew")
        # Button Frame - keep centered using pack within a gridded frame
        button_frame = ttk.Frame(swap_ctrl_frame); button_frame.grid(row=4, column=0, columnspan=2, pady=(10, 0))
        ttk.Button(button_frame, text="Execute Swap", command=self.execute_swap, width=20).pack()

        # --- Transaction Log Frame ---
        # This frame should expand vertically and horizontally
        result_frame = ttk.LabelFrame(main_frame, text="Transaction Log", padding="15")
        result_frame.grid(row=3, column=0, pady=5, padx=10, sticky="nsew") # Use grid, sticky nsew
        # Configure result_frame's inner grid for Text widget expansion
        result_frame.rowconfigure(0, weight=1)
        result_frame.columnconfigure(0, weight=1)
        self.result_text = tk.Text(result_frame, height=6, width=60, wrap=tk.WORD, state=tk.DISABLED, font=self.mono_font, bg=ENTRY_BG, fg=FG_COLOR, selectbackground=ACCENT_COLOR, selectforeground=BG_COLOR, borderwidth=1, relief='sunken', padx=5, pady=5)
        self.result_text.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") # Use grid + sticky

    def _update_ui_dynamic_labels(self):
        token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
        self.label_token_a_reserve.set(f"{token_a} Reserve:")
        self.label_token_b_reserve.set(f"{token_b} Reserve:")
        self.label_target_price.set(f"Target Price ({token_b}/{token_a}):")
        self.label_rb_a_to_b.set(f"{token_a} → {token_b}")
        self.label_rb_b_to_a.set(f"{token_b} → {token_a}")
        self.label_quick_rate_header_a_to_b.set(f"Input {token_a} → Output {token_b}")
        self.label_quick_rate_header_b_to_a.set(f"Input {token_b} ← Output {token_a}")
        self.update_quick_rates() # Refresh text content of rates

    def _create_labeled_entry(self, parent, label_var_or_text, row, textvariable, entry_width=25, validate=False, widget_name_suffix=None):
        label_options = {}; #textvariable or text determined below
        if isinstance(label_var_or_text, tk.StringVar): label_options["textvariable"] = label_var_or_text
        else: label_options["text"] = label_var_or_text
        ttk.Label(parent, **label_options).grid(row=row, column=0, padx=10, pady=6, sticky="w") # Label sticks west
        entry_options = {"width": entry_width, "textvariable": textvariable, "font": self.entry_font}
        if validate: entry_options["validate"] = "key"; entry_options["validatecommand"] = self.vcmd
        if widget_name_suffix: entry_options["name"] = widget_name_suffix
        entry = ttk.Entry(parent, **entry_options)
        entry.grid(row=row, column=1, padx=10, pady=6, sticky="ew") # Entry sticks east-west
        return entry

    def _create_quick_rate_labels(self, parent, start_row, col, is_reversed=False):
        token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
        header_var = self.label_quick_rate_header_a_to_b if not is_reversed else self.label_quick_rate_header_b_to_a
        ttk.Label(parent, textvariable=header_var, style='QuickRate.TLabel').grid(row=start_row, column=col, pady=(0, 5), padx=10, sticky='w')
        dict_key = "a_to_b" if not is_reversed else "b_to_a"
        storage = self.quick_rate_labels[dict_key]
        input_token = token_a if not is_reversed else token_b
        output_token = token_b if not is_reversed else token_a
        for i, base_amount in enumerate(self.quick_rate_amounts):
            if not is_reversed: label_text = f"{base_amount} {input_token} ≈ -- {output_token}"; label_key = base_amount
            else: label_text = f"-- {input_token} ≈ {base_amount} {output_token}"; label_key = base_amount
            label = ttk.Label(parent, text=label_text, style='QuickRate.TLabel'); label.grid(row=start_row + 1 + i, column=col, pady=1, padx=15, sticky='w') # Labels stick west
            storage[label_key] = label

    def add_traces(self):
        self.token_a_reserve_var.trace_add("write", lambda *_: self.handle_update(source='pool'))
        self.token_b_reserve_var.trace_add("write", lambda *_: self.handle_update(source='pool'))
        self.fee_var.trace_add("write", lambda *_: self.handle_update(source='pool'))
        self.pay_amount_var.trace_add("write", lambda *_: self.handle_update(source='pay'))
        self.receive_amount_var.trace_add("write", lambda *_: self.handle_update(source='receive'))
        self.direction.trace_add("write", self.on_direction_change)

    def handle_update(self, source):
        if self._updating: return
        self._updating = True
        self.info_label.config(text="")
        try:
            current_pool_params = self._get_pool_params()
            if source == 'pool':
                self.update_quick_rates()
                self._set_target_result("Result will appear here", INFO_FG)
            self.update_pay_receive_calculation(source, current_pool_params)
        except ValueError as e:
            self.info_label.config(text=f"Pool Error: {e}", foreground=ERROR_FG)
            self._clear_swap_inputs()
            self.update_quick_rates()
        except Exception as e:
            print(f"Unexpected Error during handle_update (source: {source}): {e}")
            traceback.print_exc()
            self.info_label.config(text="Update Error", foreground=ERROR_FG)
        finally:
            self._updating = False

    def update_pay_receive_calculation(self, source, pool_params):
        res_a, res_b, fee_percent = pool_params; direction = self.direction.get()
        if direction == "a_to_b": reserve_in, reserve_out = res_a, res_b; in_prec = self.token_a_decimals.get(); out_prec = self.token_b_decimals.get(); pay_var, receive_var = self.pay_amount_var, self.receive_amount_var
        else: reserve_in, reserve_out = res_b, res_a; in_prec = self.token_b_decimals.get(); out_prec = self.token_a_decimals.get(); pay_var, receive_var = self.pay_amount_var, self.receive_amount_var
        calc_target = None
        if source == 'pay': calc_target = 'receive'
        elif source == 'receive': calc_target = 'pay'
        elif source == 'pool':
            pay_val_str = pay_var.get(); rec_val_str = receive_var.get()
            if pay_val_str and self._is_valid_decimal_str(pay_val_str, False): calc_target = 'receive'
            elif rec_val_str and self._is_valid_decimal_str(rec_val_str, False): calc_target = 'pay'
        if calc_target == 'receive': self._perform_swap_calculation_and_update(pay_var.get(), calculate_amount_out, reserve_in, reserve_out, fee_percent, out_prec, receive_var, True, reserve_out)
        elif calc_target == 'pay': self._perform_swap_calculation_and_update(receive_var.get(), calculate_amount_in, reserve_in, reserve_out, fee_percent, in_prec, pay_var, False)

    def _perform_swap_calculation_and_update(self, source_val_str, calc_func, reserve_in, reserve_out, fee_percent, result_format_prec, target_var, check_insufficiency, reserve_out_check=None):
        if not source_val_str or not self._is_valid_decimal_str(source_val_str, allow_negative=False): target_var.set(""); return
        try:
            source_amount = decimal.Decimal(source_val_str)
            if source_amount <= 0: target_var.set(""); return
            result_amount, _ = calc_func(source_amount, reserve_in, reserve_out, fee_percent)
            display_val = ""
            if result_amount.is_nan(): display_val = "Error: Calc"
            elif result_amount == decimal.Decimal('inf'): display_val = "Error: Insuff."
            elif check_insufficiency and reserve_out_check is not None and result_amount >= reserve_out_check: display_val = "Error: Insuff."
            else: display_val = f"{result_amount:.{result_format_prec}f}"
            target_var.set(display_val)
        except decimal.InvalidOperation: target_var.set("Error: Invalid")
        except Exception as e: print(f"Unexpected error in _perform_swap_calculation: {e}"); target_var.set("Error: Calc")

    def update_quick_rates(self):
        reset_needed = False
        try:
            res_a, res_b, fee_percent = self._get_pool_params()
            token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
            prec_a = self.token_a_decimals.get(); prec_b = self.token_b_decimals.get()
            storage_a = self.quick_rate_labels['a_to_b']
            for amount_a in self.quick_rate_amounts:
                try:
                    label = storage_a[amount_a]
                    amount_b_out, _ = calculate_amount_out(amount_a, res_a, res_b, fee_percent)
                    if amount_b_out.is_nan(): text, fg = "Error", ERROR_FG
                    elif amount_b_out >= res_b: text, fg = f"Insuff. {token_b}", ERROR_FG
                    else: text, fg = f"{amount_b_out:.{prec_b}f} {token_b}", QUICK_RATE_FG
                    label.config(text=f"{amount_a} {token_a} ≈ {text}", foreground=fg)
                except KeyError: print(f"!!! Key Error updating A->B for {amount_a}"); reset_needed=True; continue
                except Exception as label_e: print(f"!!! Error configuring A->B label for {amount_a}: {repr(label_e)}"); reset_needed=True; continue
            storage_b = self.quick_rate_labels['b_to_a']
            for amount_a_out in self.quick_rate_amounts:
                 try:
                    label = storage_b[amount_a_out]
                    amount_b_in, _ = calculate_amount_in(amount_a_out, res_b, res_a, fee_percent)
                    if amount_b_in.is_nan(): text, fg = "Error", ERROR_FG
                    elif amount_b_in == decimal.Decimal('inf'): text, fg = f"Insuff. {token_b}", ERROR_FG
                    else: text, fg = f"{amount_b_in:,.{prec_b}f} {token_b}", QUICK_RATE_FG
                    label.config(text=f"{text} ≈ {amount_a_out} {token_a}", foreground=fg)
                 except KeyError: print(f"!!! Key Error updating B->A for {amount_a_out}"); reset_needed=True; continue
                 except Exception as label_e: print(f"!!! Error configuring B->A label for {amount_a_out}: {repr(label_e)}"); reset_needed=True; continue
        except (ValueError, decimal.InvalidOperation) as e: print(f"Quick rate update skipped due to invalid pool params: {e}"); reset_needed = True
        except Exception as e: print(f"!!! Unexpected Error during outer quick rate update scope: {repr(e)}"); reset_needed = True
        if reset_needed:
            print("!!! Resetting quick rate labels due to error.")
            token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
            for amount in self.quick_rate_amounts:
                try: self.quick_rate_labels['a_to_b'][amount].config(text=f"{amount} {token_a} ≈ -- {token_b}", foreground=QUICK_RATE_FG)
                except Exception as reset_e: print(f"!!! Error resetting A->B label for {amount}: {repr(reset_e)}")
                try: self.quick_rate_labels['b_to_a'][amount].config(text=f"-- {token_b} ≈ {amount} {token_a}", foreground=QUICK_RATE_FG)
                except Exception as reset_e: print(f"!!! Error resetting B->A label for {amount}: {repr(reset_e)}")

    def on_direction_change(self, *args):
        if self._updating: return
        self._updating = True; pay_val_str = self.pay_amount_var.get(); rec_val_str = self.receive_amount_var.get()
        self.pay_amount_var.set(rec_val_str); self.receive_amount_var.set(pay_val_str)
        self.handle_update(source='pay'); self._updating = False

    def _get_pool_params(self):
        try:
            res_a_str = self.token_a_reserve_var.get(); res_b_str = self.token_b_reserve_var.get(); fee_percent_str = self.fee_var.get()
            token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get() # Get symbols for error messages
            if not res_a_str: raise ValueError(f"{token_a} Reserve cannot be empty.")
            if not res_b_str: raise ValueError(f"{token_b} Reserve cannot be empty.")
            if not fee_percent_str: raise ValueError("Fee cannot be empty.")
            res_a = decimal.Decimal(res_a_str); res_b = decimal.Decimal(res_b_str); fee_percent = decimal.Decimal(fee_percent_str)
            if res_a <= 0: raise ValueError(f"{token_a} Reserve must be positive.")
            if res_b <= 0: raise ValueError(f"{token_b} Reserve must be positive.")
            if fee_percent < 0 or fee_percent >= 100: raise ValueError("Invalid Fee %. Min 0, Max < 100.")
            return res_a, res_b, fee_percent
        except decimal.InvalidOperation as e: raise ValueError(f"Invalid number format in pool parameters.") from e
        except ValueError as e: raise e

    def validate_inputs_for_swap(self):
        try:
            res_a, res_b, fee_percent = self._get_pool_params(); pay_amount_str = self.pay_amount_var.get()
            if not pay_amount_str: raise ValueError("Pay amount cannot be empty.")
            try: pay_amount = decimal.Decimal(pay_amount_str)
            except decimal.InvalidOperation: raise ValueError("Invalid number format for Pay amount.")
            if pay_amount <= 0: raise ValueError("Pay amount must be positive.")
            direction = self.direction.get(); token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
            reserve_in, reserve_out, out_token_symbol = (res_a, res_b, token_b) if direction == "a_to_b" else (res_b, res_a, token_a)
            amount_out_check, _ = calculate_amount_out(pay_amount, reserve_in, reserve_out, fee_percent)
            if amount_out_check.is_nan(): raise ValueError("Calculation error prevents swap. Check inputs/pool state.")
            if amount_out_check >= reserve_out: raise ValueError(f"Insufficient {out_token_symbol} in pool for this swap amount.")
            return {"token_a_reserve": res_a, "token_b_reserve": res_b, "pay_amount": pay_amount, "fee_percent": fee_percent}
        except ValueError as e: messagebox.showerror("Input Error", str(e), parent=self.root); return None
        except Exception as e: messagebox.showerror("Validation Error", f"Unexpected error during validation: {e}", parent=self.root); return None

    def execute_swap(self):
        inputs = self.validate_inputs_for_swap();
        if not inputs: return
        direction = self.direction.get(); result_lines = []; success = False
        try:
            amount_in = inputs["pay_amount"]; res_a_init, res_b_init = inputs["token_a_reserve"], inputs["token_b_reserve"]; fee_percent = inputs["fee_percent"]
            token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
            prec_a = self.token_a_decimals.get(); prec_b = self.token_b_decimals.get()
            if direction == "a_to_b": res_in_init, res_out_init = res_a_init, res_b_init; in_token, out_token, fee_token = token_a, token_b, token_a; in_prec, out_prec = prec_a, prec_b
            else: res_in_init, res_out_init = res_b_init, res_a_init; in_token, out_token, fee_token = token_b, token_a, token_b; in_prec, out_prec = prec_b, prec_a
            amount_out, fee_amount = calculate_amount_out(amount_in, res_in_init, res_out_init, fee_percent)
            if amount_out.is_nan(): raise ValueError("Internal swap calculation failed unexpectedly.")
            if direction == "a_to_b": new_res_a, new_res_b = res_a_init + amount_in, res_b_init - amount_out
            else: new_res_b, new_res_a = res_b_init + amount_in, res_a_init - amount_out
            with decimal.localcontext() as ctx: ctx.prec = 10
            market_rate_before = res_b_init / res_a_init if res_a_init else decimal.Decimal('inf')
            if direction == "a_to_b":
                eff_rate = amount_out / amount_in if amount_in else decimal.Decimal(0)
                rate_str_eff = f"1 {token_a} ≈ {eff_rate:.{out_prec}f} {token_b}"; rate_str_mkt = f"1 {token_a} ≈ {market_rate_before:.{prec_b}f} {token_b}"
            else:
                eff_rate_inv = amount_in / amount_out if amount_out else decimal.Decimal('inf')
                rate_str_eff = f"{eff_rate_inv:.{prec_b}f} {token_b} ≈ 1 {token_a}"; rate_str_mkt = f"{market_rate_before:.{prec_b}f} {token_b} ≈ 1 {token_a}"
            result_lines = [f"Direction:      {in_token} → {out_token}", f"Paid:           {amount_in:<15.{in_prec}f} {in_token}", f"Fee ({fee_percent}%):    {fee_amount:<15.{prec_a if fee_token == token_a else prec_b}f} {fee_token}", "-------------------------------------------", f"Received:       {amount_out:<15.{out_prec}f} {out_token}", "-------------------------------------------", f"Effective Rate: {rate_str_eff}", f"Market Rate:    {rate_str_mkt} (Before)", "-------------------------------------------", f"New Reserves:", f"  {token_a}: {new_res_a:<15.{prec_a}f}", f"  {token_b}:  {new_res_b:<15,.{prec_b}f}"]
            success = True
            self._updating = True; self.token_a_reserve_var.set(f"{new_res_a:.{prec_a}f}"); self.token_b_reserve_var.set(f"{new_res_b:.{prec_b}f}"); self._clear_swap_inputs(); self._updating = False
            self.handle_update(source='pool')
        except (ZeroDivisionError, ValueError, decimal.InvalidOperation) as e: messagebox.showerror("Swap Error", f"Could not execute swap: {str(e)}", parent=self.root); success = False
        except Exception as e: messagebox.showerror("Error", f"An unexpected error occurred during swap: {str(e)}", parent=self.root); print(f"Unexpected Swap Error: {type(e).__name__}: {e}"); success = False
        self._display_log(result_lines if success else ["Swap failed."])

    def calculate_swap_for_target_price(self):
        try:
            res_a, res_b, fee_percent = self._get_pool_params(); target_price_str = self.target_price_var.get()
            token_a = self.token_a_symbol.get(); token_b = self.token_b_symbol.get()
            prec_a = self.token_a_decimals.get(); prec_b = self.token_b_decimals.get()
            if not target_price_str: self._set_target_result("Enter a target price", INFO_FG); return
            target_price = decimal.Decimal(target_price_str)
            if target_price <= 0: raise ValueError("Target price must be positive.")
            k = res_a * res_b; current_price = res_b / res_a if res_a > 0 else decimal.Decimal('inf');
            price_diff = abs(target_price - current_price); price_diff_ratio = price_diff / current_price if current_price > ZERO_THRESHOLD else price_diff
            if price_diff_ratio < decimal.Decimal('1E-7'): self._set_target_result("Target price is effectively the current price.", INFO_FG); return
            try: k_div_target = k / target_price; k_mul_target = k * target_price; x_f = k_div_target.sqrt(); y_f = k_mul_target.sqrt()
            except (decimal.InvalidOperation, ValueError) as e: raise ValueError(f"Cannot calculate ideal reserves: {e}")
            result_data = None
            if target_price < current_price: result_data = self._calculate_target_swap_details(y_f, res_a, res_b, fee_percent, token_a, token_b)
            elif target_price > current_price: result_data = self._calculate_target_swap_details(x_f, res_b, res_a, fee_percent, token_b, token_a)
            if result_data:
                status = result_data['status']
                if status == 'ok':
                    token_in, amount_in = result_data['token_in'], result_data['amount_in']; token_out, amount_out = result_data['token_out'], result_data['amount_out']
                    in_prec = prec_a if token_in == token_a else prec_b; out_prec = prec_a if token_out == token_a else prec_b
                    if token_in == token_a: msg = f"Action: SELL {amount_in:.{in_prec}f} {token_in}"
                    else: msg = f"Action: BUY {amount_out:.{out_prec}f} {token_out} (Pay ≈{amount_in:,.{in_prec}f} {token_in})"
                    self._set_target_result(msg, RESULT_ACTION_FG)
                elif status == 'unfeasible': self._set_target_result(result_data['error_msg'], ERROR_FG)
                elif status == 'minimal': self._set_target_result(result_data['error_msg'], INFO_FG)
                else: self._set_target_result(f"Error: {result_data['error_msg']}", ERROR_FG)
        except ValueError as e: self._set_target_result(f"Input Error: {str(e)}", ERROR_FG)
        except decimal.InvalidOperation: self._set_target_result(f"Invalid number format for Target Price.", ERROR_FG)
        except Exception as e: self._set_target_result(f"Unexpected Error: Check logs.", ERROR_FG); print(f"Target Price Calc Unexpected Error: {type(e).__name__}: {e}")

    def _calculate_target_swap_details(self, target_res_out_ideal, current_res_in, current_res_out, fee_percent, token_in_name, token_out_name):
        amount_out_ideal = current_res_out - target_res_out_ideal
        if amount_out_ideal <= ZERO_THRESHOLD: return {'status': 'minimal', 'error_msg': f"Target requires minimal/no change ({token_in_name} → {token_out_name})."}
        if amount_out_ideal >= current_res_out: return {'status': 'unfeasible', 'error_msg': f"Target price unfeasible (Requires >100% {token_out_name} outflow)."}
        amount_in_required, _ = calculate_amount_in(amount_out_ideal, current_res_in, current_res_out, fee_percent)
        if amount_in_required.is_nan() or amount_in_required == decimal.Decimal('inf') or amount_in_required <= ZERO_THRESHOLD: return {'status': 'error', 'error_msg': "Cannot reach target (Calculation error or requires too large input)."}
        else: return {'status': 'ok', 'amount_in': amount_in_required, 'amount_out': amount_out_ideal, 'token_in': token_in_name, 'token_out': token_out_name}

    def _clear_swap_inputs(self): self.pay_amount_var.set(""); self.receive_amount_var.set("")
    def _set_target_result(self, message, color): self.target_result_var.set(message); self.target_result_label.config(foreground=color)
    def _display_log(self, lines): self.result_text.config(state=tk.NORMAL); self.result_text.delete(1.0, tk.END); self.result_text.insert(tk.END, "\n".join(lines)); self.result_text.config(state=tk.DISABLED)

if __name__ == "__main__":
    if len(sys.argv) == 1:
         root = tk.Tk()
         app = AMMSwapApp(root)
         root.mainloop()
    else:
         print("GUI not started due to command-line arguments.")