import { spawnSync } from 'child_process'
import { existsSync, readFileSync } from 'fs'
import { homedir } from 'os'
import { join } from 'path'

const ALWAYS_EXCLUDE = [
  'node_modules',
  '.sandlot',
  '.DS_Store',
  '.git',
  '.toes',
  '*~',
]

const DEFAULT_PATTERNS = [
  ...ALWAYS_EXCLUDE,
  '*.log',
  'dist',
  'build',
  '.env.local',
  'bun.lockb',
]

export interface GitignoreChecker {
  shouldExclude: (path: string) => boolean
}

export function loadGitignore(appPath: string): GitignoreChecker {
  const gitignorePath = join(appPath, '.gitignore')
  const globalPatterns = loadGlobalPatterns()
  const patterns = existsSync(gitignorePath)
    ? [...ALWAYS_EXCLUDE, ...globalPatterns, ...parseGitignore(readFileSync(gitignorePath, 'utf-8'))]
    : [...DEFAULT_PATTERNS, ...globalPatterns]

  return {
    shouldExclude: (path: string) => matchesPattern(path, patterns),
  }
}

export const _resetGlobalCache = () => {
  cachedGlobalPatterns = undefined
}

let cachedGlobalPatterns: string[] | undefined

const expandHome = (filepath: string) =>
  filepath.startsWith('~/') ? join(homedir(), filepath.slice(2)) : filepath

function getGlobalExcludesPath(): string | null {
  const result = spawnSync('git', ['config', '--global', 'core.excludesFile'], {
    encoding: 'utf-8',
  })
  if (result.status === 0 && result.stdout.trim()) {
    return expandHome(result.stdout.trim())
  }
  const xdgHome = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')
  const defaultPath = join(xdgHome, 'git', 'ignore')
  if (existsSync(defaultPath)) return defaultPath
  return null
}

function loadGlobalPatterns(): string[] {
  if (cachedGlobalPatterns !== undefined) return cachedGlobalPatterns
  const path = getGlobalExcludesPath()
  cachedGlobalPatterns =
    path && existsSync(path) ? parseGitignore(readFileSync(path, 'utf-8')) : []
  return cachedGlobalPatterns
}

function parseGitignore(content: string): string[] {
  return content
    .split('\n')
    .map(line => line.trim())
    .filter(line => line && !line.startsWith('#'))
}

function matchesPattern(path: string, patterns: string[]): boolean {
  const parts = path.split('/')
  const filename = parts[parts.length - 1]!

  for (const pattern of patterns) {
    // Patterns with wildcards or globs
    if (pattern.includes('*') || pattern.includes('?')) {
      const regex = globToRegex(pattern)
      // Try matching against full path and just filename
      if (regex.test(path) || regex.test(filename)) return true
      continue
    }

    // Directory match: "node_modules" or "node_modules/"
    const dirPattern = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern
    if (parts.includes(dirPattern)) return true

    // Prefix patterns: "dist/" or "build/"
    if (pattern.endsWith('/') && path.startsWith(pattern)) return true

    // Exact match
    if (path === pattern || filename === pattern) return true
  }

  return false
}

function globToRegex(glob: string): RegExp {
  // Handle ** specially - it can match zero or more path segments
  // Use placeholders to avoid conflicts with regex escaping
  let pattern = glob
    .replace(/\*\*\//g, '\x00') // **/ placeholder
    .replace(/\*\*/g, '\x01') // ** placeholder
    .replace(/\./g, '\\.') // Escape dots
    .replace(/\*/g, '[^/]*') // Single * matches within path segment
    .replace(/\?/g, '[^/]') // ? matches single char
    .replace(/\x00/g, '(.*/)?') // **/ matches zero or more dirs
    .replace(/\x01/g, '.*') // ** matches anything

  return new RegExp(`^${pattern}$`)
}
