// Ablera.Serdica.AI.Support.Connectors.Abstractions — the connector contract (Software Architecture § 3, § 8 rule 2).
// A connector references ONLY this assembly and its own client SDK — never Sessions, Memory or Governance. It treats case/task ids as opaque correlation and idempotency keys, never as workflow logic; the grant check happens in the Runtime's permission pipeline BEFORE any method here is invoked,
// and again inside the connector (refusal before contact) because the connector is the enforcement point
// (Trust and Data § 2). Reach is direct only (D132). Every result is a labelled envelope (Agent Runtime § 2 rule 3).
// Status: [P] — the seam the 10–12 connector libraries are written against (Delivery § 1.3 track 5).

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Ablera.Serdica.AI.Support.Connectors.Abstractions;

// ---------------------------------------------------------------------------------------------------- vocabulary
public enum Mode { Read, DryRun, Write, Send, Ddl }
public enum EffectClass { Read, Transactional, Compensable, Irreversible, Ddl }
public enum Authorization { ReadGrant, WriteGrant, Gate }
public enum EffectOfTimeout { None, Unknown, Applied }
public enum Recovery { Retryable, ReconcileFirst, NeverRetry }
public enum TrustClass { Trusted, Derived, Untrusted, Absent }
public enum WriteShape { A, B, E }   // Failure and Recovery § 1: A = apply+verify+replay in one call; B = deterministic leased transaction; E = capability-specific external/DDL effect
public enum RouteKind { Direct, Unreachable }

/// <summary>The environment dimension of every grant and every scope — a real credential boundary.</summary>
public sealed record EnvironmentScope(string CustomerCode, string Environment, string System);

/// <summary>A grant as the connector sees it: what this case may do, to which target, in which mode, until when,
/// bound to which artefact. Issued by the Gates service; the connector only checks it (Trust and Data § 2).</summary>
public sealed record GrantView(
    string GrantId, EnvironmentScope Scope, string TargetPattern, Mode Mode,
    string? BoundArtefactHash, int? ExpectedRows, DateTimeOffset ExpiresOn,
    string? ReservedCallId, long FenceEpoch, IReadOnlyList<string> DecisionIds);

/// <summary>The capability description — one CONNECTOR_SCOPES row of kind operation (Failure and Recovery § 4).
/// Its absence fails closed: an operation without a description is refused as a capability gap, never improvised.
/// Serialised form: contracts/connector-capability.schema.json.</summary>
public sealed record ConnectorOperation(
    string OperationId, int Version, EnvironmentScope Scope,
    string InputContractJsonSchema, TargetIdentity Target,
    EffectClass EffectClass, Authorization Authorization, IReadOnlyList<Mode> Modes,
    string? IdempotencyKeyShape, TimeSpan Timeout, EffectOfTimeout EffectOfTimeout, Recovery Recovery,
    IReadOnlyList<string> EvidenceReturned, WriteShape? WriteShape);

public sealed record TargetIdentity(string? Host, int? Port, string? Service, string? Schema, IReadOnlyList<string> Objects, IReadOnlyDictionary<string, string> Extra);

/// <summary>What every tool result is: the content plus its label. Origin, trust class, hash, size and truncation travel
/// with the content into the prompt and into the ledger (Agent Runtime § 2 rule 3). Large content is spilled to a paper
/// and referenced; the prompt gets the head.</summary>
public sealed record ToolResultEnvelope(
    string OperationId, int OperationVersion, EnvironmentScope Scope,
    TrustClass Trust, string ContentSha256, long ByteLength, bool Truncated,
    string? InlineHead, string? PaperRef,
    IReadOnlyDictionary<string, string> Evidence,        // row_count, returned_ids, version_id, commit_sha, pipeline_id …
    string WhatActuallyRan,                               // the statement/call as executed — never result rows
    TimeSpan Duration);

/// <summary>A refusal is a normal, readable result the agent plans around — never an exception (Agent Runtime § 2 rule 2).</summary>
public sealed record Refusal(string OperationId, RefusalReason Reason, string Detail, string? WhatWouldBeNeeded);
public enum RefusalReason { NoCapabilityRow, OutsideGrantScope, GrantExpired, ArtefactHashMismatch, ExpectedRowsMismatch, PreconditionFailed, Unreachable, ModeNotOffered, KillSwitch, BudgetStop }

// ---------------------------------------------------------------------------------------------------- the connector
public interface IConnector
{
    string Name { get; }                                   // oracle-ipal · oracle-insis · abacus-gateway · jira · mail · hdesk · gitlab · kibana · rabbitmq · browser · bi-publisher · camunda
    ICapabilityCatalogue Capabilities { get; }

    /// <summary>Probe the reach row for a scope: direct or unreachable, with the probe that decided it. Registering a
    /// connector does not establish live access (Failure and Recovery § 4); this does, and writes CONNECTOR_SCOPES.VERIFIED_ON_UTC.</summary>
    Task<ReachProbe> ProbeAsync(EnvironmentScope scope, CancellationToken ct);

    /// <summary>A read or dry-run operation under a read grant. Refused before contact when the grant does not cover
    /// scope × target × mode. Statement logging, never rows.</summary>
    Task<Either<ToolResultEnvelope, Refusal>> ReadAsync(OperationCall call, GrantView grant, CancellationToken ct);

    /// <summary>Write shape A: apply + verify + replay collapsed inside ONE connector call and one transaction; the call returns
    /// only after commit or rollback, so no agent turn ever spans an uncommitted write (CR-9). The grant must be bound to the
    /// packet's artefact hash and the expected row count; a mismatch is a refusal, not a warning.</summary>
    Task<Either<WriteResult, Refusal>> WriteCollapsedAsync(WritePacketCall call, GrantView grant, CancellationToken ct);

    /// <summary>Non-transactional effect form E: DDL, send, deployment or other external operation.
    /// Never claims rollback atomicity; the result can be unknown or partly applied.</summary>
    Task<Either<WriteResult, Refusal>> ExecuteEffectAsync(WritePacketCall call, GrantView grant, CancellationToken ct);

    /// <summary>Write shape B: a connector-owned transaction under a lease. Apply, verify and commit are three calls, but the
    /// lease is driven ONLY by deterministic executor code within one operation. No LLM, gate or agent hop
    /// occurs while it is open. Expiry rolls back an uncommitted transaction; an uncertain commit is reconciled.</summary>
    Task<Either<WriteLease, Refusal>> BeginLeasedWriteAsync(WritePacketCall call, GrantView grant, TimeSpan leaseFor, long fenceEpoch, CancellationToken ct);
    Task<Either<WriteResult, Refusal>> ApplyUnderLeaseAsync(WriteLease lease, CancellationToken ct);
    Task<Either<VerifyResult, Refusal>> VerifyUnderLeaseAsync(WriteLease lease, IReadOnlyList<Assertion> assertions, CancellationToken ct);
    Task<Either<CommitResult, Refusal>> CommitLeaseAsync(WriteLease lease, CancellationToken ct);
    Task RollbackLeaseAsync(WriteLease lease, string reason, CancellationToken ct);

    /// <summary>The resume handshake (Failure and Recovery § 3, worker dies mid-apply): "what actually landed?" — the connector
    /// answers from the target and the idempotency key, never from memory. The runtime compares with BUILD_STATE before repeating a turn.</summary>
    Task<LandedState> WhatLandedAsync(OperationCall call, GrantView grant, CancellationToken ct);

    /// <summary>Post-commit verification on the surface the customer actually uses (Support S6) or the target the stage wrote
    /// (Configuration S2–S5) — the same assertions, read-only, under a read grant.</summary>
    Task<Either<VerifyResult, Refusal>> VerifyAsync(IReadOnlyList<Assertion> assertions, EnvironmentScope scope, GrantView grant, CancellationToken ct);
}

public interface ICapabilityCatalogue
{
    IReadOnlyList<ConnectorOperation> List(EnvironmentScope scope);
    ConnectorOperation? Find(string operationId, int? version, EnvironmentScope scope);   // null = capability gap → DeadEnd
}

// ---------------------------------------------------------------------------------------------------- calls and results
public sealed record OperationCall(string OperationId, int Version, EnvironmentScope Scope, string InputJson, string IdempotencyKey, string CaseId, string TaskId, string RuntimeCallId, long FenceEpoch);

/// <summary>The instance of an approved write shape, as the auditor already passed it (contracts/write-shape.schema.json #WritePacket).
/// The connector re-derives nothing about the reasoning; it checks template ⊆ shape, scope ⊆ grant, hash == grant binding.</summary>
public sealed record WritePacketCall(
    OperationCall Call, string ShapeKey, int ShapeVersion, string ArtefactHash,
    IReadOnlyList<BoundStep> Steps, IReadOnlyList<Assertion> Assertions, IReadOnlyList<string> TeardownStatementHashes, bool PreflightRequired);

public sealed record BoundStep(string StepId, string StatementTemplateHash, IReadOnlyDictionary<string, object> BoundParameters, int ExpectedCount, EffectClass EffectClass);
public sealed record Assertion(string Name, string QueryOrCall, string Expect);

public sealed record WriteResult(
    IReadOnlyList<StepOutcome> Steps, int? TotalRows, VerifyResult? Verification, string WhatActuallyRan,
    string? TeardownLogRef, DateTimeOffset? CommittedOn, LandedDisposition Disposition);
public sealed record StepOutcome(string StepId, int ExpectedCount, int? ActualCount, IReadOnlyList<string> ReturnedIds, string StatementHash);
public sealed record VerifyResult(bool Pass, IReadOnlyList<AssertionOutcome> Assertions, string Level /* structural·priced·reachable·quoted·issued·verified */);
public sealed record AssertionOutcome(string Name, bool Pass, string Observed);
public sealed record CommitResult(DateTimeOffset CommittedOn, string SessionRef);

public sealed record WriteLease(string LeaseId, EnvironmentScope Scope, string SessionRef, long FenceEpoch, DateTimeOffset LeasedUntil);

public enum LandedDisposition { Unknown, NotApplied, PartlyApplied, Applied }
public sealed record LandedState(LandedDisposition Disposition, IReadOnlyList<StepOutcome> Landed, string Evidence);   // Evidence = how it was determined (idempotency key hit, row present, commit sha …)

public sealed record ReachProbe(RouteKind Kind, string Probe, DateTimeOffset ProbedOn, string? Detail);

/// <summary>A minimal Either so refusals stay values (not exceptions) across the seam.</summary>
public readonly struct Either<TOk, TRefused> where TOk : class where TRefused : class
{
    public TOk? Ok { get; }
    public TRefused? Refused { get; }
    public bool IsOk => Ok is not null && Refused is null;
    private Either(TOk? ok, TRefused? refused) { Ok = ok; Refused = refused; }
    public static Either<TOk, TRefused> FromOk(TOk ok) => new(ok ?? throw new ArgumentNullException(nameof(ok)), default);
    public static Either<TOk, TRefused> FromRefusal(TRefused refused) => new(default, refused ?? throw new ArgumentNullException(nameof(refused)));
}

// ---------------------------------------------------------------------------------------------------- the pipeline the runtime runs BEFORE any IConnector call
/// <summary>Agent Runtime § 2 rule 2 — policy, grant, environment scope, in that order; deny beats everything (pattern 2 of the adopted
/// harness patterns). Implemented once in AI.Support.Runtime; connectors additionally enforce grant and scope themselves.</summary>
public interface IPermissionPipeline
{
    Task<Either<GrantView, Refusal>> CheckAsync(string caseId, string taskId, OperationCall call, Mode requestedMode, CancellationToken ct);
}

/// <summary>The rendering of a case's live grants into the per-turn prompt (Trust and Data § 2: an agent is told what it may do).</summary>
public interface IGrantRenderer
{
    string RenderForPrompt(IReadOnlyList<GrantView> grants);
}
