///|
/// Validation issue for the TUI-oriented core layout subset.
pub(all) struct CoreIssue {
  node_id : String
  property : String
  detail : String
}

///|
/// Result for checked core layout computation.
pub(all) struct CoreComputeResult {
  layout : @layout_types.Layout?
  issues : Array[CoreIssue]
}

///|
/// Validate a node tree against the predictable core (TUI-oriented) subset.
pub fn validate_core_subset(node : @layout.Node) -> Array[CoreIssue] {
  let issues : Array[CoreIssue] = []
  validate_tui_subset_impl(node, issues)
  issues
}

///|
/// Compute layout only when the node tree satisfies core subset constraints.
/// Returns issues without computing if unsupported styles are present.
pub fn compute_core_layout(
  node : @layout.Node,
  viewport : @types.Size[Double],
) -> CoreComputeResult {
  let issues = validate_core_subset(node)
  if issues.length() > 0 {
    return { layout: None, issues }
  }
  let ctx : @layout_types.LayoutContext = {
    available_width: viewport.width,
    available_height: Some(viewport.height),
    sizing_mode: @layout_types.MaxContent,
    viewport_width: viewport.width,
    viewport_height: viewport.height,
    stretch_width: false,
    stretch_height: false,
  }
  { layout: Some(@layout.compute_layout_in_context(node, ctx)), issues: [] }
}

///|
fn validate_tui_subset_impl(
  node : @layout.Node,
  issues : Array[CoreIssue],
) -> Unit {
  validate_style(node.id, node.style, issues)
  for child in node.children {
    validate_tui_subset_impl(child, issues)
  }
}

///|
fn validate_style(
  node_id : String,
  style : @style.Style,
  issues : Array[CoreIssue],
) -> Unit {
  if !is_supported_display(style.display) {
    add_issue(issues, node_id, "display", "unsupported display in core subset")
  }
  if style.position != @types.Position::Static {
    add_issue(issues, node_id, "position", "only position: static is supported")
  }
  if style.float != @types.CssFloat::None {
    add_issue(issues, node_id, "float", "float layout is outside core subset")
  }
  if style.clear != @types.Clear::None {
    add_issue(issues, node_id, "clear", "clear is outside core subset")
  }
  if !is_supported_flex_direction(style.flex_direction) {
    add_issue(
      issues, node_id, "flex_direction", "only row/column are supported",
    )
  }
  if style.flex_wrap != @types.FlexWrap::NoWrap {
    add_issue(issues, node_id, "flex_wrap", "flex-wrap is outside core subset")
  }

  validate_dimension(node_id, "width", style.width, issues)
  validate_dimension(node_id, "height", style.height, issues)
  validate_dimension(node_id, "min_width", style.min_width, issues)
  validate_dimension(node_id, "min_height", style.min_height, issues)
  validate_dimension(node_id, "max_width", style.max_width, issues)
  validate_dimension(node_id, "max_height", style.max_height, issues)
  validate_dimension(node_id, "flex_basis", style.flex_basis, issues)
  validate_dimension(node_id, "row_gap", style.row_gap, issues)
  validate_dimension(node_id, "column_gap", style.column_gap, issues)
  validate_rect_dimension(node_id, "margin", style.margin, issues)
  validate_rect_dimension(node_id, "padding", style.padding, issues)
  validate_rect_dimension(node_id, "border", style.border, issues)
  validate_rect_dimension(node_id, "inset", style.inset, issues)

  if !is_supported_alignment(style.justify_content) {
    add_issue(
      issues, node_id, "justify_content", "unsupported alignment in core subset",
    )
  }
  if !is_supported_alignment(style.align_items) {
    add_issue(
      issues, node_id, "align_items", "unsupported alignment in core subset",
    )
  }
  if !is_supported_alignment(style.align_content) {
    add_issue(
      issues, node_id, "align_content", "unsupported alignment in core subset",
    )
  }
  if !is_supported_alignment(style.justify_items) {
    add_issue(
      issues, node_id, "justify_items", "unsupported alignment in core subset",
    )
  }
  if !is_supported_align_self(style.align_self) {
    add_issue(
      issues, node_id, "align_self", "unsupported align-self in core subset",
    )
  }
  if !is_supported_align_self(style.justify_self) {
    add_issue(
      issues, node_id, "justify_self", "unsupported justify-self in core subset",
    )
  }

  validate_track_list(
    node_id,
    "grid_template_columns",
    style.grid_template_columns,
    issues,
  )
  validate_track_list(
    node_id,
    "grid_template_rows",
    style.grid_template_rows,
    issues,
  )
  validate_track_list(
    node_id,
    "grid_auto_columns",
    style.grid_auto_columns,
    issues,
  )
  validate_track_list(node_id, "grid_auto_rows", style.grid_auto_rows, issues)

  if style.display == @types.Display::Grid &&
    (
      style.grid_template_columns.length() == 0 ||
      style.grid_template_rows.length() == 0
    ) {
    add_issue(
      issues, node_id, "grid_template", "grid requires explicit rows and columns in core subset",
    )
  }

  if !is_supported_grid_line(style.grid_column) {
    add_issue(
      issues, node_id, "grid_column", "grid line must use positive line/span values",
    )
  }
  if !is_supported_grid_line(style.grid_row) {
    add_issue(
      issues, node_id, "grid_row", "grid line must use positive line/span values",
    )
  }

  if style.writing_mode != @style.WritingMode::HorizontalTb {
    add_issue(
      issues, node_id, "writing_mode", "vertical writing mode is outside core subset",
    )
  }
  if style.direction != @style.Direction::Ltr {
    add_issue(
      issues, node_id, "direction", "rtl direction is outside core subset",
    )
  }
  if !style.transform.is_none() {
    add_issue(issues, node_id, "transform", "transform is outside core subset")
  }
  if style.zoom != 1.0 {
    add_issue(issues, node_id, "zoom", "zoom is outside core subset")
  }
  if style.contain.has_containment() {
    add_issue(issues, node_id, "contain", "containment is outside core subset")
  }
}

///|
fn validate_dimension(
  node_id : String,
  property : String,
  dim : @types.Dimension,
  issues : Array[CoreIssue],
) -> Unit {
  if !is_supported_dimension(dim) {
    add_issue(
      issues, node_id, property, "only auto/length/percent dimensions are supported",
    )
  }
}

///|
fn validate_rect_dimension(
  node_id : String,
  property : String,
  rect : @types.Rect[@types.Dimension],
  issues : Array[CoreIssue],
) -> Unit {
  validate_dimension(node_id, property, rect.top, issues)
  validate_dimension(node_id, property, rect.right, issues)
  validate_dimension(node_id, property, rect.bottom, issues)
  validate_dimension(node_id, property, rect.left, issues)
}

///|
fn validate_track_list(
  node_id : String,
  property : String,
  tracks : Array[@types.TrackSizingFunction],
  issues : Array[CoreIssue],
) -> Unit {
  for track in tracks {
    if !is_supported_track(track) {
      add_issue(
        issues, node_id, property, "track sizing must be length/percent/fr/auto",
      )
    }
  }
}

///|
fn add_issue(
  issues : Array[CoreIssue],
  node_id : String,
  property : String,
  detail : String,
) -> Unit {
  issues.push({ node_id, property, detail })
}

///|
fn is_supported_display(display : @types.Display) -> Bool {
  match display {
    @types.Display::Block
    | @types.Display::Inline
    | @types.Display::InlineBlock
    | @types.Display::Flex
    | @types.Display::Grid
    | @types.Display::None => true
    _ => false
  }
}

///|
fn is_supported_flex_direction(direction : @types.FlexDirection) -> Bool {
  match direction {
    @types.FlexDirection::Row | @types.FlexDirection::Column => true
    _ => false
  }
}

///|
fn is_supported_dimension(dim : @types.Dimension) -> Bool {
  match dim {
    @types.Dimension::Auto
    | @types.Dimension::Length(_)
    | @types.Dimension::Percent(_) => true
    _ => false
  }
}

///|
fn is_supported_alignment(alignment : @types.Alignment) -> Bool {
  match alignment {
    @types.Alignment::Start
    | @types.Alignment::End
    | @types.Alignment::Left
    | @types.Alignment::Right
    | @types.Alignment::FlexStart
    | @types.Alignment::FlexEnd
    | @types.Alignment::Center
    | @types.Alignment::SpaceBetween
    | @types.Alignment::SpaceAround
    | @types.Alignment::SpaceEvenly
    | @types.Alignment::Stretch => true
    @types.Alignment::Baseline => false
  }
}

///|
fn is_supported_align_self(align : @types.AlignSelf) -> Bool {
  match align {
    @types.AlignSelf::Auto
    | @types.AlignSelf::Start
    | @types.AlignSelf::End
    | @types.AlignSelf::Center
    | @types.AlignSelf::Stretch => true
    @types.AlignSelf::Baseline => false
  }
}

///|
fn is_supported_track(track : @types.TrackSizingFunction) -> Bool {
  match track {
    @types.TrackSizingFunction::Length(_)
    | @types.TrackSizingFunction::Percent(_)
    | @types.TrackSizingFunction::Fr(_)
    | @types.TrackSizingFunction::Auto => true
    _ => false
  }
}

///|
fn is_supported_grid_line(line : @types.GridLine) -> Bool {
  is_supported_grid_placement(line.start) &&
  is_supported_grid_placement(line.end)
}

///|
fn is_supported_grid_placement(placement : @types.GridPlacement) -> Bool {
  match placement {
    @types.GridPlacement::Auto => true
    @types.GridPlacement::Line(v) | @types.GridPlacement::Span(v) => v > 0
    @types.GridPlacement::Named(_) => true
    @types.GridPlacement::NamedLine(_, _)
    | @types.GridPlacement::SpanNamed(_, _) => true
  }
}