#!/bin/bash

# omarchy:summary=Add an Omarchy shell plugin from a trusted source
# omarchy:group=plugin
# omarchy:args=[plugin-id] [--from <source-id>] [--enable] [--no-enable] [--review] [--no-refresh] [--yes]
# omarchy:examples=omarchy plugin add | omarchy plugin add orbit --enable | omarchy plugin add model-usage --from owner-plugins --yes

# Copies a plugin folder out of a trusted source's local clone into
# ~/.config/omarchy/plugins/<id>/. It never runs plugin code, never runs install
# hooks, and never uses sudo — it only copies files, validates the manifest, and
# toggles enabled state over shell IPC. Reviewing and trusting the code is the
# user's call; --yes opts out of every prompt for scripts/agents.

set -o pipefail

PLUGINS_DIR="$HOME/.config/omarchy/plugins"
OMARCHY_PATH="${OMARCHY_PATH:-$HOME/.local/share/omarchy}"

fail() {
  echo "omarchy-plugin-add: $*" >&2
  exit 1
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
}

interactive() {
  [[ -t 0 && -t 1 ]]
}

plugin_discovered() {
  local id="$1" plugins
  plugins=$(omarchy-shell shell listPlugins 2>/dev/null) || return 1
  jq -e --arg id "$id" 'any(.[]; .id == $id)' <<<"$plugins" >/dev/null 2>&1
}

wait_for_plugin_discovery() {
  local id="$1" attempt
  for (( attempt = 0; attempt < 40; attempt++ )); do
    plugin_discovered "$id" && return 0
    sleep 0.05
  done
  return 1
}

PLUGIN_ID=""
FROM_SOURCE=""
ENABLE_AFTER="" # "", "true", or "false"
DO_REVIEW=0
NO_REFRESH=0
ASSUME_YES=0

while (($# > 0)); do
  case "$1" in
  --from) FROM_SOURCE="${2:-}"; shift 2 ;;
  --enable) ENABLE_AFTER=true; shift ;;
  --no-enable) ENABLE_AFTER=false; shift ;;
  --review) DO_REVIEW=1; shift ;;
  --no-refresh) NO_REFRESH=1; shift ;;
  --yes | -y) ASSUME_YES=1; shift ;;
  -h | --help)
    cat <<USAGE
Usage: omarchy plugin add [plugin-id] [--from <source-id>] [--enable] [--no-enable] [--review] [--no-refresh] [--yes]

Adds a plugin from a trusted source into ~/.config/omarchy/plugins/<id>/.
Without a plugin-id, an interactive picker lists everything available.
  --from       disambiguate when the same id exists in multiple sources
  --enable     enable the plugin afterward (bar widgets land in the bar)
  --no-enable  add but leave disabled
  --review     open the plugin's files before adding (always in a TTY)
  --no-refresh use the cached clones as-is, skip the network refresh
  --yes        skip every confirmation (review, add, enable)
USAGE
    exit 0
    ;;
  -*) fail "unknown option: $1" ;;
  *)
    [[ -z $PLUGIN_ID ]] || fail "unexpected argument: $1"
    PLUGIN_ID="$1"; shift ;;
  esac
done

require_command jq

confirm() {
  local prompt="$1"
  ((ASSUME_YES)) && return 0
  if command -v gum >/dev/null 2>&1 && interactive; then
    gum confirm "$prompt"; return
  fi
  if interactive; then
    local reply; read -r -p "$prompt [y/N] " reply; [[ $reply == [yY]* ]]; return
  fi
  fail "refusing to add without confirmation; pass --yes"
}

if ((!NO_REFRESH)); then
  omarchy-plugin-source refresh >/dev/null 2>&1 || true
fi

CATALOG=$(omarchy-plugin-scan)
if [[ $(jq 'length' <<<"$CATALOG") -eq 0 ]]; then
  fail "no plugins available; add a source with: omarchy plugin source add <git-url>"
fi

# ---------------------------------------------------------------- resolve id

if [[ -z $PLUGIN_ID ]]; then
  interactive || fail "a plugin-id is required (try: omarchy plugin available)"
  choices=$(jq -r '.[] | "\(.manifest.id)\t\(.manifest.name // .manifest.id)\t\(.manifest.version // "?")\t\(.sourceId)"' <<<"$CATALOG")
  rows=$(awk -F '\t' '{ printf "%-26s  %-26s  v%-8s  [%s]\n", $1, $2, $3, $4 }' <<<"$choices")
  if command -v gum >/dev/null 2>&1; then
    pick=$(gum choose --header="Add which plugin?" <<<"$rows") || fail "cancelled"
  elif command -v fzf >/dev/null 2>&1; then
    pick=$(fzf --prompt "Add which plugin > " <<<"$rows") || fail "cancelled"
  else
    fail "a plugin-id is required (gum or fzf needed for an interactive picker)"
  fi
  [[ -n $pick ]] || fail "nothing selected"
  PLUGIN_ID=$(awk '{print $1}' <<<"$pick")
fi

if [[ -n $FROM_SOURCE ]]; then
  matches=$(jq --arg id "$PLUGIN_ID" --arg src "$FROM_SOURCE" '[.[] | select(.manifest.id == $id and .sourceId == $src)]' <<<"$CATALOG")
else
  matches=$(jq --arg id "$PLUGIN_ID" '[.[] | select(.manifest.id == $id)]' <<<"$CATALOG")
fi

match_count=$(jq 'length' <<<"$matches")
if ((match_count == 0)); then
  if [[ -n $FROM_SOURCE ]]; then
    fail "no plugin '$PLUGIN_ID' in source '$FROM_SOURCE'"
  fi
  fail "no plugin '$PLUGIN_ID' in any configured source (try: omarchy plugin available)"
fi
if ((match_count > 1)); then
  echo "omarchy-plugin-add: '$PLUGIN_ID' is available from multiple sources:" >&2
  jq -r '.[] | "  - " + .sourceId' <<<"$matches" >&2
  fail "pick one with: --from <source-id>"
fi

entry=$(jq -c '.[0]' <<<"$matches")
src_id=$(jq -r '.sourceId' <<<"$entry")
src_url=$(jq -r '.sourceUrl' <<<"$entry")
src_path=$(jq -r '.path' <<<"$entry")
m_id=$(jq -r '.manifest.id' <<<"$entry")
m_name=$(jq -r '.manifest.name // .manifest.id' <<<"$entry")
m_version=$(jq -r '.manifest.version // "?"' <<<"$entry")
m_author=$(jq -r '.manifest.author // ""' <<<"$entry")
m_license=$(jq -r '.manifest.license // ""' <<<"$entry")
m_desc=$(jq -r '.manifest.description // ""' <<<"$entry")
m_kinds=$(jq -r '(.manifest.kinds // []) | join(", ")' <<<"$entry")

# ---------------------------------------------------------------- validate

omarchy-plugin-validate "$src_path" || fail "refusing to add: validation failed"

target="$PLUGINS_DIR/$m_id"
replacing=0
target_is_link=0
if [[ -L $target ]]; then
  target_is_link=1
  replacing=1
elif [[ -d $target ]]; then
  replacing=1
fi

# Soft warning: a third-party id that matches a built-in's alias installs as a
# separate widget rather than replacing the built-in.
alias_owner=""
while IFS= read -r fp_manifest; do
  [[ -f $fp_manifest ]] || continue
  if jq -e --arg id "$m_id" '((.barWidget.aliases // []) | index($id)) != null' "$fp_manifest" >/dev/null 2>&1; then
    alias_owner=$(jq -r '.id' "$fp_manifest")
    break
  fi
done < <(find "$OMARCHY_PATH/shell/plugins" -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null)

# Always surface an alias collision, even under --yes, since it changes what the
# added plugin actually shadows.
if [[ -n $alias_owner ]]; then
  echo "Note: '$m_id' is also an alias of the built-in $alias_owner; it is added as a separate plugin and does not replace the built-in." >&2
fi

# ---------------------------------------------------------------- summary

print_summary() {
  cat <<INFO

  Plugin:    $m_name ($m_id)
  Version:   $m_version${m_author:+
  Author:    $m_author}${m_license:+
  License:   $m_license}
  Kinds:     $m_kinds
  Source:    $src_id ($src_url)${m_desc:+
  About:     $m_desc}
INFO
  if ((target_is_link)); then
    echo "  Target:    $target (REPLACING a symlink -> $(readlink "$target"))"
  elif ((replacing)); then
    echo "  Target:    $target (REPLACING existing install)"
  else
    echo "  Target:    $target"
  fi
  echo
  echo "  Plugins run as unsandboxed code inside omarchy-shell. Review before enabling."
  echo
}

review_files() {
  echo "Files in $m_id:"
  find "$src_path" -type f -printf '%P\n' 2>/dev/null | sort | sed 's/^/  /'
  echo
  # Only spawn an editor in a real terminal; a headless run just prints the path
  # so it never launches a detached window.
  if interactive && command -v omarchy-launch-editor >/dev/null 2>&1; then
    omarchy-launch-editor "$src_path"
  else
    echo "Source folder: $src_path"
  fi
}

if ((!ASSUME_YES)); then
  print_summary

  if ((DO_REVIEW)); then
    review_files
    confirm "Add $m_id $m_version now?" || fail "aborted"
  elif interactive && command -v gum >/dev/null 2>&1; then
    action=$(gum choose --header="Add $m_id $m_version?" "Add" "Review files first" "Cancel") || fail "aborted"
    case "$action" in
    "Review files first")
      review_files
      confirm "Add $m_id $m_version now?" || fail "aborted"
      ;;
    Add) ;;
    *) fail "aborted" ;;
    esac
  else
    confirm "Add $m_id $m_version?" || fail "aborted"
  fi
fi

# ---------------------------------------------------------------- install files

mkdir -p "$PLUGINS_DIR"

stage="$PLUGINS_DIR/.${m_id}.tmp.$$"
rm -rf "$stage"
if ! cp -a "$src_path/." "$stage/"; then
  rm -rf "$stage"
  fail "failed to stage plugin into $stage"
fi

backup=""
if ((replacing)); then
  if ((target_is_link)); then
    # Don't drag a (possibly large) dev-symlink target into a backup; just drop
    # the link. The real files it points at are untouched.
    rm -f "$target"
  else
    base="$PLUGINS_DIR/.${m_id}.bak.$(date -u +%Y%m%d%H%M%S)"
    backup="$base"
    n=1
    while [[ -e $backup ]]; do backup="${base}-${n}"; n=$((n + 1)); done
    if ! mv "$target" "$backup"; then
      rm -rf "$stage"
      fail "failed to back up existing $target"
    fi
  fi
fi

if ! mv "$stage" "$target"; then
  rm -rf "$stage"
  [[ -n $backup && -d $backup ]] && mv "$backup" "$target"
  fail "failed to move staged plugin into place"
fi

echo "Added $m_id $m_version into $target"
[[ -n $backup ]] && echo "Previous version backed up to: $backup"

omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true

# ---------------------------------------------------------------- enable

if [[ -z $ENABLE_AFTER ]]; then
  if ((ASSUME_YES)) || ! interactive; then
    ENABLE_AFTER=false
  elif confirm "Enable '$m_id' now?"; then
    ENABLE_AFTER=true
  else
    ENABLE_AFTER=false
  fi
fi

if [[ $ENABLE_AFTER == true ]]; then
  if ! wait_for_plugin_discovery "$m_id"; then
    echo "Could not enable $m_id because omarchy-shell did not discover it yet. Enable later with: omarchy plugin enable $m_id" >&2
  elif result=$(omarchy-shell shell setPluginEnabled "$m_id" true) && [[ $result == "ok" ]]; then
    echo "Enabled $m_id."
    if [[ $m_kinds == *bar-widget* ]]; then
      echo "It was added to the bar; arrange it with: omarchy plugin bar move $m_id --section <left|center|right>"
    fi
  else
    echo "Could not enable $m_id over IPC (is omarchy-shell running?). Enable later with: omarchy plugin enable $m_id" >&2
  fi
else
  echo "Enable it later with: omarchy plugin enable $m_id"
fi

# Reaching here means the files are in place; never let a falsy trailing test
# leak a non-zero status to callers like `omarchy plugin update`.
exit 0
