/**
 * Create a new workleaf.
 *
 * A workleaf is an ephemeral git worktree owned by a parent worktree
 * (or main). The full lifecycle:
 *
 *   1. Refuse if the current cwd is already inside a workleaf
 *      (workleaves cannot be nested).
 *   2. Derive the parent worktree name from cwd.
 *   3. `git worktree add <dir> -b <branch> <parent-branch>` so the
 *      workleaf branches off the parent's CURRENT HEAD.
 *   4. Run `npm install` so the workleaf has a working dependency tree.
 *   5. Allocate a stable port via the shared worktrees/ports.json.
 *   6. Spawn `storyboard dev` as a fully detached child process so the
 *      Vite dev server stays alive after this CLI exits.
 *   7. Poll the server registry for self-registration and print the URL.
 *
 * The detached server registers itself in .storyboard/servers.json on
 * startup, which is how `workleaf merge` / `workleaf delete` later
 * locates and stops it.
 */

import * as p from '@clack/prompts'
import { execFileSync, spawn } from 'node:child_process'
import { mkdirSync, existsSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { detectWorktreeName, repoRoot, worktreeDir, getPort, slugify } from '../worktree/port.js'
import { findByWorktree } from '../worktree/serverRegistry.js'
import { detectWorkleafContext, workleafDir, workleafBranchName, WORKLEAF_PREFIX } from './paths.js'
import { dim, bold, green } from '../cli/intro.js'
import { parseFlags } from '../cli/flags.js'

const flagSchema = {
  parent: { type: 'string', description: 'Override parent worktree (default: current)' },
  'no-dev': { type: 'boolean', default: false, description: 'Skip starting the dev server' },
  'no-install': { type: 'boolean', default: false, description: 'Skip npm install (use parent node_modules at your own risk)' },
}

/**
 * Validate that `name` is a usable workleaf identifier.
 */
function validateName(name) {
  if (!name || typeof name !== 'string') return 'A workleaf name is required'
  const trimmed = name.trim()
  if (trimmed.length === 0) return 'Workleaf name cannot be empty'
  if (trimmed.startsWith(WORKLEAF_PREFIX)) {
    return `Don't prefix with "${WORKLEAF_PREFIX}" — it's added automatically`
  }
  if (/[\s~^:\\]/.test(trimmed)) return 'Workleaf name cannot contain spaces or ~ ^ : \\'
  if (trimmed.startsWith('-')) return 'Workleaf name cannot start with "-"'
  return null
}

/**
 * Resolve the parent worktree's on-disk path. Errors out if it's gone.
 */
function resolveParentDir(parent, root) {
  if (parent === 'main') return root
  const dir = worktreeDir(parent, root)
  if (!existsSync(resolve(dir, '.git'))) {
    p.log.error(`Parent worktree "${parent}" not found at ${dim(dir)}`)
    p.log.info(`Create it first with ${green('npx storyboard branch ' + parent)}`)
    process.exit(1)
  }
  return dir
}

/**
 * Public entry — runnable as a subcommand or imported.
 */
export async function runCreate(argv = process.argv.slice(4)) {
  const { flags, positional } = parseFlags(argv, flagSchema)
  const rawName = positional[0]

  const nameError = validateName(rawName)
  if (nameError) {
    p.log.error(nameError)
    p.log.info(`Usage: ${green('npx storyboard workleaf create <name>')}`)
    process.exit(1)
  }

  const slug = slugify(rawName.trim())

  // 1. Nesting check — refuse if cwd is already a workleaf
  const ctx = detectWorkleafContext()
  if (ctx.inWorkleaf) {
    p.log.error('Workleaves cannot be nested.')
    p.log.info(`You're inside ${bold(ctx.branch)} — cd to its parent worktree first.`)
    process.exit(1)
  }

  // 2. Derive parent from cwd (or explicit --parent)
  const parent = flags.parent || detectWorktreeName()
  const root = repoRoot()
  const parentDir = resolveParentDir(parent, root)
  const parentBranch = currentBranch(parentDir)

  const dir = workleafDir(parent, slug)
  const branch = workleafBranchName(parent, slug)

  // Refuse if the workleaf already exists
  if (existsSync(resolve(dir, '.git'))) {
    p.log.error(`Workleaf already exists at ${dim(dir)}`)
    p.log.info(`Open it with ${green(`cd ${dir}`)}`)
    process.exit(1)
  }

  p.intro('storyboard workleaf create')
  p.log.info(`${dim('parent:')} ${bold(parent)} ${dim('(branch:')} ${parentBranch}${dim(')')}`)
  p.log.info(`${dim('branch:')} ${bold(branch)}`)
  p.log.info(`${dim('dir:')}    ${dim(dir.replace(root + '/', ''))}`)

  // 3. Create the worktree
  mkdirSync(dirname(dir), { recursive: true })

  const spin = p.spinner()
  spin.start(`Creating worktree`)
  try {
    execFileSync(
      'git',
      ['worktree', 'add', dir, '-b', branch, parentBranch],
      { cwd: root, stdio: 'pipe' }
    )
    spin.stop(`Worktree created`)
  } catch (err) {
    spin.stop('Failed to create worktree')
    p.log.error(err.message || 'git worktree add failed')
    process.exit(1)
  }

  // 4. npm install (skippable)
  if (!flags['no-install']) {
    try {
      spin.start('Installing dependencies (this is the slow part)')
      const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
      execFileSync(npmBin, ['install'], { cwd: dir, stdio: 'pipe' })
      spin.stop('Dependencies installed')
    } catch {
      spin.stop('npm install failed — run it manually inside the workleaf')
    }
  }

  // 5. Allocate the port (persisted in shared worktrees/ports.json)
  const port = getPort(branch)
  p.log.info(`${dim('port:')}   ${bold(port)}`)

  // 6. Spawn detached dev server
  if (flags['no-dev']) {
    p.outro(`Workleaf ready at ${green(dir.replace(root + '/', ''))} — start dev with ${green('npx storyboard dev')}`)
    return
  }

  spin.start('Spawning dev server')
  const npmBin = process.platform === 'win32' ? 'npx.cmd' : 'npx'
  const child = spawn(npmBin, ['storyboard', 'dev'], {
    cwd: dir,
    detached: true,
    stdio: 'ignore',
    env: { ...process.env, STORYBOARD_NO_BUDDY: '1' },
  })
  child.unref()

  // 7. Poll registry for self-registration
  const start = Date.now()
  let entry = null
  while (Date.now() - start < 30000) {
    await new Promise((r) => setTimeout(r, 500))
    const matches = findByWorktree(branch)
    if (matches.length > 0) { entry = matches[matches.length - 1]; break }
  }

  if (entry) {
    spin.stop(`Dev server ready`)
    const url = `http://localhost:${entry.port}/storyboard/`
    p.note(
      [
        `  ${green(url)}`,
        '',
        `  ${dim('cd into it:')}    ${green(`cd ${dir.replace(root + '/', '')}`)}`,
        `  ${dim('stop it:')}       ${green(`npx storyboard workleaf delete ${slug}`)}`,
        `  ${dim('merge & clean:')} ${green(`npx storyboard workleaf merge ${slug}`)}`,
      ].join('\n'),
      `${bold(branch)} is alive`
    )
  } else {
    spin.stop('Spawned but did not self-register within 30s')
    p.log.warn(`Check the workleaf manually: ${dim(`cd ${dir} && npx storyboard dev`)}`)
  }
  p.outro('')
}

/** Read the current branch of a directory. */
function currentBranch(cwd) {
  try {
    return execFileSync('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' }).trim()
  } catch {
    return 'HEAD'
  }
}
