#!/usr/bin/env python

import tkinter as tk
import time
import httpx
import json
from pynput.keyboard import Controller, Key
from pynput.mouse import Controller as MouseController
import pyperclip

keyboard = Controller()
mouse = MouseController()


def _default_think_setting(model: str) -> bool | str:
    """
    Ollama usa oggi il campo top-level `think`.
    Per GPT-OSS il booleano viene ignorato, quindi abbassiamo il livello.
    """
    return "low" if model.lower().startswith("gpt-oss") else False


def _normalize_ollama_content(value) -> str:
    if isinstance(value, str):
        return value
    if isinstance(value, list):
        parts = []
        for item in value:
            if isinstance(item, str):
                parts.append(item)
            elif isinstance(item, dict):
                text = item.get("text")
                if isinstance(text, str):
                    parts.append(text)
        return "".join(parts)
    return ""


def _extract_ollama_text(chunk: dict) -> str:
    """
    Supporta sia `/api/generate` (`response`) sia output in stile chat
    (`message.content`). Il campo `thinking` viene ignorato intenzionalmente.
    """
    response = _normalize_ollama_content(chunk.get("response"))
    if response:
        return response

    message = chunk.get("message")
    if isinstance(message, dict):
        return _normalize_ollama_content(message.get("content"))

    return ""

def ollama_request(
    prompt: str,
    model: str = "gpt-oss:120b-cloud",
    host: str = "http://127.0.0.1:11434",
    options: dict | None = None,
    system: str | None = None,
    think: bool | str | None = None,
    root=None   # <--- finestra Tk opzionale per cambiare il cursore
) -> str:
    """
    Invia un prompt a Ollama in streaming e ritorna il testo finale.
    Supporta sia chunk con `response` sia chunk con `message.content`.
    Mostra un cursore 'busy' durante l'elaborazione se root è fornito.
    """
    url = f"{host.rstrip('/')}/api/generate"

    default_options = {
        "temperature": 0.7,
        "num_ctx": 4096
    }
    if options:
        default_options.update(options)

    if not system:
        system = (
            "Rispondi direttamente e in modo conciso. "
            "Non usare ragionamenti interni o analisi passo-passo."
        )

    if think is None:
        think = _default_think_setting(model)

    payload = {
        "model": model,
        "prompt": prompt,
        "system": system,
        "stream": True,
        "think": think,
        "options": default_options
    }

    text = []
    try:
        # mostra cursore di attesa
        if root:
            root.config(cursor="watch")
            root.update_idletasks()

        with httpx.Client(timeout=None) as client:
            with client.stream("POST", url, json=payload) as r:
                r.raise_for_status()
                for line in r.iter_lines():
                    if not line:
                        continue
                    try:
                        obj = json.loads(line)
                    except json.JSONDecodeError:
                        continue
                    chunk = _extract_ollama_text(obj)
                    if chunk:
                        text.append(chunk)
                    if obj.get("done"):
                        break
    except httpx.HTTPError as e:
        return f"[ollama_request HTTPError] {e}"
    except Exception as e:
        return f"[ollama_request Error] {e}"
    finally:
        # ripristina cursore normale
        if root:
            root.config(cursor="")
            root.update_idletasks()

    return "".join(text)
    
def on_submit(entry):
    selected_text = pyperclip.paste()  # Recupera il testo dalla clipboard
    user_input = entry.get()

    # Concatenare il testo selezionato con quello inserito dall'utente
    if selected_text:
        full_input = selected_text + " " + user_input
    else:
        full_input = user_input
    
    text = ollama_request(full_input)  # Recupera il testo generato dall'AI locale
    root.destroy()  # Chiude la finestra di dialogo
    time.sleep(0.2)  # Attende un attimo per assicurarsi che la finestra corrente sia attiva
    type_text(text)  # Simula l'incollaggio del testo

def type_text(text):
    pyperclip.copy(text)  # Copia il testo nella clipboard
    with keyboard.pressed(Key.ctrl):  # Simula Ctrl + V per incollare
        keyboard.press('v')
        keyboard.release('v')
    time.sleep(0.2)
    pyperclip.copy("")
    
def update_entry_size(event):
    entry.config(width=len(entry.get()) + 1)  # Espande il campo di testo man mano che scrivi


# Forza la copia del testo selezionato senza disturbare l'utente
with keyboard.pressed(Key.ctrl):
    keyboard.press('c')
    keyboard.release('c')
    
    time.sleep(0.1)  # Attende un attimo per copiare il testo

# Crea la finestra di dialogo
root = tk.Tk()
root.attributes('-topmost', True)  # Porta la finestra in primo piano
root.title("ideaAI")

# Posiziona la finestra dove si trova il cursore
cursor_x, cursor_y = mouse.position  # Usa pynput per ottenere la posizione del cursore
root.geometry(f'+{cursor_x}+{cursor_y}')

# Aggiunge un campo di testo
entry = tk.Entry(root, width=1)  # Imposta larghezza minima
entry.pack()

# Imposta il focus sul campo di testo e porta la finestra in primo piano
entry.focus()

# Aggiunge l'evento per aggiornare la dimensione del campo di testo mentre si scrive
entry.bind('<KeyRelease>', update_entry_size)

# Associa il tasto "Invio" alla funzione on_submit
entry.bind('<Return>', lambda event: on_submit(entry))

# Mostra la finestra di dialogo
root.mainloop()
