///|
/// UiPort: structured UI intents and interactive requests. UI-toolkit-
/// agnostic — hosts implement this trait and translate intents into
/// their rendering primitives. Three responsibilities: `ui_descriptor`,
/// `render`, `request`.

// Semantic slots and bodies

///|
/// Where a render intent lands: Status (single-line bar), Notice (transient toast), Widget (persistent region).
pub(all) enum UiSlot {
  Status
  Notice
  Widget
} derive(Eq, Debug)

///|
pub impl Show for UiSlot with fn to_string(self) -> String {
  match self {
    Status => "Status"
    Notice => "Notice"
    Widget => "Widget"
  }
}

///|
/// Structured payload a render intent carries. Host decides layout and color.
pub(all) enum UiBody {
  Text(String)
  Lines(Array[String])
  KeyValue(Array[(String, String)])
  Progress(Double)
  Markdown(String)
} derive(Debug)

///|
pub impl Show for UiBody with fn to_string(self) -> String {
  match self {
    Text(s) => "Text(\{s.length()} chars)"
    Lines(lines) => "Lines(\{lines.length()})"
    KeyValue(pairs) => "KeyValue(\{pairs.length()})"
    Progress(p) => "Progress(\{p})"
    Markdown(s) => "Markdown(\{s.length()} chars)"
  }
}

///|
/// One render intent. Same `key` replaces a prior intent in the same `UiSlot`.
pub(all) struct UiRender {
  slot : UiSlot
  key : String
  title : String?
  body : UiBody
  /// Visibility hint in milliseconds. `None` = persistent; hosts may ignore.
  ttl_ms : Int?
} derive(Debug)

///|
pub impl Show for UiRender with fn to_string(self) -> String {
  let title_repr = match self.title {
    Some(t) => "Some(\"" + t + "\")"
    None => "None"
  }
  "UiRender(slot=" +
  self.slot.to_string() +
  ", key=\"" +
  self.key +
  "\", title=" +
  title_repr +
  ", body=" +
  self.body.to_string() +
  ")"
}

// Autocomplete descriptor

///|
pub(all) struct AutocompleteItem {
  label : String
  insert_text : String
  detail : String?
  kind : String
} derive(Debug)

///|
/// A source of autocomplete suggestions tied to a trigger character.
pub(all) struct AutocompleteSource {
  trigger : String
  kind : String
  /// Given text typed so far (excluding trigger), return matching items.
  fetch : (String) -> Array[AutocompleteItem]
} derive(Debug)

///|
/// Collected at composition time: the host aggregates every UiPort's
/// descriptors to know which autocomplete triggers to honor.
pub(all) struct UiDescriptor {
  autocomplete_sources : Array[AutocompleteSource]
} derive(Debug)

///|
/// Empty descriptor used by UIs that contribute no autocomplete sources.
pub fn UiDescriptor::empty() -> UiDescriptor {
  { autocomplete_sources: [] }
}

// Interactive request / response

///|
/// User interaction primitives. Hosts that lack a variant raise
/// `UiError::Unsupported`.
pub(all) enum UiRequest {
  /// Free-form text input, with optional pre-fill.
  Input(prompt~ : String, default~ : String?)
  /// Yes/no confirmation.
  Confirm(prompt~ : String)
  /// Choose one option. `default` is the pre-select index.
  Select(prompt~ : String, options~ : Array[String], default~ : Int?)
} derive(Debug)

///|
pub impl Show for UiRequest with fn to_string(self) -> String {
  match self {
    Input(prompt~, default~) =>
      "UiRequest::Input(prompt=\"" +
      prompt +
      "\", default=" +
      str_default_label(default) +
      ")"
    Confirm(prompt~) => "UiRequest::Confirm(prompt=\"" + prompt + "\")"
    Select(prompt~, options~, default~) =>
      "UiRequest::Select(prompt=\"" +
      prompt +
      "\", options=" +
      options.length().to_string() +
      ", default=" +
      int_default_label(default) +
      ")"
  }
}

///|
fn str_default_label(d : String?) -> String {
  match d {
    Some(s) => "Some(\"" + s + "\")"
    None => "None"
  }
}

///|
fn int_default_label(d : Int?) -> String {
  match d {
    Some(i) => "Some(" + i.to_string() + ")"
    None => "None"
  }
}

///|
/// Response to a UiRequest. `Cancelled` = user dismissed; no UI = `UiError::Unsupported`.
pub(all) enum UiResponse {
  Text(String)
  Yes
  No
  Selected(Int)
  Cancelled
} derive(Eq, Debug)

///|
pub impl Show for UiResponse with fn to_string(self) -> String {
  match self {
    Text(s) => "UiResponse::Text(\"" + s + "\")"
    Yes => "UiResponse::Yes"
    No => "UiResponse::No"
    Selected(i) => "UiResponse::Selected(" + i.to_string() + ")"
    Cancelled => "UiResponse::Cancelled"
  }
}

// UiPort trait

///|
/// The UI extension contract. Implemented by host packages (cetas-js,
/// cetas-native) or testkit.
pub(open) trait UiPort {
  /// Declare what this UI offers (autocomplete sources). Called once.
  fn ui_descriptor(Self) -> UiDescriptor

  /// Push a render intent to the host. Same `key` replaces a prior intent.
  fn render(Self, intent : UiRender) -> Unit

  /// Send an interactive request and wait for a response. Raise
  /// `UiError::Unsupported` if this UI cannot service requests.
  async fn request(Self, req : UiRequest) -> UiResponse raise @error.UiError
}