#!/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"
WHITE="${CSI}37m"
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=""

tips=(
  "Super + Space opens the Omarchy menu for apps, settings, and more"
  "Super + K shows all the key bindings"
  "Super is the Windows or command key on your keyboard"
  "Use Xournal++ to sign PDFs"
  "Share files with phones and laptops using LocalSend"
  "Turn any website into an app with Install > Web App in the menu"
  "Super + Return opens a terminal, Super + Shift + Return the browser"
  "Edit images with Pinta, videos with Kdenlive, docs with LibreOffice"
  "Super + Ctrl + Print grabs text off the screen with OCR"
  "Print takes a screenshot, Alt + Print records the screen"
  "Set a reminder with Super + Ctrl + R"
  "Switch themes from Style > Theme in the Omarchy menu"
  "Double-click the menu bar to make it transparent"
  "Run a full Windows VM via Install > Windows in the menu"
  "Super + Ctrl + V opens the clipboard manager"
  "Super + 1 through 0 switches workspaces, add Shift to bring the window"
  "Super + Print picks a color from anywhere on screen"
  "Keep the system fresh with Update in the Omarchy menu"
)

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' "$WHITE" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET"
}

install_progress() {
  local phase finished phase_started elapsed span active pct
  local start=1 end=99 expected=90
  local -a state

  [[ -f $STATE_FILE ]] || { printf '1'; return; }

  mapfile -t state < <(jq -r '
    (.current_phase // "Starting installation"),
    ((.finished_at // 0) | tonumber? // 0 | floor),
    ((.phase_started_at // .started_at // 0) | tonumber? // 0 | floor)
  ' "$STATE_FILE" 2>/dev/null)
  (( ${#state[@]} == 3 )) || { printf '1'; return; }
  phase="${state[0]}" finished="${state[1]}" phase_started="${state[2]}"

  if (( finished > 0 )) || [[ $phase == "Installation complete" ]]; then
    printf '100'
    return
  fi

  # Phase weights: start%, end%, expected seconds in phase.
  case "$phase" in
    "Starting installation")           start=1  end=2   expected=2 ;;
    "Preparing live environment")      start=2  end=6   expected=5 ;;
    "Verifying protected mounts")      start=6  end=8   expected=3 ;;
    "Installing Arch + Omarchy")       start=8  end=86  expected=95 ;;
    "Installing base system")          start=8  end=84  expected=85 ;;
    "Configuring protected boot")      start=84 end=86  expected=8 ;;
    "Configuring hibernation")         start=86 end=88  expected=5 ;;
    "Configuring system")              start=88 end=93  expected=10 ;;
    "Finalizing Limine boot")          start=93 end=96  expected=8 ;;
    "Finalizing user")                 start=96 end=98  expected=10 ;;
    "Finalizing in chroot")            start=88 end=98  expected=10 ;;
    "Configuring login")               start=98 end=99  expected=3 ;;
    "Configuring DNS resolver")        start=99 end=100 expected=2 ;;
    "Validating protected boot setup") start=99 end=100 expected=5 ;;
    "Validating boot setup")           start=99 end=100 expected=5 ;;
  esac

  (( phase_started > 0 )) || phase_started=$EPOCHSECONDS
  elapsed=$(( EPOCHSECONDS - phase_started ))
  (( elapsed < 0 )) && elapsed=0
  span=$(( end - start ))
  (( span < 0 )) && span=0
  (( expected < 1 )) && expected=1
  active=$(( elapsed * span / expected ))
  (( active > span - 1 )) && active=$(( span - 1 ))
  (( active < 0 )) && active=0
  pct=$(( start + active ))
  (( pct > 100 )) && pct=100
  printf '%s' "$pct"
}

# One tip per ~8 seconds (frames tick every 0.5s), in order, so each stays
# readable and none are skipped when progress jumps.
current_tip() {
  local frame="$1"
  printf '%s' "${tips[$(( frame / 16 % ${#tips[@]} ))]}"
}

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

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

  printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
  center "Installing Omarchy" "$CONTENT_WIDTH"
  blank_line
  line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 34) / 2 )) ""
  progress_bar "$pct" 34
  printf '\n'
  blank_line
  center "${DIM}Tip:${RESET} ${GREEN}$(current_tip "$frame")${RESET}" "$CONTENT_WIDTH"
  printf '%s' "$CLEAR_TO_END"
}

install_duration() {
  local path line duration

  if [[ -f $STATE_FILE ]]; then
    duration="$(jq -r '
      ((.started_at // 0) | tonumber? // 0) as $started
      | ((.finished_at // 0) | tonumber? // 0) as $finished
      | if $started <= 0 or $finished <= 0 or $finished < $started then empty
        else (($finished - $started) | round) as $secs
          | if $secs >= 3600 then "\($secs / 3600 | floor)h \($secs % 3600 / 60 | floor)m \($secs % 60)s"
            else "\($secs / 60 | floor)m \($secs % 60)s"
            end
        end
    ' "$STATE_FILE" 2>/dev/null || true)"
    [[ -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 effect_canvas_width
  duration="$(install_duration || true)"
  duration="${duration:-Complete}"
  effect_canvas_width="$(term_cols)"
  (( effect_canvas_width > 1 )) && effect_canvas_width=$((effect_canvas_width - 1))

  {
    printf '%s%s' "$SHOW_CURSOR" "$CLEAR"
    blank_line
    render_logo
    blank_line
    center "Installed Omarchy in ${duration}" "$CONTENT_WIDTH"
    blank_line
  } >"$TTY_PATH"

  if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then
    # TTE reuse mode restores a saved cursor before moving up over its input.
    # Save this anchor so the effect redraws the same logo rows shown above.
    printf '%s%d;1H\0337' "$CSI" "$((LOGO_HEIGHT + 2))" >"$TTY_PATH"
    timeout 8s tte -i "$LOGO_PATH" \
      --canvas-width "$effect_canvas_width" \
      --anchor-text c \
      --frame-rate 260 \
      --reuse-canvas \
      laseretch \
      >"$TTY_PATH" 2>/dev/null || true
    printf '%s%d;1H' "$CSI" "$((LOGO_HEIGHT + 8))" >"$TTY_PATH"
  fi
}

reboot_prompt() {
  local prompt_pad

  # Position the cursor one blank line below the title so the button sits right there.
  printf '%s%d;1H%s' "$CSI" "$((LOGO_HEIGHT + 5))" "$CLEAR_LINE" >"$TTY_PATH"

  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="$(jq -r '
      (.current_phase // empty | "last installer phase: \(.)"),
      ((.phases // []) | map(select(.status == "failed")) | last // empty
        | "failed phase: \(.name): \(.error)")
    ' "$STATE_FILE" 2>/dev/null || true)"
    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 reboot_prompt; then
  if [[ ${OMARCHY_UI_AUTO_REBOOT:-} == "no" ]]; then
    exit 0
  fi
  printf '%s' "$CLEAR" >"$TTY_PATH" 2>/dev/null || true
  reboot 2>/dev/null || systemctl reboot 2>/dev/null || true
fi

exit 0
