module util

export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
        availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
        checkAgentResponse_text

using UUIDs, Dates, DataStructures, HTTP, JSON
using GeneralUtils
using ..type

# ---------------------------------------------- 100 --------------------------------------------- #

""" Clear agent chat history.

# Arguments
  - `a::agent`
    an agent

# Return
  - nothing

# Example
```jldoctest
julia> using YiemAgent, MQTTClient, GeneralUtils
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
julia> connect(client, connection)
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
julia> agentConfig = Dict(
           "receiveprompt"=>Dict(
             "mqtttopic"=> "testtopic/receive",
           ),
           "receiveinternal"=>Dict(  
             "mqtttopic"=> "testtopic/internal",
           ),
           "text2text"=>Dict(
             "mqtttopic"=> "testtopic/text2text",
           ),
         )
julia> a = YiemAgent.sommelier(
          client,
          msgMeta,
          agentConfig,
          )
julia> YiemAgent.addNewMessage(a, "user", "hello")
julia> YiemAgent.clearhistory(a)
```

# TODO
  - [PENDING] clear memory

# Signature
"""
function clearhistory(a::T)  where {T<:agent}
  empty!(a.chathistory)
  empty!(a.memory["shortmem"])
  empty!(a.memory["events"])
  a.memory["chatbox"] = ""
end


""" Add new message to agent.

  messages => Dict(
    "role" => "user",
    "content" => [
      Dict("type" => "text", "text" => "Describe this image for me"),
      Dict(
        "type" => "image_url",
        "image_url" => Dict("url" => data_uri)
      )
    ]
  )

  Arguments\n
  -----
    a::agent
      an agent
    role::String
      message sender role i.e. system, user or assistant
    text::String
      message text

  Return\n
  -----
    nothing

  Example\n
  -----
  ```jldoctest

  ```

  Signature\n
  -----
"""
function addNewMessage(a::T1, name::String, userinput::T2; 
                      maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractDict}
  # if name ∉ ["system", "user", "assistant"]   # guard against typo
  #   error("name is not in agent.availableRole $(@__LINE__)")
  # end

  #TODO summarize the oldest 10 message
  if length(a.chathistory) > maximumMsg
    summarize(a.chathistory)
  else
    # userinput["timestamp"] = Dates.now()
    push!(a.chathistory, userinput)
  end
end


""" Converts a vector of dictionaries to a formatted string.
This function takes in a vector of dictionaries and outputs a single string where each dictionary's keys are prefixed by their values.

# Arguments
  - `vecd::Vector`
    A vector of dictionaries containing chat messages
  - `withkey::Bool` 
    Whether to include the name as a prefix in the output text. Default is true
  - `range::Union{Nothing,UnitRange,Int}` 
    Optional range of messages to include. If nothing, includes all messages

# Returns
  A formatted string where each line contains either:
  - If withkey=true: "name> message\n"
  - If withkey=false: "message\n"

# Example

julia> using Revise
julia> using GeneralUtils
julia> vecd = [Dict("name" => "John", "text" => "Hello"), Dict("name" => "Jane", "text" => "Goodbye")]
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
"John> Hello\nJane> Goodbye\n"
```
"""
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
  # Initialize an empty string to hold the final text
  text = ""
  
  # Get the elements within the specified range, or all elements if no range provided
  elements = isnothing(range) ? vecd : vecd[range]
  
  # Determine whether to include the key in the output text or not
  if withkey
    # Loop through each dictionary in the input vector
    for d in elements
        # Extract the 'name' and 'text' keys from the dictionary
        name = titlecase(d[:name])
        _text = d[:text]
        
        # Append the formatted string to the text variable
        text *= "$name> $_text \n"
      end
  else
    # Loop through each dictionary in the input vector
    for d in elements
        # Iterate over all key-value pairs in the dictionary
        for (k, v) in d
            # Append the formatted string to the text variable
            text *= "$v \n"
        end
    end
  end
  
  # Return the final text
  return text
end


function availableWineToText(vecd::Vector)::String
  # Initialize an empty string to hold the final text
  rowtext = ""
  # Loop through each dictionary in the input vector
  for (i, d) in enumerate(vecd)
    # Iterate over all key-value pairs in the dictionary
    temp = []
    for (k, v) in d
      # Append the formatted string to the text variable
      t = "$k:$v"
      push!(temp, t)
    end
    _rowtext = join(temp, ',')
    rowtext *= "$i) $_rowtext "
  end
  
  return rowtext
end



""" Create a dictionary representing an event with optional details.

# Arguments
  - `event_description::Union{String, Nothing}`
    A description of the event
  - `timestamp::Union{DateTime, Nothing}`
    The time when the event occurred
  - `subject::Union{String, Nothing}`
    The subject or entity associated with the event
  - `thought::Union{AbstractDict, Nothing}`
    Any associated thoughts or metadata
  - `action_name::Union{String, Nothing}`
    The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
  - `action_input::Union{String, Nothing}`
    Input or parameters for the action
  - `location::Union{String, Nothing}`
    Where the event took place
  - `equipment_used::Union{String, Nothing}`
    Equipment involved in the event
  - `material_used::Union{String, Nothing}`
    Materials used during the event
  - `outcome::Union{String, Nothing}`
    The result or consequence of the event after action execution
  - `note::Union{String, Nothing}`
    Additional notes or comments

# Returns
  A dictionary with event details as symbol-keyed key-value pairs
"""
function eventdict(;
  event_description::Union{String, Nothing}=nothing,
  timestamp::Union{DateTime, Nothing}=nothing,
  subject::Union{String, Nothing}=nothing,
  thought::Union{AbstractDict, Nothing}=nothing,
  action_name::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENT_WINE_GUIDELINE", etc
  action_input::Union{String, Nothing}=nothing,
  location::Union{String, Nothing}=nothing,
  equipment_used::Union{String, Nothing}=nothing,
  material_used::Union{String, Nothing}=nothing,
  observation::Union{String, Nothing}=nothing,
  note::Union{String, Nothing}=nothing,
  )

  d = Dict{String, Any}(
    "event_description"=> event_description,
    "timestamp"=> timestamp,
    "subject"=> subject,
    "thought"=> thought,
    "action_name"=> action_name,
    "action_input"=> action_input,
    "location"=> location,
    "equipment_used"=> equipment_used,
    "material_used"=> material_used,
    "observation"=> observation,
    "note"=> note,
  )

  return d
end


""" Create a formatted timeline string from a sequence of events.

# Arguments
  - `events::T1`
    Vector of event dictionaries containing subject, action_input and optional outcome fields
    Each event dictionary should have the following keys:
    - :subject - The subject or entity performing the action
    - :action_input - The action or input performed by the subject
    - :observation - (Optional) The result or outcome of the action

# Returns
  - `timeline::String`
    A formatted string representing the events with their subjects, actions, and optional outcomes
    Format: "{index}) {subject}> {action_input} {outcome}\n" for each event

# Example

events = [
  Dict("subject" => "User", "action_input" => "Hello", "observation" => nothing),
  Dict("subject" => "Assistant", "action_input" => "Hi there!", "observation" => "with a smile")
]
timeline = createTimeline(events)
# 1) User> Hello
# 2) Assistant> Hi there! with a smile

"""
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
  ) where {T1<:AbstractVector}
  # Initialize empty timeline string
  timeline = ""
  
  # Determine which indices to use - either provided range or full length
  ind = 
    if eventindex !== nothing
      [eventindex...]
    else
      1:length(events)
    end

  # Iterate through events and format each one
  for i in ind
    event = events[i]
    # If no outcome exists, format without outcome
# if event["action_name"] == "CHAT_BOX"
    #   timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\n"
    # elseif event["action_name"] == "CHECKINVENTORY" && event["observation"] === nothing
    #   timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: Not done yet.\n"
    if event["action_name"] == "SEARCH_WINE_DATABASE"
      timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n"
    else
      timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\\n"
    end
  end

  # Return formatted timeline string
  return timeline
end

function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
  ) where {T1<:AbstractVector}
  # Initialize empty log array
  log = Dict{String, String}[]

  # Determine which indices to use - either provided range or full length
  ind = 
    if index !== nothing
      [index...]
    else
      1:length(events)
    end

  # Iterate through events and format each one
  for i in ind
    event = events[i]
    # If no outcome exists, format without outcome
    if event["observation"] === nothing
      subject = event["subject"]
      action_name = event["action_name"]
      action_input = event["action_input"]
      str = "action_name: $action_name, action_input: $action_input"
      d = Dict{String, String}("name"=>subject, "text"=>str)
      push!(log, d)
    else
      subject = event["subject"]
      action_name = event["action_name"]
      action_input = event["action_input"]
      observation = event["observation"]
      str = "action_name: $action_name, action_input: $action_input, observation: $observation"
      d = Dict{String, String}("name"=>subject, "text"=>str)
      push!(log, d)
    end
  end

  return log
end


function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
  ) where {T1<:AbstractVector}
  # Initialize empty log array
  log = Dict{String, String}[]

  # Determine which indices to use - either provided range or full length
  ind = 
    if index !== nothing
      [index...]
    else
      1:length(chatdict)
    end

  # Iterate through events and format each one
  for i in ind
    event = chatdict[i]
    subject = event["name"]
    text = event["text"]
    d = Dict{String, String}("name"=>subject, "text"=>text)
    push!(log, d)
  end

  return log
end


function checkAgentResponse_text(response::String, requiredHeader::T
  )::Tuple where {T<:Array{String}}
  detected_kw = GeneralUtils.detectKeywordVariation(requiredHeader, response)
  missingkeys = [k for (k, v) in detected_kw if v === nothing]
  ispass = false
  errormsg = nothing
  if !isempty(missingkeys)
    errormsg = "$missingkeys are missing from your previous response"
    ispass = false
  elseif sum([length(i) for i in values(detected_kw)]) > length(requiredHeader)
    errormsg = "Your previous attempt has duplicated points according to the required response format"
    ispass = false
  else
    ispass = true
  end
  return (ispass, errormsg)
end
































































end # module util