#!/usr/bin/env bash
#
# Jotura installer for macOS and Linux.
#
#   curl -fsSL https://jotura.io/install.sh | bash
#
# What this script does, in order:
#   1. Fetches the release manifest from releases.jotura.io.
#   2. Downloads the installer artifact for your OS and CPU.
#   3. Verifies its sha256 against the manifest before touching anything.
#   4. Installs the desktop app and puts the jotura CLI on your PATH.
#   5. Installs the Jotura agent skill for any AI coding agents found on
#      this machine (Claude Code, Codex, Gemini).
#   6. Launches the app.
#
# Rerunning the script upgrades an existing install in place.
#
# Flags (pass them like: curl ... | bash -s -- --no-launch):
#   --no-launch       do not launch the app at the end
#   --skills <mode>   all | claude | codex | gemini | copilot | none
#                     (default: install for every agent detected)
#   --deb             Linux only: install the .deb system package instead
#                     of the AppImage. The .deb does not auto-update;
#                     rerun this script to upgrade it.
#   -h, --help        print this help and exit
#
# Environment:
#   JOTURA_INSTALL_DRYRUN=1   stop after download and checksum verification,
#                             before anything is installed (used by CI)
#   JOTURA_MANIFEST_URL=...   override the release manifest URL (used by CI)

set -Eeuo pipefail

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

MANIFEST_URL="${JOTURA_MANIFEST_URL:-https://releases.jotura.io/install-manifest.json}"
DOWNLOAD_PAGE="https://jotura.io/download"

LAUNCH=1
SKILLS_MODE="auto"
USE_DEB=0

PLATFORM=""
ARCH=""
VERSION=""
MANIFEST_FLAT=""
ARTIFACT_FILE=""
APP_SUMMARY=""
CLI_PATH=""
SKILLS_INSTALLED=""

# ---------------------------------------------------------------------------
# Output helpers
# ---------------------------------------------------------------------------

step() { printf '\n==> %s\n' "$1"; }
info() { printf '    %s\n' "$1"; }
warn() { printf '    warning: %s\n' "$1" >&2; }

fail() {
  printf '\nerror: %s\n' "$1" >&2
  printf '\nManual fallback: download the installer for your platform from\n' >&2
  printf '%s and follow the instructions on that page.\n' "$DOWNLOAD_PAGE" >&2
  exit 1
}

usage() {
  sed -n '2,32p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//' ||
    printf 'Usage: install.sh [--no-launch] [--skills all|claude|codex|gemini|copilot|none] [--deb]\n'
}

# ---------------------------------------------------------------------------
# Small utilities
# ---------------------------------------------------------------------------

TMP_DIR="$(mktemp -d)"
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT
# Catch-all for anything not already wrapped in an explicit check.
trap 'fail "unexpected failure at line $LINENO"' ERR

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "required tool not found: $1"
}

# sudo works here if it can run without a password, or if we have a terminal
# to prompt on (sudo prompts on the tty even when stdin is a pipe).
can_sudo() {
  command -v sudo >/dev/null 2>&1 || return 1
  if sudo -n true 2>/dev/null; then return 0; fi
  [ -t 1 ]
}

download() {
  # Show curl's progress bar only when attached to a terminal.
  if [ -t 2 ]; then
    curl -f -L --progress-bar -o "$2" "$1" || fail "download failed: $1"
  else
    curl -fsSL -o "$2" "$1" || fail "download failed: $1"
  fi
}

sha256_of() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  else
    shasum -a 256 "$1" | awk '{print $1}'
  fi
}

verify_sha256() {
  local file="$1" expected="$2" actual
  actual="$(sha256_of "$file" | tr '[:upper:]' '[:lower:]')"
  expected="$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')"
  if [ "$actual" != "$expected" ]; then
    fail "checksum mismatch for $(basename "$file"): expected $expected, got $actual. The download may be corrupted or tampered with. Nothing was installed."
  fi
  info "sha256 verified: $actual"
}

# The manifest is machine-generated JSON with a fixed shape, so a targeted
# extraction is safe here and avoids depending on jq or python.
manifest_field() {
  local key="$1" field="$2" obj
  obj="$(printf '%s' "$MANIFEST_FLAT" | sed -n "s/.*\"$key\":{\([^}]*\)}.*/\1/p")"
  printf '%s' "$obj" | sed -n "s/.*\"$field\":\"\([^\"]*\)\".*/\1/p"
}

# ---------------------------------------------------------------------------
# Argument parsing and platform detection
# ---------------------------------------------------------------------------

parse_args() {
  while [ $# -gt 0 ]; do
    case "$1" in
      --no-launch) LAUNCH=0 ;;
      --skills)
        [ $# -ge 2 ] || fail "--skills needs a value: all, claude, codex, gemini, copilot, or none"
        case "$2" in
          all|claude|codex|gemini|copilot|none) SKILLS_MODE="$2" ;;
          *) fail "invalid --skills value '$2' (expected all, claude, codex, gemini, copilot, or none)" ;;
        esac
        shift ;;
      --deb) USE_DEB=1 ;;
      -h|--help) usage; exit 0 ;;
      *) fail "unknown flag: $1 (run with --help for usage)" ;;
    esac
    shift
  done
}

detect_platform() {
  case "$(uname -s)" in
    Darwin) PLATFORM="macos" ;;
    Linux) PLATFORM="linux" ;;
    *) fail "unsupported platform '$(uname -s)'. On Windows, use: irm https://jotura.io/install.ps1 | iex" ;;
  esac
  if [ "$PLATFORM" = "linux" ]; then
    case "$(uname -m)" in
      x86_64|amd64) ARCH="x86_64" ;;
      aarch64|arm64) ARCH="aarch64" ;;
      *) fail "unsupported Linux architecture '$(uname -m)' (x86_64 and aarch64 are supported)" ;;
    esac
  fi
  if [ "$USE_DEB" = 1 ] && [ "$PLATFORM" != "linux" ]; then
    warn "--deb only applies on Linux; ignoring it."
    USE_DEB=0
  fi
}

# ---------------------------------------------------------------------------
# Manifest fetch and artifact download
# ---------------------------------------------------------------------------

fetch_manifest() {
  step "Fetching release manifest"
  local raw
  raw="$(curl -fsSL "$MANIFEST_URL")" || fail "could not fetch the release manifest from $MANIFEST_URL"
  MANIFEST_FLAT="$(printf '%s' "$raw" | tr -d '\n\r\t ')"
  VERSION="$(printf '%s' "$MANIFEST_FLAT" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')"
  [ -n "$VERSION" ] || fail "could not read the version from the release manifest"
  info "Latest version: $VERSION"
}

download_artifact() {
  local key="$1" url sha
  url="$(manifest_field "$key" "url")"
  sha="$(manifest_field "$key" "sha256")"
  [ -n "$url" ] || fail "the release manifest has no '$key' artifact"
  printf '%s' "$sha" | grep -Eq '^[0-9a-fA-F]{64}$' || fail "the release manifest has no valid sha256 for '$key'"

  step "Downloading Jotura $VERSION ($key)"
  info "$url"
  ARTIFACT_FILE="$TMP_DIR/$(basename "$url")"
  download "$url" "$ARTIFACT_FILE"

  step "Verifying checksum"
  verify_sha256 "$ARTIFACT_FILE" "$sha"

  if [ -n "${JOTURA_INSTALL_DRYRUN:-}" ]; then
    info "JOTURA_INSTALL_DRYRUN is set: stopping before installation. Nothing was installed."
    exit 0
  fi
}

# ---------------------------------------------------------------------------
# macOS install
# ---------------------------------------------------------------------------

install_macos() {
  download_artifact "darwin-universal-app-tar-gz"

  step "Installing Jotura.app into /Applications"
  mkdir -p "$TMP_DIR/extract"
  tar -xzf "$ARTIFACT_FILE" -C "$TMP_DIR/extract" || fail "could not extract the app archive"
  local src
  src="$(find "$TMP_DIR/extract" -maxdepth 2 -type d -name 'Jotura.app' | head -n 1)"
  [ -n "$src" ] || fail "Jotura.app was not found inside the downloaded archive"

  # Replace any existing install: extracted fresh above, then swap in place.
  if [ -w /Applications ]; then
    rm -rf /Applications/Jotura.app
    mv "$src" /Applications/Jotura.app
    xattr -cr /Applications/Jotura.app 2>/dev/null || warn "could not clear the quarantine flag; macOS may warn on first launch"
  elif can_sudo; then
    info "/Applications is not writable by your user, so sudo is needed; it may prompt for your password."
    sudo rm -rf /Applications/Jotura.app
    sudo mv "$src" /Applications/Jotura.app
    sudo xattr -cr /Applications/Jotura.app 2>/dev/null || warn "could not clear the quarantine flag; macOS may warn on first launch"
  else
    fail "cannot write to /Applications and sudo is not available"
  fi
  info "Installed /Applications/Jotura.app"
  APP_SUMMARY="/Applications/Jotura.app"

  step "Putting the jotura CLI on your PATH"
  local bundled="/Applications/Jotura.app/Contents/MacOS/jotura"
  CLI_PATH="$bundled"
  info "Linking $bundled"
  info "to /usr/local/bin/jotura. This needs sudo because /usr/local/bin is"
  info "system-owned; sudo may prompt for your password."
  if can_sudo && sudo mkdir -p /usr/local/bin && sudo ln -sf "$bundled" /usr/local/bin/jotura; then
    CLI_PATH="/usr/local/bin/jotura"
    info "jotura is on your PATH at /usr/local/bin/jotura"
  else
    warn "could not create the symlink (sudo unavailable or declined)."
    warn "The CLI still works from inside the app bundle. To put it on your PATH later, run:"
    warn "  sudo ln -sf \"$bundled\" /usr/local/bin/jotura"
  fi
}

launch_macos() {
  step "Launching Jotura"
  open /Applications/Jotura.app || warn "could not launch the app; open it from /Applications"
}

# ---------------------------------------------------------------------------
# Linux install (AppImage default, --deb optional)
# ---------------------------------------------------------------------------

install_linux_appimage() {
  download_artifact "linux-$ARCH-appimage"

  step "Installing the Jotura AppImage"
  local dir="$HOME/.local/share/jotura"
  local app="$dir/Jotura.AppImage"
  mkdir -p "$dir"
  chmod +x "$ARTIFACT_FILE"
  # Stage next to the final path so the last rename is atomic.
  mv -f "$ARTIFACT_FILE" "$app.new"
  mv -f "$app.new" "$app"
  info "Installed $app"
  info "The AppImage auto-updates itself from inside the app."
  APP_SUMMARY="$app"

  # --appimage-extract works without FUSE, so this is safe on minimal systems.
  step "Extracting the bundled jotura CLI and app icon"
  local extracted=1
  (cd "$TMP_DIR" && "$app" --appimage-extract >/dev/null 2>&1) || extracted=0
  local root="$TMP_DIR/squashfs-root"

  if [ "$extracted" = 1 ] && [ -f "$root/usr/bin/jotura" ]; then
    mkdir -p "$HOME/.local/bin"
    install -m 0755 "$root/usr/bin/jotura" "$HOME/.local/bin/jotura"
    CLI_PATH="$HOME/.local/bin/jotura"
    info "Installed the CLI at $CLI_PATH"
    case ":$PATH:" in
      *":$HOME/.local/bin:"*) ;;
      *)
        warn "$HOME/.local/bin is not on your PATH. Add this line to your shell profile:"
        warn "  export PATH=\"\$HOME/.local/bin:\$PATH\""
        ;;
    esac
  else
    warn "could not extract the AppImage, so the jotura CLI was not installed."
    warn "You can extract it later with: $app --appimage-extract"
  fi

  local icon_path="" candidate
  for candidate in "$root"/usr/share/icons/hicolor/256x256/apps/*.png \
                   "$root"/usr/share/icons/hicolor/128x128/apps/*.png \
                   "$root"/*.png; do
    if [ -f "$candidate" ]; then icon_path="$candidate"; break; fi
  done
  if [ -n "$icon_path" ]; then
    mkdir -p "$HOME/.local/share/icons"
    cp -f "$icon_path" "$HOME/.local/share/icons/jotura.png"
  fi

  step "Creating the application menu entry"
  mkdir -p "$HOME/.local/share/applications"
  cat > "$HOME/.local/share/applications/jotura.desktop" <<DESKTOP
[Desktop Entry]
Type=Application
Name=Jotura
Comment=Local-first markdown notes
Exec=$app
Icon=$HOME/.local/share/icons/jotura.png
Terminal=false
Categories=Office;Utility;
DESKTOP
  if command -v update-desktop-database >/dev/null 2>&1; then
    update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
  fi
  info "Menu entry installed (jotura.desktop)"
}

install_linux_deb() {
  download_artifact "linux-$ARCH-deb"

  step "Installing the Jotura .deb package"
  info "Note: the .deb does not auto-update. Rerun this script to upgrade it."
  info "Installing a system package needs sudo; it may prompt for your password."
  can_sudo || fail "sudo is required for the .deb install; rerun without --deb for a user-level AppImage install"
  local debfile="$TMP_DIR/jotura.deb"
  mv -f "$ARTIFACT_FILE" "$debfile"
  if command -v apt-get >/dev/null 2>&1; then
    sudo apt-get install -y "$debfile" || fail "apt-get could not install the package"
  else
    warn "apt-get not found; falling back to dpkg -i (dependencies are not auto-resolved)."
    sudo dpkg -i "$debfile" || fail "dpkg could not install the package"
  fi
  CLI_PATH="/usr/bin/jotura"
  APP_SUMMARY="system package (run jotura-desktop, or use your app menu)"
  info "Installed. The CLI is at /usr/bin/jotura."
}

launch_linux() {
  step "Launching Jotura"
  local target
  if [ "$USE_DEB" = 1 ]; then
    target="jotura-desktop"
  else
    target="$HOME/.local/share/jotura/Jotura.AppImage"
  fi
  if command -v setsid >/dev/null 2>&1; then
    (setsid nohup "$target" >/dev/null 2>&1 &) || warn "could not launch the app"
  elif command -v gtk-launch >/dev/null 2>&1 && [ "$USE_DEB" = 0 ]; then
    (nohup gtk-launch jotura >/dev/null 2>&1 &) || warn "could not launch the app"
  else
    (nohup "$target" >/dev/null 2>&1 &) || warn "could not launch the app"
  fi
}

# ---------------------------------------------------------------------------
# AI agent skill install
# ---------------------------------------------------------------------------

try_skill() {
  local agent="$1"
  if "$CLI_PATH" skill install "$agent" --force >/dev/null 2>&1; then
    SKILLS_INSTALLED="${SKILLS_INSTALLED:+$SKILLS_INSTALLED }$agent"
    info "Installed the $agent skill."
  else
    warn "skill install for $agent failed; try later with: jotura skill install $agent --force"
  fi
}

install_skills() {
  step "Installing the Jotura agent skill"
  if [ "$SKILLS_MODE" = "none" ]; then
    info "Skipped (--skills none)."
    return
  fi
  if [ -z "$CLI_PATH" ] || [ ! -x "$CLI_PATH" ]; then
    warn "the jotura CLI is not available, so no skills were installed."
    warn "Once the CLI is on your PATH, run: jotura skill install all --force"
    return
  fi
  local found=0
  case "$SKILLS_MODE" in
    all)
      try_skill claude; try_skill codex; try_skill gemini; try_skill copilot; found=1 ;;
    claude|codex|gemini|copilot)
      try_skill "$SKILLS_MODE"; found=1 ;;
    *)
      if [ -d "$HOME/.claude" ]; then try_skill claude; found=1; fi
      if [ -d "$HOME/.codex" ]; then try_skill codex; found=1; fi
      if [ -d "$HOME/.gemini" ]; then try_skill gemini; found=1; fi
      if [ -d "$HOME/.copilot" ]; then try_skill copilot; found=1; fi
      ;;
  esac
  if [ "$found" = 0 ]; then
    info "No AI agents detected (looked for ~/.claude, ~/.codex, ~/.gemini, ~/.copilot)."
    info "Install later with: jotura skill install <claude|codex|gemini|copilot|all> --force"
  fi
}

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------

print_summary() {
  printf '\n'
  printf 'Jotura %s is installed.\n\n' "$VERSION"
  printf '  App:    %s\n' "$APP_SUMMARY"
  printf '  CLI:    %s\n' "${CLI_PATH:-not installed (see warnings above)}"
  printf '  Skills: %s\n' "${SKILLS_INSTALLED:-none}"
  printf '\nNext steps:\n'
  printf '  1. In the app, open an existing folder of markdown notes or create a new vault.\n'
  printf '  2. Try the CLI: jotura --help\n'
  printf '  3. Optional end-to-end encrypted sync lives in the app under Settings.\n'
  printf '\nRerun this installer any time to upgrade in place.\n'
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

main() {
  parse_args "$@"
  need_cmd curl
  need_cmd tar
  detect_platform
  fetch_manifest

  if [ "$PLATFORM" = "macos" ]; then
    install_macos
  elif [ "$USE_DEB" = 1 ]; then
    install_linux_deb
  else
    install_linux_appimage
  fi

  install_skills

  if [ "$LAUNCH" = 1 ]; then
    if [ "$PLATFORM" = "macos" ]; then launch_macos; else launch_linux; fi
  else
    info "Skipping launch (--no-launch)."
  fi

  print_summary
}

main "$@"
