///|
/// A half-open range of query source text.
pub struct QuerySpan {
  start : QueryPosition
  end : QueryPosition
} derive(Eq, Debug)

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

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

///|
pub(all) enum Severity {
  Error
  Warning
} derive(Eq, Debug)

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

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

///|
/// A compile or input diagnostic. `span` is present for query text and absent
/// for forms built in code.
pub struct QueryDiagnostic {
  code : String
  severity : Severity
  message : String
  span : QuerySpan?
} derive(Eq, Debug)

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

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

///|
/// Every diagnostic code that compilation and projection can give.
pub let query_diagnostic_codes : ReadOnlyArray[String] = [
  "query/syntax", "query/unterminated-string", "query/invalid-string", "query/invalid-name",
  "query/empty-query", "query/unsupported-quantifier", "query/unsupported-anchor",
  "query/unsupported-directive", "query/unsupported-regex", "query/unsupported-anonymous-node",
  "query/unsupported-grouping", "query/unknown-kind", "query/unknown-category", "query/unknown-field",
  "query/unbound-prefix", "query/invalid-namespace-map", "query/unknown-predicate", "query/predicate-arity",
  "query/unknown-capture", "query/duplicate-capture", "query/source-limit", "query/pattern-limit",
  "query/nesting-limit", "query/capture-limit", "query/invalid-limits", "query/unknown-projection-field",
  "query/projection-limit",
]

///|
fn error(code : String, message : String, span : QuerySpan?) -> QueryDiagnostic {
  { code, severity: Error, message, span }
}

///|
/// Maps UTF-16 offsets of a source text to lines and columns. CRLF, LF and
/// CR each count as one line break.
priv struct LineMap {
  starts : Array[Int]
}

///|
fn LineMap::new(source : String) -> LineMap {
  let starts = [0]
  let mut i = 0
  while i < source.length() {
    let unit = source.code_unit_at_int(i)
    if unit == '\r' {
      if i + 1 < source.length() && source.code_unit_at_int(i + 1) == '\n' {
        i += 1
      }
      starts.push(i + 1)
    } else if unit == '\n' {
      starts.push(i + 1)
    }
    i += 1
  }
  { starts, }
}

///|
fn LineMap::position(self : LineMap, offset : Int) -> QueryPosition {
  // The last line start at or before offset.
  let mut low = 0
  let mut high = self.starts.length() - 1
  while low < high {
    let middle = (low + high + 1) / 2
    if self.starts[middle] <= offset {
      low = middle
    } else {
      high = middle - 1
    }
  }
  { offset, line: low + 1, column: offset - self.starts[low] + 1 }
}

///|
fn LineMap::span(self : LineMap, start : Int, end : Int) -> QuerySpan {
  { start: self.position(start), end: self.position(end) }
}