///|
/// The typed subject of a trace request. An EBNF subject names its start rule
/// by rule identity.
pub(all) enum Subject {
  Regex(@regex.Regex)
  Ebnf(@ebnf.Grammar, String)
} derive(Eq, Debug)

///|
pub extend Subject with Eq::{equal, not_equal}

///|
pub extend Subject with @debug.Debug::{to_repr}

///|
pub fn Subject::kind(self : Subject) -> SubjectKind {
  match self {
    Regex(_) => Regex
    Ebnf(_, _) => Ebnf
  }
}

///|
pub fn Subject::model_id(self : Subject) -> String {
  match self {
    Regex(model) => model.id
    Ebnf(grammar, _) => grammar.id
  }
}

///|
/// The provenance of a trace: subject kind, model and view identity and the
/// structural fingerprint of the model.
pub struct SubjectIdentity {
  kind : SubjectKind
  model_id : String
  view_id : String
  fingerprint : String
  start_rule : String?
} derive(Eq, Debug)

///|
pub extend SubjectIdentity with Eq::{equal, not_equal}

///|
pub extend SubjectIdentity with @debug.Debug::{to_repr}

///|
pub fn Subject::identity(self : Subject, view_id? : String) -> SubjectIdentity {
  {
    kind: self.kind(),
    model_id: self.model_id(),
    view_id: view_id.unwrap_or(self.model_id()),
    fingerprint: self.fingerprint(),
    start_rule: match self {
      Ebnf(_, rule) => Some(rule)
      Regex(_) => None
    },
  }
}

///|
/// Explicit trace bounds. Providers honor them cooperatively; validation
/// enforces them for every supplied trace.
pub struct TraceLimits {
  input : Int
  attempts : Int
  steps : Int
  depth : Int
  work : Int
} derive(Eq, Debug)

///|
pub extend TraceLimits with Eq::{equal, not_equal}

///|
pub extend TraceLimits with @debug.Debug::{to_repr}

///|
pub fn TraceLimits::new(
  input? : Int = 65536,
  attempts? : Int = 256,
  steps? : Int = 100000,
  depth? : Int = 256,
  work? : Int = 1000000,
) -> TraceLimits {
  { input, attempts, steps, depth, work }
}

///|
pub fn TraceLimits::valid(self : TraceLimits) -> Bool {
  self.input > 0 &&
  self.attempts > 0 &&
  self.steps > 0 &&
  self.depth > 0 &&
  self.work > 0
}

///|
/// One immutable request to an external matching or parsing engine.
pub struct TraceRequest {
  subject : Subject
  view_id : String
  input : String
  limits : TraceLimits
} derive(Eq, Debug)

///|
pub extend TraceRequest with Eq::{equal, not_equal}

///|
pub extend TraceRequest with @debug.Debug::{to_repr}

///|
pub fn TraceRequest::regex(
  model : @regex.Regex,
  input : String,
  view_id? : String,
  limits? : TraceLimits = TraceLimits::new(),
) -> TraceRequest {
  { subject: Regex(model), view_id: view_id.unwrap_or(model.id), input, limits }
}

///|
pub fn TraceRequest::ebnf(
  grammar : @ebnf.Grammar,
  start_rule : String,
  input : String,
  view_id? : String,
  limits? : TraceLimits = TraceLimits::new(),
) -> TraceRequest {
  {
    subject: Ebnf(grammar, start_rule),
    view_id: view_id.unwrap_or(grammar.id),
    input,
    limits,
  }
}

///|
/// The provenance that a trace for this request must carry.
pub fn TraceRequest::identity(self : TraceRequest) -> SubjectIdentity {
  self.subject.identity(view_id=self.view_id)
}

///|
/// The semantic topology of the requested subject, for mapping engine events.
pub fn TraceRequest::topology(self : TraceRequest) -> TopologyResult {
  match self.subject {
    Regex(model) => Topology::regex(model)
    Ebnf(grammar, _) => Topology::ebnf(grammar)
  }
}

///|
/// A half-open UTF-16 span of the input.
pub struct InputSpan {
  start : Int
  end : Int
} derive(Eq, Debug)

///|
pub extend InputSpan with Eq::{equal, not_equal}

///|
pub extend InputSpan with @debug.Debug::{to_repr}

///|
pub fn InputSpan::new(start : Int, end : Int) -> InputSpan {
  { start, end }
}

///|
/// One oriented trace step. A visit passes through a term from its entry to
/// its exit. A traversal follows a drawn connector, forward or reversed.
pub(all) enum Step {
  Visit(String, InputSpan?)
  Traverse(String, Bool)
} derive(Eq, Debug)

///|
pub extend Step with Eq::{equal, not_equal}

///|
pub extend Step with @debug.Debug::{to_repr}

///|
/// The place where an attempt failed.
pub(all) enum FailureTarget {
  /// A consuming term, reference or empty term.
  Terminal(String)
  /// An anchor or lookaround constraint.
  Assertion(String)
  /// The entry boundary of a node, rule or pattern.
  Entry(String)
  /// The exit boundary of a node, rule or pattern, such as the end of input.
  Exit(String)
} derive(Eq, Debug)

///|
pub extend FailureTarget with Eq::{equal, not_equal}

///|
pub extend FailureTarget with @debug.Debug::{to_repr}

///|
pub struct FailurePoint {
  target : FailureTarget
  position : Int?
  message : String?
} derive(Eq, Debug)

///|
pub extend FailurePoint with Eq::{equal, not_equal}

///|
pub extend FailurePoint with @debug.Debug::{to_repr}

///|
pub fn FailurePoint::new(
  target : FailureTarget,
  position? : Int,
  message? : String,
) -> FailurePoint {
  { target, position, message }
}

///|
pub(all) enum AttemptStatus {
  Matched
  NotMatched
} derive(Eq, Debug)

///|
pub extend AttemptStatus with Eq::{equal, not_equal}

///|
pub extend AttemptStatus with @debug.Debug::{to_repr}

///|
/// One finite attempt of an external engine. Repeated or backtracked steps
/// stay separate, even when they reuse a term or connector.
pub struct Attempt {
  id : String
  status : AttemptStatus
  steps : ReadOnlyArray[Step]
  failure : FailurePoint?
  /// True when consecutive steps must meet at shared points.
  continuous : Bool
  label : String?
} derive(Eq, Debug)

///|
pub extend Attempt with Eq::{equal, not_equal}

///|
pub extend Attempt with @debug.Debug::{to_repr}

///|
pub fn Attempt::new(
  id : String,
  status : AttemptStatus,
  steps : ArrayView[Step],
  failure? : FailurePoint,
  continuous? : Bool = true,
  label? : String,
) -> Attempt {
  {
    id,
    status,
    steps: ReadOnlyArray::from_array(steps),
    failure,
    continuous,
    label,
  }
}

///|
/// Immutable evidence that an external engine supplied. It is separate from
/// the diagram and is never computed by this library.
pub struct Trace {
  subject : SubjectIdentity
  input : String
  provider : String
  attempts : ReadOnlyArray[Attempt]
} derive(Eq, Debug)

///|
pub extend Trace with Eq::{equal, not_equal}

///|
pub extend Trace with @debug.Debug::{to_repr}

///|
/// Make a trace bound to the subject of `request`.
pub fn Trace::new(
  request : TraceRequest,
  provider : String,
  attempts : ArrayView[Attempt],
) -> Trace {
  {
    subject: request.identity(),
    input: request.input,
    provider,
    attempts: ReadOnlyArray::from_array(attempts),
  }
}

///|
/// The outcome of a provider. Unsupported, exhausted and error results are
/// not evidence of a non-match.
pub(all) enum TraceResult {
  Matched(Trace)
  NotMatched(Trace)
  Unsupported(String)
  Exhausted(String)
  ProviderError(String)
} derive(Eq, Debug)

///|
pub extend TraceResult with Eq::{equal, not_equal}

///|
pub extend TraceResult with @debug.Debug::{to_repr}

///|
/// A publicly implementable hook for an external regex matcher or grammar
/// parser. This library bundles no implementation. A provider that supports
/// one subject kind returns `Unsupported` for the other.
pub(open) trait RailroadTraceProvider {
  trace(Self, TraceRequest) -> TraceResult
}