using System.Security.Cryptography;
using System.Text;
namespace AIStudio.Assistants.BeyondFactsLab;
///
/// Tells whether a confirmed work state of the BeyondFacts Lab still fits what it builds on.
///
///
/// The answer is computed whenever it is needed and never stored, so that it cannot drift from the work states. A
/// confirmed work state remembers the revisions of the earlier steps and a fingerprint of the form of its step;
/// comparing both with the present ones gives the answer. A changed form weighs less than a changed earlier step:
/// the person may have only corrected a typo.
///
public static class BflStaleness
{
///
/// Separates the parts of the text a fingerprint is computed from; it cannot occur in a value.
///
private const char SEPARATOR = '\0';
///
/// Tells where the work state of a step stands.
///
/// The step.
/// The work states of the preparation, by their steps; a step which is missing has none.
/// The forms of the preparation, by their steps; a step which is missing has a form this version cannot open, whose changes then do not count.
/// The status of the work state.
public static BflWorkStateStatus Of(BflStep step, IReadOnlyDictionary workStates, IReadOnlyDictionary forms)
{
if (workStates.GetValueOrDefault(step)?.Confirmed is not { } confirmed)
return BflWorkStateStatus.NOT_CONFIRMED;
var upstreamRevisions = UpstreamRevisionsOf(step, workStates);
if (upstreamRevisions.Count != confirmed.UpstreamRevisions.Count || upstreamRevisions.Any(revision => confirmed.UpstreamRevisions.GetValueOrDefault(revision.Key) != revision.Value))
return BflWorkStateStatus.OUTDATED;
if (forms.TryGetValue(step, out var form) && FingerprintOf(form) != confirmed.DetailsFingerprint)
return BflWorkStateStatus.DETAILS_CHANGED;
return BflWorkStateStatus.CONFIRMED;
}
///
/// Gets the revisions of the confirmed work states of the steps before a step, which a confirmation of that step
/// remembers.
///
/// The step.
/// The work states of the preparation, by their steps.
/// The revision of each earlier step with a confirmed work state.
public static IReadOnlyDictionary UpstreamRevisionsOf(BflStep step, IReadOnlyDictionary workStates)
{
var revisions = new Dictionary();
foreach (var earlier in BflStep.All.Where(candidate => candidate < step))
{
if (workStates.GetValueOrDefault(earlier)?.Confirmed is { } confirmed)
revisions[earlier] = confirmed.Revision;
}
return revisions;
}
///
/// Computes the fingerprint of a form, which tells whether the person changed it.
///
///
/// Line endings and spaces around a value do not count, since they change nothing the model reads.
///
/// The form.
/// The fingerprint, in hexadecimal digits.
public static string FingerprintOf(BflStepFormData form)
{
var text = new StringBuilder();
foreach (var (fieldId, value) in form.Values.OrderBy(entry => entry.Key, StringComparer.Ordinal))
{
var normalized = value.Trim().ReplaceLineEndings("\n");
if (normalized.Length is 0)
continue;
text.Append(fieldId).Append(SEPARATOR).Append(normalized).Append(SEPARATOR);
}
return Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(text.ToString())));
}
}