Hash.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using HarmonyLib;
using Hash;
using Hash.Game;
using Hash.Terminal;
using Il2CppFishNet;
using Il2CppFishNet.Object;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppScheduleOne;
using Il2CppScheduleOne.AvatarFramework;
using Il2CppScheduleOne.AvatarFramework.Emotions;
using Il2CppScheduleOne.Core.Items.Framework;
using Il2CppScheduleOne.Cutscenes;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Employees;
using Il2CppScheduleOne.Interaction;
using Il2CppScheduleOne.ItemFramework;
using Il2CppScheduleOne.Map;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppScheduleOne.Product;
using Il2CppScheduleOne.Product.Packaging;
using Il2CppScheduleOne.Property;
using Il2CppScheduleOne.Quests;
using Il2CppScheduleOne.UI;
using Il2CppScheduleOne.Variables;
using Il2CppScheduleOne.Vehicles;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using Sideload.Api;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "hash", "1.2.5", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Hash")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("Hash")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.5.0")]
[assembly: AssemblyInformationalVersion("1.2.5+58f61a52fa252eead1e81199264b59fe5bea50ad")]
[assembly: AssemblyProduct("Hash")]
[assembly: AssemblyTitle("Hash")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyVersion("1.2.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DooDesch
{
	internal static class ModVersion
	{
		internal const string Current = "1.2.5";
	}
}
namespace Sideload.Api
{
	public static class Apps
	{
		private static bool _bound;

		private static int _probeAttempts;

		private static readonly List<Action> _pending = new List<Action>();

		private static Action<string, string, string, string, Assembly> _registerApp;

		private static Action<string, string, Func<string, string, string>> _handle;

		private static Action<string, string, string> _emit;

		private static Action<string, string> _allowHost;

		private static Action<string, string> _declareOrientations;

		private static Action<string, int> _setBadge;

		private static Action<string, string, string> _notify;

		private static Action<string, string, string, float> _notifyFor;

		private static Func<string, bool> _isOnScreen;

		private static Action<string, string, byte[]> _setImage;

		private static Action<string, bool> _setIconHidden;

		private static Action<string, bool> _setAppOpen;

		private static Func<string, bool> _isAppOpen;

		private static Func<bool, bool> _setPhoneRaised;

		private static Func<bool> _isPhoneRaised;

		private static Action<string, string, Func<string, string, bool>> _claimKeys;

		private static Func<object, string, string, Assembly, float, bool> _mountSurface;

		private static Action<string> _unmountSurface;

		private static Func<string, bool> _isSurfaceMounted;

		public static bool Available
		{
			get
			{
				EnsureBound();
				return _bound;
			}
		}

		internal static bool HasSurfaces
		{
			get
			{
				EnsureBound();
				return _mountSurface != null;
			}
		}

		internal static bool HasKeys
		{
			get
			{
				EnsureBound();
				return _claimKeys != null;
			}
		}

		internal static bool HasPhone
		{
			get
			{
				EnsureBound();
				return _setPhoneRaised != null;
			}
		}

		internal static bool HasOpen
		{
			get
			{
				EnsureBound();
				if (_setAppOpen != null)
				{
					return _setIconHidden != null;
				}
				return false;
			}
		}

		public static AppHandle Register(string id, string bundlePrefix, string title = null, string iconLabel = null, Assembly hostAssembly = null)
		{
			AppHandle result = new AppHandle(id);
			if (string.IsNullOrEmpty(id))
			{
				return result;
			}
			Assembly asm = hostAssembly ?? Assembly.GetCallingAssembly();
			string t = title;
			string il = iconLabel;
			string prefix = bundlePrefix;
			EnsureBound();
			if (_registerApp != null)
			{
				_registerApp(id, t, il, prefix, asm);
			}
			else
			{
				_pending.Add(delegate
				{
					_registerApp?.Invoke(id, t, il, prefix, asm);
				});
			}
			return result;
		}

		internal static void WhenBound(Action work)
		{
			if (work != null)
			{
				EnsureBound();
				if (_bound)
				{
					work();
				}
				else
				{
					_pending.Add(work);
				}
			}
		}

		internal static void HandleCall(string appId, string name, Func<string, string> handler)
		{
			if (_handle != null && handler != null)
			{
				_handle(appId, name, (string app, string argument) => handler(argument));
			}
		}

		internal static void EmitEvent(string appId, string name, string payload)
		{
			_emit?.Invoke(appId, name, payload);
		}

		internal static void AllowNetHost(string appId, string host)
		{
			_allowHost?.Invoke(appId, host);
		}

		internal static void Orient(string appId, string orientations)
		{
			_declareOrientations?.Invoke(appId, orientations);
		}

		internal static void Badge(string appId, int count)
		{
			_setBadge?.Invoke(appId, count);
		}

		internal static void Notify(string appId, string title, string subtitle, float seconds)
		{
			if (_notifyFor != null)
			{
				_notifyFor(appId, title, subtitle, seconds);
			}
			else if (_notify != null)
			{
				_notify(appId, title, subtitle);
			}
		}

		internal static bool OnScreen(string appId)
		{
			if (_isOnScreen != null)
			{
				return _isOnScreen(appId);
			}
			return false;
		}

		internal static void SetImage(string appId, string name, byte[] png)
		{
			_setImage?.Invoke(appId, name, png);
		}

		internal static void HideIcon(string appId, bool hidden)
		{
			_setIconHidden?.Invoke(appId, hidden);
		}

		internal static void OpenApp(string appId, bool open)
		{
			_setAppOpen?.Invoke(appId, open);
		}

		internal static bool AppIsOpen(string appId)
		{
			if (_isAppOpen != null)
			{
				return _isAppOpen(appId);
			}
			return false;
		}

		internal static bool RaisePhone(bool raised)
		{
			if (_setPhoneRaised != null)
			{
				return _setPhoneRaised(raised);
			}
			return false;
		}

		internal static bool PhoneIsRaised()
		{
			if (_isPhoneRaised != null)
			{
				return _isPhoneRaised();
			}
			return false;
		}

		internal static void ClaimKeys(string appId, string keys, Func<string, string, bool> handler)
		{
			_claimKeys?.Invoke(appId, keys, handler);
		}

		internal static bool MountSurface(object rect, string id, string prefix, Assembly host, float shortSide)
		{
			if (_mountSurface != null)
			{
				return _mountSurface(rect, id, prefix, host, shortSide);
			}
			return false;
		}

		internal static void UnmountSurface(string id)
		{
			_unmountSurface?.Invoke(id);
		}

		internal static bool SurfaceIsMounted(string id)
		{
			if (_isSurfaceMounted != null)
			{
				return _isSurfaceMounted(id);
			}
			return false;
		}

		private static void EnsureBound()
		{
			if (_bound)
			{
				return;
			}
			try
			{
				Type type = FindBridge(_probeAttempts++ % 30 == 0);
				if (type == null || (type.GetField("AbiVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is int num && num < 1))
				{
					return;
				}
				_registerApp = Get<Action<string, string, string, string, Assembly>>(type, "RegisterApp");
				if (_registerApp == null)
				{
					return;
				}
				_handle = Get<Action<string, string, Func<string, string, string>>>(type, "Handle");
				_emit = Get<Action<string, string, string>>(type, "Emit");
				_allowHost = Get<Action<string, string>>(type, "AllowHost");
				_declareOrientations = Get<Action<string, string>>(type, "DeclareOrientations");
				_setBadge = Get<Action<string, int>>(type, "SetBadge");
				_notify = Get<Action<string, string, string>>(type, "Notify");
				_notifyFor = Get<Action<string, string, string, float>>(type, "NotifyFor");
				_isOnScreen = Get<Func<string, bool>>(type, "IsAppOnScreen");
				_setImage = Get<Action<string, string, byte[]>>(type, "SetImage");
				_setIconHidden = Get<Action<string, bool>>(type, "SetIconHidden");
				_setAppOpen = Get<Action<string, bool>>(type, "SetAppOpen");
				_isAppOpen = Get<Func<string, bool>>(type, "IsAppOpen");
				_setPhoneRaised = Get<Func<bool, bool>>(type, "SetPhoneRaised");
				_isPhoneRaised = Get<Func<bool>>(type, "IsPhoneRaised");
				_claimKeys = Get<Action<string, string, Func<string, string, bool>>>(type, "ClaimKeys");
				_mountSurface = Get<Func<object, string, string, Assembly, float, bool>>(type, "MountSurface");
				_unmountSurface = Get<Action<string>>(type, "UnmountSurface");
				_isSurfaceMounted = Get<Func<string, bool>>(type, "IsSurfaceMounted");
				_bound = true;
				for (int i = 0; i < _pending.Count; i++)
				{
					try
					{
						_pending[i]();
					}
					catch
					{
					}
				}
				_pending.Clear();
			}
			catch
			{
			}
		}

		private static T Get<T>(Type t, string field) where T : class
		{
			return t.GetField(field, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) as T;
		}

		private static Type FindBridge(bool scan)
		{
			Type type = Type.GetType("Sideload.Bridge.SideloadBridge, Sideload", throwOnError: false);
			if (type != null || !scan)
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					type = assembly.GetType("Sideload.Bridge.SideloadBridge", throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}
	}
	public sealed class AppHandle
	{
		private readonly string _id;

		public string Id => _id;

		public bool IsOnScreen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.OnScreen(_id);
				}
				return false;
			}
		}

		public bool IsOpen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.AppIsOpen(_id);
				}
				return false;
			}
		}

		public static bool CanOpenProgrammatically
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasOpen;
				}
				return false;
			}
		}

		public static bool CanClaimKeys
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasKeys;
				}
				return false;
			}
		}

		internal AppHandle(string id)
		{
			_id = id ?? "";
		}

		public AppHandle OnCall(string name, Func<string, string> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HandleCall(id, name, handler);
			});
			return this;
		}

		public void Emit(string name, string payload = "")
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.EmitEvent(id, name, payload);
			});
		}

		public AppHandle AllowHost(string host)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.AllowNetHost(id, host);
			});
			return this;
		}

		public AppHandle Orientation(params string[] supported)
		{
			string id = _id;
			string list = ((supported == null) ? "" : string.Join(",", supported));
			Apps.WhenBound(delegate
			{
				Apps.Orient(id, list);
			});
			return this;
		}

		public AppHandle Badge(int count)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Badge(id, count);
			});
			return this;
		}

		public AppHandle Notify(string title, string subtitle = "", float seconds = 0f)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Notify(id, title, subtitle, seconds);
			});
			return this;
		}

		public AppHandle Image(string name, byte[] png)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.SetImage(id, name, png);
			});
			return this;
		}

		public AppHandle Icon(bool visible)
		{
			Apps.HideIcon(_id, !visible);
			return this;
		}

		public AppHandle NoIcon()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HideIcon(id, hidden: true);
			});
			return this;
		}

		public AppHandle Open()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: true);
			});
			return this;
		}

		public AppHandle Close()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
			});
			return this;
		}

		public bool Show()
		{
			if (!Apps.RaisePhone(raised: true))
			{
				return false;
			}
			Apps.OpenApp(_id, open: true);
			return true;
		}

		public AppHandle OnKey(string keys, Func<string, bool> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.ClaimKeys(id, keys, (handler == null) ? null : ((Func<string, string, bool>)((string _, string key) => handler(key))));
			});
			return this;
		}

		public AppHandle Hide()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
				Apps.RaisePhone(raised: false);
			});
			return this;
		}
	}
	public static class Surfaces
	{
		public static bool Available
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasSurfaces;
				}
				return false;
			}
		}

		public static SurfaceHandle Mount(object hostRect, string id, string bundlePrefix, float designShortSide = 0f, Assembly hostAssembly = null)
		{
			SurfaceHandle result = new SurfaceHandle(id);
			if (string.IsNullOrEmpty(id) || hostRect == null)
			{
				return result;
			}
			Assembly host = hostAssembly ?? Assembly.GetCallingAssembly();
			Apps.MountSurface(hostRect, id, bundlePrefix, host, designShortSide);
			return result;
		}

		public static void Unmount(string id)
		{
			Apps.UnmountSurface(id);
		}

		public static bool IsMounted(string id)
		{
			return Apps.SurfaceIsMounted(id);
		}
	}
	public sealed class SurfaceHandle
	{
		private readonly string _id;

		public string Id => _id;

		internal SurfaceHandle(string id)
		{
			_id = id;
		}

		public SurfaceHandle OnCall(string name, Func<string, string> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HandleCall(id, name, handler);
			});
			return this;
		}

		public SurfaceHandle Emit(string name, string payload = "")
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.EmitEvent(id, name, payload);
			});
			return this;
		}

		public SurfaceHandle AllowHost(string host)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.AllowNetHost(id, host);
			});
			return this;
		}

		public SurfaceHandle Image(string name, byte[] png)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.SetImage(id, name, png);
			});
			return this;
		}

		public void Unmount()
		{
			Apps.UnmountSurface(_id);
		}
	}
	public static class PhoneScreen
	{
		public static bool IsRaised
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.PhoneIsRaised();
				}
				return false;
			}
		}

		public static bool Available
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasPhone;
				}
				return false;
			}
		}

		public static bool Raise()
		{
			return Apps.RaisePhone(raised: true);
		}

		public static bool Lower()
		{
			return Apps.RaisePhone(raised: false);
		}
	}
}
namespace Hash
{
	public class Core : MelonMod
	{
		private sealed class PreferenceSharing : IUsageSharing
		{
			public bool Enabled
			{
				get
				{
					return _needleShareUsage?.Value ?? false;
				}
				set
				{
					if (_needleShareUsage != null)
					{
						_needleShareUsage.Value = value;
					}
				}
			}
		}

		internal const string AppId = "hash";

		internal static Instance Log;

		private static MelonPreferences_Entry<bool> _hijack;

		private static MelonPreferences_Entry<bool> _needleKeepContext;

		private static MelonPreferences_Entry<bool> _needleShareUsage;

		private AppHandle _app;

		private Session _session;

		private UsageCapture _capture;

		private UsageReport _report;

		private CommandIndex _index;

		private ICommandCatalogue _naturalCatalogue;

		private ArgProviders _providers;

		private LogCapture _log;

		private CommandRunner _runner;

		private Store _store;

		private History _history;

		private Aliases _aliases;

		private Usage _usage;

		private WorldMarks _marks;

		private NeedleCommandTranslator _needle;

		private readonly ScreenPresence _screen = new ScreenPresence();

		private int _logsDrawnUpTo;

		private const float IconInterval = 0.5f;

		private float _iconCheckedAt;

		private const float NeedleWarmupDelay = 1f;

		private bool _needleWarmupPending;

		private float _needleWarmupAt;

		private int _needleWarmupFailures;

		private int _toggledOnFrame = -1;

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("Hash", "hash");
			_hijack = obj.CreateEntry<bool>("HijackConsoleKey", true, "Console key opens the terminal", "ON (default): the key that opened the console now takes the phone out with hash on it. OFF: the vanilla console bar comes back and hash stays reachable only from code. Turn it off if another mod needs the vanilla bar.", false, false, (ValueValidator)null, (string)null);
			_needleKeepContext = obj.CreateEntry<bool>("NeedleKeepContext", false, "Needle keeps conversation context", "OFF (default): every '# <request>' is translated independently. ON: later requests may refer to earlier Needle commands and their results.", false, false, (ValueValidator)null, (string)null);
			_needleShareUsage = obj.CreateEntry<bool>("NeedleShareUsage", false, "Share the '# ' request log", "OFF (default): nothing leaves this machine. hash always writes each '# <request>', the commands it produced and whether they worked to UserData/Hash/queries.jsonl - open it and read it. ON: that file is uploaded so the requests can be used to make the model better, and cleared once the server has it. It holds no name, no save and no timestamp.", false, false, (ValueValidator)null, (string)null);
			if (!PhoneScreen.Available)
			{
				Log.Error("needs Sideload 1.5.0 or newer - this one cannot take the phone out of the player's pocket, so the terminal could never be reached. Nothing was registered.");
				return;
			}
			Build();
			RegisterApp();
			Patch();
			DeclaredCommands.FlushLog();
			Log.Msg("ready. Press the console key.");
		}

		private void Build()
		{
			_store = new Store();
			_log = new LogCapture();
			_providers = new ArgProviders();
			_index = new CommandIndex(_providers);
			_history = new History();
			_aliases = new Aliases();
			_usage = new Usage();
			_history.Load(_store);
			_aliases.Load(_store);
			_marks = new WorldMarks();
			_runner = new CommandRunner(_log);
			_naturalCatalogue = new OverlayCommandCatalogue(_index, Builtins.Catalogue);
			_needle = new NeedleCommandTranslator(_naturalCatalogue, () => _needleKeepContext.Value, null, null, null, _marks);
			PreferenceSharing sharing = new PreferenceSharing();
			_capture = new UsageCapture(_store, sharing, SystemLocale.Tag())
			{
				Model = _needle.ModelName
			};
			_report = new UsageReport(_store, sharing);
			_session = new Session(_index, _runner, _usage, _history, _aliases, _marks, _needle, _naturalCatalogue, _capture);
			_runner.LogViewOpen = () => _session.Builtins.LogsOpen;
			_session.Builtins.UseFace(_store.Read(StoreScope.Global, "font"));
		}

		private void RegisterApp()
		{
			_app = Apps.Register("hash", "Hash.Assets.hash", "hash", "hash").Orientation("landscape").NoIcon()
				.OnCall("boot", (string _) => Boot())
				.OnCall("nav", Nav)
				.OnCall("run", Run)
				.OnCall("drain", (string _) => Drain())
				.OnCall("cancel", (string _) => Cancel())
				.OnCall("back", (string _) => Back())
				.OnCall("close", delegate
				{
					Toggle();
					return "";
				});
		}

		private void Patch()
		{
			ItemSourcePatch.Providers = _providers;
			ConsoleAwakePatch.OnAwake = delegate
			{
				DeclaredCommands.Apply();
				_index.MarkDirty();
				ScheduleNeedleWarmup();
			};
			ConsoleKeyPatch.OnOpen = Toggle;
			ConsoleKeyPatch.Enabled = _hijack.Value;
			try
			{
				((MelonBase)this).HarmonyInstance.PatchAll();
			}
			catch (Exception ex)
			{
				Log.Error("patching failed - the console key will open the vanilla bar: " + ex);
				ConsoleKeyPatch.Enabled = false;
			}
		}

		private bool Toggle()
		{
			if (_app == null)
			{
				return false;
			}
			int frameCount = Time.frameCount;
			if (frameCount == _toggledOnFrame)
			{
				return true;
			}
			_toggledOnFrame = frameCount;
			if (_app.IsOpen && PhoneScreen.IsRaised)
			{
				_app.Hide();
				Left();
				return true;
			}
			bool raisedPhone = !PhoneScreen.IsRaised;
			if (!_app.Show())
			{
				Log.Warning("the game would not take the phone out right now, so the terminal stayed shut.");
				return false;
			}
			Entered(raisedPhone);
			return true;
		}

		public override void OnUpdate()
		{
			WarmNeedle();
			_marks?.Tick();
			IconFollowsTheConsole();
			if (_app != null)
			{
				if (_app.IsOpen && PhoneScreen.IsRaised)
				{
					Entered(raisedPhone: false);
				}
				else if (_screen.OnScreen)
				{
					Left();
				}
			}
		}

		private void ScheduleNeedleWarmup()
		{
			_needleWarmupPending = true;
			_needleWarmupAt = Time.unscaledTime + 1f;
			_needleWarmupFailures = 0;
		}

		private void WarmNeedle()
		{
			if (!_needleWarmupPending || _needle == null)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _needleWarmupAt)
			{
				return;
			}
			if (!_needle.Available)
			{
				_needleWarmupPending = false;
				return;
			}
			try
			{
				_providers.Invalidate();
				_index.MarkDirty();
				if (_needle.Warmup())
				{
					_needleWarmupPending = false;
				}
				else
				{
					_needleWarmupAt = unscaledTime + 1f;
				}
			}
			catch (Exception ex)
			{
				_needleWarmupFailures++;
				_needleWarmupPending = _needleWarmupFailures < 3;
				_needleWarmupAt = unscaledTime + 1f;
				Log.Warning("Hash warmup could not be queued: " + ex.Message);
			}
		}

		private void Entered(bool raisedPhone)
		{
			if (_screen.Enter(raisedPhone))
			{
				_index.MarkDirty();
				_providers.Invalidate();
				_app?.Emit("shown");
			}
		}

		private void Left()
		{
			if (_screen.Leave())
			{
				_session?.CancelNatural();
				Persist();
			}
		}

		private string Back()
		{
			bool raisedPhone = _screen.RaisedPhone;
			if (raisedPhone)
			{
				Toggle();
			}
			Json json = new Json();
			json.Bool("handled", raisedPhone);
			return json.Done();
		}

		private void IconFollowsTheConsole()
		{
			if (_app != null && _runner != null)
			{
				float unscaledTime = Time.unscaledTime;
				if (!(unscaledTime - _iconCheckedAt < 0.5f))
				{
					_iconCheckedAt = unscaledTime;
					_app.Icon(_runner.CanRun);
				}
			}
		}

		public override void OnDeinitializeMelon()
		{
			_session?.CancelNatural();
			_needle?.Dispose();
			Persist();
			_log?.Dispose();
		}

		private void Persist()
		{
			_session?.CloseCapture();
			_report?.Pump();
			_history.Save(_store);
			_aliases.Save(_store);
			_usage.Save(_store);
			_store.Write(StoreScope.Global, "font", _session.Builtins.Face);
		}

		private string Boot()
		{
			_usage.Load(_store);
			_logsDrawnUpTo = _log.Ring.Count;
			if (_session.Transcript.Count == 0)
			{
				_session.Transcript.Add(_session.Banner(Identity()));
			}
			Json json = new Json();
			json.Str("session", _session.Locked ? "session:client" : "session:host");
			json.Num("commands", _session.CommandCount);
			json.Str("prompt", "hash $");
			MelonInfoAttribute info = ((MelonBase)this).Info;
			json.Str("version", ((info != null) ? info.Version : null) ?? "");
			json.Str("mark", Mark());
			json.Bool("locked", _session.Locked);
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Bool("pending", _session.NaturalPending);
			json.Str("font", _session.Builtins.Face);
			json.Raw("banner", Lines(_session.Transcript.Window()));
			return json.Done();
		}

		private string Identity()
		{
			MelonInfoAttribute info = ((MelonBase)this).Info;
			string text = ((info != null) ? info.Version : null) ?? "";
			string text2 = "";
			try
			{
				text2 = Application.version ?? "";
			}
			catch
			{
			}
			return "hash" + ((text.Length > 0) ? (" v" + text) : "") + ((text2.Length > 0) ? (" on Schedule I " + text2) : "");
		}

		private string Nav(string argument)
		{
			string line = Json.Field(argument, "line");
			string action = Json.Field(argument, "action");
			NavResult navResult = _session.Navigate(line, action);
			Json json = new Json();
			if (navResult.Line != null)
			{
				json.Str("line", navResult.Line);
			}
			json.Str("suggest", navResult.Suggest);
			json.Str("mark", Mark());
			json.Str("ghost", navResult.Ghost);
			json.Bool("pending", _session.NaturalPending);
			return json.Done();
		}

		private string Drain()
		{
			bool num = _screen.OnScreen && _session.Builtins.LogsOpen;
			List<OutputLine> list = new List<OutputLine>((_screen.OnScreen ? _session.PollNatural() : new RunResult()).Lines);
			if (num)
			{
				list.AddRange(Fresh());
			}
			Json json = new Json();
			json.Raw("lines", Lines(list));
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Bool("pending", _session.NaturalPending);
			json.Str("font", _session.Builtins.Face);
			return json.Done();
		}

		private string Cancel()
		{
			_session.CancelNatural();
			Json json = new Json();
			json.Bool("pending", value: false);
			return json.Done();
		}

		private string Mark()
		{
			Mark mark = _session.Marks.Resolve("#");
			if (!mark.Exists)
			{
				return "";
			}
			return "# " + mark.Id;
		}

		private string Run(string line)
		{
			RunResult runResult = _session.Run(line);
			if (!string.IsNullOrEmpty(runResult.Clipboard))
			{
				Clipboard.Put(runResult.Clipboard);
			}
			Json json = new Json();
			json.Raw("lines", Lines(WithLogs(runResult.Lines)));
			json.Num("commands", _session.CommandCount);
			json.Bool("cleared", runResult.Cleared);
			json.Str("mark", Mark());
			json.Bool("live", _session.Builtins.LogsOpen);
			json.Bool("pending", runResult.Pending);
			json.Str("font", _session.Builtins.Face);
			return json.Done();
		}

		private IReadOnlyList<OutputLine> WithLogs(IReadOnlyList<OutputLine> ran)
		{
			if (!_session.Builtins.LogsOpen)
			{
				_logsDrawnUpTo = _log.Ring.Count;
				return ran;
			}
			List<OutputLine> list = new List<OutputLine>(ran);
			list.AddRange(Fresh());
			return list;
		}

		private List<OutputLine> Fresh()
		{
			List<OutputLine> list = new List<OutputLine>();
			string logsFilter = _session.Builtins.LogsFilter;
			for (int i = Math.Max(0, _logsDrawnUpTo); i < _log.Ring.Count; i++)
			{
				OutputLine outputLine = _log.Ring[i];
				if (logsFilter.Length <= 0 || outputLine.Text.IndexOf(logsFilter, StringComparison.OrdinalIgnoreCase) >= 0 || logsFilter.Equals(Kind(outputLine), StringComparison.OrdinalIgnoreCase))
				{
					_session.Push(outputLine);
					list.Add(outputLine);
				}
			}
			_logsDrawnUpTo = _log.Ring.Count;
			return list;
		}

		private static string Kind(OutputLine line)
		{
			return line.Kind switch
			{
				LineKind.Warn => "warn", 
				LineKind.Error => "error", 
				_ => "log", 
			};
		}

		private static string Lines(IReadOnlyList<OutputLine> lines)
		{
			StringBuilder stringBuilder = new StringBuilder("[");
			for (int i = 0; i < lines.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				Json json = new Json();
				json.Str("cls", Css(lines[i].Kind));
				json.Str("text", lines[i].Text);
				stringBuilder.Append(json.Done());
			}
			return stringBuilder.Append(']').ToString();
		}

		private static string Css(LineKind kind)
		{
			return kind switch
			{
				LineKind.Echo => "echo", 
				LineKind.Warn => "warn", 
				LineKind.Error => "err", 
				LineKind.Dim => "dim", 
				_ => "", 
			};
		}
	}
	internal sealed class Json
	{
		private readonly StringBuilder _sb = new StringBuilder("{");

		internal Json Str(string name, string value)
		{
			return Put(name, Quote(value));
		}

		internal Json Num(string name, int value)
		{
			return Put(name, value.ToString(CultureInfo.InvariantCulture));
		}

		internal Json Num(string name, double? value)
		{
			return Put(name, value.HasValue ? value.Value.ToString("0.####", CultureInfo.InvariantCulture) : "null");
		}

		internal Json Strings(string name, IReadOnlyList<string> values)
		{
			StringBuilder stringBuilder = new StringBuilder("[");
			for (int i = 0; i < (values?.Count ?? 0); i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(Quote(values[i]));
			}
			return Put(name, stringBuilder.Append(']').ToString());
		}

		internal Json Bool(string name, bool value)
		{
			return Put(name, value ? "true" : "false");
		}

		internal Json Raw(string name, string json)
		{
			return Put(name, json ?? "null");
		}

		internal string Done()
		{
			return _sb.Append('}').ToString();
		}

		private Json Put(string name, string value)
		{
			if (_sb.Length > 1)
			{
				_sb.Append(',');
			}
			_sb.Append(Quote(name)).Append(':').Append(value);
			return this;
		}

		internal static string Quote(string value)
		{
			if (value == null)
			{
				return "\"\"";
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length + 2).Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				case '\u0085':
				case '\u2028':
				case '\u2029':
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
					continue;
				}
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder3 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder3.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		internal static string Field(string json, string name)
		{
			if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(name))
			{
				return "";
			}
			string text = "\"" + name + "\"";
			int num = json.IndexOf(text, StringComparison.Ordinal);
			if (num < 0)
			{
				return "";
			}
			int num2 = json.IndexOf(':', num + text.Length);
			if (num2 < 0)
			{
				return "";
			}
			int i;
			for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++)
			{
			}
			if (i >= json.Length || json[i] != '"')
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (i++; i < json.Length; i++)
			{
				char c = json[i];
				if (c == '\\' && i + 1 < json.Length)
				{
					char c2 = json[++i];
					switch (c2)
					{
					case 'n':
						stringBuilder.Append('\n');
						continue;
					case 'r':
						stringBuilder.Append('\r');
						continue;
					case 't':
						stringBuilder.Append('\t');
						continue;
					case 'u':
						if (i + 4 < json.Length)
						{
							if (int.TryParse(json.Substring(i + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
							{
								stringBuilder.Append((char)result);
								i += 4;
							}
							continue;
						}
						break;
					}
					stringBuilder.Append(c2);
				}
				else
				{
					if (c == '"')
					{
						break;
					}
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}
	}
}
namespace Hash.Terminal
{
	public sealed class Aliases
	{
		private const string FileName = "aliases.txt";

		private readonly Dictionary<string, string> _byName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private bool _dirty;

		public int Count => _byName.Count;

		public IEnumerable<KeyValuePair<string, string>> All
		{
			get
			{
				List<string> list = new List<string>(_byName.Keys);
				list.Sort(StringComparer.OrdinalIgnoreCase);
				foreach (string item in list)
				{
					yield return new KeyValuePair<string, string>(item, _byName[item]);
				}
			}
		}

		public string Expand(string name)
		{
			if (name == null || !_byName.TryGetValue(name, out var value))
			{
				return null;
			}
			return value;
		}

		public bool TrySet(string name, string expansion, Func<string, bool> isRealCommand, out string error)
		{
			error = null;
			string text = name?.Trim();
			string value = expansion?.Trim();
			if (string.IsNullOrEmpty(text))
			{
				error = "an alias needs a name.";
				return false;
			}
			if (string.IsNullOrEmpty(value))
			{
				error = "an alias needs something to stand for.";
				return false;
			}
			if (text.IndexOf(' ') >= 0)
			{
				error = "an alias name cannot contain a space.";
				return false;
			}
			if (isRealCommand != null && isRealCommand(text))
			{
				error = "'" + text + "' is a real command; an alias would hide it.";
				return false;
			}
			_byName[text] = value;
			_dirty = true;
			return true;
		}

		public bool Remove(string name)
		{
			if (name == null || !_byName.Remove(name))
			{
				return false;
			}
			_dirty = true;
			return true;
		}

		public void Load(IStore store)
		{
			_byName.Clear();
			_dirty = false;
			string text = store?.Read(StoreScope.Global, "aliases.txt");
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim('\r');
				if (text2.Length != 0)
				{
					int num = text2.IndexOf('\t');
					if (num > 0 && num != text2.Length - 1)
					{
						_byName[text2.Substring(0, num)] = text2.Substring(num + 1);
					}
				}
			}
		}

		public void Save(IStore store)
		{
			if (!_dirty || store == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, string> item in All)
			{
				stringBuilder.Append(item.Key).Append('\t').Append(item.Value)
					.Append('\n');
			}
			store.Write(StoreScope.Global, "aliases.txt", stringBuilder.ToString());
			_dirty = false;
		}
	}
	public readonly struct Sum
	{
		public string Text { get; }

		public string Error { get; }

		public bool Failed => Error != null;

		internal Sum(string text, string error)
		{
			Text = text ?? "";
			Error = error;
		}
	}
	public static class Arithmetic
	{
		private sealed class BadSum : Exception
		{
			internal BadSum(string message)
				: base(message)
			{
			}
		}

		private struct Reader
		{
			private readonly string _text;

			private readonly double? _stack;

			private int _at;

			internal bool Done => _at >= _text.Length;

			internal char Peek
			{
				get
				{
					if (!Done)
					{
						return _text[_at];
					}
					return '\0';
				}
			}

			internal Reader(string text, double? stack)
			{
				_text = text;
				_stack = stack;
				_at = 0;
			}

			internal void SkipSpace()
			{
				while (!Done && _text[_at] == ' ')
				{
					_at++;
				}
			}

			internal double Expression()
			{
				double num = Term();
				while (true)
				{
					SkipSpace();
					char peek = Peek;
					if (peek != '+' && peek != '-')
					{
						break;
					}
					_at++;
					double num2 = Term();
					num = ((peek == '+') ? (num + num2) : (num - num2));
				}
				return num;
			}

			private double Term()
			{
				double num = Unary();
				while (true)
				{
					SkipSpace();
					char peek = Peek;
					if (peek != '*' && peek != '/')
					{
						return num;
					}
					_at++;
					double num2 = Unary();
					if (peek == '/' && num2 == 0.0)
					{
						break;
					}
					num = ((peek == '*') ? (num * num2) : (num / num2));
				}
				throw new BadSum("it divides by zero");
			}

			private double Unary()
			{
				SkipSpace();
				if (Peek == '-')
				{
					_at++;
					return 0.0 - Unary();
				}
				if (Peek == '+')
				{
					_at++;
					return Unary();
				}
				return Primary();
			}

			private double Primary()
			{
				SkipSpace();
				if (Done)
				{
					throw new BadSum("it stops in the middle");
				}
				if (Peek == '@')
				{
					_at++;
					if (!_stack.HasValue)
					{
						throw new BadSum("nothing was counted for '@'");
					}
					return _stack.Value;
				}
				if (Peek == '(')
				{
					_at++;
					double result = Expression();
					SkipSpace();
					if (Peek != ')')
					{
						throw new BadSum("a bracket is left open");
					}
					_at++;
					return result;
				}
				int at = _at;
				while (!Done && (char.IsDigit(Peek) || Peek == '.'))
				{
					_at++;
				}
				if (_at == at)
				{
					throw new BadSum("'" + Peek + "' is not a number or an operator");
				}
				string text = _text.Substring(at, _at - at);
				if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					throw new BadSum("'" + text + "' is not a number");
				}
				return result2;
			}
		}

		public const char Sigil = '@';

		private const string Alphabet = "0123456789.@+-*/() ";

		public static bool Looks(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return false;
			}
			bool flag = false;
			foreach (char c in token)
			{
				if ("0123456789.@+-*/() ".IndexOf(c) < 0)
				{
					return false;
				}
				if (c == '@' || c == '+' || c == '-' || c == '*' || c == '/' || c == '(')
				{
					flag = true;
				}
			}
			double result;
			if (flag)
			{
				return !double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
			}
			return false;
		}

		public static bool NeedsStack(string token)
		{
			if (!string.IsNullOrEmpty(token))
			{
				return token.IndexOf('@') >= 0;
			}
			return false;
		}

		public static Sum Evaluate(string token, double? stack)
		{
			Reader reader = new Reader(token ?? "", stack);
			try
			{
				double d = reader.Expression();
				reader.SkipSpace();
				if (!reader.Done)
				{
					return Bad(token, "there is a stray '" + reader.Peek + "' in it");
				}
				if (double.IsNaN(d) || double.IsInfinity(d))
				{
					return Bad(token, "it does not work out to a number");
				}
				return new Sum(d.ToString("0.####", CultureInfo.InvariantCulture), null);
			}
			catch (BadSum badSum)
			{
				return Bad(token, badSum.Message);
			}
		}

		private static Sum Bad(string token, string why)
		{
			return new Sum(null, token + ": " + why);
		}
	}
	public sealed class Builtins
	{
		private readonly Suggestions _suggestions;

		private readonly ICommandCatalogue _catalogue;

		private readonly History _history;

		private readonly Aliases _aliases;

		private readonly Transcript _transcript;

		private readonly UsageCapture _capture;

		public const string Source = "hash";

		public static readonly IReadOnlyList<CommandInfo> Catalogue = new CommandInfo[12]
		{
			Own("help", "help [word|topic|all] [page]", "explain one command, list a topic, or search", "help all 2"),
			Own("clear", "clear", "empty the screen", "clear"),
			Own("history", "history [text]", "what you have run before, oldest first", "history give"),
			Own("alias", "alias [name] [command]", "make a short name for a command, or list them", "alias gk \"give ogkush 5\""),
			Own("unalias", "unalias <name>", "remove an alias", "unalias gk"),
			Own("grep", "grep <text>", "show only the lines on screen containing something", "grep ogkush"),
			Own("copy", "copy [text]", "put a line on the system clipboard - the last one, unless you name it", "copy ogkushseed"),
			Own("logs", "logs [filter|off]", "show what the game logs as it happens", "logs warn"),
			Own("raw", "raw <command>", "run the rest of the line as one command, semicolons and all", "raw bind t 'settime 1200'"),
			Own("repeat", "repeat <count> <command>", "run a command several times", "repeat 5 give ogkush 1"),
			Own("font", "font [mono|pixel]", "switch the typeface, or say which one is on", "font pixel"),
			Own("share", "share [on|off]", "send your '# ' requests so the model can learn from them, or stop", "share on")
		};

		public static readonly string[] Words = Vocabulary();

		public string PendingClipboard { get; private set; }

		public string Face { get; private set; } = "mono";

		public bool LogsOpen { get; private set; }

		public string LogsFilter { get; private set; } = "";

		public Builtins(Suggestions suggestions, ICommandCatalogue catalogue, History history, Aliases aliases, Transcript transcript, UsageCapture capture = null)
		{
			_suggestions = suggestions;
			_catalogue = catalogue;
			_history = history;
			_aliases = aliases;
			_transcript = transcript;
			_capture = capture;
		}

		private static CommandInfo Own(string word, string signature, string description, string usage)
		{
			return new CommandInfo(word, description, usage, signature, "hash", isVanilla: false);
		}

		private static string[] Vocabulary()
		{
			string[] array = new string[Catalogue.Count];
			for (int i = 0; i < Catalogue.Count; i++)
			{
				array[i] = Catalogue[i].Word;
			}
			return array;
		}

		public string TakeClipboard()
		{
			string pendingClipboard = PendingClipboard;
			PendingClipboard = null;
			return pendingClipboard;
		}

		public void UseFace(string face)
		{
			if (string.Equals(face, "pixel", StringComparison.OrdinalIgnoreCase))
			{
				Face = "pixel";
			}
			else if (string.Equals(face, "mono", StringComparison.OrdinalIgnoreCase))
			{
				Face = "mono";
			}
		}

		public bool TryRun(string line, out IReadOnlyList<OutputLine> output)
		{
			output = null;
			List<string> list = CommandLine.Tokenise(line);
			if (list.Count == 0)
			{
				return false;
			}
			string text = list[0].ToLowerInvariant();
			if (Array.IndexOf(Words, text) < 0)
			{
				return false;
			}
			if (_suggestions.IsCommand(text))
			{
				return false;
			}
			string text2 = ((line.Length > list[0].Length) ? line.Substring(list[0].Length).Trim() : "");
			List<OutputLine> list2 = new List<OutputLine>();
			switch (text)
			{
			case "help":
				Help(text2, list2);
				break;
			case "clear":
				_transcript.Clear();
				break;
			case "history":
				HistoryList(text2, list2);
				break;
			case "alias":
				Alias(text2, list, list2);
				break;
			case "unalias":
				Unalias(text2, list2);
				break;
			case "grep":
				Grep(text2, list2);
				break;
			case "copy":
				Copy(text2, list2);
				break;
			case "logs":
				Logs(text2, list2);
				break;
			case "font":
				Typeface(text2, list2);
				break;
			case "share":
				Share(text2, list2);
				break;
			case "raw":
				list2.Add(OutputLine.Error("raw: no command after it"));
				UsageLine("raw", list2);
				break;
			case "repeat":
				list2.Add(OutputLine.Error("repeat: no count and no command"));
				UsageLine("repeat", list2);
				break;
			}
			output = list2;
			return true;
		}

		private void Help(string query, List<OutputLine> lines)
		{
			if (query.Length == 0)
			{
				Brief(lines);
				return;
			}
			if (query.StartsWith("all", StringComparison.OrdinalIgnoreCase))
			{
				int.TryParse(query.Substring(3).Trim(), out var result);
				Everything(Math.Max(1, result), lines);
				return;
			}
			if (HelpTopics.IsTopic(query))
			{
				OneTopic(query, lines);
				return;
			}
			CommandInfo commandInfo = _suggestions.Find(query);
			if (commandInfo != null)
			{
				Detail(commandInfo, lines);
				return;
			}
			List<CommandInfo> list = new List<CommandInfo>();
			foreach (CommandInfo command in _catalogue.Commands)
			{
				if (FuzzyMatcher.IsMatch(command.Word, query) || command.Description.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(command);
				}
			}
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Warn("help: nothing matches '" + query + "'"));
				return;
			}
			lines.Add(OutputLine.Dim($"{list.Count} match '{query}':"));
			foreach (CommandInfo item in list)
			{
				lines.Add(OutputLine.Out(Row(item)));
			}
		}

		private void Brief(List<OutputLine> lines)
		{
			lines.Add(OutputLine.Dim("# <request> translates natural language; type # alone to confirm a proposal."));
			lines.Add(OutputLine.Out(""));
			string[] common = HelpTopics.Common;
			foreach (string word in common)
			{
				CommandInfo commandInfo = _suggestions.Find(word);
				if (commandInfo != null)
				{
					lines.Add(OutputLine.Out(Row(commandInfo)));
				}
			}
			lines.Add(OutputLine.Out(""));
			Dictionary<string, List<string>> dictionary = Sort();
			List<string> list = new List<string>();
			(string, string[])[] groups = HelpTopics.Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				string item = groups[i].Item1;
				if (dictionary.TryGetValue(item, out var value))
				{
					list.Add($"{item} {value.Count}");
				}
			}
			foreach (KeyValuePair<string, List<string>> item2 in dictionary)
			{
				if (!HelpTopics.IsTopic(item2.Key))
				{
					list.Add($"{item2.Key} {item2.Value.Count}");
				}
			}
			if (dictionary.TryGetValue("terminal", out var value2))
			{
				list.Add($"{"terminal"} {value2.Count}");
			}
			Wrap("topics", list, lines);
			lines.Add(OutputLine.Dim($"{Total()} commands. 'help <topic>' lists one, 'help <command>' explains one, " + "'help all' lists every one."));
		}

		private void OneTopic(string topic, List<OutputLine> lines)
		{
			if (!Sort().TryGetValue(topic, out var value) || value.Count == 0)
			{
				lines.Add(OutputLine.Warn("help: nothing is filed under '" + topic + "' in this game"));
				return;
			}
			value.Sort(StringComparer.OrdinalIgnoreCase);
			lines.Add(OutputLine.Dim($"{topic} - {value.Count} command(s)"));
			foreach (string item in value)
			{
				CommandInfo commandInfo = _suggestions.Find(item);
				if (commandInfo != null)
				{
					lines.Add(OutputLine.Out(Row(commandInfo)));
				}
			}
		}

		private void Everything(int page, List<OutputLine> lines)
		{
			Dictionary<string, List<string>> dictionary = Sort();
			List<List<OutputLine>> list = new List<List<OutputLine>>();
			(string, string[])[] groups = HelpTopics.Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				string item = groups[i].Item1;
				if (dictionary.TryGetValue(item, out var value))
				{
					list.Add(TopicBlock(item, value));
				}
			}
			foreach (KeyValuePair<string, List<string>> item2 in dictionary)
			{
				if (!HelpTopics.IsTopic(item2.Key) && !(item2.Key == "other"))
				{
					list.Add(TopicBlock(item2.Key, item2.Value));
				}
			}
			if (dictionary.TryGetValue("terminal", out var value2))
			{
				list.Add(TopicBlock("terminal", value2));
			}
			if (dictionary.TryGetValue("other", out var value3))
			{
				list.Add(TopicBlock("other", value3));
			}
			List<List<OutputLine>> list2 = new List<List<OutputLine>>
			{
				new List<OutputLine>()
			};
			foreach (List<OutputLine> item3 in list)
			{
				List<OutputLine> list3 = list2[list2.Count - 1];
				if (list3.Count > 0 && list3.Count + item3.Count > 16)
				{
					list3 = new List<OutputLine>();
					list2.Add(list3);
				}
				list3.AddRange(item3);
			}
			if (page > list2.Count)
			{
				page = list2.Count;
			}
			lines.AddRange(list2[page - 1]);
			lines.Add(OutputLine.Dim((page < list2.Count) ? $"page {page} of {list2.Count} - 'help all {page + 1}' for the next." : $"{Total()} commands in all."));
		}

		private List<OutputLine> TopicBlock(string topic, List<string> words)
		{
			List<OutputLine> list = new List<OutputLine> { OutputLine.Dim(topic) };
			words.Sort(StringComparer.OrdinalIgnoreCase);
			foreach (string word in words)
			{
				CommandInfo commandInfo = _suggestions.Find(word);
				if (commandInfo != null)
				{
					list.Add(OutputLine.Out("  " + Row(commandInfo)));
				}
			}
			return list;
		}

		private static void Wrap(string label, List<string> words, List<OutputLine> lines)
		{
			if (words.Count == 0)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(Pad(label, 10));
			int num = 10;
			foreach (string word in words)
			{
				if (num > 10 && num + 3 + word.Length > 96)
				{
					lines.Add(OutputLine.Out(stringBuilder.ToString()));
					stringBuilder = new StringBuilder(new string(' ', 10));
					num = 10;
				}
				if (num > 10)
				{
					stringBuilder.Append("   ");
					num += 3;
				}
				stringBuilder.Append(word);
				num += word.Length;
			}
			lines.Add(OutputLine.Out(stringBuilder.ToString()));
		}

		private Dictionary<string, List<string>> Sort()
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			foreach (CommandInfo command in _catalogue.Commands)
			{
				Put(dictionary, command.IsVanilla ? HelpTopics.TopicOf(command.Word) : Mod(command.Source), command.Word);
			}
			foreach (CommandInfo item in Catalogue)
			{
				if (!_suggestions.IsCommand(item.Word))
				{
					Put(dictionary, "terminal", item.Word);
				}
			}
			return dictionary;
		}

		private static string Mod(string source)
		{
			if (string.IsNullOrEmpty(source))
			{
				return "other";
			}
			int num = source.LastIndexOf(" v", StringComparison.Ordinal);
			if (num <= 0)
			{
				return source;
			}
			return source.Substring(0, num);
		}

		private static void Put(Dictionary<string, List<string>> byTopic, string topic, string word)
		{
			if (!byTopic.TryGetValue(topic, out var value))
			{
				value = (byTopic[topic] = new List<string>());
			}
			value.Add(word);
		}

		private int Total()
		{
			int num = _catalogue.Commands.Count;
			foreach (CommandInfo item in Catalogue)
			{
				if (!_suggestions.IsCommand(item.Word))
				{
					num++;
				}
			}
			return num;
		}

		private static void UsageLine(string word, List<OutputLine> lines)
		{
			foreach (CommandInfo item in Catalogue)
			{
				if (string.Equals(item.Word, word, StringComparison.OrdinalIgnoreCase))
				{
					lines.Add(OutputLine.Dim("usage: " + item.Signature));
					break;
				}
			}
		}

		private static string Fragment(string description)
		{
			if (string.IsNullOrEmpty(description))
			{
				return "";
			}
			string text = description.TrimEnd();
			if (text.EndsWith(".", StringComparison.Ordinal) && !text.EndsWith("..", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - 1);
			}
			if (text.Length > 1 && char.IsUpper(text[0]) && !char.IsUpper(text[1]))
			{
				text = char.ToLowerInvariant(text[0]) + text.Substring(1);
			}
			return text;
		}

		private void Share(string rest, List<OutputLine> lines)
		{
			string text = rest.Trim().ToLowerInvariant();
			if (text.Length == 0)
			{
				UsageCapture capture = _capture;
				lines.Add(OutputLine.Out((capture != null && capture.Sharing) ? "share: on. Your '# ' requests are sent so the model can learn from them." : "share: off. Your '# ' requests stay on this machine."));
				lines.Add(OutputLine.Dim("They are written to UserData/Hash/queries.jsonl either way - plain text, no name, no save, no timestamp."));
				UsageLine("share", lines);
			}
			else if (text != "on" && text != "off")
			{
				lines.Add(OutputLine.Error("share: say on or off"));
				UsageLine("share", lines);
			}
			else if (_capture == null)
			{
				lines.Add(OutputLine.Error("share: this build cannot change the setting."));
			}
			else
			{
				_capture.Sharing = text == "on";
				lines.Add(OutputLine.Out((text == "on") ? "share: on. Thank you - what you type is what the next model learns from." : "share: off. Nothing leaves this machine."));
			}
		}

		private static void Detail(CommandInfo command, List<OutputLine> lines)
		{
			lines.Add(OutputLine.Out(command.Signature));
			if (command.Description.Length > 0)
			{
				lines.Add(OutputLine.Out("  " + Fragment(command.Description)));
			}
			if (command.Usage.Length > 0)
			{
				lines.Add(OutputLine.Dim("  e.g. " + command.Usage));
			}
			lines.Add(OutputLine.Dim("  " + command.Source));
		}

		private static string Row(CommandInfo command)
		{
			string value = ((command.Description.Length > 0) ? Fragment(command.Description) : command.Signature);
			return Pad(command.Word, 24) + Markup.Clip(value, 72);
		}

		private static IEnumerable<string> Grid(IReadOnlyList<CommandInfo> commands, IReadOnlyList<string> builtins)
		{
			List<string> words = new List<string>();
			foreach (CommandInfo command in commands)
			{
				words.Add(command.Word);
			}
			List<string> list = new List<string>(builtins);
			list.Sort(StringComparer.OrdinalIgnoreCase);
			words.AddRange(list);
			for (int i = 0; i < words.Count; i += 5)
			{
				StringBuilder stringBuilder = new StringBuilder();
				for (int j = 0; j < 5 && i + j < words.Count; j++)
				{
					stringBuilder.Append(Pad(words[i + j], 19));
				}
				yield return stringBuilder.ToString().TrimEnd();
			}
		}

		private void HistoryList(string query, List<OutputLine> lines)
		{
			List<string> list = _history.Search(query);
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Dim((query.Length == 0) ? "history: nothing yet" : ("history: nothing matches '" + query + "'")));
				return;
			}
			for (int num = list.Count - 1; num >= 0; num--)
			{
				lines.Add(OutputLine.Out(Pad((list.Count - num).ToString(), 6) + list[num]));
			}
		}

		private void Alias(string rest, List<string> tokens, List<OutputLine> lines)
		{
			if (rest.Length == 0)
			{
				if (_aliases.Count != 0)
				{
					foreach (KeyValuePair<string, string> item in _aliases.All)
					{
						lines.Add(OutputLine.Out(Pad(item.Key, 16) + item.Value));
					}
					return;
				}
				lines.Add(OutputLine.Dim("no aliases yet - alias <name> <command> makes one"));
			}
			else if (tokens.Count < 3)
			{
				lines.Add(OutputLine.Error("alias: needs a name and a command"));
				UsageLine("alias", lines);
				lines.Add(OutputLine.Dim("       quote the command if it has spaces"));
			}
			else
			{
				string text = tokens[1];
				string text2 = rest.Substring(text.Length).Trim();
				if (!_aliases.TrySet(text, text2, _suggestions.IsKnown, out var error))
				{
					lines.Add(OutputLine.Error(error));
				}
				else
				{
					lines.Add(OutputLine.Out(text + " -> " + text2));
				}
			}
		}

		private void Unalias(string rest, List<OutputLine> lines)
		{
			if (rest.Length == 0)
			{
				lines.Add(OutputLine.Error("unalias: needs a name"));
				UsageLine("unalias", lines);
			}
			else
			{
				lines.Add(_aliases.Remove(rest) ? OutputLine.Out(rest + " removed.") : OutputLine.Warn("unalias: no alias called '" + rest + "'"));
			}
		}

		private void Grep(string pattern, List<OutputLine> lines)
		{
			if (pattern.Length == 0)
			{
				lines.Add(OutputLine.Error("grep: needs something to look for"));
				UsageLine("grep", lines);
				return;
			}
			List<OutputLine> list = new List<OutputLine>();
			foreach (OutputLine line in _transcript.Lines)
			{
				if (line.Text.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(line);
				}
			}
			if (list.Count == 0)
			{
				lines.Add(OutputLine.Dim("grep: nothing on screen matches '" + pattern + "'"));
				return;
			}
			lines.Add(OutputLine.Dim($"{list.Count} line(s) match '{pattern}':"));
			lines.AddRange(list);
		}

		private void Copy(string rest, List<OutputLine> lines)
		{
			string text = rest;
			if (text.Length == 0)
			{
				foreach (OutputLine item in _transcript.Recent(40))
				{
					if (item.Kind != LineKind.Dim && item.Kind != LineKind.Echo)
					{
						text = item.Text.Trim();
						break;
					}
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				lines.Add(OutputLine.Warn("copy: nothing on screen to copy"));
				return;
			}
			PendingClipboard = text;
			lines.Add(OutputLine.Dim("Copied: " + text));
		}

		private void Typeface(string rest, List<OutputLine> lines)
		{
			string text = (rest ?? "").Trim().ToLowerInvariant();
			if (text.Length == 0)
			{
				lines.Add(OutputLine.Out("font: " + Face));
				lines.Add(OutputLine.Dim("mono   the machine's own monospaced font, easier to read"));
				lines.Add(OutputLine.Dim("pixel  the game's own face, smaller and denser"));
			}
			else if (text != "mono" && text != "pixel")
			{
				lines.Add(OutputLine.Error("font: '" + rest.Trim() + "' is not a face"));
				lines.Add(OutputLine.Dim("try 'mono' or 'pixel'"));
			}
			else
			{
				Face = text;
				lines.Add(OutputLine.Dim("font: " + Face));
			}
		}

		private void Logs(string rest, List<OutputLine> lines)
		{
			if (string.Equals(rest, "off", StringComparison.OrdinalIgnoreCase))
			{
				LogsOpen = false;
				LogsFilter = "";
				lines.Add(OutputLine.Dim("Log view off."));
			}
			else
			{
				LogsOpen = true;
				LogsFilter = rest;
				lines.Add(OutputLine.Dim((rest.Length == 0) ? "Log view on - everything the game logs. 'logs warn' filters, 'logs off' stops." : ("Log view on, filtered to '" + rest + "'. 'logs off' stops.")));
			}
		}

		internal static string Pad(string value, int width)
		{
			if (value == null)
			{
				value = "";
			}
			if (value.Length < width)
			{
				return value.PadRight(width);
			}
			return value + " ";
		}
	}
	public static class CommandLine
	{
		public readonly struct Plan
		{
			public IReadOnlyList<string> Commands { get; }

			public string Error { get; }

			public bool Failed => Error != null;

			internal Plan(IReadOnlyList<string> commands, string error)
			{
				Commands = commands ?? Array.Empty<string>();
				Error = error;
			}
		}

		public const string RawWord = "raw";

		public const string RepeatWord = "repeat";

		public const int MaxRepeat = 100;

		private static readonly (string Word, int OwnArguments, bool RunsNow)[] TakeACommand = new(string, int, bool)[4]
		{
			("raw", 0, true),
			("repeat", 1, true),
			("alias", 1, false),
			("bind", 1, false)
		};

		public static Plan Parse(string line, Func<string, string> expandAlias = null)
		{
			if (string.IsNullOrWhiteSpace(line))
			{
				return new Plan(Array.Empty<string>(), null);
			}
			string text = line.Trim();
			if (StartsWithWord(text, "raw", out var rest))
			{
				if (rest.Length != 0)
				{
					return new Plan(new string[1] { rest }, null);
				}
				return new Plan(null, "raw: no command after it\nusage: raw <command>");
			}
			if (!NeedsShell(text))
			{
				string text2 = ExpandFirstWord(text, expandAlias);
				return Split(text2, expandAlias, (object)text2 != text);
			}
			return Split(text, expandAlias, alreadyExpanded: false);
		}

		public static bool NeedsShell(string line)
		{
			if (line != null)
			{
				if (line.IndexOf('"') < 0)
				{
					return line.IndexOf(';') >= 0;
				}
				return true;
			}
			return false;
		}

		private static Plan Split(string line, Func<string, string> expandAlias, bool alreadyExpanded)
		{
			if (!TrySplitStatements(line, out var statements, out var error))
			{
				return new Plan(null, error);
			}
			List<string> list = new List<string>();
			foreach (string item in statements)
			{
				string text = item.Trim();
				if (text.Length == 0)
				{
					continue;
				}
				if (StartsWithWord(text, "raw", out var rest))
				{
					if (rest.Length == 0)
					{
						return new Plan(null, "raw: no command after it\nusage: raw <command>");
					}
					list.Add(rest);
					continue;
				}
				if (!alreadyExpanded)
				{
					text = ExpandFirstWord(text, expandAlias);
				}
				if (Expand(text, list, out error))
				{
					continue;
				}
				return new Plan(null, error);
			}
			return new Plan(list, null);
		}

		private static bool Expand(string statement, List<string> into, out string error)
		{
			error = null;
			if (!StartsWithWord(statement, "repeat", out var rest))
			{
				into.Add(statement);
				return true;
			}
			int num = rest.IndexOf(' ');
			string text = ((num < 0) ? rest : rest.Substring(0, num));
			string text2 = ((num < 0) ? "" : rest.Substring(num + 1).Trim());
			if (!int.TryParse(text, out var result))
			{
				error = "repeat: '" + text + "' is not a number\nusage: repeat <count> <command>";
				return false;
			}
			if (result < 1)
			{
				error = "repeat: the count has to be 1 or more\nusage: repeat <count> <command>";
				return false;
			}
			if (result > 100)
			{
				error = $"repeat: {100} is the most that can run in one frame";
				return false;
			}
			if (text2.Length == 0)
			{
				error = "repeat: no command after the count\nusage: repeat <count> <command>";
				return false;
			}
			if (StartsWithWord(text2, "raw", out var rest2))
			{
				if (rest2.Length == 0)
				{
					error = "raw: no command after it\nusage: raw <command>";
					return false;
				}
				text2 = rest2;
			}
			List<string> list = new List<string>();
			for (int i = 0; i < result; i++)
			{
				if (!Expand(text2, list, out error))
				{
					return false;
				}
				if (list.Count > 100)
				{
					error = $"repeat: that comes to more than {100} commands, and they all run in one frame";
					return false;
				}
			}
			into.AddRange(list);
			return true;
		}

		private static bool TrySplitStatements(string line, out List<string> statements, out string error)
		{
			statements = new List<string>();
			error = null;
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			bool flag2 = true;
			for (int i = 0; i < line.Length; i++)
			{
				char c = line[i];
				if (flag2 && c != ' ')
				{
					flag2 = false;
					if (SuspendsSplitting(line.Substring(i)))
					{
						statements.Add(stringBuilder.Append(line.Substring(i)).ToString());
						return true;
					}
				}
				switch (c)
				{
				case '"':
					if (flag && i + 1 < line.Length && line[i + 1] == '"')
					{
						stringBuilder.Append('"');
						i++;
					}
					else
					{
						flag = !flag;
					}
					continue;
				case ';':
					if (!flag)
					{
						statements.Add(stringBuilder.ToString());
						stringBuilder.Clear();
						flag2 = true;
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			if (flag)
			{
				error = "a quote was opened and never closed.";
				statements = null;
				return false;
			}
			statements.Add(stringBuilder.ToString());
			return true;
		}

		private static bool SuspendsSplitting(string rest)
		{
			if (StartsWithWord(rest, "raw", out var rest2))
			{
				return true;
			}
			if (!StartsWithWord(rest, "repeat", out var rest3))
			{
				return false;
			}
			int num = rest3.IndexOf(' ');
			if (num <= 0)
			{
				return false;
			}
			if (!int.TryParse(rest3.Substring(0, num), out var _))
			{
				return false;
			}
			return StartsWithWord(rest3.Substring(num + 1).TrimStart(), "raw", out rest2);
		}

		private static string ExpandFirstWord(string statement, Func<string, string> expandAlias)
		{
			if (expandAlias == null)
			{
				return statement;
			}
			int num = statement.IndexOf(' ');
			string arg = ((num < 0) ? statement : statement.Substring(0, num));
			string text = expandAlias(arg);
			if (string.IsNullOrEmpty(text))
			{
				return statement;
			}
			if (num >= 0)
			{
				return text + statement.Substring(num);
			}
			return text;
		}

		public static string Unwrap(string statement)
		{
			if (string.IsNullOrEmpty(statement))
			{
				return statement ?? "";
			}
			for (int i = 0; i < 4; i++)
			{
				string text = statement.TrimStart();
				int num = WrapperWidth(text);
				if (num <= 0)
				{
					return statement;
				}
				statement = text.Substring(num);
			}
			return statement;
		}

		public static int StoredCommandAt(string statement)
		{
			if (string.IsNullOrEmpty(statement))
			{
				return -1;
			}
			string line = statement.TrimStart();
			int at = 0;
			string a = ReadWord(line, ref at);
			(string, int, bool)[] takeACommand = TakeACommand;
			for (int i = 0; i < takeACommand.Length; i++)
			{
				(string, int, bool) tuple = takeACommand[i];
				var (b, num, _) = tuple;
				if (!tuple.Item3 && string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
				{
					return num + 1;
				}
			}
			return -1;
		}

		private static int WrapperWidth(string statement)
		{
			int i = 0;
			string a = ReadWord(statement, ref i);
			int num = -1;
			(string, int, bool)[] takeACommand = TakeACommand;
			for (int j = 0; j < takeACommand.Length; j++)
			{
				var (b, num2, _) = takeACommand[j];
				if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
				{
					num = num2;
				}
			}
			if (num < 0)
			{
				return 0;
			}
			for (int k = 0; k <= num; k++)
			{
				if (i >= statement.Length || statement[i] != ' ')
				{
					return 0;
				}
				for (; i < statement.Length && statement[i] == ' '; i++)
				{
				}
				if (k < num && ReadWord(statement, ref i).Length == 0)
				{
					return 0;
				}
			}
			if (i < statement.Length && (statement[i] == '"' || statement[i] == '\''))
			{
				i++;
			}
			return i;
		}

		private static string ReadWord(string line, ref int at)
		{
			int num = at;
			while (at < line.Length && line[at] != ' ')
			{
				at++;
			}
			return line.Substring(num, at - num);
		}

		public static bool StartsWithWord(string line, string word, out string rest)
		{
			rest = null;
			if (line == null || word == null)
			{
				return false;
			}
			if (!line.StartsWith(word, StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			if (line.Length == word.Length)
			{
				rest = "";
				return true;
			}
			if (line[word.Length] != ' ')
			{
				return false;
			}
			rest = line.Substring(word.Length + 1).Trim();
			return true;
		}

		public static List<string> Tokenise(string command)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(command))
			{
				return list;
			}
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			bool flag2 = false;
			foreach (char c in command)
			{
				switch (c)
				{
				case '"':
					flag = !flag;
					flag2 = true;
					continue;
				case ' ':
					if (!flag)
					{
						if (flag2)
						{
							list.Add(stringBuilder.ToString());
							stringBuilder.Clear();
							flag2 = false;
						}
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
				flag2 = true;
			}
			if (flag2)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}

		public static void TokenAtCaret(string line, out int index, out string prefix)
		{
			index = 0;
			prefix = "";
			if (string.IsNullOrEmpty(line))
			{
				return;
			}
			bool flag = false;
			StringBuilder stringBuilder = new StringBuilder();
			bool flag2 = false;
			foreach (char c in line)
			{
				switch (c)
				{
				case '"':
					flag = !flag;
					flag2 = true;
					continue;
				case ' ':
					if (!flag)
					{
						if (flag2)
						{
							index++;
							stringBuilder.Clear();
							flag2 = false;
						}
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
				flag2 = true;
			}
			prefix = stringBuilder.ToString();
		}

		public static string ReplaceTokenAtCaret(string line, string value, bool trailingSpace)
		{
			string text = ((value != null && value.IndexOf(' ') >= 0) ? ("\"" + value + "\"") : (value ?? ""));
			int length = line?.Length ?? 0;
			if (line != null)
			{
				bool flag = false;
				for (int num = line.Length - 1; num >= 0; num--)
				{
					if (line[num] == '"')
					{
						flag = !flag;
					}
					if (line[num] == ' ' && !flag)
					{
						length = num + 1;
						break;
					}
					if (num == 0)
					{
						length = 0;
					}
				}
			}
			return ((line == null) ? "" : line.Substring(0, length)) + text + (trailingSpace ? " " : "");
		}
	}
	public enum MatchKind
	{
		None,
		Subsequence,
		Substring,
		WordStart,
		Prefix,
		Exact
	}
	public readonly struct MatchResult
	{
		public static readonly MatchResult NoMatch = new MatchResult(MatchKind.None, int.MaxValue);

		public MatchKind Kind { get; }

		public int Offset { get; }

		public bool IsMatch => Kind != MatchKind.None;

		public int Score
		{
			get
			{
				if (Kind != MatchKind.None)
				{
					return (int)Kind * 1000 - Math.Min(Offset, 999);
				}
				return 0;
			}
		}

		internal MatchResult(MatchKind kind, int offset)
		{
			Kind = kind;
			Offset = offset;
		}
	}
	public static class FuzzyMatcher
	{
		private const int MinSubsequenceQuery = 2;

		private static readonly char[] WordSeparators = new char[6] { '_', '-', '.', ' ', '/', ':' };

		public static MatchResult Match(string candidate, string query)
		{
			if (string.IsNullOrEmpty(candidate))
			{
				return MatchResult.NoMatch;
			}
			if (string.IsNullOrEmpty(query))
			{
				return new MatchResult(MatchKind.Prefix, 0);
			}
			if (candidate.Equals(query, StringComparison.OrdinalIgnoreCase))
			{
				return new MatchResult(MatchKind.Exact, 0);
			}
			if (candidate.StartsWith(query, StringComparison.OrdinalIgnoreCase))
			{
				return new MatchResult(MatchKind.Prefix, 0);
			}
			int num = candidate.IndexOf(query, StringComparison.OrdinalIgnoreCase);
			if (num > 0)
			{
				return new MatchResult(IsWordStart(candidate, num) ? MatchKind.WordStart : MatchKind.Substring, num);
			}
			if (query.Length >= 2 && TryMatchSubsequence(candidate, query, out var firstIndex))
			{
				return new MatchResult(MatchKind.Subsequence, firstIndex);
			}
			return MatchResult.NoMatch;
		}

		public static bool IsMatch(string candidate, string query)
		{
			return Match(candidate, query).IsMatch;
		}

		private static bool IsWordStart(string candidate, int index)
		{
			if (index <= 0)
			{
				return true;
			}
			char c = candidate[index - 1];
			for (int i = 0; i < WordSeparators.Length; i++)
			{
				if (c == WordSeparators[i])
				{
					return true;
				}
			}
			if (char.IsLower(c))
			{
				return char.IsUpper(candidate[index]);
			}
			return false;
		}

		private static bool TryMatchSubsequence(string candidate, string query, out int firstIndex)
		{
			firstIndex = -1;
			int num = 0;
			for (int i = 0; i < candidate.Length; i++)
			{
				if (num >= query.Length)
				{
					break;
				}
				if (char.ToLowerInvariant(candidate[i]) == char.ToLowerInvariant(query[num]))
				{
					if (num == 0)
					{
						firstIndex = i;
					}
					num++;
				}
			}
			if (num < query.Length)
			{
				firstIndex = -1;
				return false;
			}
			return true;
		}
	}
	public static class HelpTopics
	{
		public const string Other = "other";

		public const string Terminal = "terminal";

		public static readonly (string Topic, string[] Words)[] Groups = new(string, string[])[13]
		{
			("items", new string[7] { "give", "setdiscovered", "setquality", "setquantity", "packageproduct", "clearinventory", "growplants" }),
			("money", new string[3] { "changecash", "changebalance", "addxp" }),
			("world", new string[8] { "settime", "setdayduration", "settimescale", "setweather", "triggerlightning", "triggerdistantthunder", "cleartrash", "forcesleep" }),
			("places", new string[4] { "teleport", "setowned", "setregionunlocked", "spawnvehicle" }),
			("people", new string[7] { "setunlocked", "setrelationship", "addemployee", "setemotion", "destroynpcs", "disablenpcs", "disablenpcasset" }),
			("police", new string[5] { "raisewanted", "lowerwanted", "clearwanted", "setlawintensity", "setpoliceignoreplayers" }),
			("player", new string[6] { "sethealth", "setstaminareserve", "setmovespeed", "setjumpforce", "setgravitymultiplier", "freecam" }),
			("quests", new string[5] { "setqueststate", "setquestentrystate", "setvar", "endtutorial", "playcutscene" }),
			("keys", new string[3] { "bind", "unbind", "clearbinds" }),
			("display", new string[5] { "hideui", "showfps", "hidefps", "enable", "disable" }),
			("graphics", new string[9] { "enableinstancing", "disableinstancing", "enableocclusionculling", "disableocclusionculling", "enablephysics", "disablephysics", "enableterrain", "disableterrain", "disablemeshes" }),
			("game", new string[2] { "save", "quit" }),
			("dev", new string[2] { "npcworkbench", "presentationworkbench" })
		};

		public static readonly string[] Common = new string[6] { "give", "teleport", "settime", "changecash", "spawnvehicle", "setweather" };

		private static readonly Dictionary<string, string> ByWord = Index();

		public static bool IsTopic(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			(string, string[])[] groups = Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				if (string.Equals(groups[i].Item1, name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return string.Equals(name, "terminal", StringComparison.OrdinalIgnoreCase);
		}

		public static string TopicOf(string word)
		{
			if (word == null || !ByWord.TryGetValue(word, out var value))
			{
				return "other";
			}
			return value;
		}

		private static Dictionary<string, string> Index()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			(string, string[])[] groups = Groups;
			for (int i = 0; i < groups.Length; i++)
			{
				(string, string[]) tuple = groups[i];
				string item = tuple.Item1;
				string[] item2 = tuple.Item2;
				foreach (string key in item2)
				{
					dictionary[key] = item;
				}
			}
			return dictionary;
		}
	}
	public sealed class History
	{
		private const string FileName = "history.txt";

		public const int Capacity = 500;

		private readonly List<string> _lines = new List<string>();

		private bool _dirty;

		public IReadOnlyList<string> Lines => _lines;

		public int Count => _lines.Count;

		public void Add(string line)
		{
			string one = line?.Trim();
			if (!string.IsNullOrEmpty(one))
			{
				_lines.RemoveAll((string existing) => string.Equals(existing, one, StringComparison.Ordinal));
				_lines.Insert(0, one);
				if (_lines.Count > 500)
				{
					_lines.RemoveRange(500, _lines.Count - 500);
				}
				_dirty = true;
			}
		}

		public List<string> Search(string query)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(query))
			{
				list.AddRange(_lines);
				return list;
			}
			foreach (string line in _lines)
			{
				if (line.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					list.Add(line);
				}
			}
			return list;
		}

		public string ReverseSearch(string query, int skip)
		{
			if (skip < 0)
			{
				return null;
			}
			List<string> list = Search(query);
			if (skip >= list.Count)
			{
				return null;
			}
			return list[skip];
		}

		public void Clear()
		{
			if (_lines.Count != 0)
			{
				_lines.Clear();
				_dirty = true;
			}
		}

		public void Load(IStore store)
		{
			_lines.Clear();
			_dirty = false;
			string text = store?.Read(StoreScope.Global, "history.txt");
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim('\r');
				if (text2.Length != 0)
				{
					_lines.Add(text2);
					if (_lines.Count >= 500)
					{
						break;
					}
				}
			}
		}

		public void Save(IStore store)
		{
			if (!_dirty || store == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string line in _lines)
			{
				stringBuilder.Append(line).Append('\n');
			}
			store.Write(StoreScope.Global, "history.txt", stringBuilder.ToString());
			_dirty = false;
		}
	}
	public readonly struct Expansion
	{
		public string Line { get; }

		public string Error { get; }

		public bool Failed => Error != null;

		internal Expansion(string line, string error)
		{
			Line = line ?? "";
			Error = error;
		}
	}
	public sealed class MarkExpansion
	{
		private readonly Marks _marks;

		private readonly ICommandCatalogue _catalogue;

		public MarkExpansion(Marks marks, ICommandCatalogue catalogue)
		{
			_marks = marks;
			_catalogue = catalogue;
		}

		public Expansion Apply(string statement)
		{
			if (!Interesting(statement))
			{
				return new Expansion(statement, null);
			}
			List<string> list = CommandLine.Tokenise(statement);
			if (list.Count == 0)
			{
				return new Expansion(statement, null);
			}
			string text = list[0];
			int num = CommandLine.StoredCommandAt(statement);
			for (int i = 1; i < list.Count; i++)
			{
				string word = list[i];
				if ((num < 0 || i < num) && Marks.IsWord(word))
				{
					Mark mark = _marks.Resolve(word);
					int argIndex = i - 1;
					if (!mark.Exists)
					{
						return new Expansion(null, Unmarked(word));
					}
					MarkKind wanted = _catalogue.KindOf(text, argIndex);
					if (!Fits(mark.Kind, wanted))
					{
						return new Expansion(null, Mismatch(text, word, mark, wanted));
					}
					list[i] = mark.Id;
				}
			}
			string text2 = Reckon(list, text, num);
			if (text2 != null)
			{
				return new Expansion(null, text2);
			}
			StringBuilder stringBuilder = new StringBuilder(text);
			for (int j = 1; j < list.Count; j++)
			{
				stringBuilder.Append(' ').Append(Quote(list[j]));
			}
			return new Expansion(stringBuilder.ToString(), null);
		}

		private static bool Interesting(string statement)
		{
			if (string.IsNullOrEmpty(statement))
			{
				return false;
			}
			for (int i = 0; i < statement.Length; i++)
			{
				switch (statement[i])
				{
				case '#':
				case '@':
					return true;
				case '(':
				case '*':
				case '+':
				case '-':
				case '/':
					return true;
				}
			}
			return false;
		}

		private string Reckon(List<string> tokens, string command, int stored)
		{
			double? stack = null;
			for (int i = 1; i < tokens.Count; i++)
			{
				string text = tokens[i];
				if ((stored >= 0 && i >= stored) || !Arithmetic.Looks(text))
				{
					continue;
				}
				MarkKind markKind = _catalogue.KindOf(command, i - 1);
				if (markKind != MarkKind.None)
				{
					return $"{text}: that is a sum\n{command} needs {AnA(Mark.Word(markKind))} there";
				}
				if (Arithmetic.NeedsStack(text) && !stack.HasValue)
				{
					stack = Count(tokens, command, out var error);
					if (!stack.HasValue)
					{
						return error;
					}
				}
				Sum sum = Arithmetic.Evaluate(text, stack);
				if (sum.Failed)
				{
					return sum.Error;
				}
				tokens[i] = sum.Text;
			}
			return null;
		}

		private double? Count(List<string> tokens, string command, out string error)
		{
			string text = null;
			for (int i = 1; i < tokens.Count; i++)
			{
				if (_catalogue.KindOf(command, i - 1) == MarkKind.Item)
				{
					text = tokens[i];
					break;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				text = _marks.Resolve("#hand").Id;
			}
			if (string.IsNullOrEmpty(text))
			{
				error = "@: nothing in your hand to count\nname an item, or hold one";
				return null;
			}
			int num = _marks.Stack(text);
			if (num < 0)
			{
				error = "@: you are not carrying any " + text;
				return null;
			}
			error = null;
			return num;
		}

		private static bool Fits(MarkKind have, MarkKind wanted)
		{
			if (wanted == MarkKind.None)
			{
				return false;
			}
			if (have == MarkKind.Any || wanted == MarkKind.Any)
			{
				return true;
			}
			return have == wanted;
		}

		private static string Unmarked(string word)
		{
			if (!(word == "#"))
			{
				return word + ": nothing there right now";
			}
			return "#: nothing marked - look at something before opening the terminal";
		}

		private static string Mismatch(string command, string word, Mark mark, MarkKind wanted)
		{
			string text = $"{word}: marked {Mark.Word(mark.Kind)} '{mark.Id}'";
			if (wanted != MarkKind.None)
			{
				return text + $"\n{command} needs {AnA(Mark.Word(wanted))} there";
			}
			return text + "\n" + command + " does not take one there";
		}

		private static string AnA(string word)
		{
			if (word.Length == 0)
			{
				return word;
			}
			if (string.Equals(word, "npc", StringComparison.OrdinalIgnoreCase))
			{
				return "an " + word;
			}
			if ("aeiou".IndexOf(char.ToLowerInvariant(word[0])) < 0)
			{
				return "a " + word;
			}
			return "an " + word;
		}

		private static string Quote(string value)
		{
			if (value.IndexOf(' ') < 0)
			{
				return value;
			}
			return "\"" + value + "\"";
		}
	}
	public enum MarkKind
	{
		None,
		Npc,
		Vehicle,
		Property,
		Item,
		Any
	}
	public readonly struct Mark
	{
		public static readonly Mark None = new Mark(MarkKind.None, "", "");

		public MarkKind Kind { get; }

		public string Id { get; }

		public string Label { get; }

		public bool Exists
		{
			get
			{
				if (Kind != MarkKind.None)
				{
					return Id.Length > 0;
				}
				return false;
			}
		}

		public Mark(MarkKind kind, string id, string label)
		{
			Kind = kind;
			Id = id ?? "";
			Label = label ?? "";
		}

		public override string ToString()
		{
			if (!Exists)
			{
				return "nothing";
			}
			return Id + " (" + Word(Kind) + ")";
		}

		internal static string Word(MarkKind kind)
		{
			return kind switch
			{
				MarkKind.Npc => "npc", 
				MarkKind.Vehicle => "vehicle", 
				MarkKind.Property => "property", 
				MarkKind.Item => "item", 
				MarkKind.Any => "id", 
				_ => "nothing", 
			};
		}
	}
	public interface IMarks
	{
		Mark Looked { get; }

		Mark Hand { get; }

		Mark Here { get; }

		Mark Car { get; }

		Mark Home { get; }

		Mark Near { get; }

		int Stack(string itemId);
	}
	public sealed class Marks
	{
		public const char Sigil = '#';

		private readonly IMarks _world;

		private string _lastArgument = "";

		private string _lastPrinted = "";

		public static readonly string[] Words = new string[8] { "#", "#hand", "#here", "#last", "#it", "#car", "#home", "#near" };

		public Marks(IMarks world)
		{
			_world = world;
		}

		public Mark Resolve(string word)
		{
			if (string.IsNullOrEmpty(word) || word[0] != '#')
			{
				return Mark.None;
			}
			return word.ToLowerInvariant() switch
			{
				"#" => _world?.Looked ?? Mark.None, 
				"#hand" => _world?.Hand ?? Mark.None, 
				"#here" => _world?.Here ?? Mark.None, 
				"#car" => _world?.Car ?? Mark.None, 
				"#home" => _world?.Home ?? Mark.None, 
				"#near" => _world?.Near ?? Mark.None, 
				"#last" => Free(_lastArgument), 
				"#it" => Free(_lastPrinted), 
				_ => Mark.None, 
			};
		}

		public int Stack(string itemId)
		{
			return _world?.Stack(itemId) ?? (-1);
		}

		public static bool IsWord(string word)
		{
			if (string.IsNullOrEmpty(word) || word[0] != '#')
			{
				return false;
			}
			string[] words = Words;
			for (int i = 0; i < words.Length; i++)
			{
				if (string.Equals(words[i], word, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public void Ran(string line)
		{
			List<string> list = CommandLine.Tokenise(line ?? "");
			_lastArgument = ((list.Count > 1) ? list[list.Count - 1] : "");
		}

		public void Printed(string text)
		{
			string text2 = (text ?? "").Trim();
			if (text2.Length != 0 && text2.IndexOf(' ') < 0)
			{
				_lastPrinted = text2;
			}
		}

		private static Mark Free(string id)
		{
			if (!string.IsNullOrEmpty(id))
			{
				return new Mark(MarkKind.Any, id, id);
			}
			return Mark.None;
		}
	}
	public static class Markup
	{
		private const int ValueColumn = 24;

		private const int SourceColumn = 30;

		private const int LineWidth = 88;

		public static string Suggestions(SuggestionSet set, int selected, int window, bool expanded)
		{
			if (set == null)
			{
				return "";
			}
			if (set.Command == null && !set.Any)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (set.Command != null)
			{
				Signature(stringBuilder, set.Command.Signature, set.ArgIndex);
				string value = ((set.Command.Description.Length > 0) ? set.Command.Description : "No description.");
				Line(stringBuilder, "desc", Clip(value, 88 - set.Command.Source.Length - 2) + "  " + set.Command.Source);
			}
			if (!expanded)
			{
				if (set.Any)
				{
					Line(stringBuilder, "ghost", Pad("tab  " + set.Rows[Math.Min(Math.Max(selected, 0), set.Rows.Count - 1)].Value, 32) + ((set.Rows.Count > 1) ? $"up/down  browse {set.Rows.Count}" : ""));
				}
				return stringBuilder.ToString();
			}
			if (!set.Any)
			{
				return stringBuilder.ToString();
			}
			int count = set.Rows.Count;
			int num = Math.Min(8, count);
			int num2 = Math.Max(0, Math.Min(window, count - num));
			int num3 = num2 + num;
			Line(stringBuilder, "rule", Rule(num2, num3, count));
			for (int i = num2; i < num3; i++)
			{
				Suggestion suggestion = set.Rows[i];
				bool flag = i == selected;
				StringBuilder stringBuilder2 = new StringBuilder();
				stringBuilder2.Append(flag ? "> " : "  ");
				stringBuilder2.Append(Pad(Clip(suggestion.Value, 24), 28));
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append("<br>");
				}
				Span(stringBuilder, flag ? "pick" : null, stringBuilder2.ToString());
				Span(stringBuilder, (suggestion.Kind == SuggestionKind.History) ? "src-history" : (suggestion.IsVanilla ? "src-game" : "src-mod"), suggestion.Source);
			}
			return stringBuilder.ToString();
		}

		private static void Signature(StringBuilder sb, string signature, int argIndex)
		{
			if (string.IsNullOrEmpty(signature))
			{
				return;
			}
			List<string> list = new List<string>(signature.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
			int num = argIndex + 1;
			if (num <= 0 || num >= list.Count)
			{
				Span(sb, "sig", signature);
				return;
			}
			Span(sb, "sig", string.Join(" ", list.GetRange(0, num)) + " ");
			Span(sb, "cur", list[num]);
			if (num + 1 < list.Count)
			{
				Span(sb, "sig", " " + string.Join(" ", list.GetRange(num + 1, list.Count - num - 1)));
			}
		}

		private static string Rule(int first, int last, int count)
		{
			if (count <= 8)
			{
				return new string('-', 88);
			}
			string text = $" {first + 1}-{last} of {count} ";
			int count2 = Math.Max(4, 88 - text.Length);
			return new string('-', count2) + text;
		}

		public static string Transcript(IReadOnlyList<OutputLine> lines)
		{
			if (lines == null || lines.Count == 0)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (OutputLine line in lines)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append("<br>");
				}
				Span(stringBuilder, Class(line.Kind), line.Text);
			}
			return stringBuilder.ToString();
		}

		private static string Class(LineKind kind)
		{
			return kind switch
			{
				LineKind.Echo => "echo", 
				LineKind.Warn => "warn", 
				LineKind.Error => "err", 
				LineKind.Dim => "dim", 
				_ => null, 
			};
		}

		private static void Line(StringBuilder sb, string cls, string text)
		{
			if (sb.Length > 0)
			{
				sb.Append("<br>");
			}
			Span(sb, cls, text);
		}

		private static void Span(StringBuilder sb, string cls, string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				if (cls == null)
				{
					sb.Append(Escape(text));
				}
				else
				{
					sb.Append("<span class=\"").Append(cls).Append("\">")
						.Append(Escape(text))
						.Append("</span>");
				}
			}
		}

		public static string Escape(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			if (text.IndexOf('&') < 0 && text.IndexOf('<') < 0 && text.IndexOf('>') < 0)
			{
				return text;
			}
			return text.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;");
		}

		internal static string Pad(string value, int width)
		{
			if (value == null)
			{
				value = "";
			}
			if (value.Length < width)
			{
				return value.PadRight(width);
			}
			return value + " ";
		}

		internal static string Clip(string value, int width)
		{
			if (value == null)
			{
				return "";
			}
			if (width < 4 || value.Length <= width)
			{
				return value;
			}
			return value.Substring(0, width - 2) + "..";
		}
	}
	public sealed class CommandInfo
	{
		public string Word { get; }

		public string Description { get; }

		public string Usage { get; }

		public string Signature { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public CommandInfo(string word, string description, string usage, string signature, string source, bool isVanilla)
		{
			Word = word ?? "";
			Description = description ?? "";
			Usage = usage ?? "";
			Signature = signature ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
		}
	}
	public readonly struct ArgValue
	{
		public string Value { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public ArgValue(string value, string source, bool isVanilla)
		{
			Value = value ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
		}
	}
	public enum LineKind
	{
		Echo,
		Out,
		Warn,
		Error,
		Dim
	}
	public readonly struct OutputLine
	{
		public LineKind Kind { get; }

		public string Text { get; }

		public OutputLine(LineKind kind, string text)
		{
			Kind = kind;
			Text = text ?? "";
		}

		public static OutputLine Echo(string text)
		{
			return new OutputLine(LineKind.Echo, text);
		}

		public static OutputLine Out(string text)
		{
			return new OutputLine(LineKind.Out, text);
		}

		public static OutputLine Warn(string text)
		{
			return new OutputLine(LineKind.Warn, text);
		}

		public static OutputLine Error(string text)
		{
			return new OutputLine(LineKind.Error, text);
		}

		public static OutputLine Dim(string text)
		{
			return new OutputLine(LineKind.Dim, text);
		}
	}
	public sealed class NaturalCommandTranslation
	{
		public IReadOnlyList<string> Commands { get; }

		public double? Confidence { get; }

		public string Error { get; }

		public bool Proven { get; }

		public string Reasoning { get; }

		public double? PrefillTps { get; }

		public double? DecodeTps { get; }

		public double? PeakRamMb { get; }

		public bool Constrained { get; }

		public NaturalCommandTranslation(IReadOnlyList<string> commands, double? confidence, string error = null, bool proven = false, string reasoning = null, double? prefillTps = null, double? decodeTps = null, double? peakRamMb = null, bool constrained = false)
		{
			Commands = commands ?? Array.Empty<string>();
			Confidence = confidence;
			Error = error ?? "";
			Proven = proven;
			Reasoning = reasoning ?? "";
			PrefillTps = prefillTps;
			DecodeTps = decodeTps;
			PeakRamMb = peakRamMb;
			Constrained = constrained;
		}

		public NaturalCommandTranslation AsConstrained()
		{
			if (!Constrained)
			{
				return new NaturalCommandTranslation(Commands, Confidence, Error, Proven, Reasoning, PrefillTps, DecodeTps, PeakRamMb, constrained: true);
			}
			return this;
		}
	}
	public readonly struct NaturalCommandExecution
	{
		public string Command { get; }

		public bool Success { get; }

		public string Output { get; }

		public NaturalCommandExecution(string command, bool success, string output)
		{
			Command = command ?? "";
			Success = success;
			Output = output ?? "";
		}
	}
	public enum SuggestionKind
	{
		Command,
		Argument,
		History
	}
	public readonly struct Suggestion
	{
		public SuggestionKind Kind { get; }

		public string Value { get; }

		public string Source { get; }

		public bool IsVanilla { get; }

		public int Score { get; }

		public Suggestion(SuggestionKind kind, string value, string source, bool isVanilla, int score)
		{
			Kind = kind;
			Value = value ?? "";
			Source = source ?? "";
			IsVanilla = isVanilla;
			Score = score;
		}
	}
	internal sealed class NeedleToolset
	{
		internal const int MaxCalls = 8;

		internal const int MaxEnumValues = 32;

		internal const int MaxEnumCharacters = 80;

		internal const int MaxRouteDescriptionCharacters = 48;

		internal const int MaxFullDescriptionCharacters = 96;

		private readonly Dictionary<string, NeedleTool> _byName;

		private readonly Dictionary<string, NeedleTool> _byWord;

		private readonly bool _routeOnly;

		private readonly string _query;

		internal string Json { get; }

		internal string Fingerprint { get; }

		internal int Count => _byName.Count;

		internal bool RouteOnly => _routeOnly;

		private NeedleToolset(string json, string fingerprint, Dictionary<string, NeedleTool> byName, Dictionary<string, NeedleTool> byWord, bool routeOnly = false, string query = null)
		{
			Json = json;
			Fingerprint = fingerprint;
			_byName = byName;
			_byWord = byWord;
			_routeOnly = routeOnly;
			_query = query;
		}

		internal static NeedleToolset Build(ICommandCatalogue catalogue, Marks marks = null)
		{
			List<CommandInfo> list = catalogue.Commands.Where((CommandInfo command) => command != null && command.Word.Length > 0 && command.Word != "#").OrderBy<CommandInfo, string>((CommandInfo command) => command.Word, StringComparer.OrdinalIgnoreCase).ToList();
			List<NeedleTool> list2 = new List<NeedleTool>();
			HashSet<string> used = new HashSet<string>(StringComparer.Ordinal);
			foreach (CommandInfo item in list)
			{
				string name = UniqueToolName(item.Word, used);
				list2.Add(NeedleTool.Build(name, item, catalogue, marks));
			}
			string text = WriteTools(list2, includeLiveValues: false, requireAll: false, null);
			string fingerprint = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLowerInvariant();
			return new NeedleToolset(text, fingerprint, list2.ToDictionary<NeedleTool, string>((NeedleTool tool) => tool.Name, StringComparer.Ordinal), list2.ToDictionary<NeedleTool, string>((NeedleTool tool) => tool.CommandWord, StringComparer.OrdinalIgnoreCase));
		}

		internal NeedleToolset ForRouting()
		{
			string text = WriteRoutingTools(_byName.Values.OrderBy<NeedleTool, string>((NeedleTool tool) => tool.Name, StringComparer.Ordinal).ToList());
			string fingerprint = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLowerInvariant();
			return new NeedleToolset(text, fingerprint, _byName, _byWord, routeOnly: true);
		}

		internal NeedleRequest Prepare(string query)
		{
			string text = query ?? "";
			string text2 = LeadingToken(text);
			if (text2.Length == 0 || !_byWord.TryGetValue(text2, out var value))
			{
				return new NeedleRequest(text, null, null, null);
			}
			string text3 = "Console command: " + value.CommandWord + ". User request: " + text;
			string line;
			NaturalCommandTranslation direct = (value.TryBuildDirect(text, out line) ? new NaturalCommandTranslation(new string[1] { line }, 1.0, null, proven: true) : null);
			return new NeedleRequest(text3, value.Name, From(value, text), direct);
		}

		internal NaturalCommandTranslation ReadResponse(string json, string expectedToolName = null)
		{
			if (string.IsNullOrWhiteSpace(json))
			{
				return Failed("the engine returned an empty response");
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(json);
				JsonElement rootElement = jsonDocument.RootElement;
				double? confidence = Number(rootElement, "confidence");
				JsonElement value;
				string reasoning = ((rootElement.TryGetProperty("reasoning", out value) && value.ValueKind == JsonValueKind.String) ? (value.GetString() ?? "") : "");
				double? prefillTps = Number(rootElement, "prefill_tps");
				double? decodeTps = Number(rootElement, "decode_tps");
				double? peakRamMb = Number(rootElement, "peak_ram_mb");
				if (rootElement.TryGetProperty("success", out var value2) && value2.ValueKind != JsonValueKind.True)
				{
					return Reject(ReadError(rootElement, "the engine rejected the request"));
				}
				if (confidence.HasValue)
				{
					double valueOrDefault = confidence.GetValueOrDefault();
					if (valueOrDefault < 0.0 || valueOrDefault > 1.0)
					{
						return Reject("the engine returned confidence outside the 0 to 1 range");
					}
				}
				if (!rootElement.TryGetProperty("function_calls", out var value3) || value3.ValueKind != JsonValueKind.Array)
				{
					return Reject("the engine returned an invalid function_calls value");
				}
				if (value3.GetArrayLength() == 0)
				{
					return new NaturalCommandTranslation(Array.Empty<string>(), confidence, null, proven: false, reasoning, prefillTps, decodeTps, peakRamMb);
				}
				if (value3.GetArrayLength() > 8)
				{
					return Reject("the engine returned more than eight commands");
				}
				List<string> list = new List<string>();
				NeedleTool needleTool = null;
				foreach (JsonElement item in value3.EnumerateArray())
				{
					JsonElement value4;
					string text = (item.TryGetProperty("name", out value4) ? (value4.GetString() ?? "") : "");
					if (expectedToolName != null && !string.Equals(text, expectedToolName, StringComparison.Ordinal))
					{
						NeedleTool value5;
						string text2 = (_byName.TryGetValue(expectedToolName, out value5) ? value5.CommandWord : expectedToolName);
						return Reject("Hash ignored the explicit command " + text2);
					}
					if (!_byName.TryGetValue(text, out var value6))
					{
						return Reject("the engine returned an unknown tool: " + ((text.Length > 0) ? text : "(empty)"));
					}
					if (_routeOnly)
					{
						list.Add(value6.CommandWord);
						continue;
					}
					JsonElement value7;
					JsonElement values = (item.TryGetProperty("arguments", out value7) ? value7 : default(JsonElement));
					if (!value6.TryBuild(values, out var line, out var error))
					{
						return Reject(error);
					}
					list.Add(line);
					needleTool = value6;
				}
				if (list.Count == 1 && needleTool != null && !_routeOnly && _query != null)
				{
					list[0] = needleTool.RepairQuantity(list[0], _query);
				}
				return new NaturalCommandTranslation(list, confidence, null, proven: false, reasoning, prefillTps, decodeTps, peakRamMb);
				NaturalCommandTranslation Reject(string error2)
				{
					return new NaturalCommandTranslation(Array.Empty<string>(), confidence, error2, proven: false, reasoning, prefillTps, decodeTps, peakRamMb);
				}
			}
			catch (JsonException ex)
			{
				return Failed("the engine returned invalid JSON: " + ex.Message);
			}
		}

		private static NaturalCommandTranslation Failed(string error)
		{
			return new NaturalCommandTranslation(Array.Empty<string>(), null, error);
		}

		private static double? Number(JsonElement root, string name)
		{
			if (!root.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out var value2))
			{
				return null;
			}
			return value2;
		}

		private static string ReadError(JsonElement root, string fallback)
		{
			if (!root.TryGetProperty("error", out var value) || value.ValueKind != JsonValueKind.String)
			{
				return fallback;
			}
			return value.GetString() ?? fallback;
		}

		private static string UniqueToolName(string word, HashSet<string> used)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = word.ToLowerInvariant();
			foreach (char c in text)
			{
				stringBuilder.Append((char.IsLetterOrDigit(c) || c == '_') ? c : '_');
			}
			string text2 = stringBuilder.ToString().Trim('_');
			if (text2.Length == 0 || char.IsDigit(text2[0]))
			{
				text2 = "command_" + text2;
			}
			if (text2.Length == 0)
			{
				text2 = "command";
			}
			string text3 = text2;
			int num = 2;
			while (!used.Add(text3))
			{
				text3 = text2 + "_" + num++;
			}
			return text3;
		}

		private static NeedleToolset From(NeedleTool tool, string query)
		{
			string text = WriteTools(new NeedleTool[1] { tool }, includeLiveValues: true, requireAll: false, query);
			string fingerprint = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLowerInvariant();
			return new NeedleToolset(text, fingerprint, new Dictionary<string, NeedleTool>(StringComparer.Ordinal) { [tool.Name] = tool }, new Dictionary<string, NeedleTool>(StringComparer.OrdinalIgnoreCase) { [tool.CommandWord] = tool }, routeOnly: false, query);
		}

		internal bool TryConstrain(string commandWord, string query, bool requireAll, out NeedleToolset constrained, out string toolName)
		{
			constrained = null;
			toolName = null;
			if (string.IsNullOrWhiteSpace(commandWord) || !_byWord.TryGetValue(commandWord.Trim(), out var value))
			{
				return false;
			}
			string text = WriteTools(new NeedleTool[1] { value }, includeLiveValues: true, requireAll, query);
			string fingerprint = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLowerInvariant();
			constrained = new NeedleToolset(text, fingerprint, new Dictionary<string, NeedleTool>(StringComparer.Ordinal) { [value.Name] = value }, new Dictionary<string, NeedleTool>(StringComparer.OrdinalIgnoreCase) { [value.CommandWord] = value }, routeOnly: false, query);
			toolName = value.Name;
			return true;
		}

		private static string LeadingToken(string query)
		{
			if (string.IsNullOrWhiteSpace(query))
			{
				return "";
			}
			int i;
			for (i = 0; i < query.Length && char.IsWhiteSpace(query[i]); i++)
			{
			}
			int j;
			for (j = i; j < query.Length && !cha