#!/usr/bin/env node
/**
 * Exxat Design OS install interlude.
 *
 * Normal in-place terminal progress (`\r`, cursorTo, readline.clearLine) still
 * leaves repeated frames in some macOS terminal scrollback/copy flows. This
 * renderer uses the alternate screen buffer instead: animation happens on a
 * temporary stage, then disappears cleanly like vim/less/htop.
 *
 * Voice + visual language are aligned to https://exxat-design-os.pages.dev/:
 * "A design system, that designs", speak in outcomes, discovery before
 * generation, no raw tables / hex / toasts, AI-native guardrails.
 */

import { spawn } from "node:child_process"

const INTERACTIVE = Boolean(process.stdout.isTTY && !process.env.CI && process.env.TERM !== "dumb")

const ESC = "\x1b["
const RESET = `${ESC}0m`
const HIDE_CURSOR = `${ESC}?25l`
const SHOW_CURSOR = `${ESC}?25h`
const ENTER_ALT = `${ESC}?1049h`
const EXIT_ALT = `${ESC}?1049l`
const CLEAR = `${ESC}2J${ESC}H`

const COLOR = {
  accent: `${ESC}38;5;141m`,
  accent2: `${ESC}38;5;133m`,
  rose: `${ESC}38;5;168m`,
  dim: `${ESC}38;5;240m`,
  faint: `${ESC}38;5;252m`,
  ink: `${ESC}1m`,
  ok: `${ESC}38;5;114m`,
}

const CAPTIONS = [
  "Discovery before generation.",
  "Speaking in outcomes.",
  "No raw tables. No hex. No toasts.",
  "Wiring Cursor and Claude to the same playbook.",
  "Calling the patterns home.",
  "Making the first pixel feel considered.",
]

function visibleLength(value) {
  return value.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "").length
}

function padRight(value, width) {
  const pad = Math.max(0, width - visibleLength(value))
  return value + " ".repeat(pad)
}

function center(value, width) {
  const pad = Math.max(0, width - visibleLength(value))
  return `${" ".repeat(Math.floor(pad / 2))}${value}${" ".repeat(Math.ceil(pad / 2))}`
}

function bar(progress, width) {
  const clamped = Math.max(0.04, Math.min(0.96, progress))
  const filled = Math.max(2, Math.floor(width * clamped))
  const empty = Math.max(0, width - filled - 1)
  return `${COLOR.rose}${"█".repeat(filled)}${COLOR.accent}${"█"}${COLOR.faint}${"░".repeat(empty)}${RESET}`
}

function installCommand(pm) {
  switch (pm.label) {
    case "pnpm":
      return { command: "pnpm", args: ["install", "--reporter=append-only"] }
    case "yarn":
      return { command: "yarn", args: ["install"] }
    case "bun":
      return { command: "bun", args: ["install"] }
    default:
      return { command: "npm", args: ["install"] }
  }
}

function createInterlude() {
  if (!INTERACTIVE) {
    console.log("   ✦  Exxat Design OS is setting up your workspace…")
    return {
      setPhase() {},
      setProgress() {},
      stop() {},
    }
  }

  let progress = 0.06
  let phase = "Setting up the studio"
  let captionIndex = 0
  let tick = 0
  let closed = false

  function render() {
    const terminalWidth = Math.max(72, Math.min(process.stdout.columns || 88, 104))
    const boxWidth = Math.min(76, terminalWidth - 4)
    const inner = boxWidth - 4
    const line = "─".repeat(boxWidth - 2)
    const caption = CAPTIONS[captionIndex % CAPTIONS.length]
    const pulse = ["✦", "·", "✧", "·"][tick % 4]

    const rows = [
      `${COLOR.accent}╭${line}╮${RESET}`,
      `${COLOR.accent}│${RESET} ${padRight(`${COLOR.ink}Exxat Design OS${RESET}`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${padRight(`A design system, that designs.`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${" ".repeat(inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center(`${COLOR.accent2}${pulse}${RESET}  ${COLOR.ink}${phase}${RESET}`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center(`Speak in outcomes. The system handles the pattern work.`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${" ".repeat(inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center("🌿", inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center("╭─────╮", inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center("│  🎨 │", inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center("╰─────╯", inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${" ".repeat(inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center(bar(progress, Math.min(44, inner - 6)), inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${center(`${COLOR.ink}${caption}${RESET}`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${" ".repeat(inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}│${RESET} ${padRight(`${COLOR.ok}✓${RESET} Asks first    ${COLOR.ok}✓${RESET} Refuses bad UX    ${COLOR.ok}✓${RESET} Accessible    ${COLOR.ok}✓${RESET} AI-native`, inner)} ${COLOR.accent}│${RESET}`,
      `${COLOR.accent}╰${line}╯${RESET}`,
    ]

    process.stdout.write(CLEAR + rows.join("\n"))
  }

  process.stdout.write(`${ENTER_ALT}${HIDE_CURSOR}`)
  render()

  const timer = setInterval(() => {
    tick += 1
    const step = progress < 0.62 ? 0.008 : progress < 0.84 ? 0.0045 : 0.0018
    progress = Math.min(0.95, progress + step)
    if (tick % 18 === 0) captionIndex = (captionIndex + 1) % CAPTIONS.length
    render()
  }, 80)

  function close() {
    if (closed) return
    closed = true
    clearInterval(timer)
    process.stdout.write(`${SHOW_CURSOR}${EXIT_ALT}`)
  }

  const cleanup = () => close()
  process.once("SIGINT", cleanup)
  process.once("SIGTERM", cleanup)
  process.once("exit", cleanup)

  return {
    setPhase(nextPhase) {
      phase = nextPhase
      render()
    },
    setProgress(nextProgress) {
      progress = Math.max(progress, Math.min(0.95, nextProgress))
      render()
    },
    stop() {
      process.removeListener("SIGINT", cleanup)
      process.removeListener("SIGTERM", cleanup)
      process.removeListener("exit", cleanup)
      close()
    },
  }
}

function captureTail(buffer, chunk) {
  const next = buffer + chunk.toString()
  return next.length > 8000 ? next.slice(-8000) : next
}

function extractWarnings(output) {
  const warningLines = []
  for (const line of output.split(/\r?\n/)) {
    const trimmed = line.trim()
    if (!trimmed) continue
    if (/\b(warn|warning|deprecated|unmet peer|peer dep|ignored build scripts)\b/i.test(trimmed)) {
      warningLines.push(trimmed)
    }
  }
  return [...new Set(warningLines)].slice(0, 4)
}

export async function runInstallWithAnimation(pm, targetDir) {
  const interlude = createInterlude()
  const { command, args } = installCommand(pm)

  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      cwd: targetDir,
      stdio: ["ignore", "pipe", "pipe"],
      env: {
        ...process.env,
        FORCE_COLOR: "0",
        EXXAT_SYNC_EXTRAS_QUIET: "1",
        EXXAT_SYNC_EXTRAS_SKIP_HOOK_NOTICE: "1",
        EXXAT_SYNC_EXTRAS_SKIP_UPGRADE_NOTICE: "1",
      },
    })

    let outputTail = ""
    const gatheringTimer = setTimeout(() => {
      interlude.setPhase("Gathering the pieces")
      interlude.setProgress(0.46)
    }, 1800)
    const wiringTimer = setTimeout(() => {
      interlude.setPhase("Wiring the playbook")
      interlude.setProgress(0.68)
    }, 5200)
    const polishTimer = setTimeout(() => {
      interlude.setPhase("Polishing the first prompt")
      interlude.setProgress(0.82)
    }, 9200)

    child.stdout.on("data", (chunk) => {
      outputTail = captureTail(outputTail, chunk)
    })
    child.stderr.on("data", (chunk) => {
      outputTail = captureTail(outputTail, chunk)
    })

    function clearTimers() {
      clearTimeout(gatheringTimer)
      clearTimeout(wiringTimer)
      clearTimeout(polishTimer)
    }

    child.on("close", (code) => {
      clearTimers()
      interlude.stop()
      if (code === 0) {
        resolve({ warnings: extractWarnings(outputTail) })
        return
      }
      console.error(`\n   Install hit trouble (exit ${code}).`)
      if (outputTail.trim()) {
        console.error("\n   Last install output:\n")
        console.error(outputTail.trim())
      }
      console.error(`\n   Retry: cd ${targetDir} && ${pm.installCmd}\n`)
      reject(new Error("install failed"))
    })

    child.on("error", (err) => {
      clearTimers()
      interlude.stop()
      reject(err)
    })
  })
}
