/**
 * Shell/bash syntax highlighting for command input (HTML overlay, XSS-safe).
 */

export type ShellTokenType =
  | 'keyword'
  | 'builtin'
  | 'sudo'
  | 'pipe'
  | 'operator'
  | 'redirect'
  | 'string'
  | 'flag'
  | 'variable'
  | 'backtick'
  | 'comment'
  | 'number'
  | 'path'
  | 'text'

export interface ShellToken {
  type: ShellTokenType
  value: string
}

const KEYWORDS = new Set([
  'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac',
  'in', 'function', 'return', 'break', 'continue', 'select', 'until',
  'cd', 'ls', 'grep', 'egrep', 'fgrep', 'find', 'cat', 'chmod', 'chown',
  'docker', 'podman', 'kubectl', 'apt', 'apt-get', 'yum', 'dnf', 'apk',
  'pip', 'pip3', 'npm', 'npx', 'ssh', 'scp', 'rsync', 'curl', 'wget',
  'tar', 'gzip', 'gunzip', 'kill', 'ps', 'top', 'htop', 'nano', 'vim', 'vi',
  'journalctl', 'systemctl', 'service', 'supervisorctl', 'crontab', 'nginx',
  'df', 'du', 'free', 'mount', 'uname', 'hostname', 'whoami', 'id', 'pwd',
  'head', 'tail', 'wc', 'sort', 'uniq', 'awk', 'sed', 'cut', 'tr', 'xargs', 'tee',
  'netstat', 'ss', 'ip', 'ping', 'dig', 'git', 'make', 'nohup', 'timeout', 'watch',
])

const BUILTINS = new Set([
  'echo', 'printf', 'test', 'read', 'pushd', 'popd', 'exit', 'set', 'unset',
  'export', 'source', 'alias', 'type', 'command', 'hash', 'help', 'history',
])

function classifyWord(word: string): ShellTokenType {
  if (word === 'sudo') return 'sudo'
  if (KEYWORDS.has(word)) return 'keyword'
  if (BUILTINS.has(word)) return 'builtin'
  if (/^[/~]/.test(word) || word.startsWith('./') || word.startsWith('../')) return 'path'
  if (/^\d+(?:\.\d+)?$/.test(word)) return 'number'
  return 'text'
}

/** Tokenize shell command line (bash-oriented, best-effort). */
export function tokenizeShell(input: string): ShellToken[] {
  const tokens: ShellToken[] = []
  let i = 0

  while (i < input.length) {
    const ch = input[i]

    if (ch === '#') {
      let j = i + 1
      while (j < input.length && input[j] !== '\n') j++
      tokens.push({ type: 'comment', value: input.slice(i, j) })
      i = j
      continue
    }

    if (ch === "'" || ch === '"') {
      const q = ch
      let j = i + 1
      while (j < input.length) {
        if (input[j] === '\\' && q === '"') {
          j += 2
          continue
        }
        if (input[j] === q) {
          j++
          break
        }
        j++
      }
      tokens.push({ type: 'string', value: input.slice(i, j) })
      i = j
      continue
    }

    if (ch === '`') {
      let j = i + 1
      while (j < input.length && input[j] !== '`') j++
      if (j < input.length) j++
      tokens.push({ type: 'backtick', value: input.slice(i, j) })
      i = j
      continue
    }

    if (ch === '$') {
      let j = i + 1
      if (input[j] === '{') {
        while (j < input.length && input[j] !== '}') j++
        if (j < input.length) j++
      } else if (input[j] === '(') {
        j++
      } else {
        while (j < input.length && /[\w]/.test(input[j])) j++
      }
      tokens.push({ type: 'variable', value: input.slice(i, j) })
      i = j
      continue
    }

    if (ch === '|' || ch === '&' || ch === ';') {
      let j = i
      if ((input[j] === '|' && input[j + 1] === '|') || (input[j] === '&' && input[j + 1] === '&')) {
        j += 2
      } else {
        j += 1
      }
      const v = input.slice(i, j)
      tokens.push({ type: v === '|' || v === '||' ? 'pipe' : 'operator', value: v })
      i = j
      continue
    }

    if (ch === '>' || ch === '<') {
      let j = i
      if (input[j] === '>' && input[j + 1] === '>') j += 2
      else if (input[j] === '<' && input[j + 1] === '<') j += 2
      else j += 1
      if (input[j] === '&') j++
      tokens.push({ type: 'redirect', value: input.slice(i, j) })
      i = j
      continue
    }

    if (/\s/.test(ch)) {
      let j = i
      while (j < input.length && /\s/.test(input[j])) j++
      tokens.push({ type: 'text', value: input.slice(i, j) })
      i = j
      continue
    }

    if (ch === '-' && input[i + 1] === '-') {
      let j = i + 2
      while (j < input.length && /[\w-]/.test(input[j])) j++
      tokens.push({ type: 'flag', value: input.slice(i, j) })
      i = j
      continue
    }
    if (ch === '-' && /[a-zA-Z]/.test(input[i + 1] || '')) {
      let j = i + 1
      while (j < input.length && /[a-zA-Z]/.test(input[j])) j++
      tokens.push({ type: 'flag', value: input.slice(i, j) })
      i = j
      continue
    }

    let j = i
    while (j < input.length && !/[\s#"'`$|;&<>]/.test(input[j])) j++
    const word = input.slice(i, j)
    tokens.push({ type: classifyWord(word), value: word })
    i = j
  }

  return tokens
}

function escapeHtml(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

const CLASS_MAP: Record<ShellTokenType, string> = {
  keyword: 'sh-keyword',
  builtin: 'sh-builtin',
  sudo: 'sh-sudo',
  pipe: 'sh-pipe',
  operator: 'sh-operator',
  redirect: 'sh-redirect',
  string: 'sh-string',
  flag: 'sh-flag',
  variable: 'sh-variable',
  backtick: 'sh-backtick',
  comment: 'sh-comment',
  number: 'sh-number',
  path: 'sh-path',
  text: 'sh-text',
}

/** Render highlighted HTML for overlay (use only with escaped content). */
export function highlightShellToHtml(input: string): string {
  if (!input) return ''
  return tokenizeShell(input)
    .map((t) => `<span class="${CLASS_MAP[t.type]}">${escapeHtml(t.value)}</span>`)
    .join('')
}
