using System;
namespace GisConverter.Lib.Models
{
///
/// Represents the outcome of a conversion operation.
///
///
///
/// is an immutable value object intended to be returned by converter
/// implementations and propagated between layers (for example from the conversion library back to
/// the CLI). It contains:
///
///
/// - — the logical result code (Success/Failure).
/// - — a short, human-friendly message suitable for logs and CLI output.
/// - — creation time in UTC for diagnostics and ordering.
///
///
///
/// Construction
/// - Use the factory helpers and to create
/// instances. These helpers normalize the message and set the timestamp consistently.
///
///
///
/// Immutability & thread-safety
/// - Instances are immutable after construction and therefore safe to share between threads.
/// - The type exposes no mutating members.
///
///
///
/// Equality semantics
/// - Implements to compare logical results.
/// - Equality is based on and using ordinal comparison.
/// - is intentionally excluded from equality because it is diagnostic
/// metadata (creation time) rather than part of the logical outcome.
/// - If a caller needs timestamp-aware equality, implement a custom comparer or wrap this type
/// rather than changing global semantics.
///
///
///
/// Usage guidance
/// - Return from converters when the operation completed and produced
/// expected outputs. Keep messages concise (tests often assert on substrings).
/// - Return for failures; include clear diagnostic tokens suitable for logs
/// and CI debugging. Avoid exposing secrets in messages.
///
///
public sealed class ConversionResult : IEquatable
{
///
/// The status of the conversion.
///
public ConversionStatus Status { get; }
///
/// Human-friendly message describing the result. Empty string when not provided.
/// Use this to surface concise diagnostics to CLI users or logs.
///
///
/// Keep messages brief and stable; tests and automation often match on case-insensitive
/// substrings rather than exact full-text matches. Avoid embedding environment-specific
/// paths or transient identifiers unless necessary.
///
public string Message { get; }
///
/// UTC timestamp when the result instance was created.
///
///
/// This value is intended for diagnostics (correlation, ordering) and is not considered
/// in logical equality. For deterministic tests avoid asserting on this value or provide
/// a test seam that controls timestamps.
///
public DateTime TimestampUtc { get; }
///
/// True when the conversion succeeded ( is ).
///
public bool IsSuccess => Status == ConversionStatus.Success;
private ConversionResult(ConversionStatus status, string message)
{
Status = status;
Message = message ?? string.Empty;
TimestampUtc = DateTime.UtcNow;
}
///
/// Create a success result with an optional message.
///
/// Optional human-friendly message; null is treated as empty.
/// A new with == .
///
/// Use this factory from converters and hosts to represent successful conversions.
/// Example: return ConversionResult.Success("Wrote 3 files");
///
public static ConversionResult? Success(string? message = null) =>
new ConversionResult(ConversionStatus.Success, message ?? string.Empty);
///
/// Create a failure result with an optional message describing the error.
///
/// Optional error message; null is treated as empty.
/// A new with == .
///
/// Provide concise diagnostic text suitable for logs and CI failure analysis. Prefer stable
/// tokens that tests and automation can match against.
/// Example: return ConversionResult.Failure("Input archive missing .shp file");
///
public static ConversionResult? Failure(string? message = null) =>
new ConversionResult(ConversionStatus.Failure, message ?? string.Empty);
///
/// Returns a compact textual representation useful for logs.
///
/// String in the form "{Status}: {Message}".
public override string ToString() => $"{Status}: {Message}";
///
/// Strongly-typed equality comparing logical result values.
/// - Compares and (ordinal).
/// - Does not consider in equality.
///
/// Other to compare with.
/// true when logically equal; otherwise false.
public bool Equals(ConversionResult? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Status == other.Status && string.Equals(Message, other.Message, StringComparison.Ordinal);
}
///
/// Object equality override delegates to strongly-typed equality.
///
public override bool Equals(object? obj) => obj is ConversionResult other && Equals(other);
///
/// Hash code based on the logical equality components.
///
///
/// Combines and (ordinal) into the hash. The timestamp is
/// intentionally excluded so logically-equal results share the same hash code.
///
public override int GetHashCode()
{
unchecked
{
// combine Status and Message (ordinal) into hash
int hash = 17;
hash = hash * 31 + Status.GetHashCode();
hash = hash * 31 + (Message != null ? StringComparer.Ordinal.GetHashCode(Message) : 0);
return hash;
}
}
///
/// Equality operator uses the same semantics as .
///
public static bool operator ==(ConversionResult? left, ConversionResult? right)
{
if (ReferenceEquals(left, right)) return true;
if (ReferenceEquals(left, null)) return false;
return left.Equals(right);
}
///
/// Inequality operator.
///
public static bool operator !=(ConversionResult? left, ConversionResult? right) => !(left == right);
}
///
/// Status codes for .
///
public enum ConversionStatus
{
/// Conversion completed successfully.
Success = 0,
/// Conversion failed (see for details).
Failure = 1
}
}