#!/usr/bin/env bash

# Standalone public installer; updates Skill contents without replacing a granted directory.
# Distributed beside public binaries on CDN and used for first installs and upgrades.
set -euo pipefail
shopt -s dotglob nullglob

PROGRAM_NAME="dreamina-canvas"
# install.sh and all platform artifacts are published in this CDN directory.
DEFAULT_DOWNLOAD_BASE="https://jimeng.jianying.com/canvas-cli/install/1_0_0_46"
DOWNLOAD_BASE="${DREAMINA_CANVAS_DOWNLOAD_BASE:-${DEFAULT_DOWNLOAD_BASE}}"
DOWNLOAD_BASE="${DOWNLOAD_BASE%/}"
INSTALLER_TMP_DIR=""
SKILL_TARGET=""
SKILL_LOCK_DIR=""
SKILL_BACKUP=""
SKILL_UPDATE_STARTED=false
TEMPORARY_BINARY=""

say() {
  printf '%s\n' "$*"
}

fail() {
  printf 'install.sh: %s\n' "$*" >&2
  exit 1
}

has_command() {
  command -v "$1" >/dev/null 2>&1
}

cleanup() {
  local status=$?
  local keep_backup=false
  trap - EXIT INT TERM
  # The same-filesystem binary rename is the commit point, including a signal
  # delivered before the shell can clear SKILL_UPDATE_STARTED afterwards.
  if [[ "${SKILL_UPDATE_STARTED}" == true && -e "${TEMPORARY_BINARY}" ]]; then
    if replace_skill_contents "${SKILL_BACKUP}"; then
      printf 'install.sh: restored previous Skill contents: %s\n' "${SKILL_TARGET}" >&2
    else
      printf 'install.sh: cannot restore Skill; backup retained at: %s\n' "${SKILL_BACKUP}" >&2
      keep_backup=true
      status=1
    fi
  fi
  if [[ -n "${SKILL_LOCK_DIR}" ]]; then
    if ! rmdir "${SKILL_LOCK_DIR}"; then
      printf 'install.sh: cannot remove installation lock: %s\n' "${SKILL_LOCK_DIR}" >&2
      status=1
    fi
  fi
  if [[ -n "${INSTALLER_TMP_DIR}" && "${keep_backup}" == false ]]; then
    if ! rm -rf "${INSTALLER_TMP_DIR}"; then
      printf 'install.sh: cannot remove installation temporary directory: %s\n' "${INSTALLER_TMP_DIR}" >&2
      status=1
    fi
  fi
  exit "${status}"
}

copy_skill_contents() {
  local source="$1"
  local destination="$2"
  local entry
  for entry in "${source}"/*; do
    [[ "${entry}" == "${SKILL_LOCK_DIR}" ]] && continue
    cp -RP "${entry}" "${destination}/" || return 1
  done
}

# All Skill I/O is relative to the pinned working directory, including cleanup.
# Identity checks detect replacement; relative paths keep a later race from redirecting I/O.
skill_target_unchanged() {
  [[ ! -L "${SKILL_TARGET}" && "${SKILL_TARGET}" -ef . ]] || {
    printf 'install.sh: Skill directory changed during installation: %s; retry after restoring its intended location\n' "${SKILL_TARGET}" >&2
    return 1
  }
}

replace_skill_contents() {
  local source="$1"
  local entry
  skill_target_unchanged || return 1
  for entry in ./*; do
    [[ "${entry}" == "${SKILL_LOCK_DIR}" ]] && continue
    rm -rf "${entry}" || return 1
  done
  copy_skill_contents "${source}" .
}

download_file() {
  local url="$1"
  local output="$2"

  if has_command curl; then
    curl --fail --silent --show-error --location --output "${output}" "${url}"
    return
  fi
  if has_command wget; then
    wget -qO "${output}" "${url}"
    return
  fi

  fail "curl or wget is required to download the CLI"
}

pick_install_dir() {
  local existing_binary
  if [[ -n "${DREAMINA_CANVAS_INSTALL_DIR:-}" ]]; then
    printf '%s\n' "${DREAMINA_CANVAS_INSTALL_DIR}"
    return
  fi
  if [[ -n "${INSTALL_DIR:-}" ]]; then
    printf '%s\n' "${INSTALL_DIR}"
    return
  fi
  existing_binary="$(type -P "${TARGET_NAME}" 2>/dev/null || true)"
  if [[ -n "${existing_binary}" ]]; then
    dirname "${existing_binary}"
    return
  fi
  if [[ -z "${HOME:-}" ]]; then
    fail "HOME or DREAMINA_CANVAS_INSTALL_DIR is required"
  fi
  if [[ "${PLATFORM}" == windows_* ]]; then
    printf '%s/bin\n' "${HOME}"
    return
  fi
  printf '%s/.local/bin\n' "${HOME}"
}

pick_skill_root() {
  if [[ -n "${DREAMINA_CANVAS_SKILL_DIR:-}" ]]; then
    printf '%s\n' "${DREAMINA_CANVAS_SKILL_DIR}"
    return
  fi
  if [[ -z "${HOME:-}" ]]; then
    fail "HOME or DREAMINA_CANVAS_SKILL_DIR is required"
  fi
  printf '%s/.agents/skills\n' "${HOME}"
}

validate_skill_archive() {
  local archive="$1"
  local entries entry
  local has_skill="false"

  entries="$(tar -tzf "${archive}")" || fail "downloaded Skill archive is invalid"
  while IFS= read -r entry; do
    [[ -n "${entry}" ]] || continue
    case "${entry}" in
      dreamina-canvas-cli|dreamina-canvas-cli/*) ;;
      *) fail "downloaded Skill archive contains an unexpected path: ${entry}" ;;
    esac
    case "/${entry}/" in
      */../*|*/./*) fail "downloaded Skill archive contains an unsafe path: ${entry}" ;;
    esac
    if [[ "${entry}" == "dreamina-canvas-cli/SKILL.md" ]]; then
      has_skill="true"
    fi
  done <<< "${entries}"

  [[ "${has_skill}" == "true" ]] || fail "downloaded Skill archive is missing dreamina-canvas-cli/SKILL.md"
}

detect_platform() {
  local operating_system
  local architecture
  operating_system="$(uname -s)"
  architecture="$(uname -m)"

  case "${operating_system}" in
    Darwin)
      case "${architecture}" in
        x86_64|amd64) PLATFORM="darwin_amd64" ;;
        arm64|aarch64) PLATFORM="darwin_arm64" ;;
        *) fail "unsupported macOS architecture: ${architecture}" ;;
      esac
      ;;
    Linux)
      case "${architecture}" in
        x86_64|amd64) PLATFORM="linux_amd64" ;;
        arm64|aarch64) PLATFORM="linux_arm64" ;;
        *) fail "unsupported Linux architecture: ${architecture}" ;;
      esac
      ;;
    MINGW*|MSYS*|CYGWIN*)
      case "${architecture}" in
        x86_64|amd64) PLATFORM="windows_amd64" ;;
        *) fail "unsupported Windows architecture: ${architecture}" ;;
      esac
      ;;
    *) fail "unsupported operating system: ${operating_system}" ;;
  esac

  ARTIFACT_NAME="${PROGRAM_NAME}_${PLATFORM}"
  TARGET_NAME="${PROGRAM_NAME}"
  if [[ "${PLATFORM}" == windows_* ]]; then
    ARTIFACT_NAME="${ARTIFACT_NAME}.exe"
    TARGET_NAME="${TARGET_NAME}.exe"
  fi
}

# A local bundle is self-contained: incomplete or mismatched payloads never fall back to CDN.
resolve_payload_dir() {
  local script_dir
  if [[ "${DREAMINA_CANVAS_PAYLOAD_DIR+x}" == x ]]; then
    [[ "${DREAMINA_CANVAS_PAYLOAD_DIR}" == /* ]] || fail "DREAMINA_CANVAS_PAYLOAD_DIR must be an absolute bundle directory"
    [[ -d "${DREAMINA_CANVAS_PAYLOAD_DIR}" ]] || fail "local bundle directory does not exist: ${DREAMINA_CANVAS_PAYLOAD_DIR}"
    printf '%s\n' "${DREAMINA_CANVAS_PAYLOAD_DIR}"
  elif [[ -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    if [[ -f "${script_dir}/payload.sha256" || -f "${script_dir}/${ARTIFACT_NAME}" || -f "${script_dir}/${TARGET_NAME}" || -f "${script_dir}/skill.tar.gz" ]]; then
      printf '%s\n' "${script_dir}"
    fi
  fi
}

file_sha256() {
  local result
  if has_command sha256sum; then
    result="$(sha256sum "$1")" || fail "cannot checksum: $1"
  elif has_command shasum; then
    result="$(shasum -a 256 "$1")" || fail "cannot checksum: $1"
  else
    fail "sha256sum or shasum is required to verify a local bundle"
  fi
  printf '%s\n' "${result%% *}"
}

verify_payload_file() {
  local directory="$1" name="$2" path="$3"
  local checksum filename extra expected="" matches=0
  [[ -s "${directory}/payload.sha256" ]] || fail "local bundle is missing payload.sha256; obtain a complete bundle"
  [[ -s "${path}" ]] || fail "local bundle is missing or has an empty ${name}"
  while read -r checksum filename extra; do
    if [[ "${filename}" == "${name}" ]]; then
      [[ -z "${extra}" && "${checksum}" =~ ^[0-9a-f]{64}$ ]] || fail "invalid checksum entry: ${name}"
      expected="${checksum}"
      matches=$((matches + 1))
    fi
  done < "${directory}/payload.sha256"
  [[ "${matches}" == 1 ]] || fail "local bundle must contain exactly one checksum for ${name}"
  [[ "$(file_sha256 "${path}")" == "${expected}" ]] || fail "local bundle checksum mismatch: ${name}"
}

main() {
  local install_dir
  local skill_root
  local download_url skill_download_url
  local temporary_skill_archive staged_skill
  local target_path lock_directory
  local payload_dir payload_binary expected_version installed_version

  case "${DOWNLOAD_BASE}" in
    https://*) ;;
    *) fail "DREAMINA_CANVAS_DOWNLOAD_BASE must use https" ;;
  esac
  has_command tar || fail "tar is required to install the Agent Skill"

  detect_platform
  payload_dir="$(resolve_payload_dir)"
  if [[ -n "${payload_dir}" ]]; then
    payload_binary="${ARTIFACT_NAME}"
    if [[ ! -f "${payload_dir}/${payload_binary}" && -f "${payload_dir}/${TARGET_NAME}" ]]; then
      payload_binary="${TARGET_NAME}"
    fi
    verify_payload_file "${payload_dir}" "${payload_binary}" "${payload_dir}/${payload_binary}"
    verify_payload_file "${payload_dir}" skill.tar.gz "${payload_dir}/skill.tar.gz"
    verify_payload_file "${payload_dir}" version.json "${payload_dir}/version.json"
  fi
  install_dir="$(pick_install_dir)"
  skill_root="$(pick_skill_root)"
  # Preserve caller-relative inputs before entering the Skill directory.
  case "${install_dir}" in /*) ;; *) install_dir="${PWD}/${install_dir}" ;; esac
  case "${skill_root}" in /*) ;; *) skill_root="${PWD}/${skill_root}" ;; esac
  download_url="${DOWNLOAD_BASE}/${ARTIFACT_NAME}"
  skill_download_url="${DOWNLOAD_BASE}/skill.tar.gz"
  mkdir -p "${install_dir}" || fail "cannot create install directory: ${install_dir}"
  [[ -w "${install_dir}" ]] || fail "install directory is not writable: ${install_dir}"
  SKILL_TARGET="${skill_root}/dreamina-canvas-cli"
  [[ ! -L "${SKILL_TARGET}" ]] || fail "Skill target is a symbolic link; select its actual directory using DREAMINA_CANVAS_SKILL_DIR: ${SKILL_TARGET}"
  mkdir -p "${SKILL_TARGET}" || fail "cannot create Skill directory: ${SKILL_TARGET}; approve creation in its parent directory and retry this installer"
  [[ -w "${SKILL_TARGET}" ]] || fail "Skill directory is not writable: ${SKILL_TARGET}; approve writes to this directory and retry this installer"

  cd -P -- "${SKILL_TARGET}" || fail "cannot enter Skill directory: ${SKILL_TARGET}"
  skill_target_unchanged || exit 1

  INSTALLER_TMP_DIR="$(mktemp -d "${install_dir}/.${PROGRAM_NAME}.install.XXXXXX")"
  trap cleanup EXIT
  trap 'exit 130' INT
  trap 'exit 143' TERM
  skill_target_unchanged || exit 1
  lock_directory="./.${PROGRAM_NAME}.install.lock"
  if ! mkdir "${lock_directory}"; then
    fail "cannot acquire Skill installation lock: ${SKILL_TARGET}/${lock_directory#./}; check for another installer or denied directory writes before retrying"
  fi
  SKILL_LOCK_DIR="${lock_directory}"
  skill_target_unchanged || exit 1
  TEMPORARY_BINARY="${INSTALLER_TMP_DIR}/${TARGET_NAME}"
  temporary_skill_archive="${INSTALLER_TMP_DIR}/skill.tar.gz"
  target_path="${install_dir}/${TARGET_NAME}"
  staged_skill="${INSTALLER_TMP_DIR}/dreamina-canvas-cli"
  SKILL_BACKUP="${INSTALLER_TMP_DIR}/skill-backup"

  if [[ -n "${payload_dir}" ]]; then
    say "installing verified local bundle: ${payload_dir}"
    cp "${payload_dir}/${payload_binary}" "${TEMPORARY_BINARY}"
    cp "${payload_dir}/skill.tar.gz" "${temporary_skill_archive}"
    # Verify the staged bytes as well, before changing the existing binary or Skill.
    verify_payload_file "${payload_dir}" "${payload_binary}" "${TEMPORARY_BINARY}"
    verify_payload_file "${payload_dir}" skill.tar.gz "${temporary_skill_archive}"
    chmod 0755 "${TEMPORARY_BINARY}"
    expected_version="$("${TEMPORARY_BINARY}" --format json version)" || fail "local CLI cannot execute in this environment; verify OS/architecture and execution permissions"
    [[ -n "${expected_version}" ]] || fail "local CLI did not return version information"
  else
    say "downloading ${download_url}"
    download_file "${download_url}" "${TEMPORARY_BINARY}"
    [[ -s "${TEMPORARY_BINARY}" ]] || fail "downloaded CLI is empty"
    say "downloading ${skill_download_url}"
    download_file "${skill_download_url}" "${temporary_skill_archive}"
    [[ -s "${temporary_skill_archive}" ]] || fail "downloaded Skill archive is empty"
  fi
  validate_skill_archive "${temporary_skill_archive}"
  tar -xzf "${temporary_skill_archive}" -C "${INSTALLER_TMP_DIR}"
  [[ -f "${staged_skill}/SKILL.md" ]] || fail "extracted Skill is missing SKILL.md"
  [[ ! -e "${staged_skill}/.${PROGRAM_NAME}.install.lock" && ! -L "${staged_skill}/.${PROGRAM_NAME}.install.lock" ]] || fail "Skill archive contains a reserved installation lock path"

  chmod 0755 "${TEMPORARY_BINARY}"
  mkdir "${SKILL_BACKUP}"
  skill_target_unchanged || exit 1
  copy_skill_contents . "${SKILL_BACKUP}" || fail "cannot back up Skill contents: ${SKILL_TARGET}"
  SKILL_UPDATE_STARTED=true
  replace_skill_contents "${staged_skill}" || fail "cannot update Skill contents: ${SKILL_TARGET}; approve writes within this directory before retrying"
  skill_target_unchanged || exit 1
  if ! mv -f "${TEMPORARY_BINARY}" "${target_path}"; then
    fail "cannot replace CLI: ${target_path}"
  fi
  SKILL_UPDATE_STARTED=false
  skill_target_unchanged || exit 1

  if [[ "${PLATFORM}" == darwin_* ]] && has_command xattr; then
    xattr -d com.apple.quarantine "${target_path}" >/dev/null 2>&1 || true
  fi

  if [[ -n "${payload_dir}" ]]; then
    verify_payload_file "${payload_dir}" "${payload_binary}" "${target_path}"
    installed_version="$("${target_path}" --format json version)" || fail "CLI installed at ${target_path}, but version verification failed"
    [[ "${installed_version}" == "${expected_version}" ]] || fail "installed CLI version differs from the staged bundle: ${target_path}"
    say "verified installed CLI version: ${installed_version}"
  fi
  say "installed ${target_path}"
  say "installed ${SKILL_TARGET}"
  case ":${PATH:-}:" in
    *":${install_dir}:"*) ;;
    *) printf '%s\n' "${install_dir} is not in PATH" >&2 ;;
  esac
}

main "$@"
