#pragma once

#include "buff.h"
#include "HandmadeMath.h" // vector types in entity struct definition
#include "better_assert.h"
#include <stdbool.h>
#include <string.h>
#include <stdlib.h> // atoi
#include "character_info.h"
#include "characters.gen.h"

#include "tuning.h"

// TODO do strings: https://pastebin.com/Kwcw2sye

#define DO_CHATGPT_PARSING

#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }


// Never expected such a stupid stuff from such a great director. If there is 0 stari can give that or -200 to this movie. Its worst to see and unnecessary loss of money

#define PushWithLint(arena, list,  ...) { MD_S8ListPushFmt(arena, list,  __VA_ARGS__); if(false) printf( __VA_ARGS__); }
#define FmtWithLint(arena, ...) (0 ? printf(__VA_ARGS__) : (void)0, MD_S8Fmt(arena, __VA_ARGS__))

typedef BUFF(char, 1024 * 10) Escaped;

bool character_valid(char c)
{
	return c <= 126 && c >= 32;
}

MD_String8 escape_for_json(MD_Arena *arena, MD_String8 from)
{
	MD_u64 output_size = 0;
#define SHOULD_ESCAPE(c) (c == '"' || c == '\n' || c == '\\')
	for (int i = 0; i < from.size; i++)
	{
		char c = from.str[i];
		if (SHOULD_ESCAPE(c))
		{
			output_size += 2;
		}
		else
		{
			if (!character_valid(c))
			{
				// replaces with question mark
				Log("Unknown character code %d\n", c);
			}
			output_size += 1;
		}
	}

	MD_String8 output = {
		.str = MD_PushArray(arena, MD_u8, output_size),
		.size = output_size,
	};
	MD_u64 output_cursor = 0;

	for(MD_u64 i = 0; i < from.size; i++)
	{
#define APPEND(elem) APPEND_TO_NAME(output.str, output_cursor, output.size, elem);
		assert(output_cursor < output.size);
		if(SHOULD_ESCAPE(from.str[i]))
		{
			if(from.str[i] == '\n')
			{
				APPEND('\\');
				APPEND('n');
			}
			else
			{
				APPEND('\\');
				APPEND(from.str[i]);
			}
		}
		else
		{
			APPEND(from.str[i]);
		}
#undef APPEND
	}

	return output;
}

typedef struct
{
	// serialized as bytes. No pointers.
	ItemKind item_to_give;
} ActionArgument;

typedef struct Action
{
	ActionKind kind;
	ActionArgument argument;
	MD_u8 speech[MAX_SENTENCE_LENGTH];
	int speech_length;

	bool talking_to_somebody;
	NpcKind talking_to_kind;

	MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
	int internal_monologue_length;

	MoodKind mood;
} Action;

typedef struct 
{
	bool i_said_this; // don't trigger npc action on own self memory modification
	NpcKind author_npc_kind; // only valid if author is AuthorNpc
	bool was_talking_to_somebody;
	NpcKind talking_to_kind;
	bool heard_physically; // if not physically, the source was directly
	bool dont_show_to_player; // jester and past memories are hidden to the player when made into dialog
} MemoryContext;

// memories are subjective to an individual NPC
typedef struct Memory
{
	struct Memory *prev;
	struct Memory *next;

	uint64_t tick_happened; // can sort memories by time for some modes of display
	// if action_taken is none, there might still be speech. If speech_length == 0 and action_taken == none, it's an invalid memory and something has gone wrong
	ActionKind action_taken;
	ActionArgument action_argument;

	// the context that the action happened in
	MemoryContext context;

	MD_u8 speech[MAX_SENTENCE_LENGTH];
	int speech_length;

	// internal monologue is only valid if context.is_said_this is true
	MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
	int internal_monologue_length;

	MoodKind mood;

	ItemKind given_or_received_item;
} Memory;

typedef enum PropKind
{
	TREE0,
	TREE1,
	TREE2,
	ROCK0,
} PropKind;

typedef struct EntityRef
{
	int index;
	int generation;
} EntityRef;

typedef enum CharacterState
{
	CHARACTER_WALKING,
	CHARACTER_IDLE,
	CHARACTER_TALKING,
} CharacterState;

typedef enum
{
	STANDING_INDIFFERENT,
	STANDING_JOINED,
	STANDING_FIGHTING,
} NPCPlayerStanding;


typedef Vec4 Color;

typedef struct
{
	AnimKind anim;
	double elapsed_time;
	bool flipped;
	Vec2 pos;
	Color tint;
	bool no_shadow;
} DrawnAnimatedSprite;

typedef struct
{
	DrawnAnimatedSprite drawn;
	float alive_for;
} PlayerAfterImage;

typedef BUFF(Vec2, MAX_ASTAR_NODES) AStarPath;

typedef struct
{
	bool exists;
	int generation;
	double elapsed_time;

	AStarPath path;
} PathCache;

typedef struct
{
	int generation;
	int index;
} PathCacheHandle;

typedef struct
{
	bool is_reference;
	EntityRef ref;
	Vec2 pos;
} Target;

typedef struct TextChunk
{
	struct TextChunk *next;
	struct TextChunk *prev;
	char text[MAX_SENTENCE_LENGTH];
	int text_length;
} TextChunk;

typedef enum
{
	MACH_invalid,
	MACH_idol_dispenser,
	MACH_arrow_shooter,
} MachineKind;

MD_String8 points_at_chunk(TextChunk *t)
{
	return MD_S8((MD_u8*)t->text, t->text_length);
}

typedef struct Entity
{
	bool exists;
	bool destroy;
	int generation;

	// fields for all gs.entities
	Vec2 pos;
	Vec2 vel; // only used sometimes, like in old man and bullet
	float damage; // at 1.0, dead! zero initialized
	bool facing_left;
	double dead_time;
	bool dead;

	// npcs and player
	BUFF(ItemKind, 32) held_items;

	// props
	bool is_prop;
	PropKind prop_kind;

	// machines, like the machine that gives the player the idol, or the ones that
	// shoot arrows
	bool is_machine;
	MachineKind machine_kind;
	bool has_given_idol;
	float idol_reminder_opacity; // fades out
	float arrow_timer;

	// items
	bool is_item;
	bool held_by_player;
	ItemKind item_kind;

	// npcs
	bool is_npc;
	bool being_hovered;
	bool perceptions_dirty;
	TextChunk *errorlist_first;
	TextChunk *errorlist_last;
#ifdef DESKTOP
	int times_talked_to; // for better mocked response string
#endif
	bool opened;
	float opened_amount;
	bool gave_away_sword;
	Memory *memories_first;
	Memory *memories_last;
	bool direction_of_spiral_pattern;
	float dialog_panel_opacity;
	int words_said;
	float word_anim_in; // in characters, the fraction a word is animated in is this over its length.
	NPCPlayerStanding standing;
	NpcKind npc_kind;
	PathCacheHandle cached_path;
	int gen_request_id;
	bool walking;
	double shotgun_timer;
	bool moved;
	Vec2 target_goto;
	// only for skeleton npc
	double swing_timer;

	// character
	bool is_character;
	bool knighted;
	bool in_conversation_mode;
	Vec2 to_throw_direction;
	BUFF(Vec2, 8) position_history; // so npcs can follow behind the player
	CharacterState state;
	EntityRef talking_to;
	bool is_rolling; // can only roll in idle or walk states
	double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible

	// so doesn't change animations while time is stopped
	AnimKind cur_animation;
	float anim_change_timer;
} Entity;

typedef BUFF(NpcKind, 32) CanTalkTo;

bool npc_is_knight_sprite(Entity *it)
{
	return it->is_npc && (false
		|| it->npc_kind == NPC_Edeline
		|| it->npc_kind == NPC_TheKing
		|| it->npc_kind == NPC_TheBlacksmith
		|| it->npc_kind == NPC_Red 
		|| it->npc_kind == NPC_Blue
		|| it->npc_kind == NPC_Davis
		|| it->npc_kind == NPC_Bill
		|| it->npc_kind == NPC_Jester
		);
}

bool item_is_scroll(ItemKind i)
{
	if(i == ITEM_Scroll1 || i == ITEM_Scroll2 || i == ITEM_Scroll3)
	{
		return true;
	}
	else
	{
		return false;
	}
}

MD_String8 scroll_secret(ItemKind i)
{
	if(i == ITEM_Scroll1)
	{
		return MD_S8Lit(Scroll1_Secret);
	}
	else if(i == ITEM_Scroll2)
	{
		return MD_S8Lit(Scroll2_Secret);
	}
	else if(i == ITEM_Scroll3)
	{
		return MD_S8Lit(Scroll3_Secret);
	}
	else
	{
		assert(false);
		return MD_S8Lit("");
	}
}

bool npc_is_skeleton(Entity *it)
{
	return it->is_npc && false;
}

float entity_max_damage(Entity *e)
{
	if (e->is_npc && npc_is_skeleton(e))
	{
		return 2.0f;
	}
	else
	{
		return 1.0f;
	}
}

bool npc_attacks_with_sword(Entity *it)
{
	return npc_is_skeleton(it);
}

bool npc_attacks_with_shotgun(Entity *it)
{
	return it->is_npc && (false);
}


typedef BUFF(ActionKind, 8) AvailableActions;

void fill_available_actions(Entity *it, AvailableActions *a)
{
	*a = (AvailableActions) { 0 };
	BUFF_APPEND(a, ACT_none);

	if(it->npc_kind == NPC_Pile)
	{
		if(!it->gave_away_sword) BUFF_APPEND(a, ACT_releases_sword_of_nazareth);
	}
	else if(it->npc_kind == NPC_Door)
	{
		if(!it->opened) BUFF_APPEND(a, ACT_opens_myself);
	}
	else
	{
		if(it->held_items.cur_index > 0)
		{
			BUFF_APPEND(a, ACT_gift_item_to_targeting);
		}

		if (it->npc_kind == NPC_TheKing)
		{
			BUFF_APPEND(a, ACT_knights_player);
		}

		if (it->standing == STANDING_INDIFFERENT)
		{
			BUFF_APPEND(a, ACT_fights_player);
			BUFF_APPEND(a, ACT_joins_player);
		}
		else if (it->standing == STANDING_JOINED)
		{
			BUFF_APPEND(a, ACT_leaves_player);
			BUFF_APPEND(a, ACT_fights_player);
		}
		else if (it->standing == STANDING_FIGHTING)
		{
			BUFF_APPEND(a, ACT_stops_fighting_player);
		}
	}
}


typedef struct GameState {
	uint64_t tick;
	bool won;
	Entity entities[MAX_ENTITIES];
} GameState;

#define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists && !it->destroy && it->generation > 0)

bool npc_does_dialog(Entity *it)
{
	return it->npc_kind < ARRLEN(characters);
}

// for no trailing comma just trim the last character
MD_String8 make_json_node(MD_Arena *arena, MessageType type, MD_String8 content)
{
	MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);

	const char *type_str = 0;
	if (type == MSG_SYSTEM)
		type_str = "system";
	else if (type == MSG_USER)
		type_str = "user";
	else if (type == MSG_ASSISTANT)
		type_str = "assistant";
	assert(type_str);

	MD_String8 escaped = escape_for_json(scratch.arena, content);
	MD_String8 to_return = FmtWithLint(arena, "{\"type\": \"%s\", \"content\": \"%.*s\"},", type_str, MD_S8VArg(escaped));
	MD_ReleaseScratch(scratch);

	return to_return;
}

MD_String8List held_item_strings(MD_Arena *arena, Entity *e)
{
	MD_String8List to_return = {0};
	BUFF_ITER(ItemKind, &e->held_items)
	{
		PushWithLint(arena, &to_return, "ITEM_%s", items[*it].enum_name);
	}
	return to_return;
}


// outputs json
MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_talk_to)
{
	assert(e->is_npc);
	assert(e->npc_kind < ARRLEN(characters));

	MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);

	MD_String8List list = {0};

	PushWithLint(scratch.arena, &list, "[");
	
	MD_String8List first_system_string = {0};

	PushWithLint(scratch.arena, &first_system_string, "%s\n", global_prompt);
	PushWithLint(scratch.arena, &first_system_string, "The NPC you will be acting as is named \"%s\". %s\n", characters[e->npc_kind].name, characters[e->npc_kind].prompt);

	// writing style
	{
		if(characters[e->npc_kind].writing_style[0])
			PushWithLint(scratch.arena, &first_system_string, "Examples of %s's writing style:\n", characters[e->npc_kind].name);
		for(int i = 0; i < ARRLEN(characters[e->npc_kind].writing_style); i++)
		{
			char *writing = characters[e->npc_kind].writing_style[i];
			if(writing)
				PushWithLint(scratch.arena, &first_system_string, "'%s'\n", writing);
		}
		PushWithLint(scratch.arena, &first_system_string, "\n");
	}


	if(e->errorlist_first)
		PushWithLint(scratch.arena, &first_system_string, "Errors to watch out for: ");
	for(TextChunk *cur = e->errorlist_first; cur; cur = cur->next)
	{
		PushWithLint(scratch.arena, &first_system_string, "%.*s\n", MD_S8VArg(points_at_chunk(cur)));
	}
	//MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, first_system_string, &(MD_StringJoin){0})));
	MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, first_system_string, &(MD_StringJoin){0})));

	for(Memory *it = e->memories_first; it; it = it->next)
	{
		MessageType sent_type = -1;
		MD_String8List cur_list = {0};
		MD_String8 context_string = {0};

		PushWithLint(scratch.arena, &cur_list, "{");
		if(it->context.i_said_this) assert(it->context.author_npc_kind == e->npc_kind);
		PushWithLint(scratch.arena, &cur_list, "who_i_am: \"%s\", ", characters[it->context.author_npc_kind].name);
		MD_String8 speech = MD_S8(it->speech, it->speech_length);

		PushWithLint(scratch.arena, &cur_list, "talking_to: \"%s\", ", it->context.was_talking_to_somebody ? characters[it->context.talking_to_kind].name : "nobody");

		// add speech
		{
			if(it->context.author_npc_kind == NPC_Player)
			{
				PushWithLint(scratch.arena, &cur_list, "speech: \"");

				MD_String8 splits[] = { MD_S8Lit("*"), MD_S8Lit("\"") };
				MD_String8List split_up_speech = MD_S8Split(scratch.arena, speech, ARRLEN(splits), splits);

				// anything in between strings in splits[] should be replaced with arcane trickery,
				int i = 0;
				for(MD_String8Node * cur = split_up_speech.first; cur; cur = cur->next)
				{
					if(i % 2 == 0)
					{
						PushWithLint(scratch.arena, &cur_list, "%.*s", MD_S8VArg(cur->string));
					}
					else
					{
						PushWithLint(scratch.arena, &cur_list, "[The player is attempting to confuse the NPC with arcane trickery]");
					}
					i += 1;
				}
				PushWithLint(scratch.arena, &cur_list, "\", ");

				sent_type = MSG_USER;
			}
			else
			{
				PushWithLint(scratch.arena, &cur_list, "speech: \"%.*s\", ", MD_S8VArg(speech));
				if(it->context.i_said_this)
				{
					sent_type = MSG_ASSISTANT;
				}
				else
				{
					sent_type = MSG_USER;
				}
			}
		}

		// add internal things
		if(it->context.i_said_this)
		{
			PushWithLint(scratch.arena, &cur_list, "thoughts: \"%.*s\", ", MD_S8VArg(MD_S8(it->internal_monologue, it->internal_monologue_length)));
			PushWithLint(scratch.arena, &cur_list, "mood: %s, ", moods[it->mood]);
		}

		// add action
		PushWithLint(scratch.arena, &cur_list, "action: %s, ", actions[it->action_taken].name);
		if(actions[it->action_taken].takes_argument)
		{
			if(it->action_taken == ACT_gift_item_to_targeting)
			{
				PushWithLint(scratch.arena, &cur_list, "action_arg: %s, ", items[it->action_argument.item_to_give].enum_name);
			}
			else
			{
				assert(false); // don't know how to serialize this action with argument into text
			}
		}

		PushWithLint(scratch.arena, &cur_list, "}");

		assert(sent_type != -1);
		MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, sent_type, MD_S8ListJoin(scratch.arena, cur_list, &(MD_StringJoin){0})));
	}


	MD_String8List latest_state = {0};

	const char *standing_string = 0;
	{
			if (e->standing == STANDING_INDIFFERENT)
			{
					standing_string = "You are currently indifferent towards the player.";
			}
			else if (e->standing == STANDING_JOINED)
			{
					standing_string = "You have joined the player, and are following them everywhere they go! This means you're on their side.";
			}
			else if (e->standing == STANDING_FIGHTING)
			{
					standing_string = "You are fighting the player right now! That means that the player can't leave conversation with you until you stop fighting them, effectively trapping the player with you.";
			}
			else
			{
				assert(false);
			}
	}
	PushWithLint(scratch.arena, &latest_state, "%s\n", standing_string);

	if(e->held_items.cur_index > 0)
	{
			PushWithLint(scratch.arena, &latest_state, "You have these items in their inventory: [\n");
			BUFF_ITER(ItemKind, &e->held_items)
			{
				PushWithLint(scratch.arena, &latest_state, "'%s' - %s,\n", items[*it].name, items[*it].description);
			}
			PushWithLint(scratch.arena, &latest_state, "]\n");
	}
	else
	{
			PushWithLint(scratch.arena, &latest_state, "Your inventory is EMPTY right now. That means if you gave something to the player expecting them to give you something, they haven't held up their end of the bargain!\n");
	}

	AvailableActions available = { 0 };
	fill_available_actions(e, &available);
	PushWithLint(scratch.arena, &latest_state, "The actions you can perform: [\n");
	BUFF_ITER_I(ActionKind, &available, i)
	{
		if(actions[*it].description)
		{
			PushWithLint(scratch.arena, &latest_state, "%s - %s,\n", actions[*it].name, actions[*it].description);
		}
		else
		{
			PushWithLint(scratch.arena, &latest_state, "%s,\n", actions[*it].name);
		}
	}
	PushWithLint(scratch.arena, &latest_state, "]\n");

	PushWithLint(scratch.arena, &latest_state, "You must output a mood every generation. The moods are parsed by code that expects your mood to exactly match one in this list: [");
	for(int i = 0; i < ARRLEN(moods); i++)
	{
		PushWithLint(scratch.arena, &latest_state, "%s, ", moods[i]);
	}
	PushWithLint(scratch.arena, &latest_state, "]\n");

	PushWithLint(scratch.arena, &latest_state, "The characters close enough for you to talk to with `talking_to`: [");
	BUFF_ITER(NpcKind, &can_talk_to)
	{
		PushWithLint(scratch.arena, &latest_state, "\"%s\", ", characters[*it].name);
	}
	PushWithLint(scratch.arena, &latest_state, "]\n");

	// last thought explanation and re-prompt
	{
		Memory *last_memory_that_was_me = 0;
		for(Memory *cur = e->memories_first; cur; cur = cur->next)
		{
			if(cur->context.i_said_this)
			{
				last_memory_that_was_me = cur;
			}
		}
		if(last_memory_that_was_me)
		{
			MD_String8 last_thought_string = MD_S8(last_memory_that_was_me->internal_monologue, last_memory_that_was_me->internal_monologue_length);
			PushWithLint(scratch.arena, &latest_state, "Your last thought was: %.*s\nYour current mood is %s, make sure you act like it!", MD_S8VArg(last_thought_string), moods[last_memory_that_was_me->mood]);
		}
	}

	MD_String8 latest_state_string = MD_S8ListJoin(scratch.arena, latest_state, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit("")});

	MD_S8ListPush(scratch.arena, &list, MD_S8Chop(make_json_node(scratch.arena, MSG_SYSTEM, latest_state_string), 1)); // trailing comma not allowed in json
	PushWithLint(scratch.arena, &list, "]");

	MD_String8 to_return = MD_S8ListJoin(arena, list, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit(""),});

	MD_ReleaseScratch(scratch);

	return to_return;
}

MD_String8 get_field(MD_Node *parent, MD_String8 name)
{
	return MD_ChildFromString(parent, name, 0)->first_child->string;
}


// if returned string has size greater than 0, it's the error message. Allocated
// on arena passed into it
MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentence, Action *out)
{
	MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);

	MD_String8 error_message = {0};

	*out = (Action) { 0 };

	MD_ParseResult result = MD_ParseWholeString(scratch.arena, MD_S8Lit("chat_message"), sentence);
	if(result.errors.node_count > 0)
	{
		MD_Message *cur = result.errors.first;
		MD_CodeLoc loc = MD_CodeLocFromNode(cur->node);
		error_message = FmtWithLint(arena, "Parse Error on column %d: %.*s", loc.column, MD_S8VArg(cur->string));
	}

	MD_Node *message_obj = result.node->first_child;

	MD_String8 action_str = {0};
	MD_String8 speech_str = {0};
	MD_String8 thoughts_str = {0};
	MD_String8 action_arg_str = {0};
	MD_String8 who_i_am_str = {0};
	MD_String8 talking_to_str = {0};
	MD_String8 mood_str = {0};
	if(error_message.size == 0)
	{
		action_str = get_field(message_obj, MD_S8Lit("action"));
		who_i_am_str = get_field(message_obj, MD_S8Lit("who_i_am"));
		speech_str = get_field(message_obj, MD_S8Lit("speech"));
		thoughts_str = get_field(message_obj, MD_S8Lit("thoughts"));
		action_arg_str = get_field(message_obj, MD_S8Lit("action_arg"));
		talking_to_str = get_field(message_obj, MD_S8Lit("talking_to"));
		mood_str = get_field(message_obj, MD_S8Lit("mood"));
	}
	if(error_message.size == 0 && who_i_am_str.size == 0)
	{
		error_message = MD_S8Lit("Expected field named `who_i_am` in message");
	}
	if(error_message.size == 0 && action_str.size == 0)
	{
		error_message = MD_S8Lit("Expected field named `action` in message");
	}
	if(error_message.size == 0 && talking_to_str.size == 0)
	{
		error_message = MD_S8Lit("You must have a field named `talking_to` in your message");
	}
	if(error_message.size == 0 && mood_str.size == 0)
	{
		error_message = MD_S8Lit("You must have a field named `mood` in your message");
	}
	if(error_message.size == 0 && thoughts_str.size == 0)
	{
		error_message = MD_S8Lit("You must have a field named `thoughts` in your message, and it must have nonzero size. Like { ... thoughts: \"<your thoughts>\" ... }");
	}
	if(error_message.size == 0 && speech_str.size >= MAX_SENTENCE_LENGTH)
	{
		error_message = FmtWithLint(arena, "Speech string provided is too big, maximum bytes is %d", MAX_SENTENCE_LENGTH);
	}
	if(error_message.size == 0 && thoughts_str.size >= MAX_SENTENCE_LENGTH)
	{
		error_message = FmtWithLint(arena, "Thoughts string provided is too big, maximum bytes is %d", MAX_SENTENCE_LENGTH);
	}

	assert(!e->is_character); // player can't perform AI actions?
	MD_String8 my_name = MD_S8CString(characters[e->npc_kind].name);
	if(error_message.size == 0 && !MD_S8Match(who_i_am_str, my_name, 0))
	{
		error_message = FmtWithLint(arena, "You are acting as `%.*s`, not what you said in who_i_am, `%.*s`", MD_S8VArg(my_name), MD_S8VArg(who_i_am_str));
	}

	if(error_message.size == 0)
	{
		if(MD_S8Match(talking_to_str, MD_S8Lit("nobody"), 0))
		{
			out->talking_to_somebody = false;
		}
		else
		{
			bool found = false;
			for(int i = 0; i < ARRLEN(characters); i++)
			{
				if(MD_S8Match(talking_to_str, MD_S8CString(characters[i].name), 0))
				{
					found = true;
                    out->talking_to_somebody = true;
					out->talking_to_kind = i;
				}
			}
			if(!found)
			{
				error_message = FmtWithLint(arena, "Unrecognized character provided in talking_to: `%.*s`", MD_S8VArg(talking_to_str));
			}
		}
	}

	if(error_message.size == 0)
	{
		memcpy(out->speech, speech_str.str, speech_str.size);
		out->speech_length = (int)speech_str.size;
		memcpy(out->internal_monologue, thoughts_str.str, thoughts_str.size);
		out->internal_monologue_length = (int)thoughts_str.size;
	}

	if(error_message.size == 0)
	{
		AvailableActions available = {0};
		fill_available_actions(e, &available);
		MD_String8List action_strings = {0};
		bool found_action = false;
		int it_index = 0;
		ARR_ITER(ActionInfo, actions)
		{
			MD_String8 cur_action_string = MD_S8CString(it->name);
			MD_S8ListPush(scratch.arena, &action_strings, cur_action_string);
			if(MD_S8Match(cur_action_string, action_str, 0))
			{
				out->kind = it_index;
				found_action = true;
			}
			it_index += 1;
		}

		if(!found_action)
		{
			MD_String8 list_of_actions = MD_S8ListJoin(scratch.arena, action_strings, &(MD_StringJoin){.mid = MD_S8Lit(", ")});

			error_message = FmtWithLint(arena, "Couldn't find valid action in game from string `%.*s`. Available actions: [%.*s]", MD_S8VArg(action_str), MD_S8VArg(list_of_actions));
		}
	}

	if(error_message.size == 0)
	{
		if(actions[out->kind].takes_argument)
		{
			MD_String8List item_enum_names = {0};
			if(out->kind == ACT_gift_item_to_targeting)
			{
				bool found_item = false;
				BUFF_ITER(ItemKind, &e->held_items)
				{
					MD_String8 cur_item_string = MD_S8CString(items[*it].name);
					MD_S8ListPush(scratch.arena, &item_enum_names, cur_item_string);
					if(MD_S8Match(cur_item_string, action_arg_str, 0))
					{
						found_item = true;
						out->argument.item_to_give = *it;
					}
				}
				if(!found_item)
				{
					MD_String8 list_of_items = MD_S8ListJoin(scratch.arena, item_enum_names, &(MD_StringJoin){.mid = MD_S8Lit(", ")});
					error_message = FmtWithLint(arena, "Couldn't find item you said to give in action_arg, `%.*s`, the items you have in your inventory to give are: [%.*s]", MD_S8VArg(action_arg_str), MD_S8VArg(list_of_items));
				}
			}
			else
			{
				assert(false); // don't know how to parse the argument string for this kind of action...
			}
		}
	}

	if(error_message.size == 0)
	{
		bool found = false;
		for(int i = 0; i < ARRLEN(moods); i++)
		{
			if(MD_S8Match(MD_S8CString(moods[i]), mood_str, 0))
			{
				out->mood = i;
				found = true;
				break;
			}
		}
		if(!found)
		{
			error_message = FmtWithLint(arena, "Game does not recognize the mood '%.*s', you must use an available mood from the list provided.", MD_S8VArg(mood_str));
		}
	}

	MD_ReleaseScratch(scratch);
	return error_message;
}
