using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using Gallium;
using Serilog;
namespace Nitride.Entities;
///
/// A Nitride operation that creates and merges entities that are intended
/// to be indexes of another entity. Examples of this would be year and month
/// archive pages for a blog or a tag/category pages for associated data. This
/// uses the scanner to determine how many index entities are needed and then
/// merges existing entities with their data or creates new indexes for ones
/// that don't already have an index.
///
///
/// This makes the assumption that there is one index per page.
///
[WithProperties]
public partial class CreateOrUpdateIndex : OperationBase
{
private readonly ILogger logger;
private readonly IValidator validator;
public CreateOrUpdateIndex(ILogger logger, IValidator validator)
{
this.validator = validator;
this.logger = logger.ForContext(typeof(CreateOrUpdateIndex));
}
///
/// Creates an index for a given key. This will not be called for any
/// index that has been already created.
///
public Func, Entity> CreateIndex { get; set; } = null!;
///
/// Gets or sets the function to retrieve the key from an existing
/// index page. If this returns null, then the entity is considered not
/// to be an index page.
///
public Func GetIndexKey { get; set; } = null!;
///
/// Gets or sets the scanner that provides the keys.
///
public EntityScanner Scanner { get; set; } = null!;
///
/// Updates an existing index entity to include new information.
///
public Func, Entity> UpdateIndex { get; set; } = null!;
///
public override IEnumerable Run(IEnumerable input)
{
// Make sure we have sane data.
this.validator.ValidateAndThrow(this);
// Get the list of all the scanned entities.
var scanned = this.Scanner.GetScannedResults().ToDictionary(x => x.Key, x => x.Value);
// We loop through the results and look for index entities. Any one we
// find, we update with the existing entries. If we get to the end and
// still have any left over, we create those pages.
HashSet existing = new();
foreach (Entity? entity in input)
{
// See if this entity is an index for anything.
string? key = this.GetIndexKey(entity);
if (key == null)
{
// Not an index page, we don't need to pay attention.
yield return entity;
}
else
{
// This is an existing entity page that needs to be updated.
IEnumerable entries =
scanned.TryGetValue(key, out List? list) ? list : Array.Empty();
existing.Add(key);
yield return this.UpdateIndex(entity, key, entries);
}
}
// Once we're done with the list, we need to create the missing indexes.
foreach (string? key in scanned.Keys)
{
if (existing.Contains(key))
{
continue;
}
yield return this.CreateIndex(key, scanned[key]);
}
// Report the results.
this.logger.Debug(
"Found {Old:N0} and created {New:N0} index pages for {Keys:N0} keys",
existing.Count,
scanned.Count - existing.Count,
scanned.Keys.Count());
}
}