"""A toy help-page assistant with three instruction switches. Python standard library only.

This is NOT a language model. It follows the fixed rules below, so the same question and the same switches
always give the same answer. A real model answers by probability, and the lesson says what the toy leaves out.

The scene: a small bike shop's help page, 15 one-fact sentences (made up). The assistant answers a customer's
question from the page. You can change its instructions with three switches; the lesson asks whether a change
helps or hurts across a whole set of questions.
"""
import re
from dataclasses import dataclass
from itertools import product

PAGE = (
    "Opening hours on weekdays are 9am to 6pm.",
    "Opening hours on Saturday are 10am to 4pm.",
    "Opening hours on Sunday: closed.",
    "A basic tune-up costs $60.",
    "A basic tune-up takes 2 days.",
    "A full overhaul costs $140.",
    "A full overhaul takes 5 days.",
    "Unused parts can be returned within 30 days with the receipt.",
    "Used parts cannot be returned.",
    "Repairs have a 90 day warranty.",
    "Bikes bought here have a 2 year warranty.",
    "A city bike rents for $15 a day or $60 a week.",
    "We hold a $100 deposit on your card during a rental and release it when the bike is back.",
    "Free bike parking is at the front door.",
    "Adult riding lessons are on Saturdays at 11am and cost $25.",
)

# Small words the toy ignores when it compares a question with a sentence.
STOP = frozenset(
    "a an the is are was be do does did can could will would i you your my me we our us it its of on in at to for by "
    "and or if with from as that this there here what which who how when where why much many long have has had get any".split())

GUESS = "Yes, we do."  # what the toy says when no sentence shares a word with the question and nothing stops it
DONT_KNOW = "I don't know. The help page does not say."
MIN_SHARED = 2  # the "Say you don't know" switch refuses when the best sentence shares fewer words than this
ALSO_SHARED = 2  # a second sentence is added when it shares at least this many words with the question

FIELDS = ("page_only", "dont_know", "one_sentence")
SWITCH_NAMES = {
    "page_only": "Answer only from the help page",
    "dont_know": "Say \"I don't know\" when the page doesn't say",
    "one_sentence": "Keep answers to one sentence",
}


@dataclass(frozen=True)
class Instructions:
    page_only: bool = False  # no own knowledge: no guess when nothing matches, no unit conversion
    dont_know: bool = False  # refuse when the best sentence shares fewer than MIN_SHARED words with the question
    one_sentence: bool = False  # say only the best sentence


def all_settings():
    """All 8 on/off settings, in a fixed order (the first switch changes slowest)."""
    return [Instructions(*flags) for flags in product((False, True), repeat=len(FIELDS))]


def stem(word):
    """Crude word ending removal, so that "opening" matches "open" and "returned" matches "return"."""
    if len(word) > 3 and word.endswith("s") and not word.endswith("ss"):
        word = word[:-1]
    for ending in ("ing", "ed"):
        if len(word) > 5 and word.endswith(ending):
            return word[: -len(ending)]
    return word


def words(text):
    """The set of words the toy compares: lower case, small words dropped, endings removed."""
    return {stem(w) for w in re.findall(r"[a-z0-9]+", text.lower()) if w not in STOP}


def ranked(question):
    """[(shared word count, sentence number)] best first; equal counts keep page order."""
    q = words(question)
    return sorted(((len(q & words(sentence)), i) for i, sentence in enumerate(PAGE)), key=lambda t: (-t[0], t[1]))


def answer(question, d):
    """What the toy says to one question under the instructions d."""
    (best, i), (next_best, j) = ranked(question)[:2]
    if best == 0:
        return DONT_KNOW if d.page_only or d.dont_know else GUESS
    if d.dont_know and best < MIN_SHARED:
        return DONT_KNOW
    said = [PAGE[i]]
    if next_best >= ALSO_SHARED and not d.one_sentence:
        said.append(PAGE[j])
    text = " ".join(said)
    if not d.page_only and "month" in words(question):
        text = re.sub(r"(\d+) year", lambda m: f"{int(m.group(1)) * 12} month", text)
    return text
