Editor/CodeImport/ParseCode.cs
using Sandbox;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Todo.List;

namespace Todo.CodeImport;

public static class ParseCode
{
	public struct CommentMatch
	{
		public string CommentStub;
		public bool MatchSuccess;
		public int Line;
	}

	public static Dictionary<string, List<CodeEntry>> ProcessFiles( FileInfo[] files )
	{
		Dictionary<string, List<CodeEntry>> results = new();

		foreach ( FileInfo file in files )
		{
			string sourceText = FileUtility.GetFileContents( file );
			string lines = GetComments( file, out CommentMatch[] lineMatches );

			foreach ( TodoCodeWord style in TodoDock.Cookies.CodeWords )
			{
				string[] entries = ScanFor( lines, style.CodeWord );

				for ( int i = 0; i < entries.Length; i++ )
				{
					results.GetOrCreate( file.Name ).Add( new()
					{
						SourceFile = FileUtility.GetRelativePath( file.FullName ),
						Message = entries[i],
						SourceLine = CodeUtility.GetSourceLine( entries[i], lineMatches ),
						Style = style
					} );
				}
			}

			foreach ( var item in results.Values )
			{
				item.Sort( ( x, y ) => x.SourceLine.CompareTo( y.SourceLine ) );
			}
		}

		return results;
	}

	private static string GetComments( FileInfo file, out CommentMatch[] lineMatches )
	{
		StringBuilder commentString = new();

		bool lastWasComment = false;

		using StreamReader reader = file.OpenText();

		List<CommentMatch> matches = new();
		string lineContent = "";
		int lineIndex = 0;

		// all of this just to add empty lines
		// between comments that are not next to
		// each other
		while ( reader.EndOfStream is false )
		{
			lineContent = reader.ReadLine();
			lineIndex += 1;

			if ( lineContent.Contains( "//" ) is false )
			{
				if ( lastWasComment )
				{
					lastWasComment = false;
					commentString.AppendLine( "" );
				}
				continue;
			}

			lastWasComment = true;

			Regex commentRegex = new( "(?<=\\/\\/).*$", RegexOptions.Singleline | RegexOptions.IgnoreCase );
			Match commentMatch = commentRegex.Match( lineContent );

			if ( commentMatch.Success is false )
			{
				lastWasComment = false;
				continue;
			}

			Regex stubSearchRegex = new( $"(?<={GetRegexTerminator()}).*", RegexOptions.Singleline | RegexOptions.IgnoreCase );
			Match stubMatch = stubSearchRegex.Match( lineContent );

			matches.Add( new()
			{
				Line = lineIndex,
				MatchSuccess = stubMatch.Success,
				CommentStub = stubMatch.Value.Trim()
			} );

			commentString.AppendLine( commentMatch.Value.Trim() );
		}

		lineMatches = matches.ToArray();

		return commentString.ToString();
	}

	private static string GetRegexTerminator()
	{
		StringBuilder builder = new();

		string[] codeWords = TodoDock.Cookies.CodeWords.Select( x => x.CodeWord ).ToArray();
		builder.AppendJoin( "|", codeWords );

		return builder.ToString();
	}

	private static string[] ScanFor( string lines, string searchString )
	{
		Regex searchRegex = new( $"(?<=^{searchString}).*(?:\\n(?!\\s*$|{GetRegexTerminator()}).*)*", RegexOptions.Multiline | RegexOptions.IgnoreCase );
		MatchCollection results = searchRegex.Matches( lines );

		return results.Select( x => x.Value.CollapseWhiteSpace() ).ToArray();
	}
}