/**
 * Install a `mindos` shell command that delegates to the bundled (or dev) CLI.
 * Does not use npm global install — writes ~/.mindos/bin/mindos and optionally
 * appends PATH to ~/.zshrc (etc.).
 */
import { app, dialog, BrowserWindow, Notification } from 'electron';
import path from 'path';
import {
  mkdirSync,
  writeFileSync,
  chmodSync,
  existsSync,
  readFileSync,
  appendFileSync,
} from 'fs';
import { getDefaultBundledMindOsDirectory } from './mindos-runtime-path';
import { resolveCliPath } from './mindos-runtime-layout';
import { getDesktopConfigDir, getDesktopHome } from './desktop-home';
import semver from 'semver';

const SHELL_MARKER = '# MindOS Desktop — CLI (mindos)';

function shimDir(): string {
  return path.join(getDesktopConfigDir(), 'bin');
}

function shimPath(): string {
  return path.join(shimDir(), process.platform === 'win32' ? 'mindos.cmd' : 'mindos');
}

/** Git Bash / MSYS entry point on Windows — bash ignores PATHEXT, so `mindos` needs a real sh script. */
function shShimPath(): string {
  return path.join(shimDir(), 'mindos');
}

/**
 * Resolve the best CLI entry point: prefer cached runtime (Core Hot Update)
 * over bundled if it has a newer version.
 */
function resolveCliJs(): string | null {
  const bundledRoot = getDefaultBundledMindOsDirectory();
  let bundledCli: string | null = null;
  let bundledVersion: string | null = null;
  if (bundledRoot) {
    const p = resolveCliPath(bundledRoot);
    if (existsSync(p)) {
      bundledCli = p;
      try {
        const pkg = JSON.parse(readFileSync(path.join(bundledRoot, 'package.json'), 'utf-8'));
        bundledVersion = typeof pkg.version === 'string' ? pkg.version : null;
      } catch { /* ignore */ }
    }
  }

  // Check cached runtime (installed by Core Hot Update)
  const cachedRoot = path.join(getDesktopConfigDir(), 'runtime');
  const cachedCli = path.join(cachedRoot, 'bin', 'cli.js');
  if (existsSync(cachedCli)) {
    let cachedVersion: string | null = null;
    try {
      const pkg = JSON.parse(readFileSync(path.join(cachedRoot, 'package.json'), 'utf-8'));
      cachedVersion = typeof pkg.version === 'string' ? pkg.version : null;
    } catch { /* ignore */ }

    // Prefer cached if it has a newer version than bundled
    if (cachedVersion && semver.valid(cachedVersion)) {
      if (!bundledVersion || !semver.valid(bundledVersion) || semver.gt(cachedVersion, bundledVersion)) {
        return cachedCli;
      }
    }
  }

  if (bundledCli) return bundledCli;

  if (!app.isPackaged) {
    const devRoot = path.resolve(app.getAppPath(), '..');
    const dev = resolveCliPath(devRoot);
    return existsSync(dev) ? dev : null;
  }
  return null;
}

/** Safe single-quoted literal for POSIX sh. */
function shellSingleQuote(s: string): string {
  return `'${s.replace(/'/g, `'\"'\"'`)}'`;
}

function writeUnixShim(cliJs: string): void {
  const cliLit = shellSingleQuote(cliJs);
  const script = `#!/bin/sh
# Generated by MindOS Desktop — overwritten on each app launch.

# Guard against deleted working directory (e.g. AI Agent in temp dir).
# Only cd away when the cwd is actually gone — an unconditional cd would
# break relative-path arguments like \`mindos import ./notes.md\`.
if ! pwd >/dev/null 2>&1; then cd "$HOME" 2>/dev/null || true; fi

CLI=${cliLit}

# If target CLI is missing (e.g. Desktop .app deleted), try npm global fallback
if [ ! -f "$CLI" ]; then
  NPM_ROOT=\$(npm root -g 2>/dev/null)
  NPM_CLI="\${NPM_ROOT}/@geminilight/mindos/bin/cli.js"
  if [ -f "$NPM_CLI" ]; then
    CLI="$NPM_CLI"
  else
    echo "mindos: MindOS Desktop has been removed and no npm global install found." >&2
    echo "" >&2
    echo "  To reinstall CLI:      npm install -g @geminilight/mindos" >&2
    echo "  To clean up residuals:  bash ~/.mindos/uninstall.sh" >&2
    echo "  To reinstall Desktop:   https://mindos.app/download" >&2
    exit 127
  fi
fi

if [ -x "$HOME/.mindos/node/bin/node" ]; then
  exec "$HOME/.mindos/node/bin/node" "$CLI" "$@"
fi
if command -v node >/dev/null 2>&1; then
  exec node "$CLI" "$@"
fi
echo "mindos: Node.js not found. Open MindOS Desktop once (it installs Node under ~/.mindos/node), or install Node 18+." >&2
exit 127
`;
  const out = shimPath();
  mkdirSync(shimDir(), { recursive: true });
  writeFileSync(out, script, 'utf-8');
  try {
    chmodSync(out, 0o755);
  } catch {
    /* ignore */
  }
}

/**
 * Escape a value for use inside a quoted `set "VAR=value"` with delayed
 * expansion DISABLED: only `%` needs doubling (parse-time expansion); `^` and
 * `!` are literal inside quotes. Double quotes cannot be represented safely.
 */
export function escapeCmdSetValue(value: string): string {
  if (value.includes('"')) {
    throw new Error('Invalid Windows path for cmd script: double quote is not allowed');
  }
  return value.replace(/%/g, '%%');
}

/**
 * Express a path under the user's home as %USERPROFILE%-relative.
 *
 * cmd.exe parses batch files in the OEM/ANSI codepage (CP936 etc.), not the
 * UTF-8 we write — a literal `C:\Users\张三\...` would be misread and every
 * `if exist` check would fail. %USERPROFILE% keeps the file pure ASCII for
 * home-relative paths (which covers ~/.mindos and per-user NSIS installs).
 * Non-home paths (dev repos, per-machine installs) stay absolute: still
 * correct for ASCII paths, and the known residual risk for non-ASCII ones.
 */
export function toCmdUserProfilePath(p: string, home: string): string {
  const norm = path.win32.normalize(p);
  const homeNorm = path.win32.normalize(home).replace(/[\\/]+$/, '');
  if (norm.toLowerCase().startsWith(`${homeNorm.toLowerCase()}\\`)) {
    return `%USERPROFILE%${escapeCmdSetValue(norm.slice(homeNorm.length))}`;
  }
  return escapeCmdSetValue(norm);
}

/** Same idea for the Git Bash shim: "$HOME" + forward-slash remainder. */
export function toShUserProfilePath(p: string, home: string): string {
  const norm = path.win32.normalize(p);
  const homeNorm = path.win32.normalize(home).replace(/[\\/]+$/, '');
  if (norm.toLowerCase().startsWith(`${homeNorm.toLowerCase()}\\`)) {
    return `"$HOME"${shellSingleQuote(norm.slice(homeNorm.length).replace(/\\/g, '/'))}`;
  }
  return shellSingleQuote(norm.replace(/\\/g, '/'));
}

export function buildWindowsCmdShimScript(cliJs: string, home: string): string {
  const cliRef = toCmdUserProfilePath(cliJs, home);
  const nodeRef = toCmdUserProfilePath(path.win32.join(path.win32.normalize(home), '.mindos', 'node', 'node.exe'), home);
  return [
    '@echo off',
    'rem Generated by MindOS Desktop - overwritten on each app launch.',
    'rem Delayed expansion stays DISABLED: with it enabled, the `!`/`^` in user',
    'rem arguments (%*) are eaten by the delayed-expansion phase.',
    'setlocal disabledelayedexpansion',
    `set "CLI=${cliRef}"`,
    `set "NODE_PRIV=${nodeRef}"`,
    'if exist "%CLI%" goto have_cli',
    'for /f "delims=" %%i in (\'npm root -g 2^>nul\') do set "NPM_CLI=%%i\\@geminilight\\mindos\\bin\\cli.js"',
    'if not exist "%NPM_CLI%" goto no_cli',
    'set "CLI=%NPM_CLI%"',
    'goto have_cli',
    '',
    ':no_cli',
    'echo mindos: MindOS Desktop has been removed. Reinstall: npm install -g @geminilight/mindos >&2',
    'echo To clean up residuals: "%USERPROFILE%\\.mindos\\uninstall.bat" >&2',
    'exit /b 127',
    '',
    ':have_cli',
    'if not exist "%NODE_PRIV%" goto use_path_node',
    '"%NODE_PRIV%" "%CLI%" %*',
    'exit /b %errorlevel%',
    '',
    ':use_path_node',
    'where node >nul 2>&1 || (echo mindos: Node.js not found. Open MindOS Desktop or install Node 18+. >&2 & exit /b 127)',
    'node "%CLI%" %*',
    'exit /b %errorlevel%',
    '',
  ].join('\r\n');
}

export function buildWindowsShShimScript(cliJs: string, home: string): string {
  const cliRef = toShUserProfilePath(cliJs, home);
  const nodeRef = toShUserProfilePath(path.win32.join(path.win32.normalize(home), '.mindos', 'node', 'node.exe'), home);
  return `#!/bin/sh
# Generated by MindOS Desktop — Git Bash / MSYS entry point on Windows.
# bash does not use PATHEXT, so mindos.cmd alone is invisible to it.

if ! pwd >/dev/null 2>&1; then cd "$HOME" 2>/dev/null || true; fi

CLI=${cliRef}
NODE_PRIV=${nodeRef}

if [ ! -f "$CLI" ]; then
  NPM_ROOT=$(npm root -g 2>/dev/null)
  NPM_CLI="\${NPM_ROOT}/@geminilight/mindos/bin/cli.js"
  if [ -f "$NPM_CLI" ]; then
    CLI="$NPM_CLI"
  else
    echo "mindos: MindOS Desktop has been removed and no npm global install found." >&2
    echo "  To reinstall CLI: npm install -g @geminilight/mindos" >&2
    exit 127
  fi
fi

if [ -x "$NODE_PRIV" ]; then
  exec "$NODE_PRIV" "$CLI" "$@"
fi
if command -v node >/dev/null 2>&1; then
  exec node "$CLI" "$@"
fi
echo "mindos: Node.js not found. Open MindOS Desktop once, or install Node 18+." >&2
exit 127
`;
}

function writeWindowsShim(cliJs: string): void {
  const home = getDesktopHome();
  mkdirSync(shimDir(), { recursive: true });
  writeFileSync(shimPath(), buildWindowsCmdShimScript(cliJs, home), 'utf-8');
  // LF on purpose — sh scripts with CRLF break in Git Bash
  writeFileSync(shShimPath(), buildWindowsShShimScript(cliJs, home), 'utf-8');
  try { chmodSync(shShimPath(), 0o755); } catch { /* NTFS — Git Bash runs it via shebang regardless */ }
}

/** PATH entry written to the registry — %USERPROFILE% form survives profile moves and stays ASCII. */
const WINDOWS_PATH_ENTRY = '%USERPROFILE%\\.mindos\\bin';

/**
 * PowerShell that prepends ~/.mindos/bin to the user PATH via the raw
 * registry API. [Environment]::GetEnvironmentVariable expands REG_EXPAND_SZ
 * on read, so a naive read-modify-write permanently flattens entries like
 * %JAVA_HOME%\bin — DoNotExpandEnvironmentNames preserves them, ExpandString
 * keeps the value kind, and the explicit WM_SETTINGCHANGE broadcast replaces
 * the one SetEnvironmentVariable would have sent.
 */
export function buildWindowsPathRegistryScript(): string {
  return [
    `$bin = '${WINDOWS_PATH_ENTRY}'`,
    `$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')`,
    `$cur = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)`,
    `$binExpanded = [Environment]::ExpandEnvironmentVariables($bin).TrimEnd('\\')`,
    `$has = $false`,
    `foreach ($part in ($cur -split ';')) {`,
    `  if (-not $part) { continue }`,
    `  $norm = [Environment]::ExpandEnvironmentVariables($part).TrimEnd('\\')`,
    `  if ($norm -ieq $binExpanded) { $has = $true; break }`,
    `}`,
    `if ($has) { 'PRESENT'; exit 0 }`,
    `$new = if ($cur) { $bin + ';' + $cur } else { $bin }`,
    `$key.SetValue('Path', $new, [Microsoft.Win32.RegistryValueKind]::ExpandString)`,
    `Add-Type -Namespace MindOS -Name Native -MemberDefinition '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern System.IntPtr SendMessageTimeout(System.IntPtr hWnd, uint Msg, System.UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out System.UIntPtr lpdwResult);'`,
    `[System.UIntPtr]$result = [System.UIntPtr]::Zero`,
    `[MindOS.Native]::SendMessageTimeout([System.IntPtr]0xffff, 0x1A, [System.UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$result) | Out-Null`,
    `'CHANGED'`,
  ].join('\n');
}

/**
 * Append ~/.mindos/bin to user PATH on Windows via the raw registry API.
 * Returns true if PATH was modified.
 */
function appendMindosBinToWindowsPath(): boolean {
  try {
    const { execFileSync } = require('child_process');
    // -EncodedCommand sidesteps every cmd/PowerShell quoting pitfall
    const encoded = Buffer.from(buildWindowsPathRegistryScript(), 'utf16le').toString('base64');
    const out = String(execFileSync('powershell.exe', [
      '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', encoded,
    ], { encoding: 'utf-8', timeout: 10000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }));
    const changed = out.includes('CHANGED');
    if (changed) console.info(`[MindOS] Added ${WINDOWS_PATH_ENTRY} to Windows user PATH`);
    return changed;
  } catch (err) {
    console.warn('[MindOS] Could not update Windows PATH:', err instanceof Error ? err.message : err);
    return false;
  }
}

/**
 * Append ~/.mindos/bin to PATH in ALL relevant shell rc files (idempotent per file).
 *
 * AI Agents (Claude Code, Cursor, etc.) run commands via non-interactive shells:
 * - Non-interactive zsh reads ~/.zshenv (NOT ~/.zshrc)
 * - Non-interactive bash reads ~/.bashrc (some systems) or nothing
 * - Fish reads ~/.config/fish/conf.d/*.fish always
 *
 * We must write to ALL relevant files so the PATH is available everywhere.
 * Returns true if we appended to at least one file.
 */
/** True when an UNCOMMENTED line references .mindos/bin (sh and fish syntax alike). */
export function hasActiveMindosPathLine(content: string): boolean {
  return content.split('\n').some((line) => {
    const trimmed = line.trim();
    return !trimmed.startsWith('#') && trimmed.includes('.mindos/bin');
  });
}

function appendMindosBinToShellRc(): boolean {
  if (process.platform === 'win32') return appendMindosBinToWindowsPath();
  const home = getDesktopHome();
  const block = `\n${SHELL_MARKER}\nexport PATH="$HOME/.mindos/bin:$PATH"\n`;

  // Files to write, in order. "mustCreate" means create the file if it doesn't exist.
  interface RcTarget { name: string; mustCreate: boolean }

  const targets: RcTarget[] =
    process.platform === 'darwin'
      ? [
          { name: '.zshenv',       mustCreate: true },   // non-interactive zsh (AI Agents)
          { name: '.zshrc',        mustCreate: false },   // interactive zsh (user terminal)
          { name: '.bashrc',       mustCreate: true },    // non-interactive bash (AI Agents)
          { name: '.bash_profile', mustCreate: false },   // login bash
        ]
      : [
          { name: '.bashrc',  mustCreate: true },    // bash (interactive + non-interactive)
          { name: '.zshenv',  mustCreate: true },    // non-interactive zsh (AI Agents)
          { name: '.zshrc',   mustCreate: false },   // interactive zsh
          { name: '.profile', mustCreate: false },   // login shell fallback
        ];

  let appended = false;

  for (const { name, mustCreate } of targets) {
    const full = path.join(home, name);
    const fileExists = existsSync(full);

    // Skip non-existent files unless we must create them
    if (!fileExists && !mustCreate) continue;

    // Skip if already has our marker OR legacy CLI marker (idempotent).
    // A raw '.mindos/bin' mention only counts when it appears on an
    // uncommented line — a commented-out leftover must not block reinstall.
    if (fileExists) {
      try {
        const content = readFileSync(full, 'utf-8');
        if (content.includes(SHELL_MARKER) || content.includes('# MindOS CLI')
            || hasActiveMindosPathLine(content)) continue;
      } catch { continue; }
    }

    try {
      appendFileSync(full, block, 'utf-8');
      appended = true;
    } catch {
      // Best effort — don't fail the whole install for one file
    }
  }

  // Fish shell support: write fish-syntax config if fish config dir exists
  try {
    const fishConfD = path.join(home, '.config', 'fish', 'conf.d');
    if (existsSync(fishConfD)) {
      const fishFile = path.join(fishConfD, 'mindos.fish');
      const fishMarker = '# MindOS Desktop — CLI (mindos)';
      const fishBlock = `${fishMarker}\nif not contains "$HOME/.mindos/bin" $PATH\n  set -gx PATH "$HOME/.mindos/bin" $PATH\nend\n`;
      let needsWrite = !existsSync(fishFile);
      if (!needsWrite) {
        const fc = readFileSync(fishFile, 'utf-8');
        needsWrite = !fc.includes(fishMarker) && !fc.includes('# MindOS CLI') && !hasActiveMindosPathLine(fc);
      }
      if (needsWrite) {
        writeFileSync(fishFile, fishBlock, 'utf-8');
        appended = true;
      }
    }
  } catch { /* best effort */ }

  return appended;
}

export function buildWindowsUninstallScript(): string {
  return [
    '@echo off',
    'setlocal',
    'echo MindOS Cleanup',
    'echo.',
    'set "MINDOS_DIR=%USERPROFILE%\\.mindos"',
    '',
    'rem 1. Kill running MindOS processes',
    'for %%F in ("%MINDOS_DIR%\\mindos.pid" "%MINDOS_DIR%\\desktop-children.pid" "%MINDOS_DIR%\\ssh-tunnel.pid") do (',
    '  if exist "%%~F" (',
    '    for /f "usebackq delims=" %%P in ("%%~F") do (',
    '      for /f "usebackq delims=" %%C in (`powershell.exe -NoProfile -NonInteractive -Command "$p = Get-CimInstance Win32_Process -Filter \'ProcessId = %%P\' -ErrorAction SilentlyContinue; if ($p) { $p.CommandLine }" 2^>nul`) do (',
    '        echo %%C | findstr /I /C:"\\.mindos\\runtime\\" /C:"mindos-runtime" /C:"@geminilight\\mindos" /C:"\\packages\\web\\.next\\standalone\\server.js" /C:"\\dist\\protocols\\mcp-server\\index.cjs" >nul && taskkill /PID %%P /T /F >nul 2>nul',
    '      )',
    '    )',
    '    del /f /q "%%~F" >nul 2>nul',
    '  )',
    ')',
    '',
    'rem 2. Remove Desktop-managed runtimes and CLI shim. Knowledge base is NOT touched.',
    'for %%D in ("%MINDOS_DIR%\\node" "%MINDOS_DIR%\\runtime" "%MINDOS_DIR%\\runtime-downloading" "%MINDOS_DIR%\\runtime-old") do (',
    '  if exist "%%~D" rmdir /s /q "%%~D"',
    ')',
    'del /f /q "%MINDOS_DIR%\\runtime-download.tar.gz" >nul 2>nul',
    'if exist "%MINDOS_DIR%\\bin\\mindos.cmd" del /f /q "%MINDOS_DIR%\\bin\\mindos.cmd"',
    'if exist "%MINDOS_DIR%\\bin\\mindos" del /f /q "%MINDOS_DIR%\\bin\\mindos"',
    'if exist "%MINDOS_DIR%\\bin" rmdir "%MINDOS_DIR%\\bin" >nul 2>nul',
    '',
    'rem 3. Remove MindOS bin from the user PATH (raw registry read/write so',
    'rem    REG_EXPAND_SZ entries like %%JAVA_HOME%%\\bin are not flattened)',
    `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true); if ($key) { $cur = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames); $binNorm = (Join-Path $env:USERPROFILE '.mindos\\bin').TrimEnd('\\').ToLowerInvariant(); $parts = $cur -split ';' | Where-Object { $_ -and ([Environment]::ExpandEnvironmentVariables($_).TrimEnd('\\').ToLowerInvariant()) -ne $binNorm }; $key.SetValue('Path', ($parts -join ';'), [Microsoft.Win32.RegistryValueKind]::ExpandString) }" >nul 2>nul`,
    '',
    'rem 4. Remove Desktop config and state files. Knowledge base is NOT touched.',
    'del /f /q "%MINDOS_DIR%\\config.json" >nul 2>nul',
    'del /f /q "%MINDOS_DIR%\\stdin-watchdog.cjs" >nul 2>nul',
    'del /f /q "%MINDOS_DIR%\\desktop-cli-path-notify-v1" >nul 2>nul',
    'del /f /q "%MINDOS_DIR%\\update-status.json" >nul 2>nul',
    'if exist "%MINDOS_DIR%\\tmp" rmdir /s /q "%MINDOS_DIR%\\tmp"',
    'if exist "%MINDOS_DIR%\\sessions" rmdir /s /q "%MINDOS_DIR%\\sessions"',
    'if exist "%APPDATA%\\MindOS" rmdir /s /q "%APPDATA%\\MindOS"',
    'if exist "%LOCALAPPDATA%\\MindOS" rmdir /s /q "%LOCALAPPDATA%\\MindOS"',
    'if exist "%LOCALAPPDATA%\\com.geminilight.mindos" rmdir /s /q "%LOCALAPPDATA%\\com.geminilight.mindos"',
    '',
    'echo.',
    'echo Done. Your knowledge base was NOT touched.',
    'echo To also remove it: delete %USERPROFILE%\\MindOS\\mind',
    '',
    'rem 5. Self-remove — must be the LAST line: cmd reads batch files from disk',
    'rem line by line, so anything after a plain `del "%~f0"` never executes.',
    'rem The (goto) trick ends parsing while the rest of THIS line still runs.',
    '(goto) 2>nul & del /f /q "%~f0" >nul 2>nul & rmdir "%MINDOS_DIR%" >nul 2>nul',
    '',
  ].join('\r\n');
}

export function buildUnixUninstallScript(): string {
  return `#!/bin/sh
# MindOS Cleanup Script
# Generated by MindOS Desktop — cleans up residual files after app deletion.
# Your knowledge base (~/MindOS/mind) is NOT touched.

set -e
echo "MindOS Cleanup"
echo ""

is_mindos_cmd() {
  case "$1" in
    */.mindos/runtime/*|*mindos-runtime*|*node_modules/@geminilight/mindos/*|*@geminilight/mindos/*|*/packages/web/.next/standalone/server.js*|*/dist/protocols/mcp-server/index.cjs*)
      return 0
      ;;
  esac
  return 1
}

is_ssh_cmd() {
  comm="$1"
  case "$(basename "$comm")" in
    ssh|ssh.exe)
      return 0
      ;;
  esac
  return 1
}

terminate_pid() {
  pid="$1"
  label="$2"
  kill "$pid" 2>/dev/null && echo "  Stopped $label process $pid" || true
  i=0
  while [ "$i" -lt 10 ]; do
    if ! kill -0 "$pid" 2>/dev/null; then
      return 0
    fi
    sleep 0.1
    i=$((i + 1))
  done
  kill -KILL "$pid" 2>/dev/null || true
}

# 1. Kill running MindOS processes
for pid_file in "$HOME/.mindos/mindos.pid" "$HOME/.mindos/desktop-children.pid"; do
  if [ -f "$pid_file" ]; then
    while IFS= read -r pid; do
      cmd="$(ps -p "$pid" -o args= 2>/dev/null || true)"
      if is_mindos_cmd "$cmd"; then
        terminate_pid "$pid" "MindOS"
      fi
    done < "$pid_file"
    rm -f "$pid_file"
  fi
done

# 1b. Kill orphaned SSH tunnel if it is still owned by ssh.
pid_file="$HOME/.mindos/ssh-tunnel.pid"
if [ -f "$pid_file" ]; then
  while IFS= read -r pid; do
    comm="$(ps -p "$pid" -o comm= 2>/dev/null || true)"
    if is_ssh_cmd "$comm"; then
      terminate_pid "$pid" "SSH tunnel"
    fi
  done < "$pid_file"
  rm -f "$pid_file"
fi

# 2. Remove Desktop-managed runtimes and caches. Knowledge base is NOT touched.
for managed in "$HOME/.mindos/node" "$HOME/.mindos/runtime" "$HOME/.mindos/runtime-downloading" "$HOME/.mindos/runtime-old"; do
  if [ -e "$managed" ]; then
    echo "  Removing $managed..."
    rm -rf "$managed"
  fi
done
rm -f "$HOME/.mindos/runtime-download.tar.gz"

# 3. Remove CLI shim
rm -f "$HOME/.mindos/bin/mindos"
rmdir "$HOME/.mindos/bin" 2>/dev/null || true

# 4. Clean PATH injection from shell rc files
for rc in "$HOME/.zshenv" "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.profile"; do
  if [ -f "$rc" ] && grep -Eq '# MindOS Desktop|# MindOS CLI|export PATH="\$HOME/\.mindos/bin:\$PATH"' "$rc"; then
    # Remove current Desktop marker blocks, legacy CLI marker blocks, and exact raw PATH lines.
    if [ "$(uname)" = "Darwin" ]; then
      sed -i '' -e '/# MindOS Desktop/,+1d' -e '/# MindOS CLI/,+1d' -e '\\#export PATH="$HOME/.mindos/bin:$PATH"#d' "$rc"
    else
      sed -i -e '/# MindOS Desktop/,+1d' -e '/# MindOS CLI/,+1d' -e '\\#export PATH="$HOME/.mindos/bin:$PATH"#d' "$rc"
    fi
    echo "  Cleaned $rc"
  fi
done
# Clean fish config
if [ -f "$HOME/.config/fish/conf.d/mindos.fish" ]; then
  rm -f "$HOME/.config/fish/conf.d/mindos.fish"
  echo "  Cleaned fish config"
fi

# 5. Note MCP client configs that still reference MindOS
for cfg in "$HOME/.claude.json" "$HOME/.cursor/mcp.json" "$HOME/.codeium/windsurf/mcp_config.json"; do
  if [ -f "$cfg" ] && grep -q "mindos" "$cfg"; then
    echo "  Note: $cfg still references MindOS — remove the 'mindos' entry manually."
  fi
done

# 6. Remove config and state files
rm -f "$HOME/.mindos/config.json"
rm -f "$HOME/.mindos/stdin-watchdog.cjs"
rm -f "$HOME/.mindos/desktop-cli-path-notify-v1"
rm -f "$HOME/.mindos/update-status.json"
rm -rf "$HOME/.mindos/tmp"
rm -rf "$HOME/.mindos/sessions"

# 7. Remove Electron app data (macOS)
if [ "$(uname)" = "Darwin" ]; then
  rm -rf "$HOME/Library/Application Support/MindOS"
  rm -rf "$HOME/Library/Caches/com.geminilight.mindos"
  rm -f "$HOME/Library/Preferences/com.geminilight.mindos.plist"
fi

# 8. Self-remove
rm -f "$HOME/.mindos/uninstall.sh"
rmdir "$HOME/.mindos" 2>/dev/null || true

echo ""
echo "Done. Your knowledge base was NOT touched."
echo "To also remove it: rm -rf ~/MindOS/mind"
`;
}

/** Generate ~/.mindos/uninstall.sh/.bat for cleanup after app deletion */
function writeUninstallScript(): void {
  const home = getDesktopHome();
  if (process.platform === 'win32') {
    const scriptPath = path.join(home, '.mindos', 'uninstall.bat');
    if (existsSync(scriptPath)) return;
    try {
      mkdirSync(path.dirname(scriptPath), { recursive: true });
      writeFileSync(scriptPath, buildWindowsUninstallScript(), 'utf-8');
    } catch { /* best effort */ }
    return;
  }

  const script = buildUnixUninstallScript();

  const scriptPath = path.join(home, '.mindos', 'uninstall.sh');
  if (existsSync(scriptPath)) return; // idempotent — only create once
  try {
    mkdirSync(path.dirname(scriptPath), { recursive: true });
    writeFileSync(scriptPath, script, 'utf-8');
    chmodSync(scriptPath, 0o755);
  } catch { /* best effort */ }
}

const NOTIFY_FLAG = 'desktop-cli-path-notify-v1';

function notifyFlagPath(): string {
  return path.join(getDesktopConfigDir(), NOTIFY_FLAG);
}

function showMessageBoxSafe(
  parent: BrowserWindow | null | undefined,
  options: Electron.MessageBoxOptions,
): void {
  const win = parent && !parent.isDestroyed() ? parent : null;
  if (win) void dialog.showMessageBox(win, options);
  else void dialog.showMessageBox(options);
}

/** One-time hint after ~/.mindos/bin is appended to shell rc — banner, not a blocking dialog. */
function notifyPathAppendedOnce(): void {
  if (existsSync(notifyFlagPath())) return;
  try {
    writeFileSync(notifyFlagPath(), new Date().toISOString(), 'utf-8');
  } catch {
    /* still try notification */
  }

  const zh = app.getLocale()?.toLowerCase().startsWith('zh');
  const title = zh ? 'mindos 命令行已就绪' : 'mindos CLI is ready';
  const body = zh
    ? '已写入 ~/.mindos/bin 并加入 PATH。请新开终端后运行 mindos --help'
    : '~/.mindos/bin added to PATH. Open a new terminal, then run mindos --help';

  if (Notification.isSupported()) {
    try {
      new Notification({ title, body }).show();
      return;
    } catch {
      /* fall through */
    }
  }
  console.info(`[MindOS] ${title} — ${body}`);
}

/**
 * Sync shim on disk. Call on each launch so CLI tracks the current .app location.
 * Optionally append PATH once; first PATH change triggers a one-time OS notification (no modal).
 */
export function ensureMindosCliShim(opts?: {
  /** default true on macOS/Linux */
  appendPath?: boolean;
}): { ok: boolean; error?: string; pathAppended?: boolean } {
  const cliJs = resolveCliJs();
  if (!cliJs) {
    const msg =
      'Bundled MindOS CLI not found (mindos-runtime/bin/cli.js). Reinstall the app or run from a built repo.';
    console.error('[MindOS]', msg);
    return { ok: false, error: msg };
  }

  try {
    if (process.platform === 'win32') writeWindowsShim(cliJs);
    else writeUnixShim(cliJs);
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e);
    console.error('[MindOS] Failed to write CLI shim:', msg);
    return { ok: false, error: msg };
  }

  const doAppend = opts?.appendPath !== false;
  let appended = false;
  if (doAppend) {
    try {
      appended = appendMindosBinToShellRc();
    } catch (e) {
      console.warn('[MindOS] Could not append PATH to shell rc:', e);
    }
  }

  if (appended) notifyPathAppendedOnce();

  writeUninstallScript();

  console.info('[MindOS] CLI shim:', shimPath(), '→', cliJs);
  return { ok: true, pathAppended: appended };
}

export function buildRefreshCliSuccessDialog(
  platform: NodeJS.Platform,
  zh: boolean,
  pathAppended: boolean,
): Electron.MessageBoxOptions {
  if (platform === 'win32') {
    return {
      type: 'info',
      title: zh ? 'mindos 命令' : 'mindos command',
      message: pathAppended
        ? (zh
            ? `已更新 ${shimPath()}，并已将 ${shimDir()} 加入用户 PATH。请新开终端后运行 mindos --help。`
            : `Updated ${shimPath()} and added ${shimDir()} to your user PATH. Open a new terminal, then run mindos --help.`)
        : (zh
            ? `已更新 ${shimPath()}。如果新终端仍找不到 mindos，请将以下目录加入用户 PATH：`
            : `Updated ${shimPath()}. If a new terminal still cannot find mindos, add this folder to your user PATH:`),
      detail: shimDir(),
    };
  }

  return {
    type: 'info',
    title: zh ? 'mindos 命令' : 'mindos command',
    message: zh
      ? '已更新 ~/.mindos/bin/mindos。若终端仍提示找不到命令，请新开终端窗口，或执行 source ~/.zshrc。'
      : 'Updated ~/.mindos/bin/mindos. Open a new terminal or run source ~/.zshrc if the command is not found.',
    detail: shimPath(),
  };
}

/** Tray / manual: refresh shim + show result (and Windows PATH hint). */
export function refreshMindosCliAndNotify(parent: BrowserWindow | null): void {
  const r = ensureMindosCliShim({ appendPath: true });
  const zh = app.getLocale()?.toLowerCase().startsWith('zh');
  if (r.ok) {
    showMessageBoxSafe(parent, buildRefreshCliSuccessDialog(process.platform, zh, r.pathAppended === true));
    return;
  }
  showMessageBoxSafe(parent, {
    type: 'error',
    title: zh ? 'mindos 安装失败' : 'mindos install failed',
    message: r.error ?? 'Unknown error',
  });
}
