// bot/src/worktree.ts — Manages git worktree operations by delegating to sdd CLI
//
// sdd worktree add <branch>                              — creates .worktree/<branch> from homolog
// sdd worktree text-commit-card <cardId> <stage> --version <n>  — returns commit text
// NOTE: execa v8 is ESM-only; loaded via dynamic import() to remain CJS-compatible.

import fs from 'node:fs'
import path from 'node:path'

async function getExeca() {
  const mod = await import('execa')
  return mod.execa
}

// Extrai a mensagem de erro mais útil de uma falha de execa, priorizando o
// stderr (onde o sdd e o git escrevem a causa real, ex.: falha de autenticação)
// sobre a mensagem genérica "Command failed with exit code 1".
function extractStderr(err: unknown): string {
  if (err && typeof err === 'object') {
    const e = err as { stderr?: string; shortMessage?: string; message?: string }
    const stderr = typeof e.stderr === 'string' ? e.stderr.trim() : ''
    if (stderr) return stderr
    if (e.shortMessage) return e.shortMessage
    if (e.message) return e.message
  }
  return String(err)
}

export class WorktreeManager {
  constructor(
    private readonly projectRoot: string,
    private readonly sddBin: string = 'sdd',
  ) {}

  // Calls: sdd worktree add <cardId>
  // Returns the absolute path of the created worktree
  async create(cardId: string): Promise<string> {
    const execa = await getExeca()
    try {
      // Captura stderr (em vez de 'inherit') para que a causa real do erro —
      // ex.: "credencial do remoto inválida ou expirada" — chegue ao Slack,
      // em vez de ficar só no log do processo.
      await execa(this.sddBin, ['worktree', 'add', cardId], {
        cwd: this.projectRoot,
      })
    } catch (err: unknown) {
      const detail = extractStderr(err)
      throw new Error(`Failed to create worktree for card ${cardId}: ${detail}`)
    }
    return this.getPath(cardId)
  }

  // Returns .worktree/<cardId> absolute path
  getPath(cardId: string): string {
    return path.join(this.projectRoot, '.worktree', cardId)
  }

  // Raiz do projeto principal (onde vivem .claude/agents, etc.)
  getProjectRoot(): string {
    return this.projectRoot
  }

  // Calls: sdd worktree text-commit-card <cardId> <stage> --version <n>
  async getCommitText(cardId: string, stage: string, version: number): Promise<string> {
    const execa = await getExeca()
    try {
      const result = await execa(
        this.sddBin,
        ['worktree', 'text-commit-card', cardId, stage, '--version', String(version)],
        {
          cwd: this.projectRoot,
        },
      )
      return result.stdout.trim()
    } catch (err: unknown) {
      const detail = extractStderr(err)
      throw new Error(
        `Failed to get commit text for card ${cardId} stage ${stage} v${version}: ${detail}`,
      )
    }
  }

  // Full commit cycle: git add . && git commit -m <text> && git push origin <cardId>
  // Runs in worktree directory
  async commitAndPush(cardId: string, stage: string, version: number): Promise<void> {
    const execa = await getExeca()
    const worktreePath = this.getPath(cardId)
    const commitMessage = await this.getCommitText(cardId, stage, version)

    try {
      await execa('git', ['add', '.'], {
        cwd: worktreePath,
      })
    } catch (err: unknown) {
      throw new Error(`git add failed in worktree ${worktreePath}: ${extractStderr(err)}`)
    }

    try {
      await execa('git', ['commit', '-m', commitMessage], {
        cwd: worktreePath,
      })
    } catch (err: unknown) {
      throw new Error(`git commit failed in worktree ${worktreePath}: ${extractStderr(err)}`)
    }

    try {
      await execa('git', ['push', 'origin', cardId], {
        cwd: worktreePath,
      })
    } catch (err: unknown) {
      throw new Error(
        `git push failed for branch ${cardId} from ${worktreePath}: ${extractStderr(err)}`,
      )
    }
  }

  // Creates docs/<cardId>/ structure inside the worktree
  async createDocStructure(cardId: string): Promise<void> {
    const docsPath = this.getDocsPath(cardId)
    fs.mkdirSync(docsPath, { recursive: true })
  }

  // Returns .worktree/<cardId>/docs/<cardId>/ absolute path
  getDocsPath(cardId: string): string {
    return path.join(this.getPath(cardId), 'docs', cardId)
  }
}
