public sealed class SimulationRunState
{
    private readonly object _gate = new();
    private DateTimeOffset? _completedAt;

    public Guid RunId { get; init; }
    public string ScenarioId { get; init; } = null!;
    public string ScenarioName { get; init; } = null!;
    public double Speed { get; init; }
    public string StartedByUserId { get; init; } = null!;
    public DateTimeOffset StartedAt { get; init; }
    public double TotalOffsetMinutes { get; init; }
    public string PatientDisplayName { get; init; } = null!;

    public SimulationRunStatus Status { get; private set; } = SimulationRunStatus.Pending;
    public Guid? PatientId { get; private set; }
    public Guid? EncounterId { get; private set; }
    public int ObservationsSent { get; private set; }
    public int MedicationsSent { get; private set; }
    public int OrdersPlaced { get; private set; }
    public double LastOffsetMinutes { get; private set; }
    public double ProgressPercent { get; private set; }
    public string? FailureReason { get; private set; }

    public double ElapsedRealSeconds
    {
        get
        {
            lock (_gate)
            {
                var end = _completedAt ?? DateTimeOffset.UtcNow;
                return (end - StartedAt).TotalSeconds;
            }
        }
    }

    public void MarkRunning()
    {
        lock (_gate) Status = SimulationRunStatus.Running;
    }

    public void SetIds(Guid? patientId, Guid? encounterId)
    {
        lock (_gate)
        {
            if (patientId.HasValue) PatientId = patientId;
            if (encounterId.HasValue) EncounterId = encounterId;
        }
    }

    public void UpdateOffset(double offsetMinutes)
    {
        lock (_gate)
        {
            LastOffsetMinutes = offsetMinutes;
            ProgressPercent = TotalOffsetMinutes <= 0
                ? 100
                : Math.Clamp(offsetMinutes / TotalOffsetMinutes * 100.0, 0, 100);
        }
    }

    public void ApplyResultCounters(int observationsSent, int medicationsSent, int ordersPlaced)
    {
        lock (_gate)
        {
            ObservationsSent = observationsSent;
            MedicationsSent = medicationsSent;
            OrdersPlaced = ordersPlaced;
        }
    }

    public void NoteEvent(string description)
    {
        lock (_gate)
        {
            if (description.StartsWith("MEDICATION", StringComparison.Ordinal))
                MedicationsSent++;
            else if (description.StartsWith("ORDER ", StringComparison.Ordinal))
                OrdersPlaced++;
            else if (!description.StartsWith("ORDER_RESULT", StringComparison.Ordinal)
                     && !description.StartsWith("ACK ", StringComparison.Ordinal))
                ObservationsSent++;
        }
    }

    public void MarkTerminal(SimulationRunStatus status, string? failureReason = null)
    {
        lock (_gate)
        {
            Status = status;
            FailureReason = failureReason;
            _completedAt = DateTimeOffset.UtcNow;
            if (status == SimulationRunStatus.Completed)
                ProgressPercent = 100;
        }
    }

    public SimulationRunState Snapshot()
    {
        lock (_gate)
        {
            var copy = new SimulationRunState
            {
                RunId = RunId,
                ScenarioId = ScenarioId,
                ScenarioName = ScenarioName,
                Speed = Speed,
                StartedByUserId = StartedByUserId,
                StartedAt = StartedAt,
                TotalOffsetMinutes = TotalOffsetMinutes,
                PatientDisplayName = PatientDisplayName,
            };
            copy.Status = Status;
            copy.PatientId = PatientId;
            copy.EncounterId = EncounterId;
            copy.ObservationsSent = ObservationsSent;
            copy.MedicationsSent = MedicationsSent;
            copy.OrdersPlaced = OrdersPlaced;
            copy.LastOffsetMinutes = LastOffsetMinutes;
            copy.ProgressPercent = ProgressPercent;
            copy.FailureReason = FailureReason;
            copy._completedAt = _completedAt;
            return copy;
        }
    }
}
