#!/bin/sh

set -eu

NODE_VERSION="24.20.0"
NODE_ARM64_SHA256="40e5607e5ecb3db9192723776da2d75d966260fc74a7a9e731c1bd67dda96bc8"
NODE_X64_SHA256="9e5b2644cf107befb6aefca676b96d3296bc10138096f022ed378d6233ed81f4"
RUNTIME_NAME="canvasctrl-macos-runtime-1.0.0-alpha.2-1f5402c5420c078dbd0992acdf5e67991bee324a"
RUNTIME_SHA256="6bd9f3472dd03afc9a291340da727267a7f5f800ef8622c06f8262f611c0d994"
RUNTIME_URL_DEFAULT="https://canvasctrl.vivek-patel.com/releases/$RUNTIME_NAME.tar.gz"
INSTALL_PREFIX="${CANVASCTRL_INSTALL_PREFIX:-${CANVASCTL_INSTALL_PREFIX:-$HOME/.local}}"
CANONICAL_RUNTIME_ROOT="$INSTALL_PREFIX/lib/canvasctrl"
LEGACY_RUNTIME_ROOT="$INSTALL_PREFIX/lib/canvasctl"
if [ -d "$LEGACY_RUNTIME_ROOT" ] && [ ! -L "$LEGACY_RUNTIME_ROOT" ]; then
  RUNTIME_ROOT="$LEGACY_RUNTIME_ROOT"
else
  RUNTIME_ROOT="$CANONICAL_RUNTIME_ROOT"
fi
CODEX_SKILLS_DIRECTORY="${CANVASCTRL_CODEX_SKILLS_DIR:-${CANVASCTL_CODEX_SKILLS_DIR:-$HOME/.codex/skills}}"
CLAUDE_SKILLS_DIRECTORY="${CANVASCTRL_CLAUDE_SKILLS_DIR:-${CANVASCTL_CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}}"

if [ "$(uname -s)" != "Darwin" ]; then
  echo "CanvasCtrl's public installer currently supports macOS only." >&2
  exit 1
fi

case "$(uname -m)" in
  arm64)
    ARCHITECTURE="arm64"
    PINNED_NODE_SHA256="$NODE_ARM64_SHA256"
    ;;
  x86_64)
    ARCHITECTURE="x64"
    PINNED_NODE_SHA256="$NODE_X64_SHA256"
    ;;
  *)
    echo "CanvasCtrl supports Apple Silicon and Intel 64-bit Macs." >&2
    exit 1
    ;;
esac

for REQUIRED_COMMAND in curl shasum tar mktemp; do
  if ! command -v "$REQUIRED_COMMAND" >/dev/null 2>&1; then
    echo "$REQUIRED_COMMAND is required to install CanvasCtrl." >&2
    exit 1
  fi
done

NODE_ARCHIVE_NAME="node-v$NODE_VERSION-darwin-$ARCHITECTURE.tar.gz"
NODE_URL_DEFAULT="https://nodejs.org/download/release/v$NODE_VERSION/$NODE_ARCHIVE_NAME"
NODE_URL="${CANVASCTRL_NODE_URL:-${CANVASCTL_NODE_URL:-$NODE_URL_DEFAULT}}"
NODE_SHA256="${CANVASCTRL_NODE_SHA256:-${CANVASCTL_NODE_SHA256:-$PINNED_NODE_SHA256}}"
RUNTIME_URL="${CANVASCTRL_RELEASE_URL:-${CANVASCTL_RELEASE_URL:-$RUNTIME_URL_DEFAULT}}"
NODE_INSTALL_ID="$NODE_VERSION-$ARCHITECTURE-$(printf '%s' "$NODE_SHA256" | cut -c1-12)"
RUNTIME_INSTALL_ID="$RUNTIME_NAME-$(printf '%s' "$RUNTIME_SHA256" | cut -c1-12)"
NODE_DIRECTORY="$RUNTIME_ROOT/node/$NODE_INSTALL_ID"
RUNTIME_DIRECTORY="$RUNTIME_ROOT/releases/$RUNTIME_INSTALL_ID"
LAUNCHER_PATH="$INSTALL_PREFIX/bin/canvasctrl"
STAGING_DIRECTORY=$(mktemp -d "${TMPDIR:-/tmp}/canvasctrl-install.XXXXXX")
trap 'rm -rf "$STAGING_DIRECTORY"' EXIT HUP INT TERM

if [ -e "$LAUNCHER_PATH" ] && [ ! -L "$LAUNCHER_PATH" ] && \
   ! grep -Eq '^# (CANVASCTRL|CANVASCTL)_APP_OWNED_LAUNCHER=1$' "$LAUNCHER_PATH"; then
  echo "CanvasCtrl will not replace an executable it does not own: $LAUNCHER_PATH" >&2
  exit 1
fi

download_and_verify() {
  DOWNLOAD_URL=$1
  EXPECTED_SHA256=$2
  DESTINATION=$3
  LABEL=$4
  echo "Downloading $LABEL..."
  curl -fsSL "$DOWNLOAD_URL" -o "$DESTINATION"
  ACTUAL_SHA256=$(shasum -a 256 "$DESTINATION" | awk '{print $1}')
  if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
    echo "$LABEL verification failed; nothing was activated." >&2
    exit 1
  fi
}

mkdir -p "$RUNTIME_ROOT/node" "$RUNTIME_ROOT/releases" "$INSTALL_PREFIX/bin"

if [ "$RUNTIME_ROOT" = "$LEGACY_RUNTIME_ROOT" ]; then
  if [ ! -e "$CANONICAL_RUNTIME_ROOT" ]; then
    ln -s "$LEGACY_RUNTIME_ROOT" "$CANONICAL_RUNTIME_ROOT"
  elif [ ! -L "$CANONICAL_RUNTIME_ROOT" ]; then
    echo "CanvasCtrl will not replace an existing runtime directory: $CANONICAL_RUNTIME_ROOT" >&2
    exit 1
  fi
elif [ ! -e "$LEGACY_RUNTIME_ROOT" ]; then
  ln -s "$CANONICAL_RUNTIME_ROOT" "$LEGACY_RUNTIME_ROOT"
fi

if [ -x "$NODE_DIRECTORY/bin/node" ] && [ "$("$NODE_DIRECTORY/bin/node" --version)" = "v$NODE_VERSION" ]; then
  echo "Using CanvasCtrl's verified Node.js $NODE_VERSION runtime."
elif [ -e "$NODE_DIRECTORY" ]; then
  echo "The existing CanvasCtrl Node.js runtime is incomplete: $NODE_DIRECTORY" >&2
  echo "Move that directory aside and run the installer again." >&2
  exit 1
else
  NODE_ARCHIVE="$STAGING_DIRECTORY/$NODE_ARCHIVE_NAME"
  download_and_verify "$NODE_URL" "$NODE_SHA256" "$NODE_ARCHIVE" "CanvasCtrl's private Node.js $NODE_VERSION runtime"
  tar -xzf "$NODE_ARCHIVE" -C "$STAGING_DIRECTORY"
  NODE_SOURCE_DIRECTORY="$STAGING_DIRECTORY/node-v$NODE_VERSION-darwin-$ARCHITECTURE"
  if [ ! -x "$NODE_SOURCE_DIRECTORY/bin/node" ] || [ "$("$NODE_SOURCE_DIRECTORY/bin/node" --version)" != "v$NODE_VERSION" ]; then
    echo "The verified Node.js archive did not contain the expected runtime." >&2
    exit 1
  fi
  mv "$NODE_SOURCE_DIRECTORY" "$NODE_DIRECTORY"
fi

runtime_capability_error() {
  echo "CanvasCtrl installer/runtime out of date; a release maintainer must promote a new artifact." >&2
  echo "This installer will not activate a runtime that lacks the required safety and capability surface." >&2
  exit 1
}

validate_canvasctrl_runtime() {
  CANDIDATE_RUNTIME=$1
  CANDIDATE_PACKAGE_JSON="$CANDIDATE_RUNTIME/package.json"
  if [ ! -f "$CANDIDATE_PACKAGE_JSON" ]; then
    runtime_capability_error
  fi
  if ! CANDIDATE_VERSION=$("$NODE_DIRECTORY/bin/node" -e '
const fs = require("node:fs");
const document = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (typeof document.version !== "string" || !/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(document.version)) process.exit(1);
process.stdout.write(document.version);
' "$CANDIDATE_PACKAGE_JSON" 2>/dev/null); then
    runtime_capability_error
  fi
  if ! CANDIDATE_CLI_VERSION=$("$NODE_DIRECTORY/bin/node" "$CANDIDATE_RUNTIME/packages/cli/dist/index.js" --version 2>/dev/null); then
    runtime_capability_error
  fi
  if [ "$CANDIDATE_CLI_VERSION" != "$CANDIDATE_VERSION" ]; then
    runtime_capability_error
  fi
  if ! RUNTIME_HELP_OUTPUT=$("$NODE_DIRECTORY/bin/node" "$CANDIDATE_RUNTIME/packages/cli/dist/index.js" --help 2>/dev/null); then
    runtime_capability_error
  fi
  for REQUIRED_HELP_MARKER in profiles set-default test-approval-dialog authorize-privileged-writes; do
    if ! printf '%s\n' "$RUNTIME_HELP_OUTPUT" | grep -Fq "$REQUIRED_HELP_MARKER"; then
      runtime_capability_error
    fi
  done
  if [ ! -f "$CANDIDATE_RUNTIME/packages/canvas-service/dist/generic-api-route-policy.js" ] || \
     ! grep -Fq 'privilegedWrites' "$CANDIDATE_RUNTIME/packages/canvas-service/dist/generic-api-route-policy.js" 2>/dev/null || \
     ! grep -Fq 'courses.progress.self.get' "$CANDIDATE_RUNTIME/packages/canvas-service/dist/operation-catalog.js" 2>/dev/null || \
     ! grep -Fq 'canvas_get_runtime_info' "$CANDIDATE_RUNTIME/packages/mcp-server/dist/index.js" 2>/dev/null || \
     ! grep -Fq 'canvas_prepare_text_submission' "$CANDIDATE_RUNTIME/packages/mcp-server/dist/index.js" 2>/dev/null || \
     ! grep -Fq 'canvas_prepare_url_submission' "$CANDIDATE_RUNTIME/packages/mcp-server/dist/index.js" 2>/dev/null || \
     ! grep -Fq 'macos-device-owner-authentication' "$CANDIDATE_RUNTIME/packages/mcp-server/dist/index.js" 2>/dev/null || \
     ! grep -Fq 'canvasctrl-approval' "$CANDIDATE_RUNTIME/packages/cli/dist/macos-approval.js" 2>/dev/null || \
     ! grep -Eq 'CANVASCTRL_AVAILABLE|CANVASCTL_AVAILABLE' "$CANDIDATE_RUNTIME/packages/cli/dist/macos-approval.js" 2>/dev/null; then
    runtime_capability_error
  fi
}

if [ -f "$RUNTIME_DIRECTORY/packages/cli/dist/index.js" ] && \
   [ -x "$RUNTIME_DIRECTORY/packages/session-broker/native/bin/canvasctrl-keychain" ] && \
   [ -x "$RUNTIME_DIRECTORY/packages/session-broker/native/bin/canvasctrl-approval" ] && \
   [ -x "$RUNTIME_DIRECTORY/scripts/canvasctrl-launcher.sh" ]; then
  validate_canvasctrl_runtime "$RUNTIME_DIRECTORY"
  echo "Using the existing verified CanvasCtrl application runtime."
elif [ -e "$RUNTIME_DIRECTORY" ]; then
  echo "The existing CanvasCtrl application runtime is incomplete: $RUNTIME_DIRECTORY" >&2
  echo "Move that directory aside and run the installer again." >&2
  exit 1
else
  RUNTIME_ARCHIVE="$STAGING_DIRECTORY/$RUNTIME_NAME.tar.gz"
  download_and_verify "$RUNTIME_URL" "$RUNTIME_SHA256" "$RUNTIME_ARCHIVE" "the CanvasCtrl application runtime"
  tar -xzf "$RUNTIME_ARCHIVE" -C "$STAGING_DIRECTORY"
  RUNTIME_SOURCE_DIRECTORY="$STAGING_DIRECTORY/$RUNTIME_NAME"
  if [ ! -f "$RUNTIME_SOURCE_DIRECTORY/packages/cli/dist/index.js" ] || \
     [ ! -x "$RUNTIME_SOURCE_DIRECTORY/packages/session-broker/native/bin/canvasctrl-keychain" ] || \
     [ ! -x "$RUNTIME_SOURCE_DIRECTORY/packages/session-broker/native/bin/canvasctrl-approval" ] || \
     [ ! -x "$RUNTIME_SOURCE_DIRECTORY/scripts/canvasctrl-launcher.sh" ]; then
    echo "The verified CanvasCtrl archive is incomplete." >&2
    exit 1
  fi
  validate_canvasctrl_runtime "$RUNTIME_SOURCE_DIRECTORY"
  mv "$RUNTIME_SOURCE_DIRECTORY" "$RUNTIME_DIRECTORY"
fi

replace_current_link() {
  LINK_PATH=$1
  TARGET_PATH=$2
  if [ -e "$LINK_PATH" ] && [ ! -L "$LINK_PATH" ]; then
    echo "CanvasCtrl will not replace a non-symlink path: $LINK_PATH" >&2
    exit 1
  fi
  ln -sfn "$TARGET_PATH" "$LINK_PATH"
}

replace_current_link "$RUNTIME_ROOT/node/current" "$NODE_DIRECTORY"
replace_current_link "$RUNTIME_ROOT/current" "$RUNTIME_DIRECTORY"

if [ -L "$LAUNCHER_PATH" ]; then
  rm "$LAUNCHER_PATH"
fi
cp "$RUNTIME_DIRECTORY/scripts/canvasctrl-launcher.sh" "$LAUNCHER_PATH"
chmod 755 "$LAUNCHER_PATH"

install_compatibility_launcher() {
  COMPATIBILITY_PATH=$1
  if [ -L "$COMPATIBILITY_PATH" ]; then
    LINK_TARGET=$(readlink "$COMPATIBILITY_PATH")
    case "$LINK_TARGET" in
      /*) RESOLVED_TARGET="$LINK_TARGET" ;;
      *) RESOLVED_TARGET="$(dirname "$COMPATIBILITY_PATH")/$LINK_TARGET" ;;
    esac
    if [ -f "$RESOLVED_TARGET" ] && grep -Eq '^# (CANVASCTRL|CANVASCTL)_APP_OWNED_LAUNCHER=1$' "$RESOLVED_TARGET"; then
      rm "$COMPATIBILITY_PATH"
      ln -s "$LAUNCHER_PATH" "$COMPATIBILITY_PATH"
    else
      echo "Kept existing user-owned command unchanged: $COMPATIBILITY_PATH" >&2
    fi
  elif [ -f "$COMPATIBILITY_PATH" ] && grep -Eq '^# (CANVASCTRL|CANVASCTL)_APP_OWNED_LAUNCHER=1$' "$COMPATIBILITY_PATH"; then
    rm "$COMPATIBILITY_PATH"
    ln -s "$LAUNCHER_PATH" "$COMPATIBILITY_PATH"
  elif [ ! -e "$COMPATIBILITY_PATH" ]; then
    ln -s "$LAUNCHER_PATH" "$COMPATIBILITY_PATH"
  else
    echo "Kept existing user-owned command unchanged: $COMPATIBILITY_PATH" >&2
  fi
}

install_compatibility_launcher "$INSTALL_PREFIX/bin/canvas-mcp"
install_compatibility_launcher "$INSTALL_PREFIX/bin/canvasctl"

install_skill_link() (
  SKILL_PARENT=$1
  CLIENT_LABEL=$2
  SKILL_DESTINATION="$SKILL_PARENT/canvasctrl"
  SKILL_SOURCE="$RUNTIME_ROOT/current/skills/canvasctrl"
  mkdir -p "$SKILL_PARENT"
  if [ -L "$SKILL_DESTINATION" ]; then
    ln -sfn "$SKILL_SOURCE" "$SKILL_DESTINATION"
    echo "Updated $CLIENT_LABEL skill: $SKILL_DESTINATION"
  elif [ -e "$SKILL_DESTINATION" ]; then
    echo "Kept existing $CLIENT_LABEL skill unchanged: $SKILL_DESTINATION" >&2
  else
    ln -s "$SKILL_SOURCE" "$SKILL_DESTINATION"
    echo "Installed $CLIENT_LABEL skill: $SKILL_DESTINATION"
  fi
)

install_skill_link "$CODEX_SKILLS_DIRECTORY" "Codex"
install_skill_link "$CLAUDE_SKILLS_DIRECTORY" "Claude"

install_legacy_skill_link() (
  SKILL_PARENT=$1
  LEGACY_DESTINATION="$SKILL_PARENT/canvasctl"
  CANONICAL_DESTINATION="$SKILL_PARENT/canvasctrl"
  if [ -L "$LEGACY_DESTINATION" ]; then
    ln -sfn "$CANONICAL_DESTINATION" "$LEGACY_DESTINATION"
  elif [ ! -e "$LEGACY_DESTINATION" ]; then
    ln -s "$CANONICAL_DESTINATION" "$LEGACY_DESTINATION"
  fi
)

install_legacy_skill_link "$CODEX_SKILLS_DIRECTORY"
install_legacy_skill_link "$CLAUDE_SKILLS_DIRECTORY"

"$LAUNCHER_PATH" --version >/dev/null

configure_canvas_school() {
  SKIP_ONBOARDING="${CANVASCTRL_SKIP_ONBOARDING:-${CANVASCTL_SKIP_ONBOARDING:-0}}"
  if [ "$SKIP_ONBOARDING" = "1" ]; then
    echo "Skipped Canvas school setup because CANVASCTRL_SKIP_ONBOARDING=1."
    return
  fi

  SCHOOL_URL="${CANVASCTRL_SCHOOL_URL:-${CANVASCTL_SCHOOL_URL:-}}"
  if [ -z "$SCHOOL_URL" ]; then
    if DEFAULT_PROFILE_OUTPUT=$("$LAUNCHER_PATH" profiles 2>/dev/null) && \
       printf '%s\n' "$DEFAULT_PROFILE_OUTPUT" | grep -q '"defaultProfile"'; then
      echo "Kept the existing Canvas school as the default profile."
      return
    fi
    if [ ! -r /dev/tty ] || [ ! -w /dev/tty ]; then
      echo "Canvas school setup was skipped because this install is not interactive."
      echo "Run: $LAUNCHER_PATH setup --origin https://canvas.your-school.edu"
      return
    fi
    if ! printf '%s' "Canvas school URL (for example, https://school.instructure.com; Return to skip): " > /dev/tty 2>/dev/null; then
      echo "Canvas school setup was skipped because no interactive terminal is available."
      echo "Run: $LAUNCHER_PATH setup --origin https://canvas.your-school.edu"
      return
    fi
    if ! IFS= read -r SCHOOL_URL < /dev/tty; then
      SCHOOL_URL=""
    fi
  fi

  if [ -z "$SCHOOL_URL" ]; then
    echo "Canvas school setup skipped. Run canvasctrl setup --origin URL whenever you are ready."
    return
  fi

  PROFILE_NAME="${CANVASCTRL_PROFILE:-${CANVASCTL_PROFILE:-}}"
  if [ -n "$PROFILE_NAME" ]; then
    SETUP_SUCCEEDED=0
    "$LAUNCHER_PATH" setup --origin "$SCHOOL_URL" --profile "$PROFILE_NAME" &&
      "$LAUNCHER_PATH" set-default --profile "$PROFILE_NAME" &&
      SETUP_SUCCEEDED=1
  else
    SETUP_SUCCEEDED=0
    "$LAUNCHER_PATH" setup --origin "$SCHOOL_URL" && SETUP_SUCCEEDED=1
  fi
  if [ "$SETUP_SUCCEEDED" = "1" ]; then
    echo "Canvas school saved as the default. Run canvasctrl login to complete browser authentication."
  else
    echo "CanvasCtrl was installed, but the school URL could not be saved. Re-run canvasctrl setup --origin URL." >&2
  fi
}

configure_canvas_school

warn_stale_codex_mcp_config() {
  CODEX_CONFIG_PATH="${CANVASCTRL_CODEX_CONFIG:-${CANVASCTL_CODEX_CONFIG:-$HOME/.codex/config.toml}}"
  if [ ! -f "$CODEX_CONFIG_PATH" ] || \
     ! grep -Eq '(canvasctrl|canvas-mcp).*/packages/cli/dist/index\.js' "$CODEX_CONFIG_PATH"; then
    return
  fi

  echo "Warning: Codex appears to be launching Canvas MCP from an older source checkout:" >&2
  echo "  $CODEX_CONFIG_PATH" >&2
  echo "The installer left that configuration unchanged. Replace the affected MCP entry with:" >&2
  echo "  command = \"$LAUNCHER_PATH\"" >&2
  echo '  args = ["serve", "--stdio"]' >&2
  echo "Then fully quit and reopen Codex so the old MCP process exits and the installed runtime starts." >&2
}

warn_stale_codex_mcp_config

echo "CanvasCtrl installed successfully with its own private Node.js $NODE_VERSION runtime."
echo "Executable: $LAUNCHER_PATH"
echo "No system Node.js, npm, Homebrew, Apple Developer account, or Command Line Tools are required."
echo "No Canvas credentials or MCP client configuration were changed automatically."
