using Lua;
// ReSharper disable MemberCanBePrivate.Global
namespace AIStudio.Tools.PluginSystem;
///
/// Represents the base of any AI Studio plugin.
///
public abstract class PluginBase
{
private readonly string parseError;
protected readonly LuaState state;
protected readonly Guid pluginId;
protected readonly string pluginName;
protected readonly PluginType pluginType;
protected readonly string pluginDescription;
protected readonly PluginVersion pluginVersion;
protected readonly string[] pluginAuthors;
protected readonly string supportContact;
protected readonly string sourceURL;
protected readonly PluginCategory[] pluginCategories;
protected readonly PluginTargetGroup[] pluginTargetGroups;
private readonly bool isInitialized;
private bool isValid;
protected PluginBase(LuaState state, PluginType type, string parseError = "")
{
this.state = state;
this.pluginType = type;
this.pluginId = this.Id();
this.pluginName = this.Name();
this.pluginDescription = this.Description();
this.pluginVersion = this.Version();
this.pluginAuthors = this.Authors();
this.supportContact = this.SupportContact();
this.sourceURL = this.SourceURL();
this.pluginCategories = this.Categories();
this.pluginTargetGroups = this.TargetGroups();
this.parseError = parseError;
// For security reasons, we don't want to allow the plugin to load modules:
this.state.ModuleLoader = new NoModuleLoader();
//
// Check if the plugin is valid:
//
if(!string.IsNullOrWhiteSpace(this.parseError))
this.isValid = false;
if(this is NoPlugin)
this.isValid = false;
this.isInitialized = true;
}
///
/// Checks if the plugin is valid.
///
/// The state of the plugin, which may contain an error message.
public PluginState IsValid()
{
if(!string.IsNullOrWhiteSpace(this.parseError))
{
this.isValid = false;
return new(false, this.parseError);
}
if(this is NoPlugin)
{
this.isValid = false;
return new(false, "Plugin is not valid.");
}
if(this.Id() == Guid.Empty)
{
this.isValid = false;
return new(false, "The field ID does not exist, is empty, or is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).");
}
if(string.IsNullOrWhiteSpace(this.Name()))
{
this.isValid = false;
return new(false, "The field NAME does not exist, is empty, or is not a valid string.");
}
if(string.IsNullOrWhiteSpace(this.Description()))
{
this.isValid = false;
return new(false, "The field DESCRIPTION does not exist, is empty, or is not a valid string.");
}
if(this.Version() == PluginVersion.NONE)
{
this.isValid = false;
return new(false, "The field VERSION does not exist, is empty, or is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).");
}
if(this.pluginType == PluginType.NONE)
{
this.isValid = false;
return new(false, $"The field TYPE does not exist, is empty, or is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues(PluginType.NONE)}.");
}
if(this.Authors().Length == 0)
{
this.isValid = false;
return new(false, "The table AUTHORS does not exist, is empty, or is not a valid table of strings.");
}
if(string.IsNullOrWhiteSpace(this.SupportContact()))
{
this.isValid = false;
return new(false, "The field SUPPORT_CONTACT does not exist, is empty, or is not a valid string.");
}
if(string.IsNullOrWhiteSpace(this.SourceURL()))
{
this.isValid = false;
return new(false, "The field SOURCE_URL does not exist, is empty, or is not a valid string. Additional, it must start with 'http://' or 'https://'.");
}
if(this.Categories().Length == 0)
{
this.isValid = false;
return new(false, $"The table CATEGORIES does not exist, is empty, or is not a valid table of strings. Valid categories are: {CommonTools.GetAllEnumValues(PluginCategory.NONE)}.");
}
if(this.TargetGroups().Length == 0)
{
this.isValid = false;
return new(false, $"The table TARGET_GROUPS does not exist, is empty, or is not a valid table of strings. Valid target groups are: {CommonTools.GetAllEnumValues(PluginTargetGroup.NONE)}.");
}
this.isValid = true;
return new(true, string.Empty);
}
///
/// Returns the intended target groups for the plugin.
///
/// The target groups.
public PluginTargetGroup[] TargetGroups()
{
if(this.isInitialized)
return this.pluginTargetGroups;
if(!this.isValid)
return [];
if (!this.state.Environment["TARGET_GROUPS"].TryRead(out var targetGroups))
return [];
var targetGroupList = new List();
foreach(var luaTargetGroup in targetGroups.GetArraySpan())
if(luaTargetGroup.TryRead(out var targetGroupName))
if(Enum.TryParse(targetGroupName, out var targetGroup) && targetGroup != PluginTargetGroup.NONE)
targetGroupList.Add(targetGroup);
return targetGroupList.ToArray();
}
///
/// Returns the plugin categories.
///
/// The plugin categories.
public PluginCategory[] Categories()
{
if(this.isInitialized)
return this.pluginCategories;
if(!this.isValid)
return [];
if (!this.state.Environment["CATEGORIES"].TryRead(out var categories))
return [];
var categoryList = new List();
foreach(var luaCategory in categories.GetArraySpan())
if(luaCategory.TryRead(out var categoryName))
if(Enum.TryParse(categoryName, out var category) && category != PluginCategory.NONE)
categoryList.Add(category);
return categoryList.ToArray();
}
///
/// Returns the source URL of the plugin.
///
/// The source URL.
public string SourceURL()
{
if(this.isInitialized)
return this.sourceURL;
if(!this.isValid)
return string.Empty;
if (!this.state.Environment["SOURCE_URL"].TryRead(out var url))
return string.Empty;
if(!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
return string.Empty;
return url;
}
///
/// Returns the support contact of the plugin.
///
/// The support contact.
public string SupportContact()
{
if(this.isInitialized)
return this.supportContact;
if(!this.isValid)
return string.Empty;
if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out var contact))
return string.Empty;
return contact;
}
///
/// Returns the ID of the plugin.
///
/// The plugin ID.
public Guid Id()
{
if(this.isInitialized)
return this.pluginId;
if(!this.isValid)
return Guid.Empty;
if (!this.state.Environment["ID"].TryRead(out var idText))
return Guid.Empty;
if (!Guid.TryParse(idText, out var id))
return Guid.Empty;
return id;
}
///
/// Returns the name of the plugin.
///
/// The plugin name.
public string Name()
{
if(this.isInitialized)
return this.pluginName;
if(!this.isValid)
return string.Empty;
if (!this.state.Environment["NAME"].TryRead(out var name))
return string.Empty;
return name;
}
///
/// Returns the description of the plugin.
///
/// The plugin description.
public string Description()
{
if(this.isInitialized)
return this.pluginDescription;
if(!this.isValid)
return string.Empty;
if (!this.state.Environment["DESCRIPTION"].TryRead(out var description))
return string.Empty;
return description;
}
///
/// Returns the version of the plugin.
///
/// The plugin version.
public PluginVersion Version()
{
if(this.isInitialized)
return this.pluginVersion;
if(!this.isValid)
return PluginVersion.NONE;
if (!this.state.Environment["VERSION"].TryRead(out var versionText))
return PluginVersion.NONE;
if (!PluginVersion.TryParse(versionText, out var version))
return PluginVersion.NONE;
return version;
}
///
/// Returns the authors of the plugin.
///
/// The plugin authors.
public string[] Authors()
{
if(this.isInitialized)
return this.pluginAuthors;
if (!this.isValid)
return [];
if (!this.state.Environment["AUTHORS"].TryRead(out var authors))
return [];
var authorList = new List();
foreach(var author in authors.GetArraySpan())
if(author.TryRead(out var authorName))
authorList.Add(authorName);
return authorList.ToArray();
}
}