#!/usr/bin/env bash
set -euo pipefail

VERSION="0.1.0"

profiles_root="${CLAUDE_PROFILES_ROOT:-$HOME/.claude-profiles}"
source_config="${CLAUDE_PROFILE_SOURCE_CONFIG:-$HOME/.claude}"
shared_items_default="skills agents commands output-styles CLAUDE.md"
shared_items="${CLAUDE_PROFILE_SHARED_ITEMS:-$shared_items_default}"

auth_env_names=(
  ANTHROPIC_API_KEY
  ANTHROPIC_AUTH_TOKEN
  CLAUDE_CODE_OAUTH_TOKEN
  CLAUDE_CODE_USE_BEDROCK
  CLAUDE_CODE_USE_VERTEX
  CLAUDE_CODE_USE_FOUNDRY
  CLAUDE_CODE_USE_ANTHROPIC_AWS
  CLAUDE_CODE_USE_MANTLE
  ANTHROPIC_BASE_URL
)

usage() {
  cat <<'EOF'
claude-profile - run Claude Code with isolated local account profiles

Usage:
  claude-profile <profile> [--] [claude args...]
  claude-profile --init <profile>
  claude-profile --path <profile>
  claude-profile --sync-settings <profile>
  claude-profile --remove <profile>
  claude-profile --trash-list
  claude-profile --install-shell
  claude-profile --list
  claude-profile --doctor
  claude-profile --help

Examples:
  claude-profile personal-one
  claude-profile work -- --version
  claude-profile work --dangerously-skip-permissions

Profiles live under ~/.claude-profiles by default. Each profile gets its own
CLAUDE_CONFIG_DIR, credentials, sessions, history, cache, telemetry, and project
state. Stable customization is bootstrapped from ~/.claude:

  - settings.json is copied once and stripped of auth-bearing keys.
  - skills, agents, commands, output-styles, and CLAUDE.md are symlinked if they exist.

For account separation, auth/provider environment variables are always removed
before Claude starts, so ANTHROPIC_API_KEY and similar variables cannot override
the profile's /login subscription OAuth.
EOF
}

err() {
  printf 'claude-profile: %s\n' "$*" >&2
}

validate_profile() {
  local profile="$1"

  if [[ -z "$profile" ]]; then
    err "profile name is required"
    return 2
  fi

  if [[ "$profile" == "." || "$profile" == ".." ]]; then
    err "invalid profile name: $profile"
    return 2
  fi

  if [[ ! "$profile" =~ ^[a-z0-9][a-z0-9._-]{0,79}$ ]]; then
    err "invalid profile name: $profile"
    err "use 1-80 chars: lowercase letters, numbers, dot, underscore, dash; start with letter/number"
    return 2
  fi
}

profile_path() {
  local profile="$1"
  printf '%s/%s\n' "$profiles_root" "$profile"
}

resolve_script_path() {
  local src="${BASH_SOURCE[0]}"
  local dir

  while [[ -L "$src" ]]; do
    dir="$(cd -P "$(dirname "$src")" && pwd)"
    src="$(readlink "$src")"
    [[ "$src" == /* ]] || src="$dir/$src"
  done

  cd -P "$(dirname "$src")" && pwd
}

find_claude_bin() {
  if [[ -n "${CLAUDE_PROFILE_CLAUDE_BIN:-}" ]]; then
    printf '%s\n' "$CLAUDE_PROFILE_CLAUDE_BIN"
    return 0
  fi

  command -v claude
}

write_sanitized_settings() {
  local src="$1"
  local dst="$2"

  [[ -f "$src" ]] || return 0

  local node_bin
  node_bin="$(command -v node || true)"
  if [[ -z "$node_bin" ]]; then
    err "node is required to safely copy settings.json; leaving settings unset"
    return 0
  fi

  "$node_bin" - "$src" "$dst" <<'NODE'
const fs = require("fs");

const src = process.argv[2];
const dst = process.argv[3];
const raw = fs.readFileSync(src, "utf8");
const settings = JSON.parse(raw);

const blockedTopLevel = [
  "env",
  "apiKeyHelper"
];

for (const key of blockedTopLevel) {
  if (Object.prototype.hasOwnProperty.call(settings, key)) {
    delete settings[key];
  }
}

fs.writeFileSync(dst, `${JSON.stringify(settings, null, 2)}\n`, { mode: 0o600 });
NODE
}

copy_sanitized_settings() {
  local src="$1"
  local dst="$2"

  [[ ! -e "$dst" ]] || return 0
  write_sanitized_settings "$src" "$dst"
}

sync_settings() {
  local profile="$1"
  validate_profile "$profile" || return $?

  local profile_dir
  profile_dir="$(profile_path "$profile")"

  if [[ ! -d "$profile_dir" ]]; then
    err "profile does not exist: $profile"
    err "run: claude-profile --init $profile"
    return 2
  fi

  write_sanitized_settings "$source_config/settings.json" "$profile_dir/settings.json"
  printf '%s/settings.json\n' "$profile_dir"
}

link_shared_item() {
  local profile_dir="$1"
  local name="$2"
  local src="$source_config/$name"
  local dst="$profile_dir/$name"

  [[ -e "$src" || -L "$src" ]] || return 0

  if [[ -e "$dst" || -L "$dst" ]]; then
    return 0
  fi

  ln -s "$src" "$dst"
}

init_profile() {
  local profile="$1"
  validate_profile "$profile" || return $?

  local profile_dir
  profile_dir="$(profile_path "$profile")"

  umask 077
  mkdir -p "$profiles_root" "$profile_dir"
  chmod 700 "$profiles_root" "$profile_dir" 2>/dev/null || true

  copy_sanitized_settings "$source_config/settings.json" "$profile_dir/settings.json"

  local item
  for item in $shared_items; do
    link_shared_item "$profile_dir" "$item"
  done

  printf '%s\n' "$profile_dir"
}

list_profiles() {
  if [[ ! -d "$profiles_root" ]]; then
    return 0
  fi

  find "$profiles_root" -mindepth 1 -maxdepth 1 -type d -print \
    | awk -F/ '{ if ($NF !~ /^\./) print }' \
    | sed "s#^$profiles_root/##" \
    | LC_ALL=C sort
}

remove_profile() {
  local profile="$1"
  validate_profile "$profile" || return $?

  local profile_dir trash_dir trash_path timestamp
  profile_dir="$(profile_path "$profile")"

  if [[ ! -d "$profile_dir" ]]; then
    err "profile does not exist: $profile"
    return 2
  fi

  timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
  trash_dir="$profiles_root/.trash"
  trash_path="$trash_dir/$profile-$timestamp"

  umask 077
  mkdir -p "$trash_dir"
  chmod 700 "$trash_dir" 2>/dev/null || true
  mv "$profile_dir" "$trash_path"

  printf 'moved %s to %s\n' "$profile" "$trash_path"
}

list_trash() {
  local trash_dir="$profiles_root/.trash"

  if [[ ! -d "$trash_dir" ]]; then
    return 0
  fi

  find "$trash_dir" -mindepth 1 -maxdepth 1 -type d -print \
    | sed "s#^$trash_dir/##" \
    | LC_ALL=C sort
}

install_shell_integration() {
  local shell_file="${CLAUDE_PROFILE_SHELL_FILE:-$HOME/.zshrc}"
  local script_dir package_root shell_source
  local begin_marker="# >>> claude-profile >>>"
  local end_marker="# <<< claude-profile <<<"

  script_dir="$(resolve_script_path)"
  package_root="$(cd "$script_dir/.." && pwd)"
  shell_source="$package_root/shell/zsh.zsh"

  if [[ ! -f "$shell_source" ]]; then
    err "shell integration file not found: $shell_source"
    return 1
  fi

  touch "$shell_file"

  local tmp_file
  tmp_file="$(mktemp "${TMPDIR:-/tmp}/claude-profile-zshrc.XXXXXX")"
  awk -v begin="$begin_marker" -v end="$end_marker" '
    $0 == begin { skip = 1; next }
    $0 == end { skip = 0; next }
    skip != 1 { print }
  ' "$shell_file" > "$tmp_file"

  {
    cat "$tmp_file"
    printf '\n%s\n' "$begin_marker"
    printf 'source "%s"\n' "$shell_source"
    printf '%s\n' "$end_marker"
  } > "$shell_file"

  rm -f "$tmp_file"
  printf 'Installed shell integration in %s\n' "$shell_file"
  printf 'Restart your shell or run: source %s\n' "$shell_file"
}

strip_auth_env() {
  local name
  for name in "${auth_env_names[@]}"; do
    unset "$name" || true
  done
}

set_terminal_title() {
  local profile="$1"

  if [[ "${CLAUDE_PROFILE_SET_TERMINAL_TITLE:-1}" == "0" ]]; then
    return 0
  fi

  if [[ -t 1 ]]; then
    local title="${CLAUDE_PROFILE_TITLE_PREFIX:-claude:}$profile"
    title="${title//$'\033'/}"
    title="${title//$'\007'/}"
    printf '\033]0;%s\007' "$title"
  fi
}

has_name_arg() {
  local arg

  for arg in "$@"; do
    case "$arg" in
      -n|--name|--name=*)
        return 0
        ;;
    esac
  done

  return 1
}

should_inject_name() {
  if [[ "${CLAUDE_PROFILE_SET_CLAUDE_NAME:-1}" == "0" ]]; then
    return 1
  fi

  if has_name_arg "$@"; then
    return 1
  fi

  local arg
  for arg in "$@"; do
    case "$arg" in
      -h|--help|-v|--version|-p|--print)
        return 1
        ;;
      auth|doctor|install|mcp|plugin|plugins|project|setup-token|update|upgrade)
        return 1
        ;;
    esac
  done

  return 0
}

doctor() {
  printf 'claude-profile %s\n' "$VERSION"
  printf 'profiles root: %s\n' "$profiles_root"
  printf 'source config: %s\n' "$source_config"
  printf 'shared items: %s\n' "$shared_items"

  local claude_bin
  if claude_bin="$(find_claude_bin 2>/dev/null)"; then
    printf 'claude binary: %s\n' "$claude_bin"
    "$claude_bin" --version 2>/dev/null | sed 's/^/claude version: /' || true
  else
    printf 'claude binary: not found\n'
  fi

  local present=()
  local name
  for name in "${auth_env_names[@]}"; do
    if [[ -n "${!name:-}" ]]; then
      present+=("$name")
    fi
  done

  if (( ${#present[@]} )); then
    printf 'auth env in current shell: present, blocked at launch (%s)\n' "${present[*]}"
  else
    printf 'auth env in current shell: none detected\n'
  fi
}

main() {
  local command="${1:-}"

  case "$command" in
    -h|--help|help)
      usage
      return 0
      ;;
    --version|version)
      printf '%s\n' "$VERSION"
      return 0
      ;;
    --list|list)
      list_profiles
      return 0
      ;;
    --trash-list|trash-list)
      list_trash
      return 0
      ;;
    --install-shell|install-shell)
      install_shell_integration
      return 0
      ;;
    --doctor|doctor)
      doctor
      return 0
      ;;
    --init|init)
      shift || true
      init_profile "${1:-}"
      return 0
      ;;
    --path|path)
      shift || true
      validate_profile "${1:-}" || return $?
      profile_path "$1"
      return 0
      ;;
    --sync-settings|sync-settings)
      shift || true
      sync_settings "${1:-}"
      return 0
      ;;
    --remove|remove|rm)
      shift || true
      remove_profile "${1:-}"
      return 0
      ;;
    "")
      usage >&2
      return 2
      ;;
    -*)
      err "unknown option: $command"
      usage >&2
      return 2
      ;;
  esac

  local profile="$1"
  shift

  if [[ "${1:-}" == "--" ]]; then
    shift
  fi

  local profile_dir
  profile_dir="$(init_profile "$profile")" || return $?

  local claude_bin
  claude_bin="$(find_claude_bin)" || {
    err "claude binary not found in PATH"
    return 127
  }

  strip_auth_env
  export CLAUDE_CONFIG_DIR="$profile_dir"
  set_terminal_title "$profile"

  if should_inject_name "$@"; then
    set -- --name "$profile" "$@"
  fi

  exec "$claude_bin" "$@"
}

main "$@"
