///|
/// Return the package release version.
pub fn package_version() -> String {
  "0.1.0"
}

///|
/// Selects the rendered representation of a Pinyin syllable.
pub(all) enum ToneStyle {
  ToneMarks
  ToneNumbers
  PlainPinyin
} derive(Eq, Debug)

///|
/// Controls casing after a syllable has been normalized.
pub(all) enum TextCase {
  Lowercase
  Uppercase
} derive(Eq, Debug)

///|
/// Controls how text without a known Pinyin reading is handled.
pub(all) enum UnknownPolicy {
  PreserveUnknown
  DropUnknown
  RejectUnknown
} derive(Eq, Debug)

///|
/// Controls how multiple documented readings are exposed.
pub(all) enum AmbiguityPolicy {
  SelectFirstReading
  PreserveAllReadings
  RejectAmbiguous
} derive(Eq, Debug)

///|
/// Stable errors returned by parsing, configuration, and conversion APIs.
pub(all) enum PinyinError {
  InvalidSeparator(String)
  InvalidSyllable(String, String)
  UnknownCharacter(Int)
  AmbiguousCharacter(Int, Int)
  InvalidSourceSpan(Int, Int)
  InvalidLexiconEntry(String, String)
  ConflictingLexiconEntry(String)
  InvalidLexiconText(String)
  InvalidBatchItem(String, String)
  InvalidSearchDocument(String, String)
  InvalidSearchOptions(String, String)
  InvalidSearchQuery(String)
  InvalidVariantLimit(Int)
  InvalidConversionResult(String)
  InvalidAlignment(String)
  InvalidWeight(String, Double)
  InvalidGeneratedData(String)
} derive(Eq, Debug)

///|
/// Half-open character offsets in the caller's source text.
pub struct SourceSpan {
  start_offset : Int
  end_offset : Int
} derive(Eq, Debug)

///|
/// Structured warning or decision note emitted by an operation.
pub struct Diagnostic {
  code_value : String
  message_value : String
  source_span : SourceSpan?
} derive(Eq, Debug)

///|
/// Immutable options shared by conversion and rendering operations.
pub struct ConvertOptions {
  output_style : ToneStyle
  token_separator : String
  output_case : TextCase
  unknown_behavior : UnknownPolicy
  ambiguity_behavior : AmbiguityPolicy
} derive(Eq, Debug)

///|
/// Creates a validated half-open source span.
pub fn source_span(start : Int, end : Int) -> Result[SourceSpan, PinyinError] {
  if start < 0 || end < start {
    return Err(InvalidSourceSpan(start, end))
  }
  Ok({ start_offset: start, end_offset: end })
}

///|
pub fn SourceSpan::start(self : SourceSpan) -> Int {
  self.start_offset
}

///|
pub fn SourceSpan::end(self : SourceSpan) -> Int {
  self.end_offset
}

///|
pub fn SourceSpan::length(self : SourceSpan) -> Int {
  self.end_offset - self.start_offset
}

///|
/// Creates a structured diagnostic without interpreting its message.
pub fn diagnostic(
  code : String,
  message : String,
  span : SourceSpan?,
) -> Diagnostic {
  { code_value: code, message_value: message, source_span: span }
}

///|
pub fn Diagnostic::code(self : Diagnostic) -> String {
  self.code_value
}

///|
pub fn Diagnostic::message(self : Diagnostic) -> String {
  self.message_value
}

///|
pub fn Diagnostic::span(self : Diagnostic) -> SourceSpan? {
  self.source_span
}

///|
/// Builds a validated conversion configuration.
pub fn convert_options(
  style : ToneStyle,
  separator : String,
  text_case : TextCase,
  unknown_policy : UnknownPolicy,
  ambiguity_policy : AmbiguityPolicy,
) -> Result[ConvertOptions, PinyinError] {
  if separator.length() == 0 {
    return Err(InvalidSeparator("empty"))
  }
  if separator == "\n" || separator == "\r" || separator == "\r\n" {
    return Err(InvalidSeparator("line_break"))
  }
  Ok({
    output_style: style,
    token_separator: separator,
    output_case: text_case,
    unknown_behavior: unknown_policy,
    ambiguity_behavior: ambiguity_policy,
  })
}

///|
/// Returns conservative display defaults without hiding ambiguity metadata.
pub fn default_convert_options() -> ConvertOptions {
  convert_options(
    ToneMarks,
    " ",
    Lowercase,
    PreserveUnknown,
    SelectFirstReading,
  ).unwrap()
}

///|
pub fn ConvertOptions::tone_style(self : ConvertOptions) -> ToneStyle {
  self.output_style
}

///|
pub fn ConvertOptions::separator(self : ConvertOptions) -> String {
  self.token_separator
}

///|
pub fn ConvertOptions::text_case(self : ConvertOptions) -> TextCase {
  self.output_case
}

///|
pub fn ConvertOptions::unknown_policy(self : ConvertOptions) -> UnknownPolicy {
  self.unknown_behavior
}

///|
pub fn ConvertOptions::ambiguity_policy(
  self : ConvertOptions,
) -> AmbiguityPolicy {
  self.ambiguity_behavior
}