SboxSuperuser/Core/Prefixes/PrefixDefinition.cs

One prefix definition (tag text, tag and name styling, name line template, color palette) used by the Superuser package. Validates and normalizes input, splits the template into pieces and caches a script-facing palette value.

using SboxSuperuser.Core.Contracts;
using SboxSuperuser.Core.Effects;

namespace SboxSuperuser.Core.Prefixes;

/// <summary>One prefix: its tag text, how the tag and the player's name are styled, and the line template.</summary>
internal sealed class PrefixDefinition
{
	internal const int MaximumIdLength = 32;
	internal const int MaximumNameLength = 48;
	internal const int MaximumTagLength = 32;
	internal const int MaximumPaletteColors = 16;

	private PrefixDefinition( string id, string name, string tag, string template, PrefixStyle tagStyle, PrefixStyle nameStyle, IReadOnlyList<string> palette )
	{
		Id = id;
		Name = name;
		Tag = tag;
		Template = template;
		TagStyle = tagStyle;
		NameStyle = nameStyle;
		Palette = palette;
		Pieces = PrefixTemplate.Split( template );
		PaletteValue = EffectValue.Of( (palette.Count == 0 ? EffectSymbols.DefaultPalette : palette.Select( Color ).ToArray())
			.Select( EffectValue.Of ).ToArray() );
	}

	internal string Id { get; }
	internal string Name { get; }
	internal string Tag { get; }
	internal string Template { get; }

	/// <summary>The template split into literal text, %prefix% and %name%.</summary>
	internal IReadOnlyList<string> Pieces { get; }
	internal PrefixStyle TagStyle { get; }
	internal PrefixStyle NameStyle { get; }

	/// <summary>Hex colors; empty means the WaW ^1 to ^5 palette.</summary>
	internal IReadOnlyList<string> Palette { get; }

	/// <summary>The palette as scripts see it.</summary>
	internal EffectValue PaletteValue { get; }

	internal static PrefixDefinition Create( string id, string name, string tag, string template = null,
		PrefixStyle tagStyle = null, PrefixStyle nameStyle = null, IEnumerable<string> palette = null )
	{
		ContractGuard.Key( id, nameof( id ) );
		if ( id.Length > MaximumIdLength ) throw new ArgumentException( $"Prefix IDs can be at most {MaximumIdLength} characters.", nameof( id ) );
		name = string.IsNullOrWhiteSpace( name ) ? id : name.Trim();
		if ( name.Length > MaximumNameLength || name.Any( char.IsControl ) )
			throw new ArgumentException( $"Prefix names are one line of at most {MaximumNameLength} characters.", nameof( name ) );
		tag ??= "";
		if ( tag.Length > MaximumTagLength || tag.Any( char.IsControl ) )
			throw new ArgumentException( $"Tags are one line of at most {MaximumTagLength} characters.", nameof( tag ) );

		var colors = (palette ?? Array.Empty<string>()).ToArray();
		if ( colors.Length > MaximumPaletteColors || colors.Any( color => !EffectColor.TryParseHex( color, out _ ) ) )
			throw new ArgumentException( $"A palette holds at most {MaximumPaletteColors} hex colors like #ff3333.", nameof( palette ) );
		return new PrefixDefinition( id, name, tag, PrefixTemplate.Validate( template ), tagStyle ?? PrefixStyle.Plain,
			nameStyle ?? PrefixStyle.Plain, Array.AsReadOnly( colors.Select( Normalize ).ToArray() ) );
	}

	internal PrefixDefinition With( string name = null, string tag = null, string template = null,
		PrefixStyle tagStyle = null, PrefixStyle nameStyle = null, IEnumerable<string> palette = null ) =>
		Create( Id, name ?? Name, tag ?? Tag, template ?? Template, tagStyle ?? TagStyle, nameStyle ?? NameStyle, palette ?? Palette );

	/// <summary>The line in plain text, for logs and screen readers.</summary>
	internal string Format( string playerName ) => PrefixTemplate.Format( Template, Tag, playerName ?? "" );

	private static EffectColor Color( string hex ) => EffectColor.TryParseHex( hex, out var color ) ? color : EffectColor.White;

	private static string Normalize( string hex ) => Color( hex ).ToHex().ToLowerInvariant();
}