<!--
  WebTerminal.vue
  Web 模拟终端组件
  
  用途：
  提供一个在浏览器中可交互的简易终端环境，作为本章节的综合练习场。
  让用户在学完所有理论后，能够在一个受控环境中实际体验输入输出、命令执行等操作。
  
  交互功能：
  - 命令执行：支持简单的模拟命令（如 help, clear, echo 等）。
  - 历史记录：支持上下键翻阅命令历史。
  - 真实反馈：模拟真实的终端响应延迟和输出格式。
-->
<template>
  <div class="web-terminal-wrapper">
    <div class="terminal-container">
      <div class="terminal-header">
        <div class="terminal-buttons">
          <span class="btn red" />
          <span class="btn yellow" />
          <span class="btn green" />
        </div>
        <div class="terminal-title">
          Terminal - zsh
        </div>
      </div>
      <div
        ref="terminalBody"
        class="terminal-body"
        @click="focusInput"
      >
        <div
          v-for="(line, index) in history"
          :key="index"
          class="terminal-line"
        >
          <span
            v-if="line.type === 'input'"
            class="prompt"
          >
            <span class="path">{{ currentPath }}</span>
            <span class="arrow">$ </span>
          </span>
          <span :class="line.type">{{ line.content }}</span>
        </div>
        <div class="input-line">
          <span class="prompt">
            <span class="path">{{ currentPath }}</span>
            <span class="arrow">$ </span>
          </span>
          <input
            ref="inputField"
            v-model="currentInput"
            type="text"
            autocomplete="off"
            spellcheck="false"
            @keyup.enter="executeCommand"
            @keydown.up.prevent="navigateHistory(-1)"
            @keydown.down.prevent="navigateHistory(1)"
            @keydown.tab.prevent="handleTabCompletion"
          >
        </div>
      </div>
    </div>

    <div class="cheat-sheet">
      <div class="sheet-title">
        <span class="icon">📖</span>
        <span class="en">Command Cheat Sheet</span>
        <span class="divider">|</span>
        <span class="zh">命令速查表</span>
      </div>
      <div class="sheet-content">
        <div
          v-for="(group, gIndex) in cheatSheet"
          :key="gIndex"
          class="cmd-group"
        >
          <div class="group-title">
            {{ group.category }}
          </div>
          <div
            v-for="(cmd, cIndex) in group.commands"
            :key="cIndex"
            class="cmd-item"
          >
            <div
              class="cmd-name"
              @click="fillCommand(cmd.name)"
            >
              {{ cmd.name }}
            </div>
            <div class="cmd-desc">
              <div class="en">
                {{ cmd.descEn }}
              </div>
              <div class="zh">
                {{ cmd.descZh }}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue'

const history = ref([
  {
    type: 'output',
    content:
      'Welcome to the interactive terminal simulator! / 欢迎使用交互式终端模拟器！'
  },
  {
    type: 'output',
    content:
      'Type "help" to see available commands. / 输入 "help" 查看可用命令。'
  }
])
const currentInput = ref('')
const inputField = ref(null)
const terminalBody = ref(null)
const commandHistory = ref([])
const historyIndex = ref(-1)

// 模拟文件系统
const fileSystem = {
  name: '/',
  type: 'dir',
  children: {
    home: {
      name: 'home',
      type: 'dir',
      children: {
        user: {
          name: 'user',
          type: 'dir',
          children: {
            'hello.txt': {
              name: 'hello.txt',
              type: 'file',
              content:
                'Hello World! This is a mock file.\n你好！这是一个模拟文件。'
            },
            'notes.md': {
              name: 'notes.md',
              type: 'file',
              content:
                '# My Notes\n- Learn Terminal\n- Learn Shell\n- Learn Kernel'
            },
            projects: {
              name: 'projects',
              type: 'dir',
              children: {
                'app.js': {
                  name: 'app.js',
                  type: 'file',
                  content: 'console.log("Hello");'
                }
              }
            },
            Downloads: { name: 'Downloads', type: 'dir', children: {} }
          }
        }
      }
    },
    etc: {
      name: 'etc',
      type: 'dir',
      children: {
        passwd: {
          name: 'passwd',
          type: 'file',
          content:
            'root:x:0:0:root:/root:/bin/bash\nuser:x:1000:1000:user:/home/user:/bin/zsh'
        }
      }
    },
    bin: {
      name: 'bin',
      type: 'dir',
      children: {
        ls: { name: 'ls', type: 'file', content: 'Binary file' },
        cat: { name: 'cat', type: 'file', content: 'Binary file' }
      }
    }
  }
}

let currentPath = '~'
let currentDirObj = fileSystem.children['home'].children['user']

const resolvePath = (path) => {
  if (path === '~' || path === '')
    return fileSystem.children['home'].children['user']
  if (path === '/') return fileSystem

  let parts = path.split('/').filter((p) => p)
  let current = path.startsWith('/') ? fileSystem : currentDirObj

  for (const part of parts) {
    if (part === '.') continue
    if (part === '..') {
      // Find parent (simplification: we don't store parent refs, so we re-traverse or just mock it)
      // For this simple mock, '..' support is limited or we implement path string manipulation
      return null // path manipulation logic needs to be separate
    }
    if (current.type === 'dir' && current.children && current.children[part]) {
      current = current.children[part]
    } else {
      return null
    }
  }
  return current
}

const getParentPath = (path) => {
  if (path === '/' || path === '~') return path // Simplified
  const parts = path.split('/')
  parts.pop()
  return parts.join('/') || '/'
}

// Better path resolution logic
const navigateTo = (target) => {
  let newPath = currentPath
  let newDir = currentDirObj

  if (target === '/') {
    newPath = '/'
    newDir = fileSystem
  } else if (target === '~') {
    newPath = '~'
    newDir = fileSystem.children['home'].children['user']
  } else if (target === '..') {
    if (currentPath === '/') return { path: '/', dir: fileSystem }
    if (currentPath === '~') {
      // ~ is /home/user
      newPath = '/home'
      newDir = fileSystem.children['home']
    } else {
      // Simple string manipulation for path
      const parts = currentPath.split('/')
      parts.pop()
      newPath = parts.join('/') || '/'

      // Re-resolve dir from root for safety
      if (newPath === '/') newDir = fileSystem
      else if (newPath === '/home') newDir = fileSystem.children['home']
      else if (newPath === '/home/user')
        newDir = fileSystem.children['home'].children['user']
      else {
        // Fallback for deeper paths if we supported them
        // For now, let's keep it simple
      }
    }
  } else {
    // Relative path
    if (currentDirObj.children && currentDirObj.children[target]) {
      const targetObj = currentDirObj.children[target]
      if (targetObj.type === 'dir') {
        newDir = targetObj
        newPath =
          currentPath === '/' ? `/${target}` : `${currentPath}/${target}`
      } else {
        return { error: `cd: not a directory: ${target}` }
      }
    } else {
      return { error: `cd: no such file or directory: ${target}` }
    }
  }
  return { path: newPath, dir: newDir }
}

const cheatSheet = [
  {
    category: 'Navigation / 导航',
    commands: [
      {
        name: 'ls',
        descEn: 'List directory contents',
        descZh: '列出当前目录下的文件和文件夹'
      },
      {
        name: 'cd <dir>',
        descEn: 'Change directory',
        descZh: '进入指定目录 (例如: cd projects)'
      },
      {
        name: 'pwd',
        descEn: 'Print working directory',
        descZh: '显示当前所在的完整路径'
      }
    ]
  },
  {
    category: 'File Operations / 文件操作',
    commands: [
      {
        name: 'cat <file>',
        descEn: 'Show file contents',
        descZh: '查看文件内容 (例如: cat hello.txt)'
      },
      {
        name: 'touch <file>',
        descEn: 'Create empty file',
        descZh: '创建一个新文件'
      },
      {
        name: 'mkdir <dir>',
        descEn: 'Make directory',
        descZh: '创建一个新文件夹'
      },
      { name: 'rm <file>', descEn: 'Remove file', descZh: '删除文件' }
    ]
  },
  {
    category: 'System / 系统',
    commands: [
      {
        name: 'echo <text>',
        descEn: 'Print text',
        descZh: '在屏幕上打印一段文字'
      },
      { name: 'whoami', descEn: 'Current user', descZh: '显示当前用户名' },
      { name: 'date', descEn: 'Show date/time', descZh: '显示当前日期和时间' },
      { name: 'clear', descEn: 'Clear screen', descZh: '清空屏幕内容' }
    ]
  },
  {
    category: 'Package Manager / 软件包 (Mock)',
    commands: [
      {
        name: 'apt update',
        descEn: 'Update package list',
        descZh: '更新软件包列表'
      },
      {
        name: 'apt install <pkg>',
        descEn: 'Install package',
        descZh: '安装软件 (例如: apt install git)'
      }
    ]
  }
]

const commands = {
  help: () => {
    return `Available commands:
  ls, cd, pwd, cat, touch, mkdir, rm, echo, whoami, date, clear, apt`
  },

  ls: (args) => {
    if (!currentDirObj.children) return ''
    const items = Object.values(currentDirObj.children)
    if (items.length === 0) return ''

    // Simple column formatting
    const names = items.map((item) => {
      return item.type === 'dir' ? `\x1b[1;34m${item.name}/\x1b[0m` : item.name
    })
    return names.join('  ')
  },

  pwd: () => {
    // Expand ~ to /home/user for display if needed, but keeping ~ is also standard zsh
    return currentPath === '~' ? '/home/user' : currentPath
  },

  cd: (args) => {
    const target = args[0] || '~'
    const result = navigateTo(target)
    if (result.error) return result.error
    currentPath = result.path
    currentDirObj = result.dir
    return null
  },

  clear: () => {
    history.value = []
    return null
  },

  echo: (args) => {
    return args.join(' ')
  },

  cat: (args) => {
    const file = args[0]
    if (!file) return 'usage: cat <file>'

    if (currentDirObj.children && currentDirObj.children[file]) {
      const target = currentDirObj.children[file]
      if (target.type === 'dir') return `cat: ${file}: Is a directory`
      return target.content
    }
    return `cat: ${file}: No such file or directory`
  },

  touch: (args) => {
    const name = args[0]
    if (!name) return 'usage: touch <file>'
    if (currentDirObj.children[name]) return null // Already exists, update time (mock: do nothing)
    currentDirObj.children[name] = { name, type: 'file', content: '' }
    return null
  },

  mkdir: (args) => {
    const name = args[0]
    if (!name) return 'usage: mkdir <dir>'
    if (currentDirObj.children[name])
      return `mkdir: cannot create directory '${name}': File exists`
    currentDirObj.children[name] = { name, type: 'dir', children: {} }
    return null
  },

  rm: (args) => {
    const name = args[0]
    if (!name) return 'usage: rm <file>'
    // Mock: -r not supported for simplicity
    if (currentDirObj.children[name]) {
      if (currentDirObj.children[name].type === 'dir')
        return `rm: cannot remove '${name}': Is a directory`
      delete currentDirObj.children[name]
      return null
    }
    return `rm: cannot remove '${name}': No such file or directory`
  },

  whoami: () => 'user',

  date: () => new Date().toString(),

  apt: (args) => {
    const cmd = args[0]
    if (cmd === 'update') {
      return `Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Get:2 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Fetched 110 kB in 1s (135 kB/s)
Reading package lists... Done`
    }
    if (cmd === 'install') {
      const pkg = args[1]
      if (!pkg) return 'apt install: missing package name'
      return `Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  ${pkg}
0 upgraded, 1 newly installed, 0 to remove.
Need to get 1,234 kB of archives.
After this operation, 5,678 kB of additional disk space will be used.
Selecting previously unselected package ${pkg}.
(Reading database ... 25432 files and directories currently installed.)
Preparing to unpack .../${pkg}_1.0.0_amd64.deb ...
Unpacking ${pkg} (1.0.0) ...
Setting up ${pkg} (1.0.0) ...`
    }
    return 'apt: usage: apt update | apt install <package>'
  }
}

const executeCommand = () => {
  const input = currentInput.value.trim()

  if (!input) {
    history.value.push({ type: 'input', content: '' })
    currentInput.value = ''
    scrollToBottom()
    return
  }

  // Add to command history
  commandHistory.value.push(input)
  historyIndex.value = commandHistory.value.length

  history.value.push({ type: 'input', content: input })

  const [cmd, ...args] = input.split(/\s+/)

  if (commands[cmd]) {
    try {
      const output = commands[cmd](args)
      if (output !== null) {
        // Handle colored output simply by not escaping HTML if we trust it (Vue escapes by default)
        // For simple color simulation, we can strip codes or use a span.
        // Here we just keep simple text, but `ls` returns ANSI codes which we might want to handle or strip.
        // For this demo, let's strip ANSI codes for safety/simplicity in Vue or parse them.
        // Let's simple string replace for blue color

        let safeOutput = output

        const lines = safeOutput.split('\n')
        lines.forEach((line) => {
          // Basic ANSI parser for ls colors
          const isDir = line.includes('\x1b[1;34m')
          const cleanContent = line.replace(/\x1b\[[0-9;]*m/g, '')
          history.value.push({
            type: isDir ? 'output-dir' : 'output',
            content: cleanContent
          })
        })
      }
    } catch (e) {
      history.value.push({
        type: 'error',
        content: `Error executing command: ${e.message}`
      })
    }
  } else {
    history.value.push({
      type: 'error',
      content: `zsh: command not found: ${cmd}`
    })
  }

  currentInput.value = ''
  scrollToBottom()
}

const navigateHistory = (direction) => {
  if (commandHistory.value.length === 0) return

  historyIndex.value += direction

  if (historyIndex.value < 0) historyIndex.value = 0
  if (historyIndex.value > commandHistory.value.length)
    historyIndex.value = commandHistory.value.length

  if (historyIndex.value === commandHistory.value.length) {
    currentInput.value = ''
  } else {
    currentInput.value = commandHistory.value[historyIndex.value]
  }
}

const handleTabCompletion = () => {
  // Simple tab completion for current directory
  const input = currentInput.value
  const [cmd, ...args] = input.split(/\s+/)
  const partial = args[args.length - 1] || ''

  if (cmd && currentDirObj.children) {
    const matches = Object.keys(currentDirObj.children).filter((name) =>
      name.startsWith(partial)
    )
    if (matches.length === 1) {
      const completed = matches[0]
      // Replace last arg with completed
      args[args.length - 1] = completed
      currentInput.value = `${cmd} ${args.join(' ')}`
    }
  }
}

const focusInput = () => {
  inputField.value?.focus()
}

const scrollToBottom = () => {
  nextTick(() => {
    if (terminalBody.value) {
      terminalBody.value.scrollTop = terminalBody.value.scrollHeight
    }
  })
}

const fillCommand = (cmdName) => {
  // Extract command from example (e.g., "cd <dir>" -> "cd")
  const cmd = cmdName.split(' ')[0]
  currentInput.value = cmd + ' '
  focusInput()
}

onMounted(() => {
  focusInput()
})
</script>

<style scoped>
.web-terminal-wrapper {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 20px;
  margin: 20px 0;
  font-family: 'JetBrains Mono', 'Menlo', monospace;
}

.terminal-container {
  background-color: #0a0a0a;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  border: 1px solid #27272a;
  display: flex;
  flex-direction: column;
  height: 400px;
}

.terminal-header {
  background-color: #18181b;
  padding: 10px 15px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid #27272a;
}

.terminal-buttons {
  display: flex;
  gap: 8px;
}

.btn {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}

.red {
  background-color: #ef4444;
}
.yellow {
  background-color: #facc15;
}
.green {
  background-color: #22c55e;
}

.terminal-title {
  flex: 1;
  text-align: center;
  color: #71717a;
  font-size: 12px;
  margin-left: -50px;
}

.terminal-body {
  padding: 15px;
  flex: 1;
  
  color: #e4e4e7;
  font-size: 14px;
  line-height: 1.6;
  cursor: text;
}

.terminal-line {
  margin-bottom: 2px;
  white-space: pre-wrap;
  word-break: break-all;
}

.input-line {
  display: flex;
  align-items: center;
}

.prompt {
  color: #22c55e;
  margin-right: 8px;
  user-select: none;
  white-space: nowrap;
}

.prompt .path {
  color: #3b82f6;
  margin-right: 4px;
}

.error {
  color: #ef4444;
}

.output-dir {
  color: #3b82f6;
  font-weight: bold;
}

input {
  background: transparent;
  border: none;
  color: #e4e4e7;
  outline: none;
  flex: 1;
  font-family: inherit;
  font-size: inherit;
  padding: 0;
  margin: 0;
}

/* Cheat Sheet Styles */
.cheat-sheet {
  background: #18181b;
  border: 1px solid #27272a;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 400px;
}

.sheet-title {
  padding: 12px 15px;
  background: #27272a;
  color: #e4e4e7;
  font-weight: bold;
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.sheet-title .divider {
  color: #52525b;
  font-weight: normal;
}

.sheet-content {
  padding: 15px;
  
  flex: 1;
}

.cmd-group {
  margin-bottom: 20px;
}

.group-title {
  color: #facc15;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 8px;
  font-weight: 600;
  border-bottom: 1px solid #27272a;
  padding-bottom: 4px;
}

.cmd-item {
  margin-bottom: 10px;
  cursor: pointer;
  transition: transform 0.1s;
}

.cmd-item:hover {
  transform: translateX(4px);
}

.cmd-name {
  color: #22d3ee;
  font-weight: bold;
  font-size: 12px;
  margin-bottom: 2px;
}

.cmd-desc {
  font-size: 11px;
  color: #a1a1aa;
}

.cmd-desc .zh {
  color: #71717a;
  margin-top: 1px;
}

@media (max-width: 768px) {
  .web-terminal-wrapper {
    grid-template-columns: 1fr;
    height: auto;
  }

  .terminal-container,
  .cheat-sheet {
    height: 350px;
  }
}
</style>
