import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir, tmpdir, userInfo } from 'os';
import log from 'electron-log';
import { BrowserWindow } from 'electron';
import { execAsync, getEnvWithPath } from '../utils/shell-utils';
import { createOutputSender } from '../utils/ipc-output-helper';

export interface InstCliStatus {
  imageExists: boolean;
  imageId?: string;
  imageCreated?: string;
  versions?: {
    inst?: string;
    instCliQuizzes?: string;
    ruby?: string;
    pnpm?: string;
    instFunctions?: string;
    claude?: string;
  };
  latestVersions?: {
    inst?: string;
    instCliQuizzes?: string;
  };
  updateAvailable?: boolean;
  instUpdateAvailable?: boolean;
  instCliQuizzesUpdateAvailable?: boolean;
}

/**
 * Generate Dockerfile content with the current username
 */
function generateDockerfileContent(): string {
  const username = userInfo().username;

  return `# Use the latest official Alpine image as a lean base
FROM alpine:latest

# Set the necessary environment variables
ENV LANG="en_US.UTF-8"
ENV LC_ALL="en_US.UTF-8"
ENV TERM="xterm-256color"
ENV MUSL_LOCPATH="/usr/share/i18n/locales/musl"
ENV HOME="/Users/${username}"

# Create the user home directory
RUN mkdir -p /Users/${username}

# Install all necessary packages in a single RUN command to reduce image layers.
# - Use --no-cache to avoid storing the package index.
# - build-base is the Alpine equivalent of build-essential.
# - docker-cli & docker-compose are available in Alpine's official repositories.
RUN apk add --no-cache \\
    openssh \\
    ncurses \\
    ca-certificates \\
    sudo \\
    bash \\
    git \\
    build-base \\
    ruby \\
    ruby-dev \\
    yaml-dev \\
    docker-cli \\
    docker-compose \\
    curl \\
    jq \\
    nodejs \\
    npm \\
    ttyd \\
    yq \\
    icu-libs \\
    musl-locales \\
    musl-locales-lang \\
    ncurses-terminfo-base

RUN npm install -g pnpm

# Copy your gem configuration file as configured here based on these instructions:
# https://instructure.atlassian.net/wiki/spaces/CE/pages/85912256716/inst-cli+-+The+CLI+tool+for+developing+with+Canvas+locally
COPY .gemrc /Users/${username}/.gemrc
COPY .npmrc /Users/${username}/.npmrc

# Install your gem
RUN gem install inst
RUN gem install inst-cli-quizzes

# Use the Native Installer (this enables the auto-updater)
# We use 'latest' instead of 'stable' to bypass the cached versioning
RUN curl -fsSL https://claude.ai/install.sh | bash -s latest

# Ensure the claude binary is in the PATH
ENV PATH="/root/.local/bin:/Users/${username}/.local/bin:\${PATH}"

# Install Claude Code
# RUN npm install -g @anthropic-ai/claude-code

# Pre-configure Claude Code with ANSI theme (most compatible in Docker)
# RUN mkdir -p /Users/${username}/.claude && \
#    printf '{"theme":"dark-ansi"}' > /Users/${username}/.claude/settings.json

# Download the CLI specifically for Alpine ARM64
RUN curl -Lk 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-arm64' --output /Users/${username}/vscode_cli.tar.gz
RUN tar -xf /Users/${username}/vscode_cli.tar.gz -C /Users/${username}
RUN mv /Users/${username}/code /usr/bin/code

# Copy custom scripts and add to PATH
COPY enable-canvas-multi-deployments.sh /usr/local/bin/enable-canvas-multi-deployments.sh
RUN chmod +x /usr/local/bin/enable-canvas-multi-deployments.sh
COPY disable-canvas-multi-deployments.sh /usr/local/bin/disable-canvas-multi-deployments.sh
RUN chmod +x /usr/local/bin/disable-canvas-multi-deployments.sh

#RUN addgroup -S docker && adduser -S docker -G docker
#USER docker
#ENV HOME="/Users/${username}"

# Set working directory
WORKDIR /Users/${username}
`;
}

const INST_FUNCTIONS_CONTENT = `
mkdir -p  ~/.claude
[ ! -f "$HOME/.claude.json" ] && echo "{}" > "$HOME/.claude.json"

update-frpc() {
  INST_CONFIG="$HOME/.inst-cli/inst.yml"
  FRP_CONFIG="$HOME/frp/frpc.toml"
  FRP_SERVICE="com.fatedier.frpc"
  if [[ -f "$INST_CONFIG" ]] && [[ -f "$FRP_CONFIG" ]]; then
      INST_DOMAIN=$(grep 'inst_domain:' "$INST_CONFIG" | awk '{print $2}')
      if [[ -n "$INST_DOMAIN" ]]; then
        DOMAINS_LIST=$( {
          docker ps -a --filter "name=.*canvas-" --format "{{.Labels}}" | tr ',' '\\n' | grep "traefik\\.http\\..*\\.service" | cut -d'=' -f2
          docker ps -a --filter "name=.*canvas-" --format "{{.Names}}"
          echo "dashboard"
        } | sort -u | sed "s/$/.$INST_DOMAIN/" | sed 's/.*/"&"/' | paste -sd "," - )
        if launchctl list | grep -q "$FRP_SERVICE"; then
            launchctl stop "$FRP_SERVICE"
            sed -i '' "s/customDomains = \\[.*\\]/customDomains = [$DOMAINS_LIST]/" "$FRP_CONFIG"
            launchctl start "$FRP_SERVICE"
        fi
      fi
  fi
  if [[ ! -f "$INST_CONFIG" ]] || [[ ! -f "$FRP_CONFIG" ]]; then
    return 0
  fi

  USER_NAME=$(awk -F'"' '/^name = / {print $2; exit}' "$FRP_CONFIG")
  INST_DOMAIN=$(grep 'inst_domain:' "$INST_CONFIG" | awk '{print $2}')
  CUSTOM_DOMAINS=$(grep '^customDomains = ' "$FRP_CONFIG" | head -1 | sed 's/^customDomains = //')

  if [[ -z "$USER_NAME" ]] || [[ -z "$INST_DOMAIN" ]] || [[ -z "$CUSTOM_DOMAINS" ]]; then
    return 0
  fi

  HASH_FILE="$HOME/frp/.frpc_last_hash"
  CURRENT_HASH=$(md5 -q "$FRP_CONFIG" 2>/dev/null)
  LAST_HASH=$(cat "$HASH_FILE" 2>/dev/null)

  if [[ "$CURRENT_HASH" == "$LAST_HASH" ]]; then
    return 0
  fi

  JSON_BODY=$(printf '{"username": "%s", "inst_domain": "%s", "domains": %s}' "$USER_NAME" "$INST_DOMAIN" "$CUSTOM_DOMAINS")
  curl -X POST https://nginx.loc8data.com/update \\
    -H "Content-Type: application/json" \\
    -d "$JSON_BODY" > /dev/null 2>&1 || true

  echo "$CURRENT_HASH" > "$HASH_FILE"
}

inst-functions() {
      # Check if stdin is a TTY
      if [ -t 0 ]; then
        local IT_FLAGS="-it"
      else
        local IT_FLAGS="-i"
      fi
      echo "1.0.0"
}

inst-canvas-docker() {
  # 1. Terminal Check
  local IT_FLAGS="-i"
  [ -t 0 ] && IT_FLAGS="-it"

  # 2. Base Canvas Path Logic
  local CONFIG_FILE="$HOME/.inst-cli/inst.yml"
  local CANVAS_PATH=$(sed -n '/canvas-lms:/,/source:/ s/.*source: //p' "$CONFIG_FILE" | sed 's/"//g')
  
  if [ ! -d "$CANVAS_PATH" ]; then
    echo "Error: canvas-lms:source '$CANVAS_PATH' does not exist."
    return 1
  fi

  # 3. Initialize Volume Array with mandatory mounts
  local VOLUMES=(
    -v "$CANVAS_PATH:$CANVAS_PATH"
    -v "/etc/resolver:/etc/resolver"
    -v "$HOME/.inst-cli:$HOME/template"
    -v "$SSH_AUTH_SOCK:/run/ssh-auth.sock"
    -v "$HOME/.claude.json:/$HOME/.claude.json"
    -v "$HOME/.claude:/$HOME/.claude"
    -v "$HOME/.ssh:$HOME/.ssh"
    -v "$HOME/.ssh:/root/.ssh_template:ro"
    -v "/var/run/docker.sock:/var/run/docker.sock"
  )

  # 4. Optional Career Config Volumes
  local CAREER_CONFIG_FILE="$HOME/.inst-agent/career.yml"
  if [ -f "$CAREER_CONFIG_FILE" ]; then
    local HORIZON_PATH=$(sed -n '/canvas-horizon:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')
    local JOURNEY_PATH=$(sed -n '/journey:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')

    # Only add if the directory actually exists on your Mac
    [ -d "$HORIZON_PATH" ] && VOLUMES+=("-v" "$HORIZON_PATH:$HORIZON_PATH")
    [ -d "$JOURNEY_PATH" ] && VOLUMES+=("-v" "$JOURNEY_PATH:$JOURNEY_PATH")
  fi

  # 5. Handle command arguments
  local cmd="\${*:-sh}"

  # 6. Ensure Claude files exist locally to prevent Docker creating directories
  [ ! -f "$HOME/.claude.json" ] && echo "{}" > "$HOME/.claude.json"
  [ ! -d "$HOME/.claude" ] && mkdir -p "$HOME/.claude"

  update-frpc

  # 7. Run Docker
  docker run --rm $IT_FLAGS \\
    --workdir "$CANVAS_PATH" \\
    --entrypoint sh \\
    -e GIT_PAGER=cat \\
    -e TERM="\${TERM:-xterm-256color}" \\
    -e COLORTERM="\${COLORTERM}" \\
    -e FORCE_COLOR="\${FORCE_COLOR:-1}" \\
    -e CLICOLOR="\${CLICOLOR:-1}" \\
    -e CLICOLOR_FORCE="\${CLICOLOR_FORCE:-1}" \\
    -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
    "\${VOLUMES[@]}" \\
    inst-cli -c "mkdir -p \$HOME/.inst-cli && cp -R \$HOME/template/. \$HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && $cmd"

  update-frpc
}

inst-journey-docker() {
  # 1. Terminal Check
  local IT_FLAGS="-i"
  [ -t 0 ] && IT_FLAGS="-it"

  # 2. Base Canvas Path Logic
  local CONFIG_FILE="$HOME/.inst-cli/inst.yml"
  local CANVAS_PATH=$(sed -n '/canvas-lms:/,/source:/ s/.*source: //p' "$CONFIG_FILE" | sed 's/"//g')
  
  if [ ! -d "$CANVAS_PATH" ]; then
    echo "Error: canvas-lms:source '$CANVAS_PATH' does not exist."
    return 1
  fi

  # 3. Initialize Volume Array with mandatory mounts
  local VOLUMES=(
    -v "$CANVAS_PATH:$CANVAS_PATH"
    -v "/etc/resolver:/etc/resolver"
    -v "$HOME/.inst-cli:$HOME/template"
    -v "$SSH_AUTH_SOCK:/run/ssh-auth.sock"
    -v "$HOME/.claude.json:/$HOME/.claude.json"
    -v "$HOME/.claude:/$HOME/.claude"
    -v "$HOME/.ssh:$HOME/.ssh"
    -v "$HOME/.ssh:/root/.ssh_template:ro"
    -v "/var/run/docker.sock:/var/run/docker.sock"
  )

  # 4. Optional Career Config Volumes
  local CAREER_CONFIG_FILE="$HOME/.inst-agent/career.yml"
  if [ -f "$CAREER_CONFIG_FILE" ]; then
    local HORIZON_PATH=$(sed -n '/canvas-horizon:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')
    local JOURNEY_PATH=$(sed -n '/journey:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')

    # Only add if the directory actually exists on your Mac
    [ -d "$HORIZON_PATH" ] && VOLUMES+=("-v" "$HORIZON_PATH:$HORIZON_PATH")
    [ -d "$JOURNEY_PATH" ] && VOLUMES+=("-v" "$JOURNEY_PATH:$JOURNEY_PATH")
  fi

  if [ ! -d "$JOURNEY_PATH" ]; then
    echo "Error: journey:source '$JOURNEY_PATH' does not exist."
    return 1
  fi

  # 5. Handle command arguments
  local cmd="\${*:-sh}"

  # 6. Ensure Claude files exist locally to prevent Docker creating directories
  [ ! -f "$HOME/.claude.json" ] && echo "{}" > "$HOME/.claude.json"
  [ ! -d "$HOME/.claude" ] && mkdir -p "$HOME/.claude"

  # 7. Run Docker
  docker run --rm $IT_FLAGS \\
    --workdir "$JOURNEY_PATH" \\
    --entrypoint sh \\
    -e GIT_PAGER=cat \\
    -e TERM="\${TERM:-xterm-256color}" \\
    -e COLORTERM="\${COLORTERM}" \\
    -e FORCE_COLOR="\${FORCE_COLOR:-1}" \\
    -e CLICOLOR="\${CLICOLOR:-1}" \\
    -e CLICOLOR_FORCE="\${CLICOLOR_FORCE:-1}" \\
    -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
    "\${VOLUMES[@]}" \\
    inst-cli -c "mkdir -p \$HOME/.inst-cli && cp -R \$HOME/template/. \$HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && $cmd"
}

inst-horizon-docker() {
  # 1. Terminal Check
  local IT_FLAGS="-i"
  [ -t 0 ] && IT_FLAGS="-it"

  # 2. Base Canvas Path Logic
  local CONFIG_FILE="$HOME/.inst-cli/inst.yml"
  local CANVAS_PATH=$(sed -n '/canvas-lms:/,/source:/ s/.*source: //p' "$CONFIG_FILE" | sed 's/"//g')
  
  if [ ! -d "$CANVAS_PATH" ]; then
    echo "Error: canvas-lms:source '$CANVAS_PATH' does not exist."
    return 1
  fi

  # 3. Initialize Volume Array with mandatory mounts
  local VOLUMES=(
    -v "$CANVAS_PATH:$CANVAS_PATH"
    -v "/etc/resolver:/etc/resolver"
    -v "$HOME/.inst-cli:$HOME/template"
    -v "$SSH_AUTH_SOCK:/run/ssh-auth.sock"
    -v "$HOME/.claude.json:/$HOME/.claude.json"
    -v "$HOME/.claude:/$HOME/.claude"
    -v "$HOME/.ssh:$HOME/.ssh"
    -v "/var/run/docker.sock:/var/run/docker.sock"
  )

  # 4. Optional Career Config Volumes
  local CAREER_CONFIG_FILE="$HOME/.inst-agent/career.yml"
  if [ -f "$CAREER_CONFIG_FILE" ]; then
    local HORIZON_PATH=$(sed -n '/canvas-horizon:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')
    local JOURNEY_PATH=$(sed -n '/journey:/,/source:/ s/.*source: //p' "$CAREER_CONFIG_FILE" | sed 's/"//g')

    # Only add if the directory actually exists on your Mac
    [ -d "$HORIZON_PATH" ] && VOLUMES+=("-v" "$HORIZON_PATH:$HORIZON_PATH")
    [ -d "$JOURNEY_PATH" ] && VOLUMES+=("-v" "$JOURNEY_PATH:$JOURNEY_PATH")
  fi

  if [ ! -d "$HORIZON_PATH" ]; then
    echo "Error: canvas-horizon:source '$HORIZON_PATH' does not exist."
    return 1
  fi

  # 5. Handle command arguments
  local cmd="\${*:-sh}"

  # 6. Ensure Claude files exist locally to prevent Docker creating directories
  [ ! -f "$HOME/.claude.json" ] && echo "{}" > "$HOME/.claude.json"
  [ ! -d "$HOME/.claude" ] && mkdir -p "$HOME/.claude"

  # 7. Run Docker
  docker run --rm $IT_FLAGS \\
    --workdir "$HORIZON_PATH" \\
    --entrypoint sh \\
    -e GIT_PAGER=cat \\
    -e TERM="\${TERM:-xterm-256color}" \\
    -e COLORTERM="\${COLORTERM}" \\
    -e FORCE_COLOR="\${FORCE_COLOR:-1}" \\
    -e CLICOLOR="\${CLICOLOR:-1}" \\
    -e CLICOLOR_FORCE="\${CLICOLOR_FORCE:-1}" \\
    -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
    "\${VOLUMES[@]}" \\
    inst-cli -c "mkdir -p \$HOME/.inst-cli && cp -R \$HOME/template/. \$HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && $cmd"
}

inst() {
  # Check if stdin is a TTY
  if [ -t 0 ]; then
    local IT_FLAGS="-it"
  else
    local IT_FLAGS="-i"
  fi

  case "$1" in
    config-file)
      if [[ -f "$HOME/.inst-cli/inst.yml" && $2 != "--force" ]]; then
        docker run --rm $IT_FLAGS \\
          --workdir $HOME/instructure \\
          --entrypoint sh \\
          -e GIT_PAGER=cat \\
          -e TERM=\${TERM:-xterm-256color} \\
          -e COLORTERM=\${COLORTERM} \\
          -e FORCE_COLOR=\${FORCE_COLOR:-1} \\
          -e CLICOLOR=\${CLICOLOR:-1} \\
          -e CLICOLOR_FORCE=\${CLICOLOR_FORCE:-1} \\
          -v "$HOME/.inst-cli:$HOME/.inst-cli" \\
          -v "$REPO_VOL" \\
          -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
          -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
          -v "$HOME/.claude.json:/$HOME/.claude.json" \\
          -v "$HOME/.claude:/$HOME/.claude" \\
          -v "$HOME/.ssh:$HOME/.ssh" \\
          -v "$HOME/.ssh:/root/.ssh_template:ro" \\
          -v /var/run/docker.sock:/var/run/docker.sock \\
          inst-cli -c "cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && inst $*"
        return 0
      fi

      echo "? What is the base dir for repos? (e.g., $HOME/code)"
      read -r REPO_BASE

      REPO_BASE="\${REPO_BASE/$HOME/$HOME}"
      REPO_BASE="\${REPO_BASE/#~/$HOME}"
      REPO_BASE="\${REPO_BASE:-$HOME/code}"

      echo "base_code_directory: $REPO_BASE"
      if [ -d "$REPO_BASE" ]; then
        REPO_VOL="$REPO_BASE:$REPO_BASE"
      else
        echo "Error: Directory '$REPO_BASE' does not exist."
        return 1
      fi

      printf "%s" "$REPO_BASE" | docker run --rm -i \\
        --workdir $HOME/instructure \\
        --entrypoint sh \\
        -e GIT_PAGER=cat \\
        -v "/etc/resolver:/etc/resolver" \\
        -v "$HOME/.inst-cli:$HOME/.inst-cli" \\
        -v "$REPO_VOL" \\
        -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
        -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
        -v "$HOME/.claude.json:/$HOME/.claude.json" \\
        -v "$HOME/.claude:/$HOME/.claude" \\
        -v "$HOME/.ssh:$HOME/.ssh" \\
        -v "$HOME/.ssh:/root/.ssh_template:ro" \\
        -v /var/run/docker.sock:/var/run/docker.sock \\
        inst-cli -c "cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && inst $*"
      ;;
    proxy)
      docker run --rm $IT_FLAGS \\
        --workdir $HOME/instructure \\
        --entrypoint sh \\
        -e GIT_PAGER=cat \\
        -e TERM=\${TERM:-xterm-256color} \\
        -e COLORTERM=\${COLORTERM} \\
        -e FORCE_COLOR=\${FORCE_COLOR:-1} \\
        -e CLICOLOR=\${CLICOLOR:-1} \\
        -e CLICOLOR_FORCE=\${CLICOLOR_FORCE:-1} \\
        -v "/etc/resolver:/etc/resolver" \\
        -v "$HOME/.inst-cli:$HOME/.inst-cli" \\
        -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
        -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
        -v "$HOME/.ssh:$HOME/.ssh" \\
        -v "$HOME/.ssh:/root/.ssh_template:ro" \\
        -v "$HOME/.claude.json:/$HOME/.claude.json" \\
        -v "$HOME/.claude:/$HOME/.claude" \\
        -v /var/run/docker.sock:/var/run/docker.sock \\
        inst-cli -c "cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && inst $*"
      ;;
    *)
      local CONFIG_FILE="$HOME/.inst-cli/inst.yml"
      local CANVAS_PATH=$(sed -n '/canvas-lms:/,/source:/ s/.*source: //p' "$CONFIG_FILE" | sed 's/"//g')
      if [ -d "$CANVAS_PATH" ]; then
        REPO_VOL="$CANVAS_PATH:$CANVAS_PATH"
      else
        echo "Error: canvas-lms:source '$CANVAS_PATH' does not exist. Please update $HOME/.inst-cli/inst.yml before running this command."
        return 1
      fi

      if [[ "$1" == "canvas" && "$2" == "multideploy" ]]; then
        docker run --rm $IT_FLAGS \\
          --workdir $CANVAS_PATH \\
          --entrypoint sh \\
          -e GIT_PAGER=cat \\
          -e TERM=\${TERM:-xterm-256color} \\
          -e COLORTERM=\${COLORTERM} \\
          -e FORCE_COLOR=\${FORCE_COLOR:-1} \\
          -e CLICOLOR=\${CLICOLOR:-1} \\
          -e CLICOLOR_FORCE=\${CLICOLOR_FORCE:-1} \\
          -v "/etc/resolver:/etc/resolver" \\
          -v "$HOME/.inst-cli:$HOME/template" \\
          -v "$REPO_VOL" \\
          -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
          -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
          -v "$HOME/.claude.json:/$HOME/.claude.json" \\
          -v "$HOME/.claude:/$HOME/.claude" \\
          -v "$HOME/.ssh:$HOME/.ssh" \\
          -v "$HOME/.ssh:/root/.ssh_template:ro" \\
          -v /var/run/docker.sock:/var/run/docker.sock \\
          inst-cli -c "mkdir -p $HOME/.inst-cli && cp -R $HOME/template/. $HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && enable-canvas-multi-deployments.sh"
      elif [[ "$1" == "canvas" && "$2" == "singledeploy" ]]; then
        docker run --rm $IT_FLAGS \\
          --workdir $CANVAS_PATH \\
          --entrypoint sh \\
          -e GIT_PAGER=cat \\
          -e TERM=\${TERM:-xterm-256color} \\
          -e COLORTERM=\${COLORTERM} \\
          -e FORCE_COLOR=\${FORCE_COLOR:-1} \\
          -e CLICOLOR=\${CLICOLOR:-1} \\
          -e CLICOLOR_FORCE=\${CLICOLOR_FORCE:-1} \\
          -v "/etc/resolver:/etc/resolver" \\
          -v "$HOME/.inst-cli:$HOME/template" \\
          -v "$REPO_VOL" \\
          -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
          -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
          -v "$HOME/.claude.json:/$HOME/.claude.json" \\
          -v "$HOME/.claude:/$HOME/.claude" \\
          -v "$HOME/.ssh:$HOME/.ssh" \\
          -v "$HOME/.ssh:/root/.ssh_template:ro" \\
          -v /var/run/docker.sock:/var/run/docker.sock \\
          inst-cli -c "mkdir -p $HOME/.inst-cli && cp -R $HOME/template/. $HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && disable-canvas-multi-deployments.sh"      
      else
        docker run --rm $IT_FLAGS \\
          --workdir $CANVAS_PATH \\
          --entrypoint sh \\
          -e GIT_PAGER=cat \\
          -e TERM=\${TERM:-xterm-256color} \\
          -e COLORTERM=\${COLORTERM} \\
          -e FORCE_COLOR=\${FORCE_COLOR:-1} \\
          -e CLICOLOR=\${CLICOLOR:-1} \\
          -e CLICOLOR_FORCE=\${CLICOLOR_FORCE:-1} \\
          -v "/etc/resolver:/etc/resolver" \\
          -v "$HOME/.inst-cli:$HOME/template" \\
          -v "$REPO_VOL" \\
          -v $SSH_AUTH_SOCK:/run/ssh-auth.sock \\
          -e SSH_AUTH_SOCK=/run/ssh-auth.sock \\
          -v "$HOME/.claude.json:/$HOME/.claude.json" \\
          -v "$HOME/.claude:/$HOME/.claude" \\
          -v "$HOME/.ssh:$HOME/.ssh" \\
          -v "$HOME/.ssh:/root/.ssh_template:ro" \\
          -v /var/run/docker.sock:/var/run/docker.sock \\
          inst-cli -c "mkdir -p $HOME/.inst-cli && cp -R $HOME/template/. $HOME/.inst-cli && cp -R /root/.ssh_template /root/.ssh && sed -i '/UseKeychain/d' /root/.ssh/config && inst $*"
      fi

      if [ -z "$1" ]; then
        echo "Extra Commands:"
        echo "  inst canvas singledeploy # Enable single deployment in the same machine"
        echo "  inst canvas multideploy  # Enable multiple deployments in the same machine"
        echo "  inst-functions           # Prints the version of the inst-functions script"
        echo "  inst-canvas-docker       # Starts a shell inside the inst-cli Docker container with the Canvas volumes and environment configured"
        echo "  inst-journey-docker      # Starts a shell inside the inst-cli Docker container with the Journey volumes and environment configured"
        echo "  inst-horizon-docker      # Starts a shell inside the inst-cli Docker container with the Horizon volumes and environment configured"
        echo "  inst journey setup       # Create a new Canvas Career Journey Docker environment"
      fi

      ;;
  esac
  update-frpc
}
`;

/**
 * Update shell configuration files to source ~/.inst_functions
 */
async function updateShellConfig(sendOutput: (msg: string) => void): Promise<void> {
  const instFunctionsPath = join(homedir(), '.inst_functions');
  const sourceeLine = '[[ -f ~/.inst_functions ]] && source ~/.inst_functions';

  // Create ~/.inst_functions file
  sendOutput('\nCreating ~/.inst_functions file...\n');
  try {
    await fs.writeFile(instFunctionsPath, INST_FUNCTIONS_CONTENT, 'utf-8');
    sendOutput('✓ Created ~/.inst_functions\n');
  } catch (error: any) {
    sendOutput(`✗ Failed to create ~/.inst_functions: ${error.message}\n`);
    throw error;
  }

  // Update ~/.zshrc
  const zshrcPath = join(homedir(), '.zshrc');
  try {
    let zshrcContent = '';
    try {
      zshrcContent = await fs.readFile(zshrcPath, 'utf-8');
    } catch {
      // File doesn't exist, create it
      zshrcContent = '';
    }

    if (!zshrcContent.includes(sourceeLine)) {
      const updatedContent = zshrcContent + (zshrcContent.endsWith('\n') ? '' : '\n') + sourceeLine + '\n';
      await fs.writeFile(zshrcPath, updatedContent, 'utf-8');
      sendOutput('✓ Updated ~/.zshrc\n');
    } else {
      sendOutput('~/.zshrc already configured (skipping)\n');
    }
  } catch (error: any) {
    sendOutput(`Warning: Failed to update ~/.zshrc: ${error.message}\n`);
  }

  // Update ~/.bash_profile
  const bashProfilePath = join(homedir(), '.bash_profile');
  try {
    let bashProfileContent = '';
    try {
      bashProfileContent = await fs.readFile(bashProfilePath, 'utf-8');
    } catch {
      // File doesn't exist, create it
      bashProfileContent = '';
    }

    if (!bashProfileContent.includes(sourceeLine)) {
      const updatedContent = bashProfileContent + (bashProfileContent.endsWith('\n') ? '' : '\n') + sourceeLine + '\n';
      await fs.writeFile(bashProfilePath, updatedContent, 'utf-8');
      sendOutput('✓ Updated ~/.bash_profile\n');
    } else {
      sendOutput('~/.bash_profile already configured (skipping)\n');
    }
  } catch (error: any) {
    sendOutput(`Warning: Failed to update ~/.bash_profile: ${error.message}\n`);
  }
}

/**
 * Check if inst-cli Docker image exists
 */
/**
 * Get latest available Claude Code version from npm registry
 */
async function getLatestClaudeVersion(): Promise<string | undefined> {
  try {
    const { stdout } = await execAsync('npm view @anthropic-ai/claude-code version', { timeout: 10000 });
    const version = stdout.trim();
    log.info('Latest Claude Code version:', version);
    return version;
  } catch (error: any) {
    log.error('Failed to get latest Claude Code version:', error);
    return undefined;
  }
}

/**
 * Get latest available gem versions using gem list
 */
async function getLatestGemVersions(): Promise<{ inst?: string; instCliQuizzes?: string }> {
  try {
    const { stdout } = await execAsync('gem list "^inst" -r', { timeout: 10000 });

    const versions: { inst?: string; instCliQuizzes?: string } = {};

    const lines = stdout.trim().split('\n');
    for (const line of lines) {
      if (line.startsWith('inst (')) {
        const match = line.match(/inst \(([^)]+)\)/);
        if (match) {
          // Extract first version (latest)
          versions.inst = match[1].split(',')[0].trim();
        }
      } else if (line.startsWith('inst-cli-quizzes (')) {
        const match = line.match(/inst-cli-quizzes \(([^)]+)\)/);
        if (match) {
          // Extract first version (latest)
          versions.instCliQuizzes = match[1].split(',')[0].trim();
        }
      }
    }

    log.info('Latest gem versions:', versions);
    return versions;
  } catch (error: any) {
    log.error('Failed to get latest gem versions:', error);
    return {};
  }
}

/**
 * Compare two version strings
 * Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
 */
function compareVersions(v1: string, v2: string): number {
  const v1Parts = v1.split('.').map(Number);
  const v2Parts = v2.split('.').map(Number);

  const maxLength = Math.max(v1Parts.length, v2Parts.length);

  for (let i = 0; i < maxLength; i++) {
    const part1 = v1Parts[i] || 0;
    const part2 = v2Parts[i] || 0;

    if (part1 > part2) return 1;
    if (part1 < part2) return -1;
  }

  return 0;
}

/**
 * Get inst-functions version by running the shell function
 */
async function getInstFunctionsVersion(): Promise<string | undefined> {
  try {
    const instFunctionsPath = join(homedir(), '.inst_functions');

    // Check if file exists
    try {
      await fs.access(instFunctionsPath);
    } catch {
      log.warn('.inst_functions file does not exist yet');
      return undefined;
    }

    // Source the file and run inst-functions command
    const { stdout } = await execAsync(
      `source ${instFunctionsPath} && inst-functions`,
      { timeout: 5000, shell: '/bin/bash' }
    );

    const version = stdout.trim();
    log.info('inst-functions version:', version);
    return version;
  } catch (error: any) {
    log.error('Failed to get inst-functions version:', error);
    return undefined;
  }
}

/**
 * Get version information from inst-cli image
 */
async function getInstCliVersions(): Promise<{ inst?: string; instCliQuizzes?: string; ruby?: string; pnpm?: string; instFunctions?: string; claude?: string }> {
  try {
    const { stdout } = await execAsync(
      'docker run --rm inst-cli /bin/bash -c \'gem list | grep "^inst" && ruby -v && echo "pnpm ("$(pnpm -v)")" && claude --version 2>/dev/null || true\'',
      { timeout: 10000 }
    );

    const versions: { inst?: string; instCliQuizzes?: string; ruby?: string; pnpm?: string; instFunctions?: string; claude?: string } = {};

    const lines = stdout.trim().split('\n');
    for (const line of lines) {
      if (line.startsWith('inst (')) {
        const match = line.match(/inst \(([^)]+)\)/);
        if (match) versions.inst = match[1];
      } else if (line.startsWith('inst-cli-quizzes (')) {
        const match = line.match(/inst-cli-quizzes \(([^)]+)\)/);
        if (match) versions.instCliQuizzes = match[1];
      } else if (line.includes('ruby ')) {
        const match = line.match(/ruby ([\d.]+)/);
        if (match) versions.ruby = match[1];
      } else if (line.includes('pnpm ')) {
        const match = line.match(/pnpm \(([^)]+)\)/);
        if (match) versions.pnpm = match[1];
      } else if (line.toLowerCase().includes('claude')) {
        const match = line.match(/([\d]+\.[\d]+\.[\d]+)/);
        if (match) versions.claude = match[1];
      }
    }

    // Get inst-functions version separately since it's a shell function
    versions.instFunctions = await getInstFunctionsVersion();

    log.info('Parsed versions:', versions);
    return versions;
  } catch (error: any) {
    log.error('Failed to get inst-cli versions:', error);
    return {};
  }
}

export async function checkInstCliStatus(): Promise<InstCliStatus> {
  try {
    const { stdout } = await execAsync('docker images inst-cli --format "{{.ID}}|{{.CreatedAt}}"');

    if (stdout.trim()) {
      const [imageId, createdAt] = stdout.trim().split('|');

      // Get version information from the Docker image
      const versions = await getInstCliVersions();

      // Get latest available versions
      const [latestGemVersions, latestClaudeVersion] = await Promise.all([
        getLatestGemVersions(),
        getLatestClaudeVersion()
      ]);

      const latestVersions = { ...latestGemVersions, claude: latestClaudeVersion };

      // Check if updates are available
      let instUpdateAvailable = false;
      let instCliQuizzesUpdateAvailable = false;
      let claudeUpdateAvailable = false;

      if (versions.inst && latestVersions.inst) {
        instUpdateAvailable = compareVersions(latestVersions.inst, versions.inst) > 0;
      }

      if (versions.instCliQuizzes && latestVersions.instCliQuizzes) {
        instCliQuizzesUpdateAvailable = compareVersions(latestVersions.instCliQuizzes, versions.instCliQuizzes) > 0;
      }

      if (versions.claude && latestVersions.claude) {
        claudeUpdateAvailable = compareVersions(latestVersions.claude, versions.claude) > 0;
      }

      const updateAvailable = instUpdateAvailable || instCliQuizzesUpdateAvailable || claudeUpdateAvailable;

      const status = {
        imageExists: true,
        imageId,
        imageCreated: createdAt,
        versions,
        latestVersions,
        updateAvailable,
        instUpdateAvailable,
        instCliQuizzesUpdateAvailable,
        claudeUpdateAvailable
      };

      log.info('Returning InstCliStatus:', JSON.stringify(status));
      return status;
    }

    return { imageExists: false };
  } catch (error: any) {
    log.error('Failed to check inst-cli status:', error);
    return { imageExists: false };
  }
}

/**
 * Build inst-cli Docker image with streaming output
 */
export async function buildInstCliImage(window: BrowserWindow): Promise<{ success: boolean; message: string }> {
  let buildDir: string | null = null;

  try {
    // Create temporary build directory
    buildDir = join(tmpdir(), `inst-cli-build-${Date.now()}`);
    await fs.mkdir(buildDir, { recursive: true });
    log.info('Created build directory:', buildDir);

    // Send initial output
    window.webContents.send('inst-cli:build-output', 'Creating build context...\n');

    // Write Dockerfile with dynamic username
    const dockerfilePath = join(buildDir, 'Dockerfile');
    const dockerfileContent = generateDockerfileContent();
    await fs.writeFile(dockerfilePath, dockerfileContent, 'utf-8');
    log.info('Created Dockerfile with username:', userInfo().username);
    window.webContents.send('inst-cli:build-output', 'Created Dockerfile\n');

    // Write enable-canvas-multi-deployments.sh script
    const enableCanvasScriptPath = join(buildDir, 'enable-canvas-multi-deployments.sh');
    const enableCanvasScriptContent = String.raw`#!/bin/bash

INST_CONFIG="$HOME/.inst-cli/inst.yml"
FRP_CONFIG="$HOME/frp/frpc.toml"
FRP_SERVICE="com.fatedier.frpc"
if [[ -f "$INST_CONFIG" ]] && [[ -f "$FRP_CONFIG" ]]; then
    INST_DOMAIN=$(grep 'inst_domain:' "$INST_CONFIG" | awk '{print $2}')
    if [[ -n "$INST_DOMAIN" ]]; then
      DOMAINS_LIST=$( {
        docker ps -a --filter "name=.*canvas-" --format "{{.Labels}}" | tr ',' '\n' | grep "traefik\.http\..*\.service" | cut -d'=' -f2
        docker ps -a --filter "name=.*canvas-" --format "{{.Names}}"
        echo "dashboard"
      } | sort -u | sed "s/$/.$INST_DOMAIN/" | sed 's/.*/"&"/' | paste -sd "," - )
      if launchctl list | grep -q "$FRP_SERVICE"; then
          launchctl stop "$FRP_SERVICE"
          sed -i '' "s/customDomains = \[.*\]/customDomains = [$DOMAINS_LIST]/" "$FRP_CONFIG"
          launchctl start "$FRP_SERVICE"
      fi
    fi
fi

# Ensure yq is installed
if ! command -v yq &> /dev/null; then
    echo "Error: 'yq' is not installed."
    exit 1
fi

CONFIG_FILE="$HOME/.inst-cli/inst.yml"

# 1. Extract values
SOURCE_PATH=$(yq -r '.inst["canvas-lms"].source' "$CONFIG_FILE")
INST_DOMAIN_VAL=$(yq -r '.inst.inst_domain' "$CONFIG_FILE")
FOLDER_NAME=$(basename "$SOURCE_PATH")
CANVAS_LMS_ADMIN_EMAIL=$(yq -r '.inst["canvas-lms"].environment.CANVAS_LMS_ADMIN_EMAIL // ""' "$CONFIG_FILE")
CANVAS_LMS_ADMIN_PASSWORD=$(yq -r '.inst["canvas-lms"].environment.CANVAS_LMS_ADMIN_PASSWORD // ""' "$CONFIG_FILE")

[ ! -f "$SOURCE_PATH/docker-compose.local.dev.yml" ] && cp "$SOURCE_PATH/inst-cli/docker-compose/docker-compose.local.dev.yml" "$SOURCE_PATH/docker-compose.local.dev.yml"

# 2. Generate .env file
cat <<EOF > "$SOURCE_PATH/.env"
COMPOSE_FILE=docker-compose.local.dev.yml:docker-compose.local.dev.overrides.yml

# Custom domain for all Inst services
INST_DOMAIN=$INST_DOMAIN_VAL

ADDITIONAL_ALLOWED_HOSTS=".$INST_DOMAIN_VAL"
EOF

# 3. Generate docker-compose overrides
# Note: $FOLDER_NAME is unescaped (will be replaced)
# Note: \$INST_DOMAIN is escaped (will remain literal in the file)
cat <<EOF > "$SOURCE_PATH/docker-compose.local.dev.overrides.yml"
name: canvas-lms-$FOLDER_NAME

x-environment: &OVERRIDE-ENV
  CANVAS_DATABASE_HOST: canvas-postgres-$FOLDER_NAME
  CANVAS_REDIS_HOST: canvas-redis-$FOLDER_NAME
  CANVAS_DEV_DOMAIN: canvas-web-$FOLDER_NAME.\$INST_DOMAIN
  CANVAS_PROD_DOMAIN: canvas-web-$FOLDER_NAME.\$INST_DOMAIN
  RCE_HOST: canvas-rce-api-web-$FOLDER_NAME.\$INST_DOMAIN

services:
  web:
    container_name: canvas-web-$FOLDER_NAME
    environment:
      <<: *OVERRIDE-ENV
      HTTPS_METHOD: noredirect
      VIRTUAL_HOST: .canvas-web-$FOLDER_NAME.\$INST_DOMAIN
    ports: !reset
      - 80
      - 7681
      - 12345-12354
    networks:
      default: !override
        aliases:
          - canvas-web-$FOLDER_NAME
          - canvas-web-$FOLDER_NAME.\$INST_DOMAIN
    user: root
    command: >
      sh -c "apt-get update &&
      apt-get install -y ttyd && 
      /usr/bin/ttyd -c $CANVAS_LMS_ADMIN_EMAIL:$CANVAS_LMS_ADMIN_PASSWORD --writable /bin/bash &
      chown -R docker:docker . >/dev/null 2>&1 || true &&
      exec runuser -u docker -- /usr/src/entrypoint"
    labels: !override
      - traefik.enable=true
      # --- Service 1: Web (Port 80) ---
      - "traefik.http.routers.canvas-web-$FOLDER_NAME.rule=Host(\`canvas-web-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-web-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-web-$FOLDER_NAME.service=canvas-web-$FOLDER_NAME"
      - "traefik.http.services.canvas-web-$FOLDER_NAME.loadbalancer.server.port=80"
      # --- Service 2: Terminal/API (Port 7681) ---
      - "traefik.http.routers.canvas-web-term-$FOLDER_NAME.rule=Host(\`canvas-web-term-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-web-term-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-web-term-$FOLDER_NAME.service=canvas-web-term-$FOLDER_NAME"
      - "traefik.http.services.canvas-web-term-$FOLDER_NAME.loadbalancer.server.port=7681"

  web-dev-logs:
    container_name: canvas-web-dev-logs-$FOLDER_NAME
    environment:
      <<: *OVERRIDE-ENV
    ports:
      - 7681
    user: root
    command: >
      sh -c "apt-get update &&
      apt-get install -y ttyd && 
      /usr/bin/ttyd -c $CANVAS_LMS_ADMIN_EMAIL:$CANVAS_LMS_ADMIN_PASSWORD --writable /bin/bash & 
      exec runuser -u docker -- tail -f log/development.log"
    labels: !override
      - traefik.enable=true
      # --- Service 1: Web (Port 80) ---
      - "traefik.http.routers.canvas-web-dev-logs-term-$FOLDER_NAME.rule=Host(\`canvas-web-dev-logs-term-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-web-dev-logs-term-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-web-dev-logs-term-$FOLDER_NAME.service=canvas-web-dev-logs-term-$FOLDER_NAME"
      - "traefik.http.services.canvas-web-dev-logs-term-$FOLDER_NAME.loadbalancer.server.port=7681"

  jobs:
    container_name: canvas-jobs-$FOLDER_NAME
    environment:
      <<: *OVERRIDE-ENV
    ports:
      - 7681
    user: root
    command: >
      sh -c "apt-get update &&
      apt-get install -y ttyd && 
      /usr/bin/ttyd -c $CANVAS_LMS_ADMIN_EMAIL:$CANVAS_LMS_ADMIN_PASSWORD --writable /bin/bash & 
      exec runuser -u docker -- bundle exec script/delayed_job run"
    networks:
      default: !override
        aliases:
          - canvas-jobs-$FOLDER_NAME
    labels: !override
      - traefik.enable=true
      # --- Service 1: Web (Port 80) ---
      - "traefik.http.routers.canvas-jobs-term-$FOLDER_NAME.rule=Host(\`canvas-jobs-term-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-jobs-term-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-jobs-term-$FOLDER_NAME.service=canvas-jobs-term-$FOLDER_NAME"
      - "traefik.http.services.canvas-jobs-term-$FOLDER_NAME.loadbalancer.server.port=7681"

  webpack:
    container_name: canvas-webpack-$FOLDER_NAME
    environment:
      <<: *OVERRIDE-ENV
    ports:
      - 7681
    user: root
    command: >
      sh -c "apt-get update &&
      apt-get install -y ttyd && 
      /usr/bin/ttyd -c $CANVAS_LMS_ADMIN_EMAIL:$CANVAS_LMS_ADMIN_PASSWORD --writable /bin/bash & 
      exec runuser -u docker -- yarn run webpack"
    networks:
      default: !override
        aliases:
          - canvas-webpack-$FOLDER_NAME
    labels: !override
      - traefik.enable=true
      # --- Service 1: Web (Port 80) ---
      - "traefik.http.routers.canvas-webpack-term-$FOLDER_NAME.rule=Host(\`canvas-webpack-term-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-webpack-term-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-webpack-term-$FOLDER_NAME.service=canvas-webpack-term-$FOLDER_NAME"
      - "traefik.http.services.canvas-webpack-term-$FOLDER_NAME.loadbalancer.server.port=7681"

  postgres:
    container_name: canvas-postgres-$FOLDER_NAME
    networks:
      default: !override
        aliases:
          - canvas-postgres-$FOLDER_NAME

  redis:
    container_name: canvas-redis-$FOLDER_NAME
    ports:
      - 7681
    networks:
      default: !override
        aliases:
          - canvas-redis-$FOLDER_NAME
    user: root
    command: >
      sh -c "apk add --no-cache ttyd bash &&
      /usr/bin/ttyd -c $CANVAS_LMS_ADMIN_EMAIL:$CANVAS_LMS_ADMIN_PASSWORD --writable /bin/bash & 
      redis-server"
    labels: !override
      - traefik.enable=true
      # --- Service 1: Terminal/API (Port 7681) ---
      - "traefik.http.routers.canvas-redis-term-$FOLDER_NAME.rule=Host(\`canvas-redis-term-$FOLDER_NAME.\$INST_DOMAIN\`)"
      - "traefik.http.routers.canvas-redis-term-$FOLDER_NAME.entrypoints=web"
      - "traefik.http.routers.canvas-redis-term-$FOLDER_NAME.service=canvas-redis-term-$FOLDER_NAME"
      - "traefik.http.services.canvas-redis-term-$FOLDER_NAME.loadbalancer.server.port=7681"

  githook-installer:
    container_name: canvas-githook-installer-$FOLDER_NAME
    networks:
      default: !override
        aliases:
          - canvas-githook-installer-$FOLDER_NAME

  pgadmin:
      image: dpage/pgadmin4
      container_name: canvas-pgadmin-$FOLDER_NAME
      environment:
        PGADMIN_DEFAULT_EMAIL: $CANVAS_LMS_ADMIN_EMAIL
        PGADMIN_DEFAULT_PASSWORD: $CANVAS_LMS_ADMIN_PASSWORD
        PGADMIN_CONFIG_ENABLE_PSQL: "True"
        PGPASSFILE: /config/pgpass
      configs:
        - source: pgadmin-pgpass
          target: /config/pgpass
          uid: "5050"
          gid: "5050"
          mode: 0400
        - source: pgadmin-servers
          target: /pgadmin4/servers.json
          mode: 0444
      labels:
        - traefik.enable=true
      ports:
        - 80
      depends_on:
        - postgres
      networks:
        default:
          aliases:
            - canvas-pgadmin-$FOLDER_NAME

configs:
  pgadmin-servers:
    content: |
      {
        "Servers": {
          "1": {
            "Name": "canvas-postgres-$FOLDER_NAME",
            "Group": "Servers",
            "Host": "canvas-postgres-$FOLDER_NAME",
            "Port": 5432,
            "MaintenanceDB": "postgres",
            "Username": "postgres",
            "PassFile": "/config/pgpass",
            "SSLMode": "prefer"
          }
        }
      }
  pgadmin-pgpass:
    content: |
      canvas-postgres-$FOLDER_NAME:5432:*:postgres:sekret
EOF
echo "Multiple deployments enabled"
`;
    await fs.writeFile(enableCanvasScriptPath, enableCanvasScriptContent, 'utf-8');
    log.info('Created enable-canvas-multi-deployments.sh');
    window.webContents.send('inst-cli:build-output', 'Created enable-canvas-multi-deployments.sh\n');

    // Write disable-canvas-multi-deployments.sh script
    const disableCanvasScriptPath = join(buildDir, 'disable-canvas-multi-deployments.sh');
    const disableCanvasScriptContent = String.raw`#!/bin/bash

# Ensure yq is installed
if ! command -v yq &> /dev/null; then
    echo "Error: 'yq' is not installed."
    exit 1
fi

CONFIG_FILE="$HOME/.inst-cli/inst.yml"

SOURCE_PATH=$(yq -r '.inst["canvas-lms"].source' "$CONFIG_FILE")
INST_DOMAIN_VAL=$(yq -r '.inst.inst_domain' "$CONFIG_FILE")

[ ! -f "$SOURCE_PATH/docker-compose.local.dev.yml" ] && cp "$SOURCE_PATH/inst-cli/docker-compose/docker-compose.local.dev.yml" "$SOURCE_PATH/docker-compose.local.dev.yml"

cat <<EOF > "$SOURCE_PATH/.env"
COMPOSE_FILE=docker-compose.local.dev.yml

# Custom domain for all Inst services
INST_DOMAIN=$INST_DOMAIN_VAL
EOF
echo "Multiple deployments disabled"
`;
    await fs.writeFile(disableCanvasScriptPath, disableCanvasScriptContent, 'utf-8');
    log.info('Created disable-canvas-multi-deployments.sh');
    window.webContents.send('inst-cli:build-output', 'Created disable-canvas-multi-deployments.sh\n');

    // Copy .gemrc file
    const gemrcSource = join(homedir(), '.gemrc');
    const gemrcDest = join(buildDir, '.gemrc');

    try {
      await fs.copyFile(gemrcSource, gemrcDest);
      log.info('Copied .gemrc to build context');
      window.webContents.send('inst-cli:build-output', 'Copied .gemrc to build context\n');
    } catch (error: any) {
      if (error.code === 'ENOENT') {
        window.webContents.send('inst-cli:build-output', 'ERROR: .gemrc file not found\n');
        return {
          success: false,
          message: '.gemrc file not found. Please configure Artifactory first.'
        };
      }
      throw error;
    }

    // Copy .npmrc file
    const npmrcSource = join(homedir(), '.npmrc');
    const npmrcDest = join(buildDir, '.npmrc');

    try {
      await fs.copyFile(npmrcSource, npmrcDest);
      log.info('Copied .npmrc to build context');
      window.webContents.send('inst-cli:build-output', 'Copied .npmrc to build context\n');
    } catch (error: any) {
      if (error.code === 'ENOENT') {
        window.webContents.send('inst-cli:build-output', 'ERROR: .npmrc file not found\n');
        return {
          success: false,
          message: '.npmrc file not found. Please configure Artifactory first.'
        };
      }
      throw error;
    }

    // Uninstall existing gems before building
    window.webContents.send('inst-cli:build-output', '\nUninstalling existing gems...\n');

    // Uninstall inst-cli-quizzes
    try {
      window.webContents.send('inst-cli:build-output', 'Running: gem uninstall inst-cli-quizzes\n');
      await execAsync('gem uninstall inst-cli-quizzes -a -x');
      window.webContents.send('inst-cli:build-output', '✓ inst-cli-quizzes uninstalled\n');
    } catch (error: any) {
      // Ignore if gem is not installed
      if (error.message?.includes('not installed')) {
        window.webContents.send('inst-cli:build-output', 'inst-cli-quizzes not installed (skipping)\n');
      } else {
        window.webContents.send('inst-cli:build-output', `Warning: ${error.message}\n`);
      }
    }

    // Uninstall inst (with automatic Yes response)
    try {
      window.webContents.send('inst-cli:build-output', 'Running: gem uninstall inst\n');
      await execAsync('gem uninstall inst -a -x');
      window.webContents.send('inst-cli:build-output', '✓ inst uninstalled\n');
    } catch (error: any) {
      // Ignore if gem is not installed
      if (error.message?.includes('not installed')) {
        window.webContents.send('inst-cli:build-output', 'inst not installed (skipping)\n');
      } else {
        window.webContents.send('inst-cli:build-output', `Warning: ${error.message}\n`);
      }
    }

    // Build Docker image with streaming output
    log.info('Starting Docker build...');
    window.webContents.send('inst-cli:build-output', '\nStarting Docker build...\n\n');

    return new Promise((resolve) => {
      const dockerBuild = spawn('docker', ['build', '--no-cache', '-t', 'inst-cli', '.'], {
        cwd: buildDir!,
        shell: true,
        env: getEnvWithPath()
      });

      dockerBuild.stdout.on('data', (data) => {
        const output = data.toString();
        log.info('Docker build stdout:', output);
        window.webContents.send('inst-cli:build-output', output);
      });

      dockerBuild.stderr.on('data', (data) => {
        const output = data.toString();
        log.info('Docker build stderr:', output);
        window.webContents.send('inst-cli:build-output', output);
      });

      dockerBuild.on('close', async (code) => {
        // Clean up build directory
        if (buildDir) {
          try {
            await fs.rm(buildDir, { recursive: true, force: true });
            log.info('Cleaned up build directory');
          } catch (error) {
            log.warn('Failed to clean up build directory:', error);
          }
        }

        if (code === 0) {
          log.info('Docker build completed successfully');
          window.webContents.send('inst-cli:build-output', '\n✓ Build completed successfully!\n');

          // Update shell configuration files
          try {
            const sendOutput = createOutputSender(window, 'inst-cli:build-output');
            await updateShellConfig(sendOutput);
            window.webContents.send('inst-cli:build-output', '\n✓ Shell configuration updated!\n');
          } catch (error: any) {
            log.error('Failed to update shell config:', error);
            window.webContents.send('inst-cli:build-output', `\nWarning: Failed to update shell config: ${error.message}\n`);
          }

          resolve({
            success: true,
            message: 'inst-cli Docker image built successfully'
          });
        } else {
          log.error('Docker build failed with code:', code);
          window.webContents.send('inst-cli:build-output', `\n✗ Build failed with exit code ${code}\n`);
          resolve({
            success: false,
            message: `Docker build failed with exit code ${code}`
          });
        }
      });

      dockerBuild.on('error', async (error) => {
        log.error('Docker build error:', error);
        window.webContents.send('inst-cli:build-output', `\nError: ${error.message}\n`);

        // Clean up build directory
        if (buildDir) {
          try {
            await fs.rm(buildDir, { recursive: true, force: true });
            log.info('Cleaned up build directory');
          } catch (err) {
            log.warn('Failed to clean up build directory:', err);
          }
        }

        resolve({
          success: false,
          message: error.message || 'Failed to start Docker build'
        });
      });
    });
  } catch (error: any) {
    log.error('Failed to build inst-cli image:', error);

    // Clean up build directory
    if (buildDir) {
      try {
        await fs.rm(buildDir, { recursive: true, force: true });
        log.info('Cleaned up build directory');
      } catch (err) {
        log.warn('Failed to clean up build directory:', err);
      }
    }

    return {
      success: false,
      message: error.message || 'Failed to build Docker image'
    };
  }
}
