namespace ZB.MOM.WW.ScadaBridge.SiteRuntime;

/// <summary>
/// Configuration options for the Site Runtime component.
/// Bound from ScadaBridge:SiteRuntime configuration section.
/// </summary>
public class SiteRuntimeOptions
{
    /// <summary>
    /// Number of Instance Actors to create per batch during staggered startup.
    /// Default: 20.
    /// </summary>
    public int StartupBatchSize { get; set; } = 20;

    /// <summary>
    /// Delay in milliseconds between startup batches to prevent reconnection storms.
    /// Default: 100ms.
    /// </summary>
    public int StartupBatchDelayMs { get; set; } = 100;

    /// <summary>
    /// Maximum call depth for recursive script calls (CallScript/CallShared).
    /// Default: 10.
    /// </summary>
    public int MaxScriptCallDepth { get; set; } = 10;

    /// <summary>
    /// Default script execution timeout in seconds.
    /// Default: 30 seconds.
    /// </summary>
    public int ScriptExecutionTimeoutSeconds { get; set; } = 30;

    /// <summary>
    /// Per-subscriber buffer size for the site-wide Akka stream.
    /// Slow subscribers drop oldest messages when buffer is full.
    /// Default: 1000.
    /// </summary>
    public int StreamBufferSize { get; set; } = 1000;

    /// <summary>
    /// FLOOR for the number of dedicated threads in the script-execution scheduler.
    /// Script and alarm on-trigger bodies run on these threads instead of the shared
    /// .NET thread pool, so blocking script I/O cannot starve the global pool.
    ///
    /// <para>WP3.1: this was a fixed size and is now the lower bound of a grow-only,
    /// instance-scaled pool — see
    /// <see cref="Scripts.ScriptExecutionScheduler.ComputeTargetThreads"/>. Existing
    /// configurations keep exactly the previous behaviour at or below
    /// <c>ScriptExecutionThreadCount * InstancesPerScriptThread</c> deployed instances.</para>
    ///
    /// Default: 8.
    /// </summary>
    public int ScriptExecutionThreadCount { get; set; } = 8;

    /// <summary>
    /// WP3.1: CEILING for the instance-scaled script-execution pool. The pool grows
    /// towards <c>ceil(enabledInstances / 8)</c> threads but never past this value; beyond
    /// it, <see cref="MaxConcurrentRunsPerScript"/> is the real regulator. A 1 MB-stack
    /// dedicated thread is cheap, so 32 (≈ 256 instances at the /8 ratio) is a generous
    /// default. Must be greater than or equal to <see cref="ScriptExecutionThreadCount"/>.
    /// Default: 32.
    /// </summary>
    public int ScriptExecutionMaxThreadCount { get; set; } = 32;

    /// <summary>
    /// WP3.1 (finding #4): maximum number of trigger-expression evaluations allowed to run
    /// concurrently across the whole process. Trigger expressions are non-blocking by
    /// construction (<see cref="Scripts.TriggerExpressionGlobals"/> exposes only reads over an
    /// in-memory snapshot, and the script trust gate has already denied I/O, network, and
    /// threading), so they run as plain async work on the shared .NET thread pool behind this
    /// gate rather than on the blocking script-execution pool. That is what keeps an alarm's
    /// Expression trigger from queueing behind blocking script bodies.
    /// Default: <c>max(2, Environment.ProcessorCount)</c>.
    /// </summary>
    public int TriggerEvalMaxConcurrency { get; set; } = Math.Max(2, Environment.ProcessorCount);

    /// <summary>
    /// WP3.1: timeout (seconds) for a single trigger-expression evaluation. Previously
    /// hardcoded at 2 s in both <c>ScriptActor</c> and <c>AlarmActor</c>. The deadline is now
    /// armed when the evaluation is ENQUEUED, not when it is dequeued, so time spent waiting
    /// on <see cref="TriggerEvalMaxConcurrency"/> burns the same budget — a saturated gate
    /// produces a timely "false" instead of an unbounded stall.
    /// Default: 2.
    /// </summary>
    public int TriggerEvalTimeoutSeconds { get; set; } = 2;

    /// <summary>
    /// WP3.1: maximum number of concurrent runs (queued or executing) for any single script
    /// or alarm on-trigger script. A trigger that arrives while the cap is reached is SHED —
    /// the newest run is refused, the four already in flight (which are closest to their own
    /// deadlines) are kept, and no reordering occurs. A shed increments
    /// <c>ISiteHealthCollector.IncrementScriptRunShed</c>, emits a rate-limited Warning site
    /// event, and — for an Ask-based <c>CallScript</c> — replies with an explicit error rather
    /// than letting the caller hang to its Ask timeout.
    /// Default: 4.
    /// </summary>
    public int MaxConcurrentRunsPerScript { get; set; } = 4;

    /// <summary>
    /// Max mirrored native alarms retained per source binding before older entries are dropped (logged).
    /// Default: 1000.
    /// </summary>
    public int MirroredAlarmCapPerSource { get; set; } = 1000;

    /// <summary>
    /// Interval in milliseconds to retry a failed native alarm subscription.
    /// Default: 5000ms.
    /// </summary>
    public int NativeAlarmRetryIntervalMs { get; set; } = 5000;

    /// <summary>HTTP timeout (seconds) for fetching a deployment config from central (notify-and-fetch).</summary>
    public int ConfigFetchTimeoutSeconds { get; set; } = 30;

    /// <summary>
    /// Fixed interval (ms) at which an Instance Actor re-sends a tag-subscribe
    /// request that either failed or whose response was lost (S4/UA6). The retry is
    /// armed on every send and cancelled on the first Success reply, so it closes
    /// both the failed-response and the lost-response gaps. Default: 5000ms.
    /// </summary>
    public int TagSubscribeRetryIntervalMs { get; set; } = 5000;

    /// <summary>
    /// Grace period (ms) after a script's execution timeout elapses before the
    /// stuck-script watchdog declares the script's dedicated thread blocked and
    /// emits a loud site event naming it (S2). The CTS timeout only requests
    /// cooperative cancellation; a script blocked in synchronous I/O never
    /// observes it, so this watchdog is the only operator signal that one of the
    /// bounded script-execution threads is gone. Default: 30000ms.
    /// </summary>
    public int StuckScriptGraceMs { get; set; } = 30000;

    /// <summary>
    /// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
    /// capacity of the bounded hand-off queue feeding <see cref="Streaming.SiteStreamManager"/>'s
    /// dedicated alarm publish path. Alarm state changes are staged through this
    /// queue into their OWN broadcast source — separate from the (far higher-volume)
    /// attribute publish source — so an attribute storm cannot evict a pending alarm
    /// transition. DropOldest overflow; drops are counted (surfaced on the site health
    /// report) and logged. Default: 2000 (alarm volume is inherently far lower than
    /// attribute volume, so this rarely needs to be anywhere near
    /// <see cref="StreamBufferSize"/>).
    /// </summary>
    public int AlarmPublishQueueCapacity { get; set; } = 2000;

    /// <summary>
    /// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
    /// global switch restoring the legacy per-run Started/Completed Info site events for
    /// EVERY instance script run. Off by default: those two rows per run were the dominant
    /// <c>site_events</c> writer (~12 physical row-writes across a replicated pair per run,
    /// counting CDC oplog + row-version rows and their eventual purge tombstones), for
    /// content nobody looks at unless something went wrong. With this off, an interval
    /// aggregate (<see cref="Scripts.ScriptRunSummaryRecorder"/>) replaces the per-run rows
    /// and script Error events (timeout/failure/stuck-watchdog) are unaffected — they are
    /// unconditional regardless of this switch. Read via
    /// <see cref="Microsoft.Extensions.Options.IOptionsMonitor{TOptions}"/> at the top of
    /// every run, so it is hot-togglable (no restart, no redeploy). Default: false.
    /// </summary>
    public bool PerRunScriptEvents { get; set; }

    /// <summary>
    /// WP3.2: per-script opt-in restoring the legacy per-run Started/Completed Info site
    /// events for NAMED scripts only, for live debugging without flipping the global
    /// <see cref="PerRunScriptEvents"/> switch (and therefore without paying its volume
    /// cost for every other script). Effective only while the global switch is off.
    /// Entries are <c>"{InstanceName}/{ScriptName}"</c> for an exact match, or
    /// <c>"{InstanceName}/*"</c> to opt in every script on that instance. Matched by
    /// <see cref="Scripts.ScriptRunEventPolicy.ShouldEmitPerRun"/>, read from
    /// <see cref="Microsoft.Extensions.Options.IOptionsMonitor{TOptions}"/> per run — hot
    /// -togglable like <see cref="PerRunScriptEvents"/>. Default: empty (no opt-ins).
    /// </summary>
    public List<string> PerRunScriptEventScripts { get; set; } = new();

    /// <summary>
    /// WP3.2: flush interval (seconds) for <see cref="Scripts.ScriptRunSummaryRecorder"/>'s
    /// interval aggregate — one "script" Info site event per interval summarizing every
    /// instance script run since the last flush (headline totals + a top-50-scripts
    /// breakdown, capped so a pathological deployment cannot mint an oversized row). An
    /// interval with zero activity emits no row. <c>0</c> disables the summary flush
    /// entirely (the recorder still accumulates counters; they are just never emitted or
    /// reset). Default: 300 (5 minutes) — at most 288 summary rows/day, versus 2 rows per
    /// script run under the legacy per-run behaviour.
    /// </summary>
    public int ScriptRunSummaryIntervalSeconds { get; set; } = 300;
}
