#!/usr/bin/env bash
# ui/rule.module.sh — rendering for rule data
# Replaces rule::render_* functions from rule.module.sh.
# All functions pure rendering — no writes, no state changes.

# ======================================================
# Entry Rendering (shared primitives)
# ======================================================

# ui::rule::entries <rule_name> [indent]
# Renders the fully resolved entries for a rule (allow + block).
function ui::rule::entries() {
  local rule_name="${1:-}" indent="${2:-4}"
 
  local allow_ports allow_ips block_ips block_ports dns
  allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
  allow_ips=$(rule::get   "$rule_name" "allow_ips"   2>/dev/null || true)
  block_ips=$(rule::get   "$rule_name" "block_ips"   2>/dev/null || true)
  block_ports=$(rule::get "$rule_name" "block_ports"  2>/dev/null || true)
  dns=$(rule::get_own     "$rule_name" "dns_redirect")
 
  while IFS= read -r e; do
    [[ -z "$e" ]] && continue
    net::print_entry "+" "$e" "$indent"
  done <<< "$allow_ports"$'\n'"$allow_ips"
 
  while IFS= read -r e; do
    [[ -z "$e" ]] && continue
    net::print_entry "-" "$e" "$indent"
  done <<< "$block_ips"$'\n'"$block_ports"
 
  [[ "${dns,,}" == "true" ]] && \
    net::print_dns_redirect "$(config::dns)" 6 "DNS"
}

# ui::rule::own_entries <rule_name> [indent]
# Renders only the rule's own (non-inherited) entries.
function ui::rule::own_entries() {
  local rule_name="${1:-}" indent="${2:-4}"
  local rule_file
  rule_file="$(rule::path "$rule_name")" || return 0
  [[ -z "$rule_file" ]] && return 0
 
  local allow_ports allow_ips block_ips block_ports dns
  allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
  allow_ips=$(json::get   "$rule_file" "allow_ips"   2>/dev/null || true)
  block_ips=$(json::get   "$rule_file" "block_ips"   2>/dev/null || true)
  block_ports=$(json::get "$rule_file" "block_ports"  2>/dev/null || true)
  dns=$(json::get         "$rule_file" "dns_redirect" 2>/dev/null || true)
 
  local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
  [[ -z "${combined//[$'\n']/}" ]] && return 0
 
  while IFS= read -r e; do
    [[ -z "$e" ]] && continue
    net::print_entry "+" "$e" "$indent"
  done <<< "$allow_ports"$'\n'"$allow_ips"
 
  while IFS= read -r e; do
    [[ -z "$e" ]] && continue
    net::print_entry "-" "$e" "$indent"
  done <<< "$block_ips"$'\n'"$block_ports"
 
  [[ "${dns,,}" == "true" ]] && \
    net::print_dns_redirect "$(config::dns)" 6 "DNS"
}

# ui::rule::flat <rule_name>
# Renders the full resolved entries as a flat list.
function ui::rule::flat() {
  local rule_name="${1:-}"
 
  local allow_ports allow_ips block_ips block_ports dns
  allow_ports=$(rule::get "$rule_name" "allow_ports")
  allow_ips=$(rule::get   "$rule_name" "allow_ips")
  block_ips=$(rule::get   "$rule_name" "block_ips")
  block_ports=$(rule::get "$rule_name" "block_ports")
  dns=$(rule::get_own     "$rule_name" "dns_redirect")
 
  local has_content=false
  [[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && has_content=true
 
  if ! $has_content; then
    printf "\n    full access (no restrictions)\n"
    return 0
  fi
 
  if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
    printf "\n"
    while IFS= read -r e; do
      [[ -z "$e" ]] && continue
      net::print_entry "+" "$e" 2
    done <<< "$allow_ports"$'\n'"$allow_ips"
  fi
 
  if [[ -n "$block_ips" || -n "$block_ports" ]]; then
    printf "\n"
    while IFS= read -r e; do
      [[ -z "$e" ]] && continue
      net::print_entry "-" "$e" 2
    done <<< "$block_ips"$'\n'"$block_ports"
  fi
 
  [[ "${dns,,}" == "true" ]] && \
    net::print_dns_redirect "$(config::dns)" 6 "DNS"
}

# ======================================================
# Shared Base Renderer
# ======================================================

# ui::rule::_render_bases <array_nameref> [entry_indent] [label_indent]
# Renders a list of base rule names with newlines between them.
# Used by both ui::rule::tree and ui::rule::_identity_rule_entry.
function ui::rule::_render_bases() {
  local -n _bases="$1"
  local entry_indent="${2:-6}" label_indent="${3:-4}"
  local label_pad
  label_pad=$(printf '%*s' "$label_indent" '')
 
  local first=true
  for base_name in "${_bases[@]}"; do
    [[ -z "$base_name" ]] && continue
    $first || printf "\n"
    first=false
    printf "%s\033[0;37m↳ %s\033[0m\n" "$label_pad" "$base_name"
    ui::rule::entries "$base_name" "$entry_indent"
  done
}

# ======================================================
# Tree Rendering
# ======================================================

# ui::rule::tree <rule_name>
# Renders a rule's extends tree — one level deep with own entries.
# Returns 1 if rule has no extends (caller can fall back to flat).
function ui::rule::tree() {
  local rule_name="${1:-}"
  local rule_file
  rule_file="$(rule::path "$rule_name")" || return 1
  [[ -z "$rule_file" ]] && return 1
 
  local extends_raw=()
  mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
 
  if [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]]; then
    return 1
  fi
 
  ui::rule::_render_bases extends_raw 6 4
 
  local own_output
  own_output=$(ui::rule::own_entries "$rule_name" 6)
  if [[ -n "$own_output" ]]; then
    printf "\n    \033[0;37mOwn:\033[0m\n"
    printf "%s\n" "$own_output"
  fi
 
  return 0
}

# ======================================================
# Identity Rule Block
# ======================================================

# ui::rule::identity_block <identity_name> <strict_rule>
# Renders the full identity rule block in inspect.
function ui::rule::identity_block() {
  local identity_name="${1:-}" strict="${2:-false}" no_header=false
 
  # Parse optional flags
  shift 2 || true
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --no-header) no_header=true; shift ;;
      *) shift ;;
    esac
  done
 
  local rules
  rules=$(identity::rules "$identity_name")
  [[ -z "$rules" ]] && return 0
 
  if ! $no_header; then
    printf "\n    \033[0;37m· identity:%s\033[0m\n" "$identity_name"
  fi
 
  # Indentation levels:
  # normal:    entry=6  label=6   own=10  own_label=8
  # no-header: entry=4  label=4   own=8   own_label=6
  local entry_indent label_indent own_indent own_label_indent
  if $no_header; then
    entry_indent=4
    label_indent=4
    own_indent=8
    own_label_indent=6
  else
    entry_indent=6
    label_indent=6
    own_indent=10
    own_label_indent=8
  fi
 
  local first=true
  while IFS= read -r rule_name; do
    [[ -z "$rule_name" ]] && continue
    $first || printf "\n"
    first=false
    ui::rule::_identity_rule_entry "$rule_name" \
      "$entry_indent" "$label_indent" "$own_indent" "$own_label_indent"
  done <<< "$rules"
 
  if [[ "$strict" == "true" ]]; then
    printf "\n      \033[2m(strict — peer rule suppressed)\033[0m\n"
  fi
}

# ui::rule::_identity_rule_entry <rule_name>
# Renders one rule within an identity block.
function ui::rule::_identity_rule_entry() {
  local rule_name="${1:-}"
  local entry_indent="${2:-6}"
  local label_indent="${3:-6}"
  local own_indent="${4:-10}"
  local own_label_indent="${5:-8}"
 
  local rule_file
  rule_file="$(rule::path "$rule_name")" || return 0
 
  local label_pad
  label_pad=$(printf '%*s' "$label_indent" '')
  printf "%s\033[0;37m↳ %s\033[0m\n" "$label_pad" "$rule_name"
 
  local extends_raw=()
  mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
 
  if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
    ui::rule::_render_bases extends_raw "$own_indent" "$(( entry_indent + 2 ))"
 
    local own_output
    own_output=$(ui::rule::own_entries "$rule_name" "$own_indent")
    if [[ -n "$own_output" ]]; then
      local own_pad
      own_pad=$(printf '%*s' "$(( entry_indent + 2 ))" '')
      printf "\n%s\033[0;37mOwn:\033[0m\n" "$own_pad"
      printf "%s\n" "$own_output"
    fi
  else
    local own_output
    own_output=$(ui::rule::own_entries "$rule_name" "$entry_indent")
    if [[ -n "$own_output" ]]; then
      printf "%s\n" "$own_output"
    else
      local full_pad
      full_pad=$(printf '%*s' "$(( entry_indent + 2 ))" '')
      printf "%s\033[2mfull access (no restrictions)\033[0m\n" "$full_pad"
    fi
  fi
}

# ======================================================
# Peer Entry
# ======================================================

function ui::rule::_peer_rule_entry() {
  local rule_name="${1:-}"
  local rule_file
  rule_file="$(rule::path "$rule_name")" || return 0
 
  printf "      \033[0;37m↳ %s\033[0m\n" "$rule_name"
 
  local extends_raw=()
  mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true) || true
 
  if [[ ${#extends_raw[@]} -gt 0 && -n "${extends_raw[0]:-}" ]]; then
    ui::rule::_render_bases extends_raw 10 8
 
    local own_output
    own_output=$(ui::rule::own_entries "$rule_name" 10)
    if [[ -n "$own_output" ]]; then
      printf "\n        \033[0;37mOwn:\033[0m\n"
      printf "%s\n" "$own_output"
    fi
  else
    local own_output
    own_output=$(ui::rule::own_entries "$rule_name" 8)
    if [[ -n "$own_output" ]]; then
      printf "%s\n" "$own_output"
    else
      printf "        \033[2mfull access (no restrictions)\033[0m\n"
    fi
  fi
}

# ======================================================
# List Rendering
# ======================================================
 
# ui::rule::list_group_header <group_name>
function ui::rule::list_group_header() {
  local group="${1:-}"
  printf "\n  \033[0;36m▸ %s\033[0m\n" "$group"
}
 
# ui::rule::list_base_header
function ui::rule::list_base_header() {
  printf "\n  \033[2m── Base Rules ──────────────────────\033[0m\n"
}

 
# ui::rule::list_row <name> <n_allows> <n_blocks> <peer_count> <w_name>
function ui::rule::list_row() {
  local name="${1:-}" n_allows="${2:-0}" n_blocks="${3:-0}" \
        peer_count="${4:-0}" w_name="${5:-16}" extends_csv="${6:-}"
 
  local name_pad
  name_pad=$(printf "%-${w_name}s" "$name")
 
  local peer_word="peers"
  [[ "$peer_count" -eq 1 ]] && peer_word="peer"
 
  local peers_display
  peers_display=$(printf "%s %s" "$peer_count" "$peer_word")
  local peers_pad_n=$(( 10 - ${#peers_display} ))
  [[ $peers_pad_n -lt 0 ]] && peers_pad_n=0
 
  local extends_indicator=""
  if [[ -n "$extends_csv" ]]; then
    local extends_display="${extends_csv//,/, }"
    extends_indicator="   \033[2m↳ ${extends_display}\033[0m"
  fi
 
  # Allows — green +N, padded to 5 visible chars
  # Append spaces after reset code so printf doesn't miscount
  local allows_str
  if [[ "$n_allows" -gt 0 ]]; then
    printf -v allows_str "\033[1;32m+%s\033[0m" "$n_allows"
    allows_str="${allows_str}$(printf '%*s' "$(( 4 - ${#n_allows} ))" '')"
  else
    printf -v allows_str "\033[2m+0\033[0m   "  # "+0" = 2 visible + 3 spaces = 5
  fi
 
  # Blocks — red -N, padded to 5 visible chars
  local blocks_str
  if [[ "$n_blocks" -gt 0 ]]; then
    printf -v blocks_str "\033[1;31m-%s\033[0m" "$n_blocks"
    blocks_str="${blocks_str}$(printf '%*s' "$(( 4 - ${#n_blocks} ))" '')"
  else
    printf -v blocks_str "\033[2m-0\033[0m   "  # "-0" = 2 visible + 3 spaces = 5
  fi
 
  # Peers — dim if zero
  local peers_colored
  if [[ "$peer_count" -eq 0 ]]; then
    peers_colored="\033[2m${peers_display}\033[0m"
  else
    peers_colored="$peers_display"
  fi
 
  printf "  %s  %b%b  %b%*s%b\n" \
    "$name_pad" "$allows_str" "$blocks_str" \
    "$peers_colored" "$peers_pad_n" "" "$extends_indicator"
}

# ui::rule::list_extends <extends_csv>
# Renders the extends tree for a rule in list view (compact, one level)
function ui::rule::list_extends() {
  local extends_csv="${1:-}"
  [[ -z "$extends_csv" ]] && return 0
 
  local extend_list=()
  IFS=',' read -ra extend_list <<< "$extends_csv"
  for base in "${extend_list[@]}"; do
    [[ -z "$base" ]] && continue
    printf "  \033[0;37m  ↳ %s\033[0m\n" "$base"
  done
}
 
# ui::rule::list_extends_detailed <extends_csv> <rules_dir>
# Renders the extends tree with entries expanded (--detailed mode)
function ui::rule::list_extends_detailed() {
  local extends_csv="${1:-}" rules_dir="${2:-}"
  [[ -z "$extends_csv" ]] && return 0
 
  local extend_list=()
  IFS=',' read -ra extend_list <<< "$extends_csv"
  for base in "${extend_list[@]}"; do
    [[ -z "$base" ]] && continue
    printf "  \033[0;37m  ↳ %s\033[0m\n" "$base"
    ui::rule::entries "$base" 6
  done
}

# ======================================================
# Table view
# ======================================================

function ui::rule::list_header_table() {
  printf "\n  %-20s %-6s %-6s %-8s %-20s %s\n" \
    "NAME" "ALLOW" "BLOCK" "PEERS" "EXTENDS" "GROUP"
  printf "  %s\n" "$(printf '─%.0s' {1..75})"
}
 
function ui::rule::list_row_table() {
  local name="${1:-}" n_allows="${2:-0}" n_blocks="${3:-0}" \
        peer_count="${4:-0}" extends="${5:-}" group="${6:-}"
  printf "  %-20s %-6s %-6s %-8s %-20s %s\n" \
    "$name" "+${n_allows}" "-${n_blocks}" "$peer_count" \
    "${extends:--}" "${group:--}"
}

# ======================================================
# Show helpers
# ======================================================
 
function ui::rule::section_header() {
  local title="${1:-}"
  printf "\n  \033[0;37m── %s ──────────────────────────────────\033[0m\n" "$title"
}