SessionZeroWasm/SessionZero/Data/CharacterTemplate.cs

176 lines
6.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace SessionZero.Data;
[SzfObject(SzfObjectType.CharacterTemplate, "character_template")]
public class CharacterTemplate : SzfObject
{
public string GameSystem { get; set; } = string.Empty;
public List<RequiredDatasetReference> RequiredDatasets { get; set; } = new();
public List<TemplateSection> Sections { get; set; } = new(); // Top-level sections
public override SzfObjectType ObjectType => SzfObjectType.CharacterTemplate;
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
{
base.PopulateFromMetadata(metadata);
if (metadata.TryGetValue("GameSystem", out var gameSystemField))
GameSystem = gameSystemField.Value?.ToString() ?? string.Empty;
}
public override void ParseSections(List<SzfSection> sections)
{
// Parse "Required Datasets" section
var requiredDatasetsSection =
sections.FirstOrDefault(s => s.Name.Equals("Required Datasets", StringComparison.OrdinalIgnoreCase));
if (requiredDatasetsSection != null)
{
foreach (var field in requiredDatasetsSection.Fields)
{
var datasetReference = DatasetReference.Parse(field.Value?.ToString() ?? string.Empty);
if (datasetReference != null)
{
RequiredDatasets.Add(new RequiredDatasetReference
{
Alias = field.Name,
Reference = datasetReference
});
}
}
}
// Parse and build the hierarchical template sections
// Use a map to quickly find existing sections by their full path for nesting
var sectionPathMap = new Dictionary<string, TemplateSection>(StringComparer.OrdinalIgnoreCase);
foreach (var szfSection in sections)
{
// Only process sections that start with "Section:"
if (!szfSection.Name.StartsWith("Section:", StringComparison.OrdinalIgnoreCase)) continue;
// Normalize the section name (e.g., "Section:Ability Scores.Modifiers" to "Ability Scores.Modifiers")
var rawSectionPath = szfSection.Name.Substring("Section:".Length).Trim();
var nameParts = rawSectionPath.Split('.');
TemplateSection? currentParentSection = null;
List<TemplateSection> currentSectionList = Sections; // Start from the top-level Sections list
string accumulatedPath = "Section"; // Used for dictionary key to ensure uniqueness and hierarchy
for (int i = 0; i < nameParts.Length; i++)
{
var sectionNamePart = nameParts[i];
accumulatedPath += "." + sectionNamePart; // Build the full path for the map key
TemplateSection? targetSection = null;
// Check if this specific section (at this level of hierarchy) already exists
if (sectionPathMap.TryGetValue(accumulatedPath, out var existingSection))
{
targetSection = existingSection;
}
else
{
// Create a new section if it doesn't exist
var newSection = new TemplateSection { Name = sectionNamePart };
sectionPathMap[accumulatedPath] = newSection;
if (currentParentSection == null)
{
// This is a top-level section
currentSectionList.Add(newSection);
}
else
{
// This is a subsection of the current parent
currentParentSection.Subsections.Add(newSection);
}
targetSection = newSection;
}
currentParentSection = targetSection; // Move down the hierarchy
currentSectionList =
targetSection.Subsections; // Next iteration will look in subsections of this section
}
// Once we've traversed/created the full path, currentParentSection points to the innermost section.
// Now populate its fields from the SzfSection.
if (currentParentSection != null)
{
foreach (var field in szfSection.Fields)
{
currentParentSection.Fields.Add(ConvertToTemplateField(field));
}
}
}
}
private TemplateField ConvertToTemplateField(SzfField szfField)
{
return new TemplateField
{
Name = szfField.Name,
Type = ParseDatasetFieldType(szfField.Type), // Reusing DatasetFieldType for consistency
Value = szfField.Value,
};
}
// Helper method to parse DatasetFieldType, can be shared or defined here.
// Copying from Dataset.cs for now, consider a common utility.
private DatasetFieldType ParseDatasetFieldType(string typeString)
{
return typeString.ToLower() switch
{
"text" => DatasetFieldType.Text,
"text-field" => DatasetFieldType.TextField,
"number" => DatasetFieldType.Number,
"bool" => DatasetFieldType.Boolean,
"group" => DatasetFieldType.Group,
"calculated" => DatasetFieldType.Calculated,
"system" => DatasetFieldType.System,
"dataset-reference" => DatasetFieldType.DatasetReference,
"dataset-type" => DatasetFieldType.DatasetType,
"dataset-reference-multiple" => DatasetFieldType.DatasetReferenceMultiple,
"dataset-type-multiple" => DatasetFieldType.DatasetTypeMultiple,
_ => DatasetFieldType.Text
};
}
public override SzfValidationResult Validate()
{
var result = base.Validate();
// if (string.IsNullOrWhiteSpace(GameSystem))
// result.AddError("GameSystem is required for CharacterTemplate");
// Further validation for required datasets and template sections can be added here
// e.g., checking for duplicate section names or missing required fields.
return result;
}
}
public class RequiredDatasetReference
{
public string Alias { get; set; } = string.Empty;
public DatasetReference? Reference { get; set; }
}
public class TemplateSection
{
public string Name { get; set; } = string.Empty;
public List<TemplateField> Fields { get; set; } = new();
public List<TemplateSection> Subsections { get; set; } = new();
}
public class TemplateField
{
public string Name { get; set; } = string.Empty;
public DatasetFieldType Type { get; set; }
public object? Value { get; set; }
}