#!/usr/bin/env bash
# archbaseos-guided-install.sh — guided (dialog-based) Arch Linux base installer
#
# If /answerfile.json exists (e.g. embedded via build.sh --preconf), all prompts
# are answered from it. Missing fields fall back to interactive prompts.

# -E: propagate ERR trap into functions/subshells; -e: abort on error;
# -u: treat unset variables as errors; -o pipefail: fail on pipe errors.
set -Eeuo pipefail

############################################
# LOGGING
############################################
LOGFILE="$HOME/archbaseos-guided-install.log"
{
    echo
    echo "############################################"
    echo " Arch Guided Install Log - Started $(date)"
    echo "############################################"
    echo
} >> "$LOGFILE"
# Redirect all subsequent stdout/stderr to both the terminal and the log file.
# `tee -a` appends so repeated runs accumulate without overwriting; process
# substitution keeps the descriptor open for the full lifetime of the script.
exec > >(tee -a "$LOGFILE") 2>&1

############################################
# Error handler — TUI prompt to send log via croc
############################################
error_handler() {
    # Capture exit code before any other command can overwrite $?.
    # $LINENO is passed as $1 from the trap; default to '?' if absent.
    local exit_code=$? line_num="${1:-?}"
    echo "" >> "$LOGFILE"
    echo "ERROR: installer failed at line $line_num (exit code $exit_code)" >> "$LOGFILE"

    echo ""
    echo "Installation failed at line $line_num (exit code $exit_code)."
    read -rp "Send log via croc? [y/N]: " _croc_ans
    # ${_croc_ans,,} lowercases the answer so "Y" and "y" both match.
    if [[ "${_croc_ans,,}" == "y" ]]; then
        # Install croc on demand — it may not be present in the base live ISO.
        command -v croc &>/dev/null || pacman -Sy --noconfirm croc 2>/dev/null || true
        croc send "$LOGFILE" || true
    else
        echo "Log saved to: $LOGFILE"
    fi
    exit "$exit_code"
}
# Fire error_handler on any ERR signal, passing the current line number.
# Single quotes prevent $LINENO from being evaluated at trap-definition time.
trap 'error_handler $LINENO' ERR

############################################
# Helper Functions
############################################

ask_password() {
    # Prompt for a password and confirm it — BOTH shown in clear text (by request).
    # Loops until the two entries are identical and non-empty, so a typo can't
    # silently lock the operator out. The password is therefore typed exactly
    # twice (once + confirmation) and never again. The result is printed to stdout
    # for capture via $(...); the prompts and error messages go to stderr so they
    # never pollute the captured value.
    local label=$1 _p1 _p2
    while true; do
        read -rp "$label: " _p1
        if [[ -z "$_p1" ]]; then
            echo "  Password cannot be empty — try again." >&2
            continue
        fi
        read -rp "$label (confirm): " _p2
        if [[ "$_p1" == "$_p2" ]]; then
            printf '%s' "$_p1"
            return 0
        fi
        echo "  Passwords do not match — try again." >&2
    done
}

ask() {
    # Thin wrapper around read -rp so callers can capture the result via $().
    local prompt=$1
    local var
    read -rp "$prompt: " var
    echo "$var"
}

pause() {
    # Used before interactive hardware steps (e.g. FIDO2 enrollment) to give the
    # operator time to insert the key before the script continues.
    read -rp "Press ENTER to continue..."
}

# Returns the correct partition device for a given drive and partition number.
# NVMe and eMMC use a 'p' separator (e.g. /dev/nvme0n1p1), others don't.
part() { [[ "$1" == *nvme* || "$1" == *mmcblk* ]] && echo "${1}p${2}" || echo "${1}${2}"; }

############################################
# ANSWERFILE
############################################
# Allow an external override via the environment; default to /answerfile.json,
# the path where build.sh --preconf embeds the file in the live ISO.
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
AF_MODE=false
[[ -f "$ANSWERFILE" ]] && AF_MODE=true

af_get() {
    # Reads one field from the answerfile using a jq filter expression.
    # `// empty` returns an empty string instead of "null" when the field is absent;
    # `|| true` prevents a jq parse error from aborting the script via set -e.
    local val
    val=$(jq -r "${1} // empty" "$ANSWERFILE" 2>/dev/null || true)
    if [[ -z "$val" ]]; then printf '%s' "${2:-}"; else printf '%s' "$val"; fi
}

af_bool() {
    # `// false` provides a safe default so missing boolean keys don't cause errors.
    local val; val=$(jq -r "${1} // false" "$ANSWERFILE" 2>/dev/null || true)
    [[ "$val" == "true" ]] && echo "YES" || echo "NO"
}

get_mac_suffix() {
    # Produce a compact MAC address (no colons) from the first non-loopback interface
    # to create a unique per-machine hostname suffix during unattended deployment.
    # The awk pattern skips "lo" by requiring the 3rd character to differ from 'o'.
    local mac
    mac=$(ip link show 2>/dev/null \
        | awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
    # Strip any separator (colon/dot/dash) so the suffix is a bare hex string,
    # e.g. "aa:bb:cc:dd:ee:ff" -> "aabbccddeeff". The character class [:.-] matches
    # all three common MAC delimiters regardless of how the kernel formats them.
    printf '%s' "${mac//[:.-]/}"
}

if $AF_MODE; then
    echo "== Arch Linux Guided Installer (answerfile mode) =="
    # jq is required to parse the answerfile; it may not ship in the base live ISO.
    command -v jq &>/dev/null || pacman -Sy --noconfirm jq
else
    echo "== Arch Linux FIDO2-Ready Installer =="
fi

############################################
# NETWORK CHECK
############################################
# Send one ICMP packet (-c1) with a 3-second timeout (-W3) to Cloudflare DNS.
# In AF_MODE we warn but continue — packages may already be cached in the live env.
if ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
    if $AF_MODE; then
        echo "Warning: no internet connection detected — continuing in answerfile mode."
    else
        echo "No internet connection detected."
        echo "  Wired: ensure the cable is plugged in."
        echo "  WiFi:  switch to another TTY (Alt+F2) and run: iwctl"
        echo ""
        read -rp "Press Enter once connected (or Ctrl+C to abort)..."
        if ! ping -c1 -W3 1.1.1.1 &>/dev/null; then
            echo "Warning: still offline. Packages cannot be downloaded without network."
            read -rp "Continue anyway? [y/N]: " _net_ans
            [[ "${_net_ans,,}" == "y" ]] || { echo "Aborted — no network."; exit 1; }
        fi
    fi
fi

############################################
# Begin
############################################

# Each entry is "keymap-code|Human-readable name"; the pipe delimiter lets both
# pieces of data live in a single array element without needing two parallel arrays.
KEYMAPS=(
    "us|English US"
    "de|German"
)

############################################
# User input — gather EVERYTHING up front
############################################
# All operator input (including passwords) is collected here, before any
# destructive action runs. Passwords are shown in clear text by request and are
# each entered twice (once + confirmation) so a typo can't silently lock you out.
# After this block the only remaining interaction is the single final "type YES"
# gate plus any physical FIDO2 key taps.
if $AF_MODE; then
    KERNEL=$(af_get '.kernel' 'linux')
    RAW_HOSTNAME=$(af_get '.hostname' '')
    # Always append the machine's MAC (no separators) to the base name so every
    # node provisioned from the same answerfile gets a unique hostname, e.g.
    # "arch-aabbccddeeff". Falls back to "arch" when the answerfile omits a name.
    # The dash is only added when a MAC was actually found, so a NIC-less machine
    # never ends up with a trailing "-".
    BASE_HOSTNAME="${RAW_HOSTNAME:-arch}"
    MAC_SUFFIX=$(get_mac_suffix)
    if [[ -n "$MAC_SUFFIX" ]]; then
        HOSTNAME="${BASE_HOSTNAME}-${MAC_SUFFIX}"
    else
        HOSTNAME="$BASE_HOSTNAME"
    fi
    USERNAME=$(af_get '.username' '')
    ENCRYPT_DISK=$(af_bool '.encrypt')
    ENABLE_FIDO_ROOT=$(af_bool '.fido2_root')
    ENABLE_FIDO_USER=$(af_bool '.fido2_user')
    RUN_TUI=$(af_bool '.run_tui')
    KEYMAP=$(af_get '.keymap' 'us')
    echo "Kernel:   $KERNEL / Hostname: $HOSTNAME / Username: $USERNAME"
    echo "Encrypt:  $ENCRYPT_DISK / FIDO2 root: $ENABLE_FIDO_ROOT / FIDO2 user: $ENABLE_FIDO_USER"
    echo "Keymap:   $KEYMAP"
else
    KERNEL=$(ask "Kernel (linux, linux-lts, linux-zen)")
    HOSTNAME=$(ask "Hostname")
    USERNAME=$(ask "Username")
    read -rp "Enable disk encryption? (YES/NO): " ENCRYPT_DISK
    # Default FIDO2 root to NO; only ask when encryption is active because FIDO2
    # unlocking only makes sense on an encrypted partition.
    ENABLE_FIDO_ROOT="NO"
    if [[ "$ENCRYPT_DISK" == "YES" ]]; then
        read -rp "Enable FIDO2 for unlocking root? (YES/NO): " ENABLE_FIDO_ROOT
    fi
    read -rp "Enable FIDO2 for user login? (YES/NO): " ENABLE_FIDO_USER

    echo ""
    echo "Select keyboard layout for installed system:"
    for i in "${!KEYMAPS[@]}"; do
        # %%|* strips from the first '|' onward → keymap code.
        _km_code="${KEYMAPS[$i]%%|*}"
        # ##*| strips up to and including the last '|' → display name.
        _km_name="${KEYMAPS[$i]##*|}"
        printf "  %d) %-14s (%s)\n" $((i+1)) "$_km_name" "$_km_code"
    done
    read -rp "Choice [1]: " _KM_CHOICE
    # Convert 1-based user input to 0-based array index; default to 1 if blank.
    _KM_CHOICE=$(( ${_KM_CHOICE:-1} - 1 ))
    if (( _KM_CHOICE >= 0 && _KM_CHOICE < ${#KEYMAPS[@]} )); then
        KEYMAP="${KEYMAPS[$_KM_CHOICE]%%|*}"
    else
        # Out-of-range input silently falls back to the first entry (us).
        KEYMAP="${KEYMAPS[0]%%|*}"
    fi

    # Whether to run the dotfiles TUI inside the chroot — asked now so the whole
    # back half of the install can proceed without further interaction.
    read -rp "Run dotfiles TUI setup inside chroot after base install? [YES/no]: " _TUI_IN
    _TUI_IN="${_TUI_IN:-YES}"
    [[ "${_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
fi

# ── Target drive ──────────────────────────────────────────────────────────────
# Print the block device layout so the operator can identify the correct disk.
lsblk
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
    DRIVE=$(af_get '.drive')
    echo "Drive (from answerfile): $DRIVE"
else
    DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
fi

# ── Passwords ───────────────────────────────────────────────────────────────
# In answerfile mode, honor a password embedded by the operator (needed for
# truly unattended netboot/WDS deployments where no one is at the console);
# otherwise fall back to the interactive prompt. Captured once here and reused
# everywhere so the operator is never prompted for them again mid-install.
if $AF_MODE && [[ -n "$(af_get '.password')" ]]; then
    USERPASS=$(af_get '.password')
else
    USERPASS=$(ask_password "Password for $USERNAME")
fi

# LUKS passphrase — only needed when encrypting. Reused below for luksFormat /
# open / luksAddKey / cryptenroll so it is typed exactly once (plus confirmation),
# never again during the destructive phase.
LUKS_PASS=""
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
    if $AF_MODE && [[ -n "$(af_get '.luks_password')" ]]; then
        LUKS_PASS=$(af_get '.luks_password')
    else
        LUKS_PASS=$(ask_password "Disk encryption (LUKS) passphrase")
    fi
fi

# ── Final confirmation ────────────────────────────────────────────────────────
# A single gate before anything destructive happens. Summarise the choices so the
# operator can sanity-check them, then require one explicit all-caps YES.
echo ""
echo "──────────────────────────────────────────────"
echo "  Review your selections:"
echo "    Kernel:        $KERNEL"
echo "    Hostname:      $HOSTNAME"
echo "    Username:      $USERNAME"
echo "    Keymap:        $KEYMAP"
echo "    Encrypt disk:  $ENCRYPT_DISK"
echo "    FIDO2 root:    $ENABLE_FIDO_ROOT"
echo "    FIDO2 user:    $ENABLE_FIDO_USER"
echo "    Run TUI:       $RUN_TUI"
echo "    Target drive:  $DRIVE"
echo "──────────────────────────────────────────────"
echo "  WARNING: ALL DATA on $DRIVE will be PERMANENTLY ERASED."
echo ""
if $AF_MODE; then
    # Unattended deployment: no human to type YES — give a short abort window.
    echo "Answerfile mode — proceeding in 5 seconds (Ctrl-C to abort)..."
    sleep 5
else
    read -rp "Type YES (all caps) to begin installation: " _final_ans
    [[ "$_final_ans" == "YES" ]] || { echo "Aborted."; exit 1; }
fi

# Wait for the live ISO's keyring initialization before any install. archiso
# initializes the pacman keyring asynchronously at boot via pacman-init.service
# (master-key generation can take a minute+ in VMs with little entropy), and
# this installer autostarts at login — racing that service leaves keys
# unpopulated/untrusted, so every package fails with "signature is unknown
# trust" / "invalid or corrupted package (PGP signature)". Do NOT run
# pacman-key --init ourselves here: doing so concurrently with
# pacman-init.service corrupts the half-built keyring. Wait, then refresh
# archlinux-keyring so an ISO built weeks ago still trusts current packager keys.
if systemctl cat pacman-init.service &>/dev/null; then
    echo "Waiting for pacman keyring initialization (pacman-init.service)..."
    while [[ "$(systemctl is-active pacman-init.service 2>/dev/null)" == "activating" ]]; do
        sleep 2
    done
else
    # Not on archiso (e.g. re-run from an installed system) — ensure a keyring exists.
    [[ -d /etc/pacman.d/gnupg ]] || { pacman-key --init; pacman-key --populate archlinux; }
fi
pacman -Sy --noconfirm archlinux-keyring

# Required packages — installed into the live environment before partitioning.
# -d skips full dependency resolution for speed (these are standalone tools).
# systemd-ukify included for Unified Kernel Image support if needed post-install.
pacman -Syd --noconfirm parted cryptsetup libfido2 pam-u2f systemd-ukify jq

############################################
# Partitioning
############################################

# Read installed RAM in GiB for swap sizing; awk extracts the total-memory column.
RAM_GB=$(free --giga | awk '/Mem/ {print $2}')
# lsblk -b: bytes; -d: disk only (no partitions); -n: no header; -o SIZE: size column.
DISK_GB=$(lsblk -dn -o SIZE -b "$DRIVE" | awk '{print int($1/1024/1024/1024)}')

# Reserve 10 GiB for the EFI partition and 1 GiB for alignment/overhead.
EFI_SIZE=10
SWAP_SIZE=$RAM_GB
ROOT_SIZE=$((DISK_GB - SWAP_SIZE - EFI_SIZE - 1))

if (( ROOT_SIZE < 8 )); then
    echo "ERROR: Disk too small for layout."
    exit 1
fi

echo "EFI=${EFI_SIZE}G, Root=${ROOT_SIZE}G, Swap=${SWAP_SIZE}G"

# -s: script mode (no interactive prompts); GPT is required for UEFI booting.
# `set 1 esp on` marks partition 1 as the EFI System Partition.
# Starting at 1MiB aligns to SSD erase-block / spinner track boundaries.
# The last partition uses 100% to consume all remaining space.
parted -s "$DRIVE" mklabel gpt \
    mkpart EFI fat32 1MiB "${EFI_SIZE}GiB" \
    set 1 esp on \
    mkpart ROOT "${EFI_SIZE}GiB" "$((EFI_SIZE + ROOT_SIZE))GiB" \
    mkpart SWAP "$((EFI_SIZE + ROOT_SIZE))GiB" 100%

# Resolve partition paths via `part()` which inserts the NVMe/eMMC 'p' suffix.
EFI_PART=$(part "$DRIVE" 1)
ROOT_PART=$(part "$DRIVE" 2)
SWAP_PART=$(part "$DRIVE" 3)

# FAT32 is mandated by the UEFI specification for the EFI System Partition.
mkfs.fat -F32 "$EFI_PART"
# mkswap writes the swap header; swapon activates it immediately for use during install.
mkswap "$SWAP_PART"
swapon "$SWAP_PART"

############################################
# Encryption (optional)
############################################
# Initialise to empty; gets set to a temp file path only when encryption is active.
LUKS_BACKUP_KEY=""

if [[ "$ENCRYPT_DISK" == "YES" ]]; then
    echo "Formatting LUKS2 root..."
    # --type luks2 uses the newer LUKS2 format required by systemd-cryptenroll for FIDO2.
    # Feed the pre-gathered passphrase on stdin (--key-file=-) and --batch-mode to
    # skip cryptsetup's own interactive "type uppercase YES" + passphrase prompts.
    printf '%s' "$LUKS_PASS" | cryptsetup luksFormat "$ROOT_PART" --type luks2 --batch-mode --key-file=-
    # Open (decrypt) the container; exposes it as /dev/mapper/cryptroot for formatting.
    printf '%s' "$LUKS_PASS" | cryptsetup open "$ROOT_PART" cryptroot --key-file=-

    # ── Auto-generate backup LUKS key ─────────────────────────────────────────
    # A random key enrolled in a second LUKS slot enables recovery without
    # the primary passphrase. Stored inside the encrypted volume so it is
    # only accessible when the system is unlocked.
    LUKS_BACKUP_KEY=$(mktemp /tmp/luks-backup-key.XXXXXX)
    # Read 64 bytes of entropy; base64-encode with -w0 to disable line wrapping.
    dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
    echo "Enrolling auto-generated backup LUKS key..."
    # --key-file=- supplies the EXISTING passphrase (on stdin) to authorise the
    # operation; the positional file is the NEW backup key added to a second slot.
    printf '%s' "$LUKS_PASS" | cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" --key-file=-

    if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
        echo "Enroll FIDO2 key for LUKS2"
        # pause() gives the operator time to insert the FIDO2 hardware key before enrollment.
        pause
        # systemd-cryptenroll writes the FIDO2 credential into a LUKS2 token slot.
        # --fido2-with-client-pin=no: presence tap only, no PIN required at boot.
        # $PASSWORD supplies the existing passphrase to unlock a slot for enrollment,
        # so only the physical key tap remains interactive here.
        PASSWORD="$LUKS_PASS" systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
    fi

    # Format btrfs on the decrypted mapper device, not the raw partition.
    mkfs.btrfs /dev/mapper/cryptroot
    # Mount flat (no subvolume) first so the subvolume tree can be created.
    mount /dev/mapper/cryptroot /mnt
    # @ is the conventional root subvolume; @home separates user data for independent snapshots.
    btrfs subvolume create /mnt/@
    btrfs subvolume create /mnt/@home
    # Unmount the flat view before remounting through named subvolumes.
    umount /mnt

    # Remount each subvolume at its intended mountpoint for pacstrap to populate.
    mount -o subvol=@ /dev/mapper/cryptroot /mnt
    mkdir -p /mnt/home
    mount -o subvol=@home /dev/mapper/cryptroot /mnt/home

else
    echo "Skipping encryption — formatting root directly."

    # Same btrfs subvolume layout as the encrypted path for consistency.
    mkfs.btrfs "$ROOT_PART"
    mount "$ROOT_PART" /mnt
    btrfs subvolume create /mnt/@
    btrfs subvolume create /mnt/@home
    umount /mnt

    mount -o subvol=@ "$ROOT_PART" /mnt
    mkdir -p /mnt/home
    mount -o subvol=@home "$ROOT_PART" /mnt/home
fi

mkdir -p /mnt/boot
# Mount the EFI partition at /mnt/boot so GRUB and the kernel install inside the new tree.
mount "$EFI_PART" /mnt/boot

# Place backup key inside the new system (readable only by root, inside LUKS container)
if [[ -n "$LUKS_BACKUP_KEY" ]]; then
    # `install -m 400` creates the destination with mode 0400 (root read-only) atomically,
    # avoiding an intermediate world-readable state from a separate chmod.
    install -m 400 "$LUKS_BACKUP_KEY" /mnt/_LUKS_BACKUP_KEY
    # Remove the temp file from the live environment immediately after copying.
    rm -f "$LUKS_BACKUP_KEY"
    echo "Backup LUKS key written to /_LUKS_BACKUP_KEY in new system."
fi

############################################
# Base System Install
############################################
# lspci lists PCI devices; grep for VGA and 3D controller lines to identify the GPU.
# `|| true` prevents set -e from aborting when no matching line is found.
GPU_INFO=$(lspci | grep -E "VGA|3D" || true)
GPU_PKGS=""

if echo "$GPU_INFO" | grep -qi nvidia; then
    # nvidia-open is the open-source kernel module (Pascal+); preferred over nvidia-dkms.
    GPU_PKGS="nvidia-open"
elif echo "$GPU_INFO" | grep -qi amd; then
    GPU_PKGS="xf86-video-amdgpu"
elif echo "$GPU_INFO" | grep -qi intel; then
    GPU_PKGS="xf86-video-intel"
fi

# GPU_PKGS is intentionally unquoted so it word-splits into individual package names
# (or expands to nothing when no GPU was detected).
# shellcheck disable=SC2086
pacstrap /mnt \
    base base-devel "$KERNEL" linux-firmware vim zsh git networkmanager grub efibootmgr \
    btrfs-progs cryptsetup libfido2 pam-u2f sudo less jq $GPU_PKGS

# -U: use UUIDs rather than device names so fstab entries survive hardware changes.
genfstab -U /mnt >> /mnt/etc/fstab

############################################
# COPY ANSWERFILE INTO NEW SYSTEM
############################################
# Make the answerfile available inside the new system so the dotfiles TUI installer
# can read the same selections without re-prompting.
if $AF_MODE; then
    install -m 644 "$ANSWERFILE" /mnt/answerfile.json
fi

############################################
# DOTFILES CLONE TO SKEL (with retry)
############################################
# Clone before entering the chroot so useradd -m inside will copy Dotfiles to
# the new user's home.  Interactive retry is available so a transient network
# failure doesn't silently leave the system without dotfiles.
echo "Cloning dotfiles into /mnt/etc/skel..."
_clone_ok=false
while ! $_clone_ok; do
    if git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /mnt/etc/skel/Dotfiles; then
        mkdir -p /mnt/etc/skel/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos}
        _clone_ok=true
    else
        if $AF_MODE; then
            echo "Warning: dotfiles clone failed — continuing without dotfiles."
            _clone_ok=true
        else
            read -rp "Clone failed — retry? [y/N]: " _retry
            [[ "${_retry,,}" == "y" ]] || { echo "Skipping dotfiles — clone manually after first boot."; _clone_ok=true; }
        fi
    fi
done

############################################
# CHROOT Configuration
############################################
# Capture the root partition UUID before entering the chroot; used in GRUB cmdline.
ROOT_UUID=$(blkid -s UUID -o value "$ROOT_PART")

# Variables are passed via `/usr/bin/env` on the arch-chroot command line rather
# than `export` in the outer shell — this makes the environment explicit and
# avoids leaking unrelated exported variables into the chroot.
# The heredoc delimiter is single-quoted ('CHROOT_EOF') to prevent the outer
# shell from expanding any $variables inside the heredoc body.
arch-chroot /mnt /usr/bin/env \
  HOSTNAME="$HOSTNAME" \
  USERNAME="$USERNAME" \
  USERPASS="$USERPASS" \
  ENCRYPT_DISK="$ENCRYPT_DISK" \
  ENABLE_FIDO_ROOT="$ENABLE_FIDO_ROOT" \
  ENABLE_FIDO_USER="$ENABLE_FIDO_USER" \
  ROOT_UUID="$ROOT_UUID" \
  ROOT_PART="$ROOT_PART" \
  KEYMAP="$KEYMAP" \
  /bin/bash <<'CHROOT_EOF'

set -euo pipefail

# Uncomment (create) the UTF-8 locale entry so locale-gen can compile it.
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf

# vconsole.conf sets the console keymap permanently for all TTY sessions.
echo "KEYMAP=${KEYMAP}" > /etc/vconsole.conf

# Symlink the zoneinfo file; hwclock --systohc syncs the RTC to current system time.
ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
hwclock --systohc

echo "$HOSTNAME" > /etc/hostname
# Enable NetworkManager at boot so the installed system has networking on first login.
systemctl enable NetworkManager

# -m: create home from /etc/skel (dotfiles cloned before chroot); -G wheel: allow sudo; -s /bin/zsh: default shell.
useradd -m -G wheel -s /bin/zsh "$USERNAME"
# chpasswd reads "user:pass" from stdin to set the password non-interactively.
echo "$USERNAME:$USERPASS" | chpasswd
# Ensure all files under the new home dir are owned by the user (skel copy may be root-owned).
chown -R "$USERNAME:$USERNAME" "/home/$USERNAME"

# Grant wheel group full sudo access (ALL covers any host/user/group runas context).
# Use a drop-in rather than appending to /etc/sudoers: the default sudoers ends
# with '@includedir /etc/sudoers.d', so an appended '%wheel' rule would be parsed
# AFTER the drop-ins and — since the last matching rule wins — override the
# temporary 99-setup-nopasswd NOPASSWD rule used during the in-chroot TUI run,
# making the user re-enter their password on every sudo. A 10-wheel drop-in sorts
# before 99-setup-nopasswd, so NOPASSWD wins while it is present and password
# auth resumes once it is removed.
# Guard that drop-ins are actually read (the stock sudoers already includes this).
grep -q '^@includedir /etc/sudoers.d' /etc/sudoers || echo '@includedir /etc/sudoers.d' >> /etc/sudoers
echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/10-wheel
chmod 0440 /etc/sudoers.d/10-wheel

# Initramfs hook selection:
# 1. FIDO2 root unlock: needs `systemd` + `sd-encrypt` for systemd-cryptsetup.
# 2. Password-only LUKS: classic `encrypt` hook (no systemd dependency in initramfs).
# 3. Unencrypted: minimal hook set — no encrypt hook needed.
if [[ "$ENCRYPT_DISK" == "YES" && "$ENABLE_FIDO_ROOT" == "YES" ]]; then
    sed -i 's/^HOOKS=.*/HOOKS=(base udev systemd autodetect microcode modconf kms consolefont block sd-encrypt btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
elif [[ "$ENCRYPT_DISK" == "YES" ]]; then
    sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block encrypt btrfs filesystems keyboard keymap fsck)/' /etc/mkinitcpio.conf
else
    sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms consolefont block btrfs filesystems keyboard fsck)/' /etc/mkinitcpio.conf
fi

# -P regenerates all installed kernel presets (more thorough than -p <kernel>).
mkinitcpio -P

# GRUB
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
    if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
        # systemd-cryptsetup (sd-encrypt hook) reads rd.luks.name=<UUID>=<name>.
        # rd.luks.options=fido2-device=auto instructs it to probe for a FIDO2 token.
        GRUB_CMDLINE="rd.luks.name=$ROOT_UUID=cryptroot rd.luks.options=fido2-device=auto root=/dev/mapper/cryptroot"
    else
        # Classic encrypt hook syntax: cryptdevice=UUID=<UUID>:<dm-name>.
        GRUB_CMDLINE="cryptdevice=UUID=$ROOT_UUID:cryptroot root=/dev/mapper/cryptroot"
    fi
else
    # Unencrypted btrfs: root= by UUID; rootflags selects the @ subvolume.
    GRUB_CMDLINE="root=UUID=${ROOT_UUID} rootflags=subvol=@"
fi

# Inject the kernel command line; `|` delimiter avoids escaping `/` in device paths.
sed -i "s|^GRUB_CMDLINE_LINUX=.*|GRUB_CMDLINE_LINUX=\"$GRUB_CMDLINE\"|" /etc/default/grub

# Install GRUB to the EFI partition; --bootloader-id names the NVRAM/EFI menu entry.
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
# Also install to the UEFI removable-media fallback path (/EFI/BOOT/BOOTX64.EFI).
# The --bootloader-id install above only creates an NVRAM boot variable; a machine
# whose NVRAM is empty or gets cleared (fresh board, CMOS reset, firmware that drops
# stale entries, or a disk cloned/redeployed to different hardware) then has "No
# bootable device". The removable fallback is what firmware boots when no NVRAM entry
# matches, making the installed disk portable and self-booting regardless of NVRAM.
grub-install --target=x86_64-efi --efi-directory=/boot --removable
# Generate grub.cfg from /etc/default/grub and discovered kernels/initramfs images.
grub-mkconfig -o /boot/grub/grub.cfg

# User login FIDO2 — directory + PAM only; key enrollment happens outside chroot
if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
    # Create the Yubico config dir that pam_u2f expects for the u2f_keys file.
    # `mkdir -p` here creates ~/.config itself as root, so chown the whole tree
    # (not just Yubico/) — otherwise ~/.config stays root-owned and every later
    # user-level step (shell-setup symlinks, systemd --user timers, Hyprland's
    # own ~/.config/hypr) fails with "Permission denied".
    mkdir -p "/home/$USERNAME/.config/Yubico"
    chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.config"
    # `cue` option: pam_u2f prints a prompt so the user knows to touch the key.
    echo "auth required pam_u2f.so cue" >> /etc/pam.d/system-local-login
fi

CHROOT_EOF

# pamu2fcfg must run outside arch-chroot: inside the chroot the host's udev manages
# /dev/hidraw* permissions and the new user has no access to the device.
if [[ "$ENABLE_FIDO_USER" == "YES" ]]; then
    echo "Enrolling FIDO2 key for user login (outside chroot)..."
    U2F_KEYFILE="/mnt/home/${USERNAME}/.config/Yubico/u2f_keys"
    mkdir -p "/mnt/home/${USERNAME}/.config/Yubico"
    # -o and -i set the origin/app-id to the hostname, scoping the credential so it
    # cannot be replayed on a different system's PAM stack.
    pamu2fcfg -u "$USERNAME" -o "pam://$HOSTNAME" -i "pam://$HOSTNAME" > "$U2F_KEYFILE"
    # Query the UID/GID from inside the chroot to get the correct numeric IDs, since
    # the live environment may have different /etc/passwd entries.
    _NEWUID=$(arch-chroot /mnt id -u "$USERNAME" 2>/dev/null || echo "1000")
    _NEWGID=$(arch-chroot /mnt id -g "$USERNAME" 2>/dev/null || echo "1000")
    # chown the whole ~/.config tree: the mkdir above created ~/.config as root,
    # so reclaiming only Yubico/ would leave ~/.config itself root-owned.
    chown -R "$_NEWUID:$_NEWGID" "/mnt/home/${USERNAME}/.config"
    # 600: only the owning user can read or write the key file.
    chmod 600 "$U2F_KEYFILE"
    echo "FIDO2 key enrolled for $USERNAME."
fi

############################################
# DOTFILES SETUP (in-chroot, optional)
############################################
# RUN_TUI was gathered up front (answerfile field or the initial prompt), so no
# additional interaction is needed at this point.
_DO_TUI="${RUN_TUI}"

if [[ "${_DO_TUI^^}" == "YES" ]]; then
    # Grant temporary passwordless sudo so the TUI installer can call pacman/yay
    # inside the chroot without a password. Removed immediately after the script exits.
    # `Defaults:<user> !authenticate` is required alongside the NOPASSWD command
    # rule: installers like starship/rustup call `sudo -v`, which still demands a
    # password whenever the user has any password-required entry (the wheel rule).
    printf 'Defaults:%s !authenticate\n%s ALL=(ALL:ALL) NOPASSWD: ALL\n' "$USERNAME" "$USERNAME" \
        | arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
    arch-chroot /mnt chmod 0440 /etc/sudoers.d/99-setup-nopasswd

    echo "Running tui-install.sh as ${USERNAME} inside chroot..."
    # `runuser -u` switches to the unprivileged user inside the chroot so that
    # AUR helpers and dotfiles are owned/built by the correct UID.
    arch-chroot /mnt runuser -u "${USERNAME}" -- \
        bash "/home/${USERNAME}/Dotfiles/setup/tui-install.sh" \
        || echo "Warning: tui-install exited with errors — check ~/dotfiles-install.log in the new system."

    # Remove the temporary no-password sudoers drop-in after setup completes.
    arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd
fi

# Remove answerfile from new system after setup completes
# (it contains drive paths and config that are no longer needed post-install).
if $AF_MODE && [[ -f /mnt/answerfile.json ]]; then
    rm -f /mnt/answerfile.json
fi

# Copy the install log into /boot so it is readable before the first login.
cp "$LOGFILE" /mnt/boot/ 2>/dev/null || true

echo "Installation complete! Log saved to /mnt/boot/$(basename "$LOGFILE")"
echo "  umount -R /mnt && reboot"
if [[ "${_DO_TUI^^}" != "YES" ]]; then
    echo
    echo "After first boot, login as ${USERNAME} and run:"
    echo "  ~/Dotfiles/setup/tui-install.sh"
fi
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
    echo
    echo "LUKS backup key stored at /_LUKS_BACKUP_KEY in the new system."
    echo "Keep this file secure — it can unlock the root partition."
fi
