#!/usr/bin/env bash
# Foreground Omarchy ISO install UI supervisor.
#
# This process is the only owner of the visible install UI. It launches the
# actual installer as a non-interactive child, sends child output to the support
# log, renders progress while the child runs, then renders the final installed
# time + reboot prompt itself. There is deliberately no background dashboard.
set -euo pipefail

usage() {
  echo "Usage: $0 <log-file> <state-file> -- <installer command...>" >&2
  exit 2
}

(( $# >= 4 )) || usage
LOG_FILE="$1"
STATE_FILE="$2"
shift 2
[[ ${1:-} == "--" ]] || usage
shift
(( $# > 0 )) || usage
INSTALL_CMD=("$@")

TTY_PATH="${OMARCHY_DASHBOARD_TTY:-/dev/tty}"
LOGO_PATH="${OMARCHY_PATH:-/usr/share/omarchy}/logo.txt"
CSI=$'\033['
RESET="${CSI}0m"
DIM="${CSI}2m"
HIDE_CURSOR="${CSI}?25l"
SHOW_CURSOR="${CSI}?25h"
CLEAR="${CSI}2J${CSI}H"
CLEAR_LINE="${CSI}2K"
CLEAR_TO_END="${CSI}J"
GREEN="${CSI}32m"
RED="${CSI}31m"
DARK="${CSI}90m"

# The dashboard owns the final reboot prompt now, so keep the gum confirm
# styling that used to come from install/helpers/presentation.sh.
export GUM_CONFIRM_PROMPT_FOREGROUND="${GUM_CONFIRM_PROMPT_FOREGROUND:-6}"
export GUM_CONFIRM_SELECTED_FOREGROUND="${GUM_CONFIRM_SELECTED_FOREGROUND:-0}"
export GUM_CONFIRM_SELECTED_BACKGROUND="${GUM_CONFIRM_SELECTED_BACKGROUND:-2}"
export GUM_CONFIRM_UNSELECTED_FOREGROUND="${GUM_CONFIRM_UNSELECTED_FOREGROUND:-7}"
export GUM_CONFIRM_UNSELECTED_BACKGROUND="${GUM_CONFIRM_UNSELECTED_BACKGROUND:-0}"

child_pid=""
child_pgid=""

whimsy=(
  "making Linux feel like home"
  "installing the good defaults"
  "putting the system in order"
  "setting up a civilized desktop"
  "clearing the path to Hyprland"
  "preparing the command line sanctuary"
  "teaching pacman the house rules"
  "laying down the Tokyo Night carpet"
  "arranging the sharp tools safely"
  "keeping the boot path clean"
  "giving Limine the keys"
  "making snapshots worth having"
  "saving future you a few hours"
  "choosing boring where boring wins"
  "leaving room for your dotfiles"
  "tightening the last few screws"
)

cleanup() {
  printf '%s%s' "$RESET" "$SHOW_CURSOR" >"$TTY_PATH" 2>/dev/null || true
}

dashboard_log() {
  printf '[dashboard] %s\n' "$*" >>"$LOG_FILE" 2>/dev/null || true
}

stop_child() {
  [[ -n ${child_pid:-} ]] || return 0
  if kill -0 "$child_pid" 2>/dev/null; then
    if [[ -n ${child_pgid:-} ]]; then
      kill -- -"$child_pgid" 2>/dev/null || true
    fi
    kill "$child_pid" 2>/dev/null || true
    for _ in {1..20}; do
      kill -0 "$child_pid" 2>/dev/null || break
      sleep 0.05
    done
    if kill -0 "$child_pid" 2>/dev/null; then
      [[ -n ${child_pgid:-} ]] && kill -9 -- -"$child_pgid" 2>/dev/null || true
      kill -9 "$child_pid" 2>/dev/null || true
    fi
  fi
  wait "$child_pid" 2>/dev/null || true
}

interrupt() {
  local code="$1"
  stop_child
  cleanup
  exit "$code"
}

trap cleanup EXIT
trap 'interrupt 130' INT
trap 'interrupt 143' TERM

term_size() { stty size 2>/dev/null <"$TTY_PATH" || printf '24 80\n'; }
term_cols() { local size; size="$(term_size)"; printf '%s\n' "${size#* }"; }

repeat() {
  local char="$1" count="$2" out="" i
  for ((i=0; i<count; i++)); do out+="$char"; done
  printf '%s' "$out"
}

logo_width() {
  if [[ -f $LOGO_PATH ]]; then
    awk '{ if (length > max) max = length } END { print max+0 }' "$LOGO_PATH"
  else
    printf '81\n'
  fi
}

logo_height() {
  if [[ -f $LOGO_PATH ]]; then
    wc -l <"$LOGO_PATH"
  else
    printf '1\n'
  fi
}

LOGO_WIDTH="$(logo_width)"
LOGO_HEIGHT="$(logo_height)"
(( LOGO_WIDTH > 0 )) || LOGO_WIDTH=81
(( LOGO_HEIGHT > 0 )) || LOGO_HEIGHT=1
CONTENT_WIDTH="$LOGO_WIDTH"
DYNAMIC_ROW=$((LOGO_HEIGHT + 3))
LAST_PCT=1

left_padding() {
  local width="${1:-$CONTENT_WIDTH}" cols pad
  cols="$(term_cols)"
  pad=$(( (cols - width) / 2 ))
  (( pad < 0 )) && pad=0
  printf '%*s' "$pad" ''
}

visible_len() {
  local text="$1"
  text="$(printf '%b' "$text" | sed -E $'s/\x1b\\[[0-9;?]*[A-Za-z]//g')"
  printf '%s' "${#text}"
}

blank_line() { printf '\r%s\n' "$CLEAR_LINE"; }

line_at() {
  local width="${1:-$CONTENT_WIDTH}" indent="${2:-0}"
  shift 2
  printf '\r%s%s%*s' "$CLEAR_LINE" "$(left_padding "$width")" "$indent" ''
  printf '%b' "$*"
}

center() {
  local text="$1" width="${2:-$CONTENT_WIDTH}" len inner_pad
  len="$(visible_len "$text")"
  inner_pad=$(( (width - len) / 2 ))
  (( inner_pad < 0 )) && inner_pad=0
  printf '\r%s%s%*s%b%s\n' "$CLEAR_LINE" "$(left_padding "$width")" "$inner_pad" '' "$text" "$RESET"
}

render_logo() {
  local pad
  pad="$(left_padding "$LOGO_WIDTH")"
  if [[ -f $LOGO_PATH ]]; then
    while IFS= read -r line; do
      printf '\r%s%s%b%s%b\n' "$CLEAR_LINE" "$pad" "$GREEN" "$line" "$RESET"
    done <"$LOGO_PATH"
  else
    center "${GREEN}Omarchy${RESET}" "$CONTENT_WIDTH"
  fi
}

progress_bar() {
  local pct="$1" width="${2:-34}" filled empty
  (( pct < 0 )) && pct=0
  (( pct > 100 )) && pct=100
  filled=$(( pct * width / 100 ))
  empty=$(( width - filled ))
  printf '%s%s%s%s%s %3d%%' "$GREEN" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET" "$pct"
}

install_progress() {
  [[ -f $STATE_FILE ]] || { printf '1'; return; }
  python - "$STATE_FILE" <<'PY' 2>/dev/null || printf '1'
import json, sys, time
path = sys.argv[1]
try:
    state = json.load(open(path))
except Exception:
    print(1, end='')
    raise SystemExit
phase = state.get('current_phase') or 'Starting installation'
if state.get('finished_at') or phase == 'Installation complete':
    print(100, end='')
    raise SystemExit
weights = [
    ('Starting installation', 1, 2, 2),
    ('Preparing live environment', 2, 6, 5),
    ('Verifying protected mounts', 6, 8, 3),
    ('Installing Arch + Omarchy', 8, 86, 95),
    ('Installing base system', 8, 84, 85),
    ('Configuring protected boot', 84, 86, 8),
    ('Configuring hibernation', 86, 88, 5),
    ('Configuring system', 88, 93, 10),
    ('Finalizing Limine boot', 93, 96, 8),
    ('Finalizing user', 96, 98, 10),
    ('Finalizing in chroot', 88, 98, 10),
    ('Configuring login', 98, 99, 3),
    ('Configuring DNS resolver', 99, 100, 2),
    ('Validating protected boot setup', 99, 100, 5),
    ('Validating boot setup', 99, 100, 5),
]
start, end, expected = 1, 99, 90
for name, s, e, seconds in weights:
    if phase == name:
        start, end, expected = s, e, seconds
        break
phase_started = state.get('phase_started_at') or state.get('started_at') or time.time()
elapsed = max(0, time.time() - float(phase_started))
span = max(0, end - start)
active = min(max(span - 1, 0), int((elapsed / max(expected, 1)) * span))
print(min(100, start + active), end='')
PY
}

current_whimsy() {
  local frame="$1" pct="$2"
  printf '%s' "${whimsy[$(( (pct / 8 + frame / 8) % ${#whimsy[@]} ))]}"
}

render_static() {
  printf '%s' "$CLEAR"
  blank_line

  if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then
    if timeout 8s tte -i "$LOGO_PATH" --canvas-width 0 --anchor-text c --frame-rate 920 laseretch >"$TTY_PATH" 2>/dev/null; then
      blank_line
      return
    fi
    printf '%s' "$CLEAR"
    blank_line
  fi

  render_logo
  blank_line
}

render_dynamic() {
  local frame="$1" pct spin
  pct="$(install_progress)"
  [[ $pct =~ ^[0-9]+$ ]] || pct=$LAST_PCT
  (( pct > 100 )) && pct=100
  (( pct < LAST_PCT )) && pct=$LAST_PCT
  LAST_PCT=$pct
  spin=("◐" "◓" "◑" "◒")

  printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
  center "Installing Omarchy" "$CONTENT_WIDTH"
  blank_line
  line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 40) / 2 )) ""
  progress_bar "$pct" 34
  printf '\n'
  blank_line
  center "${GREEN}${spin[$((frame % 4))]} $(current_whimsy "$frame" "$pct") ${RESET}" "$CONTENT_WIDTH"
  blank_line
  center "${DIM}Logs are being saved for support${RESET}" "$CONTENT_WIDTH"
  printf '%s' "$CLEAR_TO_END"
}

install_duration() {
  local path line duration

  if [[ -f $STATE_FILE ]]; then
    duration="$(python - "$STATE_FILE" <<'PY' 2>/dev/null || true
import json, sys
try:
    state = json.load(open(sys.argv[1]))
    started = float(state.get("started_at") or 0)
    finished = float(state.get("finished_at") or 0)
except Exception:
    raise SystemExit(0)
if started <= 0 or finished <= 0 or finished < started:
    raise SystemExit(0)
seconds = int(round(finished - started))
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if hours:
    print(f"{hours}h {minutes}m {seconds}s", end="")
else:
    print(f"{minutes}m {seconds}s", end="")
PY
)"
    [[ -n $duration ]] && { printf '%s' "$duration"; return 0; }
  fi

  for path in /mnt/var/log/omarchy-install.log "$LOG_FILE"; do
    [[ -f $path ]] || continue
    for prefix in Total Omarchy; do
      line="$(grep "^$prefix:" "$path" 2>/dev/null | tail -n 1 || true)"
      [[ -n $line ]] || continue
      duration="${line#*:}"
      duration="${duration#${duration%%[![:space:]]*}}"
      [[ -n $duration ]] && { printf '%s' "$duration"; return 0; }
    done
  done
  return 0
}

render_finish() {
  local duration message cols
  cols="$(term_cols)"
  printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH"
  if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then
    timeout 8s tte -i "$LOGO_PATH" --canvas-width 0 --anchor-text c --frame-rate 920 laseretch >"$TTY_PATH" 2>/dev/null || true
  else
    render_logo >"$TTY_PATH"
  fi
  duration="$(install_duration || true)"
  message="Finished installing"
  [[ -n $duration ]] && message="Installed in $duration"
  {
    blank_line
    center "$message" "$cols"
    blank_line
  } >"$TTY_PATH"
}

reboot_prompt() {
  local prompt_pad
  prompt_pad=$(( ( $(term_cols) - 18 ) / 2 ))
  (( prompt_pad < 0 )) && prompt_pad=0
  gum confirm \
    --padding "0 0 0 $prompt_pad" \
    --show-help=false \
    --default \
    --affirmative "Reboot Now" \
    --negative "" \
    "" <"$TTY_PATH" >"$TTY_PATH" 2>&1
}

truncate_for_width() {
  local text="$1" width="$2"
  if (( ${#text} > width )); then
    printf '%s…' "${text:0:$((width - 1))}"
  else
    printf '%s' "$text"
  fi
}

render_failure_log_tail() {
  local rows lines width line tail_log label
  rows=$(term_size)
  rows=${rows%% *}
  lines=$((rows - LOGO_HEIGHT - 18))
  (( lines < 6 )) && lines=6
  (( lines > 14 )) && lines=14
  width=$((CONTENT_WIDTH - 4))
  (( width < 40 )) && width=40

  tail_log="${OMARCHY_FAILURE_TAIL_LOG:-}"
  if [[ -z $tail_log ]]; then
    if [[ -f /mnt/var/log/omarchy-install.log ]]; then
      tail_log=/mnt/var/log/omarchy-install.log
    else
      tail_log="$LOG_FILE"
    fi
  fi
  [[ -f $tail_log ]] || return 0

  label="Last log lines:"
  [[ $tail_log == /mnt/* ]] && label="Last target log lines:"
  line_at "$CONTENT_WIDTH" 2 "${DIM}${label}${RESET}"
  printf '\n'
  tail -n "$lines" "$tail_log" 2>/dev/null | while IFS= read -r line; do
    line_at "$CONTENT_WIDTH" 2 "$(truncate_for_width "$line" "$width")"
    printf '\n'
  done
}

find_log_uploader() {
  local candidate
  for candidate in omarchy-upload-log /usr/local/bin/omarchy-upload-log /usr/bin/omarchy-upload-log /mnt/usr/bin/omarchy-upload-log; do
    if [[ $candidate == */* ]]; then
      [[ -x $candidate ]] && { printf '%s' "$candidate"; return 0; }
    elif command -v "$candidate" >/dev/null 2>&1; then
      command -v "$candidate"
      return 0
    fi
  done
  return 1
}

prompt_enter() {
  printf '\nPress Enter to continue…' >"$TTY_PATH"
  IFS= read -r _ <"$TTY_PATH" || true
}

upload_failure_log() {
  local uploader
  printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" 2>/dev/null || true
  if uploader=$(find_log_uploader); then
    "$uploader" install >"$TTY_PATH" 2>&1 || true
  else
    {
      echo "No log uploader is available in this environment."
      echo "Install log: $LOG_FILE"
      [[ -f /mnt/var/log/omarchy-install.log ]] && echo "Target log: /mnt/var/log/omarchy-install.log"
    } >"$TTY_PATH"
  fi
  prompt_enter
}

view_failure_log() {
  printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" 2>/dev/null || true
  if command -v less >/dev/null 2>&1 && [[ -f $LOG_FILE ]]; then
    less "$LOG_FILE" <"$TTY_PATH" >"$TTY_PATH" 2>&1 || true
  else
    tail -n 200 "$LOG_FILE" >"$TTY_PATH" 2>&1 || true
    prompt_enter
  fi
}

render_failure() {
  local status="$1" state_summary="${2:-}" line
  printf '%s%s' "$SHOW_CURSOR" "$CLEAR"
  blank_line
  render_logo
  blank_line
  center "${RED}Omarchy installation stopped${RESET}" "$CONTENT_WIDTH"
  center "Installer exited with status $status" "$CONTENT_WIDTH"

  if [[ -n $state_summary ]]; then
    blank_line
    while IFS= read -r line; do
      [[ -n $line ]] || continue
      center "${DIM}$(truncate_for_width "$line" "$CONTENT_WIDTH")${RESET}" "$CONTENT_WIDTH"
    done <<<"$state_summary"
  fi

  blank_line
  render_failure_log_tail
  blank_line
  center "${DIM}Get help at https://omarchy.org/discord${RESET}" "$CONTENT_WIDTH"
  blank_line
}

failure_menu() {
  local choice uploader

  [[ ${OMARCHY_UI_FAILURE_ACTION:-} == "exit" ]] && return 0

  while true; do
    uploader=""
    uploader=$(find_log_uploader || true)
    if [[ -n $uploader ]]; then
      choice=$(gum choose \
        --height 6 \
        --header "What would you like to do?" \
        "Upload log for support" \
        "View full log" \
        "Drop to shell" \
        "Reboot" \
        "Power off" \
        <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
    else
      choice=$(gum choose \
        --height 5 \
        --header "What would you like to do?" \
        "View full log" \
        "Drop to shell" \
        "Reboot" \
        "Power off" \
        <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
    fi

    case "$choice" in
      "Upload log for support")
        upload_failure_log
        render_failure "${failure_status:-1}" "${failure_summary:-}" >"$TTY_PATH" 2>/dev/null || true
        ;;
      "View full log")
        view_failure_log
        render_failure "${failure_status:-1}" "${failure_summary:-}" >"$TTY_PATH" 2>/dev/null || true
        ;;
      "Reboot")
        reboot 2>/dev/null || systemctl reboot 2>/dev/null || true
        ;;
      "Power off")
        poweroff 2>/dev/null || systemctl poweroff 2>/dev/null || true
        ;;
      "Drop to shell"|"")
        return 0
        ;;
    esac
  done
}

sanitize_child_output() {
  # Archinstall is only a library subsystem in Omarchy's installer. Its own
  # post-install success/reboot banner can be buffered until process exit and
  # appear after an Omarchy failure, which makes the unified log contradictory.
  # Keep that third-party banner out of Omarchy's support log; the raw
  # archinstall log is still uploaded separately by omarchy-upload-log.
  sed -u \
    -e $'s/\x1b\\[[0-9;?]*[A-Za-z]//g' \
    -e '/^Activating systemd-timesyncd for time synchronization using Arch Linux and ntp.org NTP servers$/d' \
    -e '/^Enabling service systemd-timesyncd$/d' \
    -e '/^Setting password for root$/d' \
    -e '/^Updating \/mnt\/etc\/fstab$/d' \
    -e '/^Syncing the system\.\.\.$/d' \
    -e '/^Installation completed without any errors\.$/d' \
    -e '/^Log files temporarily available at \/var\/log\/archinstall\.$/d' \
    -e '/^You may reboot when ready\.$/d'
}

launch_child() {
  mkdir -p "$(dirname "$LOG_FILE")"
  # Child is non-interactive. All visible UI belongs to this supervisor.
  PYTHONUNBUFFERED=1 setsid "${INSTALL_CMD[@]}" </dev/null > >(sanitize_child_output >>"$LOG_FILE") 2>&1 &
  child_pid=$!
  child_pgid=$child_pid
}

[[ -e $TTY_PATH ]] || exit 2
printf '%s' "$HIDE_CURSOR" >"$TTY_PATH"
render_static >"$TTY_PATH"
launch_child

frame=0
while jobs -pr | grep -qx "$child_pid"; do
  render_dynamic "$frame" >"$TTY_PATH" 2>/dev/null || true
  frame=$((frame + 1))
  sleep 0.5
done

set +e
wait "$child_pid"
status=$?
set -e
child_pid=""
child_pgid=""

dashboard_log "installer child exited with status $status"

if (( status != 0 )); then
  failure_status="$status"
  failure_summary=""
  if [[ -f $STATE_FILE ]]; then
    failure_summary="$(python - "$STATE_FILE" <<'PY' 2>/dev/null || true
import json, sys
try:
    state = json.load(open(sys.argv[1]))
except Exception:
    raise SystemExit(0)
phase = state.get("current_phase")
if phase:
    print(f"last installer phase: {phase}")
for item in reversed(state.get("phases", [])):
    if item.get("status") == "failed":
        print(f"failed phase: {item.get('name')}: {item.get('error')}")
        break
PY
)"
    if [[ -n $failure_summary ]]; then
      while IFS= read -r line; do
        [[ -n $line ]] && dashboard_log "$line"
      done <<<"$failure_summary"
    fi
  fi
  render_failure "$failure_status" "$failure_summary" >"$TTY_PATH" 2>/dev/null || true
  failure_menu
  exit "$failure_status"
fi

set +e
render_finish
finish_render_status=$?
set -e
if (( finish_render_status != 0 )); then
  dashboard_log "finish screen render failed with status $finish_render_status; using fallback"
  {
    printf '%s%s' "$SHOW_CURSOR" "$CLEAR"
    printf '\nFinished installing\n\n'
  } >"$TTY_PATH" 2>/dev/null || true
fi

if [[ ${OMARCHY_UI_AUTO_REBOOT:-} == "no" ]]; then
  exit 0
fi

if reboot_prompt; then
  printf '%s' "$CLEAR" >"$TTY_PATH" 2>/dev/null || true
  reboot 2>/dev/null || systemctl reboot 2>/dev/null || true
fi

exit 0
