#!/usr/bin/env bash
# ==============================================================
#  Sort - Downloads cleaner
# --------------------------------------------------------------
#  Author   : Kris Yotam (aka. khr1st)
#  Contact  : krisyotam@protonmail.com
#  License  : GNU GPLv3
#  Date     : 2025-12-04
# --------------------------------------------------------------
#  Description:
#    Minimal, suckless-style bin script to tidy a Downloads
#    directory by moving files into categorized subfolders under
#    "$HOME/Misc" (Compressed, Installers, Packages, Other) and
#    user directories (Pictures, Music, Videos). Shows a small TUI
#    grid progress indicator and prints each move as it happens.
#    At the end the script offers to run the separate `clean`
#    utility which interactively prunes files in "$HOME/Misc".
# ==============================================================

set -eu

# Configuration
DEFAULT_SRCS=("$HOME/Downloads" "$HOME/downloads")
MISC_BASE="$HOME/Misc"
DIR_COMPRESSED="Compressed"
DIR_INSTALLERS="Installers"
DIR_PACKAGES="Packages"
DIR_OTHER="Other"

# User dirs
PICTURES_DIR="$HOME/Pictures"
MUSIC_DIR="$HOME/Music"
VIDEOS_DIR="$HOME/Videos"

# Grid config (10x10 => 100 cells mapping to percent)
GRID_COLS=10
GRID_ROWS=10
GRID_CELLS=$((GRID_COLS * GRID_ROWS))

declare -a FILES=()
declare -a LOGS=()

ext_lower() {
  # Get lowercase extension without leading dot
  fname="$1"
  ext="${fname##*.}"
  printf "%s" "${ext,,}"
}

ensure_dirs() {
  mkdir -p "$MISC_BASE/$DIR_COMPRESSED" \
           "$MISC_BASE/$DIR_INSTALLERS" \
           "$MISC_BASE/$DIR_PACKAGES" \
           "$MISC_BASE/$DIR_OTHER" \
           "$PICTURES_DIR" "$MUSIC_DIR" "$VIDEOS_DIR"
}

pick_dest() {
  filename="$1"
  ext="$(ext_lower "$filename")"
  case "$ext" in
    # Compressed
    zip|tar|gz|tgz|tbz|tbz2|bz2|xz|7z|rar|tar.gz|tar.bz2|tar.xz)
      printf "%s" "$MISC_BASE/$DIR_COMPRESSED" ;;
    # Images -> Pictures
    jpg|jpeg|png|gif|webp|svg|bmp|tif|tiff|heic)
      printf "%s" "$PICTURES_DIR" ;;
    # Audio -> Music
    mp3|wav|flac|m4a|aac|ogg|opus|wma)
      printf "%s" "$MUSIC_DIR" ;;
    # Video -> Videos
    mp4|mkv|webm|mov|avi|mpeg|mpg|flv|3gp)
      printf "%s" "$VIDEOS_DIR" ;;
    # Installers
    exe|msi|dmg|AppImage|sh|run)
      printf "%s" "$MISC_BASE/$DIR_INSTALLERS" ;;
    # Packages
    deb|rpm|pkg|apk)
      printf "%s" "$MISC_BASE/$DIR_PACKAGES" ;;
    *) printf "%s" "$MISC_BASE/$DIR_OTHER" ;;
  esac
}

unique_dest() {
  destdir="$1"
  base="$2"
  dest="$destdir/$base"
  if [ ! -e "$dest" ]; then
    printf "%s" "$dest"
    return 0
  fi
  i=1
  name="${base%.*}"
  ext=""
  if [[ "$base" == *.* ]]; then
    ext=".${base##*.}"
  fi
  while :; do
    candidate="$destdir/${name}_$i$ext"
    if [ ! -e "$candidate" ]; then
      printf "%s" "$candidate" && return 0
    fi
    i=$((i + 1))
  done
}

move_one() {
  src="$1"
  bn="$(basename -- "$src")"
  destdir="$(pick_dest "$bn")"
  mkdir -p "$destdir"
  dest="$(unique_dest "$destdir" "$bn")"
  if mv -- "$src" "$dest"; then
    LOGS+=("Moved: $bn -> $(realpath --relative-to="$HOME" "$dest")")
    echo "Moved: $bn -> $(realpath --relative-to="$HOME" "$dest")"
  else
    LOGS+=("Failed: $bn")
    echo "Failed to move: $bn" >&2
  fi
}

gather_files() {
  src="$1"
  # gather regular files only (no dirs)
  while IFS= read -r -d '' f; do
    FILES+=("$f")
  done < <(find "$src" -maxdepth 1 -type f -print0 2>/dev/null)
}

draw_grid() {
  filled_cells=$1
  columns=$GRID_COLS
  rows=$GRID_ROWS
  idx=0
  for ((r=0;r<rows;r++)); do
    line=""
    for ((c=0;c<columns;c++)); do
      idx=$((r*columns + c + 1))
      if [ $idx -le $filled_cells ]; then
        cell='[**]'
      else
        cell='[  ]'
      fi
      line+="$cell"
    done
    printf "%s\n" "$line"
  done
}

draw_tui() {
  total=$1
  done=$2
  pct=0
  if [ "$total" -gt 0 ]; then
    pct=$((done * 100 / total))
  fi
  # map done -> number of filled cells
  filled_cells=0
  if [ "$total" -gt 0 ]; then
    filled_cells=$((done * GRID_CELLS / total))
  fi
  clear
  printf "Sort: %d/%d files (%d%%)\n\n" "$done" "$total" "$pct"
  draw_grid $filled_cells
  printf "\nRecent actions:\n"
  # show last 8 logs
  start=0
  if [ ${#LOGS[@]} -gt 8 ]; then
    start=$((${#LOGS[@]} - 8))
  fi
  for ((i=start;i<${#LOGS[@]};i++)); do
    printf "  %s\n" "${LOGS[$i]}"
  done
}

main() {
  srcdir=""
  if [ $# -ge 1 ]; then
    srcdir="$1"
  else
    # pick first existing default
    for cand in "${DEFAULT_SRCS[@]}"; do
      [ -d "$cand" ] && { srcdir="$cand"; break; }
    done
  fi
  if [ -z "$srcdir" ]; then
    echo "No Downloads folder found. Specify a directory: sort /path/to/dir" >&2
    exit 1
  fi

  ensure_dirs
  gather_files "$srcdir"
  total=${#FILES[@]}
  if [ "$total" -eq 0 ]; then
    echo "No files to sort in $srcdir"
    exit 0
  fi

  moved=0
  # iterate and move files
  for f in "${FILES[@]}"; do
    move_one "$f"
    moved=$((moved + 1))
    # update tui
    draw_tui "$total" "$moved"
    # tiny pause so user sees progress
    sleep 0.08
  done

  printf "\nDone. Moved %d files into %s and user dirs.\n" "$moved" "$MISC_BASE"

  # Offer to run clean
  printf "\nRun interactive clean on %s? [y/N]: " "$MISC_BASE"
  IFS= read -r ans || ans=""
  case "$ans" in
    [yY]|[yY][eE][sS])
      # call clean script if exists else run inline
      if [ -x "$HOME/.local/bin/clean" ]; then
        "$HOME/.local/bin/clean" "$MISC_BASE"
      elif [ -x "/usr/local/bin/clean" ]; then
        "/usr/local/bin/clean" "$MISC_BASE"
      else
        # try to call script in repo
        if [ -x "$(dirname "$0")/clean" ]; then
          "$(dirname "$0")/clean" "$MISC_BASE"
        else
          echo "clean utility not found. Skipping." >&2
        fi
      fi
      ;;
    *) echo "Skipping clean." ;;
  esac
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then
  main "$@"
fi
