# uuid=63e765ef-6c95-48a7-93b5-59fba59b7aea

if version\0 < 1:

    from channels    import send_message_to_thread, wait_for_new_messages_on_thread, format_messages, User, Family
    from core_skills import answer_question
    from llm         import agent

    type Recipe:
        val kind                                -- for now "INGREDIENT" or "RECIPE"
        val title
        val summary
        -- more to come

    type DietLogEntry:
        val who
        val when                                -- For now an ISO time string
        val when_text                           -- How the user initially described the time (e.g., "lunch")
        val items                               -- A list of items eaten

    type User:
        one diet_log                            -- We can monkey-patch new properties onto types rather than creating global maps.

    type Family:
        one recipes                             -- Vector database of recipes ; maps Recipe to "kind: title - summary"

    def diet_log(user):                         -- TODO: We need factory fields on types!
        if not user.diet_log?:
            user.diet_log = []
        return user.diet_log

    def recipes(user):
        fam = user.family
        if not fam.recipes?:
            fam.recipes = new_vdb()
        return fam.recipes

    format_diet_log_entry = <e: "[{e.when}] ({e.when_text}) {e.who} had {sjoin(e.items, ', ')}">
    format_diet_log       = <l: joinlines([format_diet_log_entry(e) for e in l])>

    def summarize_recipe(r):
        return "{r.kind}: {r.title} - {r.summary}"

    {|
     | Find good search terms for looking up matching recipes and ingredients
     |}
    def extract_search_terms(thread, user):
        username = user.name
        return answer_question(thread, user,
            instructions = $"- We need to search a vector database for known recipes and ingredients encompassing {username}'s log entry.
                             - Return a json list of phrases to search for.  These can be single ingredients like "milk" but also
                                try to include likely implied recipes like "chicken with tomatoes and rice" which might be merely
                                descriptive (three separate ingredients) or might refer to a family recipe.  We need to search to
                                find out."$,
            answer_eg = $"["chicken", "tomatoes", "rice", "chicken with tomatoes and rice"]"$)

    {|
     | Map named foods to known recipes and ingredients
     |}
    def map_foods(thread, user, hits):
        username = user.name
        return answer_question(thread, user,
            instructions = $"- We need to re-state {username}'s log entry entirely in terms of known recipes and ingredients.
                             - Summarize who ate what and when using recipe titles with the recipe number in parentheses like "(R132)".
                             - We're looking for the simplest formulation.  E.g., prefer a recipe that encompasses multiple mentioned things over listing them separately.
                             - If you're unsure if something refers to a specific recipe, ask {username}.
                             - If there is no matching recipe or ingredient for something, reply with action="add recipe", recipe="<short description of the item or recipe we need to add>"
                             - Here are the best matching recipes:
                                 {joinlines(["(R{i}) {summarize_recipe(r)}" for i:r in hits])}
                             "$,
            answer_eg = "John and Sue had Mustard Crusted Salmon (R12) and Soda (R3) for lunch at about 2:15pm. John had Milk (R17) and Chocolate Chip Cookies (R19) for a snack at 4pm.",
            more_actions=$"If we need to add/register a new ingredient or recipe, reply, e.g.:
                                action="add recipe",
                                recipe="pizza (home made)"
                           "$)

    {|
     | Determine who consumed what when
     |}
    def model_meal_events(thread, user):
        return answer_question(thread, user,
            instructions = $"- We need a model of the described meal(s): Who consumed what at what meal and when.
                             - Make sure it's clear who consumed each thing and at what time.
                             - Don't assume it was a self-only entry unless so-specified.  Ask who had what unless clear.
                             - Assume "we" or similar refers to the whole family unless otherwise specified.
                             - We need *absolute* times (like "2:15pm") for everything, not relative.  Infer absolute where possible, else ask.
                             - Assume relative times are from the time-stamp of the message.
                             - DO NOT infer times from meal names.  If the time isn't (roughly) specified, ask.
                             - You MAY infer meal names from the time and foods if obvious enough.
                             - When the model is clear and complete (who had each thing at what rough absolute time), reply with a full and explicit description.
                             "$,
            answer_eg = "John and Sue had mustard crusted salmon and soda for lunch at about 2:15pm. John had milk and cookies for a snack at 4pm.")

    {|
     | Format meal events as JSON
     |}
    def format_meal_events(thread, user, sofar):
        return answer_question(thread, user,
            instructions = $"- We need a model of the described meal(s): Who ate what at what meal and when.
                             - It should be a chronological list of entries.  Each entry must be a JSON object with, e.g.:
                                - who="John"        -- one person's name
                                - meal="lunch"      -- must be one of "breakfast"|"lunch"|"dinner"|"snack"
                                - time="2:15pm"     -- local time, approximate is fine
                                - foods=["mustard crusted salmon", "soda", ...] -- List of all foods that person ate at that time
                            "$,
            sofar     = sofar,
            answer_eg = json([[who="John", meal="lunch", time="2:15pm", foods=["mustard crusted salmon", "soda"]],
                              [who="John", meal="snack", time="4pm", foods=["milk", "cookies"]]]))

    {|
     | Match food name to saved recipe or ingredient
     |}
    def find_recipe(thread, user, food, sofar):

        rbook = recipes(user)   -- Recipe book

        for i in 0..2:  -- First pass may add a new ingredient.  If it fails to match on second round it's hard to fix.
            opts = vfind(rbook, food)   -- Find nearest matching recipes/ingredients from the recipe book
            n = answer_question(thread, user,
                    instructions=$"- We need to determine which of the following, if any, {json(food)} refers to:
                                        {if length(opts) > 0 then joinlines(["[{i}] {summarize_recipe(r)}" for i:r in opts]) else '<no recipes on file>'}
                                   - If a good match to one of these, return the number.
                                   - If it's none of these, return answer=null, indicating we need to create a new entry.
                                   - If unsure, ask the user about the ambiguous possible matches.  Note {user.name} can't see
                                        the (vector db based) match list above--only you can.
                                   "$,
                    sofar       = sofar,
                    answer_eg   = "2")
            if n?:
                return opts[n]
            -- No match!  Add one.  Arguably this should spawn a sub-thread so we don't polute the main thread with this?  TODO
            a = answer_question(thread, user,
                    instructions=$"- We need a type, title, and summary for the new ingredient or recipe {json(food)}.
                                   - type can be one of "INGREDIENT" or "RECIPE"
                                   - for an ingredient, title should just be the ingredient, and summary is optional refinement.
                                   - for a recipe, work out a good title and summary with the user, but make sure the summary
                                     mentions all the main/defining ingredients so it can found by a vector search.
                                   - for a recipe, DO NOT ASSUME a title or ingredients!
                                   - confirm the title with {user.name} before proceeding.
                                     "$,
                    sofar = sofar,
                    answer_eg = '{"type":"RECIPE", "title":"Kung Pao Chicken", "summary":"Spicy chicken with green onions, sesame oil, and peanuts."}')
            r = Recipe(kind=a.type, title=a.title, summary=a.summary)
            vadd(rbook, r, summarize_recipe(r))

        return none

    {|
     | Track what the user eats and drinks.
     |}
    def track_diet(thread, user):

        while true:
            search_terms = extract_search_terms(thread, user)
            rbook        = recipes(user)
            hits         = {}
            for search_term in search_terms:
                for recipe in vfind(rbook, search_term):
                    append(hits, recipe)
            hits = list(hits)   -- Maintain order henceforth for consistent ID'ing
            print("Search terms: {pformat(search_terms, indent='    ')}")
            print("Candidate matches: {pformat(hits, indent='    ')}")
            reply = map_foods(thread, user, hits)
            print("Map_foods:")
            pprint(reply)
            if reply.action == 'answer':
                break while
            else if reply.action == 'cancel':
                return
            else if reply.action == 'add recipe':
                food = reply.recipe
                a = answer_question(thread, user,
                        instructions=$"- We need a type, title, and summary for the new ingredient or recipe {json(food)}.
                                       - type can be one of "INGREDIENT" or "RECIPE"
                                       - for an ingredient, title should just be the ingredient, and summary is optional refinement.
                                       - for a recipe, work out a good title and summary with the user, but make sure the summary
                                         mentions all the main/defining ingredients so it can found by a vector search.
                                       - for a recipe, DO NOT ASSUME a title or ingredients!
                                       - confirm the title with {user.name} before proceeding.
                                         "$,
                        answer_eg = '{"type":"RECIPE", "title":"Kung Pao Chicken", "summary":"Spicy chicken with green onions, sesame oil, and peanuts."}')
                r = Recipe(kind=a.type, title=a.title, summary=a.summary)
                vadd(rbook, r, summarize_recipe(r))
            else:
                send_message_to_thread(thread, "(Internal Error)")
                print("track_diet: Badly formatted reply")
                pprint(reply)
                return none -- probably should raise

        ---------------------------------------

        model  = model_meal_events(thread, user)
        events = format_meal_events(thread, user, sofar="- {model}") -- model here should have everything we need in English; now re-format to JSON.

        e2 = []         -- Rebuild events with Recipe objects instead of string foods.
        fcache = [:]    -- cache food -> recipe map
        for e in events:
            f2 = []
            for food in e.foods:
                if not (r = fcache[food])?:
                    fcache[food] = r = find_recipe(thread, user, food, sofar="- {model}")
                if r?:
                    append(f2, r)
                else:
                    print("WARNING: Couldn't index {food} in recipe file.") -- todo -- need better recovery.
            append(e2, [meal=e.meal, time=e.time, who=e.who, foods=f2])

        send_message_to_thread(thread, joinlines(["{e.meal} at {e.time}: {e.who} had {sjoin([r.title for r in e.foods], sep=', ')}" for e in e2]))

    {|
     | Show a requested recipe
     |}
    def show_recipe(thread, user):
        rbook = recipes(user)
        opts  = vfind(rbook, thread.messages[-1].text)   -- TODO: could ask LLM for search phrase
        n = answer_question(thread, user,
                instructions=$"- We need to determine which of the following, if any, matches what the user is asking about:
                                    {if length(opts) > 0 then joinlines(["[{i}] {summarize_recipe(r)}" for i:r in opts]) else '<no recipes on file>'}
                               - If a good match to one of these, return the number.
                               - If it's none of these, return answer=null, indicating no match found.
                               - If unsure, ask the user about the ambiguous possible matches.  Note {user.name} can't see
                                    the (vector db based) match list above--only you can.
                               "$,
                answer_eg   = "2")
        if n?:
            send_message_to_thread(thread, summarize_recipe(opts[n]))
        else:
            send_message_to_thread(thread, "No matching recipe or ingredient found.")

    if true:
        from channels import get_family
        fam = get_family("e0ee81dc-2a1f-4398-bad0-1c3c5663072b")
        if not fam.recipes?:
            fam.recipes = new_vdb()
        rbook = fam.recipes
        for r in [
                Recipe(kind="RECIPE", title="Mustard Crusted Salmon", summary="baked salmon with mustard, garlic, and wine"),
                Recipe(kind="RECIPE", title="Beef, Lentils, and Rice", summary="stewed beef cooked with lentils and served with rice"),
                Recipe(kind="INGREDIENT", title="Milk", summary="whole milk"),
                Recipe(kind="INGREDIENT", title="Rice, Basmatti", summary="basmatti rice (white)"),
                Recipe(kind="INGREDIENT", title="Rice, Jasmine", summary="Jasmine rice (white)")
            ]:
            vadd(rbook, r, summarize_recipe(r))

    version = 1