using System;
using System.Text;
using System.Text.RegularExpressions;
using CSNetwork;
namespace BrewMonster.Scripts.Chat
{
///
/// Chat 输入/协议:wire(MarshalEditBoxText)↔ TMP <sprite> 显示。
/// Chat input/protocol: wire (MarshalEditBoxText) ↔ TMP <sprite> display.
///
public static class ChatWireTmpCodec
{
///
/// Thẻ <sprite …> nội bộ (TMP cho phép <sprite="asset" …> không có khoảng sau "sprite").
/// English: Inner <sprite …> tag (TMP allows <sprite="asset" …> with no space after "sprite").
///
private static readonly Regex InnerSpriteTagRegex = new Regex(@"]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
///
/// Một emoji hiển thị trong input = <size> + sprite + </size> (EmotionTMPTagBuilder).
/// English: One in-game emoji in input = size wrapper + sprite + closing size tag.
///
private static readonly Regex EmotionSizedBlockRegex =
new Regex(@"\s*]*>\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex OrphanSpriteFragmentRegex = new Regex(@"^\s*sprite\s[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
///
/// Tạo một đoạn wire marshal cho một emotion (set:index) — gửi server đúng protocol.
/// Build one marshaled wire segment for a single emotion (set:index) — correct server protocol.
///
public static string BuildMarshaledEmotionWire(int emotionSet, int emotionIndex)
{
var item = new EditBoxItemBase(AUICommon.EditboxItemType.enumEIEmotion);
item.SetName("W");
item.SetInfo(AUICommon.MarshalEmotionInfo(emotionSet, emotionIndex));
var items = new EditBoxItemsSet();
char c = items.AppendItem(item);
if (c == '\0')
return "";
string display = c.ToString();
return AUICommon.MarshalEditBoxText(display, items);
}
///
/// TMP 正文(无频道前缀)→ wire marshal(用于发送)。
/// TMP body text (no channel prefix) → marshaled wire (for sending).
///
public static string TmpBodyToWire(string tmpBody, IEmotionSpriteMap map)
{
if (string.IsNullOrEmpty(tmpBody))
return "";
if (map == null)
return tmpBody;
var sb = new StringBuilder(tmpBody.Length);
int last = 0;
foreach (Match block in EmotionSizedBlockRegex.Matches(tmpBody))
{
AppendTmpSegmentToWire(sb, tmpBody, last, block.Index - last, map);
last = block.Index + block.Length;
Match inner = InnerSpriteTagRegex.Match(block.Value);
if (inner.Success && TryMatchSpriteTagToEmotion(map, inner.Value, out int es, out int ei))
sb.Append(BuildMarshaledEmotionWire(es, ei));
else
sb.Append(block.Value);
}
AppendTmpSegmentToWire(sb, tmpBody, last, tmpBody.Length - last, map);
return sb.ToString();
}
///
/// Đoạn TMP (không gồm khối <size>…</size> đã xử lý ở ngoài): chữ thường + <sprite> rời.
/// English: TMP segment: plain text plus bare sprite tags (outside size-wrapped emotion blocks).
///
private static void AppendTmpSegmentToWire(StringBuilder sb, string tmpBody, int start, int length, IEmotionSpriteMap map)
{
if (length <= 0)
return;
string gap = tmpBody.Substring(start, length);
int last = 0;
foreach (Match m in InnerSpriteTagRegex.Matches(gap))
{
AppendSanitizedPlainText(sb, gap, last, m.Index - last);
string tag = m.Value;
if (TryMatchSpriteTagToEmotion(map, tag, out int es, out int ei))
sb.Append(BuildMarshaledEmotionWire(es, ei));
else
sb.Append(tag);
last = m.Index + m.Length;
var orphan = OrphanSpriteFragmentRegex.Match(gap, last);
if (orphan.Success)
last += orphan.Length;
}
AppendSanitizedPlainText(sb, gap, last, gap.Length - last);
}
private static void AppendSanitizedPlainText(StringBuilder sb, string source, int start, int length)
{
if (length <= 0)
return;
int end = start + length;
for (int i = start; i < end; i++)
{
char ch = source[i];
if (!AUICommon.IsEditboxItemCode(ch))
sb.Append(ch);
}
}
///
/// Nếu charIndex nằm trong một emoji TMP (khối <size>… hoặc thẻ <sprite> rời), trả khoảng xóa [start, end).
/// English: If charIndex is inside one TMP emoji unit, returns delete span [start, end).
///
public static bool TryGetSpriteTagRangeContainingCharacterIndex(string text, int charIndex, out int tagStart, out int tagEndExclusive)
{
tagStart = 0;
tagEndExclusive = 0;
if (string.IsNullOrEmpty(text) || charIndex < 0 || charIndex >= text.Length)
return false;
foreach (Match m in EmotionSizedBlockRegex.Matches(text))
{
int end = m.Index + m.Length;
if (charIndex >= m.Index && charIndex < end)
{
tagStart = m.Index;
tagEndExclusive = end;
return true;
}
}
foreach (Match m in InnerSpriteTagRegex.Matches(text))
{
int end = m.Index + m.Length;
if (charIndex >= m.Index && charIndex < end)
{
tagStart = m.Index;
tagEndExclusive = end;
return true;
}
}
return false;
}
///
/// Khớp tag <sprite…> với EmotionTMPTagBuilder — so sánh phần sprite bên trong <size>…</size>.
/// English: Match inner <sprite…> to EmotionTMPTagBuilder output (full tag includes size wrapper).
///
public static bool TryMatchSpriteTagToEmotion(IEmotionSpriteMap map, string spriteTag, out int emotionSet, out int emotionIndex)
{
emotionSet = 0;
emotionIndex = 0;
if (map == null || string.IsNullOrEmpty(spriteTag))
return false;
string normalized = spriteTag.Trim();
for (int s = 0; s < AUICommon.AUIMANAGER_MAX_EMOTIONGROUPS; s++)
{
for (int e = 0; e < 512; e++)
{
if (!EmotionTMPTagBuilder.TryBuildEmotionTag(map, s, e, out string built))
continue;
Match builtSprite = InnerSpriteTagRegex.Match(built);
if (!builtSprite.Success)
continue;
if (string.Equals(builtSprite.Value, normalized, StringComparison.OrdinalIgnoreCase))
{
emotionSet = s;
emotionIndex = e;
return true;
}
}
}
return false;
}
///
/// Wire → TMP 富文本(FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp)。
/// Wire → TMP rich text (FilterEmotionSet + Unmarshal + ConvertInlineItemsToTmp).
///
public static string WireBodyToTmpForDisplay(string wireBody, IEmotionSpriteMap map, int cEmotion)
{
return ChatEmotionDisplayPipeline.ConvertWireBodyToTmpDisplay(wireBody, map, cEmotion);
}
}
}