"""Doprovodný příklad k článku „Jak vytvořit chatbota v Pythonu“."""

import math
import re
from collections import Counter


KNOWLEDGE_BASE = [
    {
        "question": "Musím před kurzem něco instalovat?",
        "answer": (
            "Ne. Python kód se píše a spouští přímo v prohlížeči, "
            "takže pro první lekce není potřeba instalace."
        ),
        "source": "https://zacnikodit.cz/",
    },
    {
        "question": "Mohu si akademii nejdřív vyzkoušet zdarma?",
        "answer": (
            "Ano. O aktivační kód na vyzkoušení lze požádat bez platební karty."
        ),
        "source": "https://zacnikodit.cz/ziskat-kod",
    },
    {
        "question": "Pro koho je Python akademie určená?",
        "answer": (
            "Je určená úplným začátečníkům a organizacím, které je učí: "
            "školám, vzdělávacím platformám a týmům."
        ),
        "source": "https://zacnikodit.cz/pricing",
    },
]

WORD_RE = re.compile(r"[a-záčďéěíňóřšťúůýž0-9]+", re.IGNORECASE)

STOP_WORDS = {
    "a",
    "aby",
    "ale",
    "do",
    "i",
    "je",
    "jsem",
    "k",
    "ke",
    "na",
    "nebo",
    "o",
    "od",
    "po",
    "pro",
    "se",
    "si",
    "s",
    "u",
    "v",
    "ve",
    "z",
    "za",
    "že",
}


def tokenize(text: str) -> list[str]:
    """Převede text na malá písmena a vrátí významová slova."""
    words = WORD_RE.findall(text.casefold())
    return [word for word in words if word not in STOP_WORDS]


def vectorize(text: str) -> Counter[str]:
    """Vrátí četnosti významových slov v textu."""
    return Counter(tokenize(text))


def cosine_similarity(left: Counter[str], right: Counter[str]) -> float:
    """Spočítá kosinovou podobnost dvou vektorů četností."""
    common_words = left.keys() & right.keys()
    numerator = sum(left[word] * right[word] for word in common_words)

    left_length = math.sqrt(sum(value**2 for value in left.values()))
    right_length = math.sqrt(sum(value**2 for value in right.values()))

    if left_length == 0 or right_length == 0:
        return 0.0

    return numerator / (left_length * right_length)


def find_answer(query: str, threshold: float = 0.12) -> dict[str, object]:
    """Najde nejbližší záznam, nebo bezpečně přizná, že odpověď nezná."""
    query_vector = vectorize(query)
    ranked: list[tuple[float, dict[str, str]]] = []

    for item in KNOWLEDGE_BASE:
        searchable_text = f"{item['question']} {item['answer']}"
        score = cosine_similarity(query_vector, vectorize(searchable_text))
        ranked.append((score, item))

    best_score, best_item = max(ranked, key=lambda result: result[0])

    if best_score < threshold:
        return {
            "answer": (
                "Tuhle odpověď ve znalostní bázi nemám. "
                "Zkus otázku přeformulovat nebo se zeptej člověka."
            ),
            "source": None,
            "score": round(best_score, 3),
        }

    return {
        "answer": best_item["answer"],
        "source": best_item["source"],
        "score": round(best_score, 3),
    }


def main() -> None:
    print("Chatbot je připravený. Pro ukončení napiš konec.\n")

    while True:
        query = input("Ty: ").strip()

        if query.casefold() in {"konec", "exit", "quit"}:
            print("Chatbot: Ahoj!")
            break

        if not query:
            continue

        result = find_answer(query)
        print(f"Chatbot: {result['answer']}")

        if result["source"]:
            print(f"Zdroj: {result['source']}")

        print(f"Skóre: {result['score']}\n")


if __name__ == "__main__":
    main()
