#!/usr/bin/env bash
# gstack setup — build browser binary + register skills with Claude Code / Codex
set -e

if ! command -v bun >/dev/null 2>&1; then
  echo "Error: bun is required but not installed." >&2
  echo "Install it: curl -fsSL https://bun.sh/install | bash" >&2
  exit 1
fi

INSTALL_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
SOURCE_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)"
INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"
BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse"
CODEX_SKILLS="$HOME/.codex/skills"
CODEX_GSTACK="$CODEX_SKILLS/gstack"

IS_WINDOWS=0
case "$(uname -s)" in
  MINGW*|MSYS*|CYGWIN*|Windows_NT) IS_WINDOWS=1 ;;
esac

# ─── Parse --host flag ─────────────────────────────────────────
HOST="claude"
while [ $# -gt 0 ]; do
  case "$1" in
    --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
    --host=*) HOST="${1#--host=}"; shift ;;
    *) shift ;;
  esac
done

case "$HOST" in
  claude|codex|kiro|auto) ;;
  *) echo "Unknown --host value: $HOST (expected claude, codex, kiro, or auto)" >&2; exit 1 ;;
esac

# For auto: detect which agents are installed
INSTALL_CLAUDE=0
INSTALL_CODEX=0
INSTALL_KIRO=0
if [ "$HOST" = "auto" ]; then
  command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
  command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
  command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1
  # If none found, default to claude
  if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ]; then
    INSTALL_CLAUDE=1
  fi
elif [ "$HOST" = "claude" ]; then
  INSTALL_CLAUDE=1
elif [ "$HOST" = "codex" ]; then
  INSTALL_CODEX=1
elif [ "$HOST" = "kiro" ]; then
  INSTALL_KIRO=1
fi

migrate_direct_codex_install() {
  local gstack_dir="$1"
  local codex_gstack="$2"
  local migrated_dir="$HOME/.gstack/repos/gstack"

  [ "$gstack_dir" = "$codex_gstack" ] || return 0
  [ -L "$gstack_dir" ] && return 0

  mkdir -p "$(dirname "$migrated_dir")"
  if [ -e "$migrated_dir" ] && [ "$migrated_dir" != "$gstack_dir" ]; then
    echo "gstack setup failed: direct Codex install detected at $gstack_dir" >&2
    echo "A migrated repo already exists at $migrated_dir; move one of them aside and rerun setup." >&2
    exit 1
  fi

  echo "Migrating direct Codex install to $migrated_dir to avoid duplicate skill discovery..."
  mv "$gstack_dir" "$migrated_dir"
  SOURCE_GSTACK_DIR="$migrated_dir"
  INSTALL_GSTACK_DIR="$migrated_dir"
  INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"
  BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse"
}

if [ "$INSTALL_CODEX" -eq 1 ]; then
  migrate_direct_codex_install "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
fi

ensure_playwright_browser() {
  if [ "$IS_WINDOWS" -eq 1 ]; then
    # On Windows, Bun can't launch Chromium due to broken pipe handling
    # (oven-sh/bun#4253). Use Node.js to verify Chromium works instead.
    (
      cd "$SOURCE_GSTACK_DIR"
      node -e "const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); await b.close(); })()" 2>/dev/null
    )
  else
    (
      cd "$SOURCE_GSTACK_DIR"
      bun --eval 'import { chromium } from "playwright"; const browser = await chromium.launch(); await browser.close();'
    ) >/dev/null 2>&1
  fi
}

# 1. Build browse binary if needed (smart rebuild: stale sources, package.json, lock)
NEEDS_BUILD=0
if [ ! -x "$BROWSE_BIN" ]; then
  NEEDS_BUILD=1
elif [ -n "$(find "$SOURCE_GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then
  NEEDS_BUILD=1
elif [ "$SOURCE_GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then
  NEEDS_BUILD=1
elif [ -f "$SOURCE_GSTACK_DIR/bun.lock" ] && [ "$SOURCE_GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then
  NEEDS_BUILD=1
fi

if [ "$NEEDS_BUILD" -eq 1 ]; then
  echo "Building browse binary..."
  (
    cd "$SOURCE_GSTACK_DIR"
    bun install
    bun run build
  )
  # Safety net: write .version if build script didn't (e.g., git not available during build)
  if [ ! -f "$SOURCE_GSTACK_DIR/browse/dist/.version" ]; then
    git -C "$SOURCE_GSTACK_DIR" rev-parse HEAD > "$SOURCE_GSTACK_DIR/browse/dist/.version" 2>/dev/null || true
  fi
fi

if [ ! -x "$BROWSE_BIN" ]; then
  echo "gstack setup failed: browse binary missing at $BROWSE_BIN" >&2
  exit 1
fi

# 1b. Generate .agents/ Codex skill docs if missing or stale
# .agents/ is no longer committed — generated at setup time from .tmpl templates.
# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh
# but .agents/ hasn't been generated yet, e.g., fresh clone).
AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills"
NEEDS_AGENTS_GEN=0
if [ ! -d "$AGENTS_DIR" ]; then
  NEEDS_AGENTS_GEN=1
elif [ -n "$(find "$SOURCE_GSTACK_DIR" -maxdepth 2 -name 'SKILL.md.tmpl' -newer "$AGENTS_DIR" -print -quit 2>/dev/null)" ]; then
  NEEDS_AGENTS_GEN=1
fi

if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
  echo "Generating .agents/ skill docs..."
  (
    cd "$SOURCE_GSTACK_DIR"
    bun install --frozen-lockfile 2>/dev/null || bun install
    bun run gen:skill-docs --host codex
  )
fi

# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
  echo "Installing Playwright Chromium..."
  (
    cd "$SOURCE_GSTACK_DIR"
    bunx playwright install chromium
  )

  if [ "$IS_WINDOWS" -eq 1 ]; then
    # On Windows, Node.js launches Chromium (not Bun — see oven-sh/bun#4253).
    # Ensure playwright is importable by Node from the gstack directory.
    if ! command -v node >/dev/null 2>&1; then
      echo "gstack setup failed: Node.js is required on Windows (Bun cannot launch Chromium due to a pipe bug)" >&2
      echo "  Install Node.js: https://nodejs.org/" >&2
      exit 1
    fi
    echo "Windows detected — verifying Node.js can load Playwright..."
    (
      cd "$SOURCE_GSTACK_DIR"
      # Bun's node_modules already has playwright; verify Node can require it
      node -e "require('playwright')" 2>/dev/null || npm install --no-save playwright
    )
  fi
fi

if ! ensure_playwright_browser; then
  if [ "$IS_WINDOWS" -eq 1 ]; then
    echo "gstack setup failed: Playwright Chromium could not be launched via Node.js" >&2
    echo "  This is a known issue with Bun on Windows (oven-sh/bun#4253)." >&2
    echo "  Ensure Node.js is installed and 'node -e \"require('playwright')\"' works." >&2
  else
    echo "gstack setup failed: Playwright Chromium could not be launched" >&2
  fi
  exit 1
fi

# 3. Ensure ~/.gstack global state directory exists
mkdir -p "$HOME/.gstack/projects"

# ─── Helper: link Claude skill subdirectories into a skills parent directory ──
link_claude_skill_dirs() {
  local gstack_dir="$1"
  local skills_dir="$2"
  local linked=()
  for skill_dir in "$gstack_dir"/*/; do
    if [ -f "$skill_dir/SKILL.md" ]; then
      skill_name="$(basename "$skill_dir")"
      # Skip node_modules
      [ "$skill_name" = "node_modules" ] && continue
      target="$skills_dir/$skill_name"
      # Create or update symlink; skip if a real file/directory exists
      if [ -L "$target" ] || [ ! -e "$target" ]; then
        ln -snf "gstack/$skill_name" "$target"
        linked+=("$skill_name")
      fi
    fi
  done
  if [ ${#linked[@]} -gt 0 ]; then
    echo "  linked skills: ${linked[*]}"
  fi
}

# ─── Helper: link generated Codex skills into a skills parent directory ──
# Installs from .agents/skills/gstack-* (the generated Codex-format skills)
# instead of source dirs (which have Claude paths).
link_codex_skill_dirs() {
  local gstack_dir="$1"
  local skills_dir="$2"
  local agents_dir="$gstack_dir/.agents/skills"
  local linked=()

  if [ ! -d "$agents_dir" ]; then
    echo "  Generating .agents/ skill docs..."
    ( cd "$gstack_dir" && bun run gen:skill-docs --host codex )
  fi

  if [ ! -d "$agents_dir" ]; then
    echo "  warning: .agents/skills/ generation failed — run 'bun run gen:skill-docs --host codex' manually" >&2
    return 1
  fi

  for skill_dir in "$agents_dir"/gstack*/; do
    if [ -f "$skill_dir/SKILL.md" ]; then
      skill_name="$(basename "$skill_dir")"
      # Skip the sidecar directory — it contains runtime asset symlinks (bin/,
      # browse/), not a skill. Linking it would overwrite the root gstack
      # symlink that Step 5 already pointed at the repo root.
      [ "$skill_name" = "gstack" ] && continue
      target="$skills_dir/$skill_name"
      # Create or update symlink
      if [ -L "$target" ] || [ ! -e "$target" ]; then
        ln -snf "$skill_dir" "$target"
        linked+=("$skill_name")
      fi
    fi
  done
  if [ ${#linked[@]} -gt 0 ]; then
    echo "  linked skills: ${linked[*]}"
  fi
}

# ─── Helper: create .agents/skills/gstack/ sidecar symlinks ──────────
# Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime
# assets (bin/, browse/dist/, review/, qa/, etc.) so skill templates can
# resolve paths like $SKILL_ROOT/review/design-checklist.md.
create_agents_sidecar() {
  local repo_root="$1"
  local agents_gstack="$repo_root/.agents/skills/gstack"
  mkdir -p "$agents_gstack"

  # Sidecar directories that skills reference at runtime
  for asset in bin browse review qa; do
    local src="$SOURCE_GSTACK_DIR/$asset"
    local dst="$agents_gstack/$asset"
    if [ -d "$src" ] || [ -f "$src" ]; then
      if [ -L "$dst" ] || [ ! -e "$dst" ]; then
        ln -snf "$src" "$dst"
      fi
    fi
  done

  # Sidecar files that skills reference at runtime
  for file in ETHOS.md; do
    local src="$SOURCE_GSTACK_DIR/$file"
    local dst="$agents_gstack/$file"
    if [ -f "$src" ]; then
      if [ -L "$dst" ] || [ ! -e "$dst" ]; then
        ln -snf "$src" "$dst"
      fi
    fi
  done
}

# ─── Helper: create a minimal ~/.codex/skills/gstack runtime root ───────────
# Codex scans ~/.codex/skills recursively. Exposing the whole repo here causes
# duplicate skills because source SKILL.md files and generated Codex skills are
# both discoverable. Keep this directory limited to runtime assets + root skill.
create_codex_runtime_root() {
  local gstack_dir="$1"
  local codex_gstack="$2"
  local agents_dir="$gstack_dir/.agents/skills"

  if [ -L "$codex_gstack" ]; then
    rm -f "$codex_gstack"
  elif [ -d "$codex_gstack" ] && [ "$codex_gstack" != "$gstack_dir" ]; then
    # Old direct installs left a real directory here with stale source skills.
    # Remove it so we start fresh with only the minimal runtime assets.
    rm -rf "$codex_gstack"
  fi

  mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review"

  if [ -f "$agents_dir/gstack/SKILL.md" ]; then
    ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md"
  fi
  if [ -d "$gstack_dir/bin" ]; then
    ln -snf "$gstack_dir/bin" "$codex_gstack/bin"
  fi
  if [ -d "$gstack_dir/browse/dist" ]; then
    ln -snf "$gstack_dir/browse/dist" "$codex_gstack/browse/dist"
  fi
  if [ -d "$gstack_dir/browse/bin" ]; then
    ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin"
  fi
  if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then
    ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md"
  fi
  # Review runtime assets (individual files, NOT the whole review/ dir which has SKILL.md)
  for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
    if [ -f "$gstack_dir/review/$f" ]; then
      ln -snf "$gstack_dir/review/$f" "$codex_gstack/review/$f"
    fi
  done
  # ETHOS.md — referenced by "Search Before Building" in all skill preambles
  if [ -f "$gstack_dir/ETHOS.md" ]; then
    ln -snf "$gstack_dir/ETHOS.md" "$codex_gstack/ETHOS.md"
  fi
}

# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
CODEX_REPO_LOCAL=0
if [ "$SKILLS_BASENAME" = "skills" ] && [ "$SKILLS_PARENT_BASENAME" = ".agents" ]; then
  CODEX_REPO_LOCAL=1
fi

if [ "$INSTALL_CLAUDE" -eq 1 ]; then
  if [ "$SKILLS_BASENAME" = "skills" ]; then
    link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
    echo "gstack ready (claude)."
    echo "  browse: $BROWSE_BIN"
  else
    echo "gstack ready (claude)."
    echo "  browse: $BROWSE_BIN"
    echo "  (skipped skill symlinks — not inside .claude/skills/)"
  fi
fi

# 5. Install for Codex
if [ "$INSTALL_CODEX" -eq 1 ]; then
  if [ "$CODEX_REPO_LOCAL" -eq 1 ]; then
    CODEX_SKILLS="$INSTALL_SKILLS_DIR"
    CODEX_GSTACK="$INSTALL_GSTACK_DIR"
  fi
  mkdir -p "$CODEX_SKILLS"

  # Skip runtime root creation for repo-local installs — the checkout IS the runtime root.
  # create_codex_runtime_root would create self-referential symlinks (bin → bin, etc.).
  if [ "$CODEX_REPO_LOCAL" -eq 0 ]; then
    create_codex_runtime_root "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
  fi
  # Install generated Codex-format skills (not Claude source dirs)
  link_codex_skill_dirs "$SOURCE_GSTACK_DIR" "$CODEX_SKILLS"

  echo "gstack ready (codex)."
  echo "  browse: $BROWSE_BIN"
  echo "  codex skills: $CODEX_SKILLS"
fi

# 6. Install for Kiro CLI (copy from .agents/skills, rewrite paths)
if [ "$INSTALL_KIRO" -eq 1 ]; then
  KIRO_SKILLS="$HOME/.kiro/skills"
  AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills"
  mkdir -p "$KIRO_SKILLS"

  # Create gstack dir with symlinks for runtime assets, copy+sed for SKILL.md
  KIRO_GSTACK="$KIRO_SKILLS/gstack"
  # Remove old whole-dir symlink from previous installs
  [ -L "$KIRO_GSTACK" ] && rm -f "$KIRO_GSTACK"
  mkdir -p "$KIRO_GSTACK" "$KIRO_GSTACK/browse" "$KIRO_GSTACK/gstack-upgrade" "$KIRO_GSTACK/review"
  ln -snf "$SOURCE_GSTACK_DIR/bin" "$KIRO_GSTACK/bin"
  ln -snf "$SOURCE_GSTACK_DIR/browse/dist" "$KIRO_GSTACK/browse/dist"
  ln -snf "$SOURCE_GSTACK_DIR/browse/bin" "$KIRO_GSTACK/browse/bin"
  # ETHOS.md — referenced by "Search Before Building" in all skill preambles
  if [ -f "$SOURCE_GSTACK_DIR/ETHOS.md" ]; then
    ln -snf "$SOURCE_GSTACK_DIR/ETHOS.md" "$KIRO_GSTACK/ETHOS.md"
  fi
  # gstack-upgrade skill
  if [ -f "$AGENTS_DIR/gstack-upgrade/SKILL.md" ]; then
    ln -snf "$AGENTS_DIR/gstack-upgrade/SKILL.md" "$KIRO_GSTACK/gstack-upgrade/SKILL.md"
  fi
  # Review runtime assets (individual files, not whole dir)
  for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
    if [ -f "$SOURCE_GSTACK_DIR/review/$f" ]; then
      ln -snf "$SOURCE_GSTACK_DIR/review/$f" "$KIRO_GSTACK/review/$f"
    fi
  done

  # Rewrite root SKILL.md paths for Kiro
  sed -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \
      -e "s|\.claude/skills/gstack|.kiro/skills/gstack|g" \
      -e "s|\.claude/skills|.kiro/skills|g" \
      "$SOURCE_GSTACK_DIR/SKILL.md" > "$KIRO_GSTACK/SKILL.md"

  if [ ! -d "$AGENTS_DIR" ]; then
    echo "  warning: no .agents/skills/ directory found — run 'bun run build' first" >&2
  else
    for skill_dir in "$AGENTS_DIR"/gstack*/; do
      [ -f "$skill_dir/SKILL.md" ] || continue
      skill_name="$(basename "$skill_dir")"
      target_dir="$KIRO_SKILLS/$skill_name"
      mkdir -p "$target_dir"
      # Generated Codex skills use $HOME/.codex (not ~/), plus $GSTACK_ROOT variables.
      # Rewrite the default GSTACK_ROOT value and any remaining literal paths.
      sed -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \
          -e "s|~/.codex/skills/gstack|~/.kiro/skills/gstack|g" \
          -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \
          "$skill_dir/SKILL.md" > "$target_dir/SKILL.md"
    done
    echo "gstack ready (kiro)."
    echo "  browse: $BROWSE_BIN"
    echo "  kiro skills: $KIRO_SKILLS"
  fi
fi

# 7. Create .agents/ sidecar symlinks for the real Codex skill target.
# The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack,
# so the runtime assets must live there for both global and repo-local installs.
if [ "$INSTALL_CODEX" -eq 1 ]; then
  create_agents_sidecar "$SOURCE_GSTACK_DIR"
fi

# 8. First-time welcome + legacy cleanup
if [ ! -d "$HOME/.gstack" ]; then
  mkdir -p "$HOME/.gstack"
  echo "  Welcome! Run /gstack-upgrade anytime to stay current."
fi
rm -f /tmp/gstack-latest-version
