/**
 * Generate desktop wallpapers with CoreGraphics. Used for:
 *   - prepare-mac / setup stage: #2C3E50 deep blue-grey bg, device info only
 *   - agent running stage:       #1A2F1A deep moss-green bg, device info + agent name
 *
 * Each wallpaper is saved to ~/.visionclaw/wallpaper-<fingerprint>.png.
 * Using a fingerprint-specific filename forces macOS to reload the image
 * when content changes (Finder caches by path, not by file content).
 *
 * Zero external dependencies on macOS.
 */
import { execSync, spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { getCurrentVersion } from "../utils/version-check.js";
const WALLPAPER_META_FILENAME = "wallpaper.json";
/** Background colour when no agent name is set (setup / prepare-mac stage). */
const BG_SETUP = "#2C3E50";
/** Background colour when agent name is present (running stage). */
const BG_RUNNING = "#1A2F1A";
function getWallpaperDir() {
    return path.join(os.homedir(), ".visionclaw");
}
/**
 * Build the wallpaper PNG path for a given fingerprint.
 * Uses a fingerprint-specific filename so macOS is forced to reload
 * when content changes (Finder caches images by path).
 */
function wallpaperPathForFingerprint(fingerprint) {
    // Use the first 12 hex chars to keep filenames short
    return path.join(getWallpaperDir(), `wallpaper-${fingerprint.slice(0, 12)}.png`);
}
/**
 * Legacy fixed path kept only for backward-compatible cleanup.
 * @deprecated Use wallpaperPathForFingerprint() instead.
 */
export function getWallpaperPath() {
    return path.join(getWallpaperDir(), "wallpaper.png");
}
function getWallpaperMetaPath() {
    return path.join(getWallpaperDir(), WALLPAPER_META_FILENAME);
}
/** Read the wallpaper metadata, or null if missing / corrupt. */
function readWallpaperMeta() {
    try {
        const raw = fs.readFileSync(getWallpaperMetaPath(), "utf-8");
        const meta = JSON.parse(raw);
        if (meta.agentName !== undefined && meta.fingerprint !== undefined) {
            return {
                agentName: meta.agentName,
                fingerprint: meta.fingerprint,
                // eslint-disable-next-line @typescript-eslint/no-deprecated -- backward compat fallback for old meta without wallpaperPath
                wallpaperPath: meta.wallpaperPath ?? getWallpaperPath(),
            };
        }
        return null;
    }
    catch {
        return null;
    }
}
/** Persist wallpaper metadata. */
function writeWallpaperMeta(meta) {
    try {
        fs.mkdirSync(getWallpaperDir(), { recursive: true });
        fs.writeFileSync(getWallpaperMetaPath(), JSON.stringify(meta), "utf-8");
    }
    catch { /* best-effort */ }
}
/** Remove old wallpaper-*.png files that don't match the current fingerprint. */
function cleanupOldWallpapers(currentPath) {
    try {
        const dir = getWallpaperDir();
        for (const entry of fs.readdirSync(dir)) {
            if (entry.startsWith("wallpaper") && entry.endsWith(".png")) {
                const full = path.join(dir, entry);
                if (full !== currentPath) {
                    try {
                        fs.unlinkSync(full);
                    }
                    catch { /* best-effort */ }
                }
            }
        }
    }
    catch { /* best-effort */ }
}
/**
 * Lazily computed hash of the Swift renderer source. Changes whenever the
 * rendering code is modified (font, colours, layout, etc.), ensuring that
 * both the compiled binary cache AND the wallpaper fingerprint are
 * invalidated on upgrade.
 */
let _rendererHash;
function getRendererHash() {
    _rendererHash ??= createHash("sha256").update(swiftSource()).digest("hex").slice(0, 12);
    return _rendererHash;
}
/**
 * Compute a SHA-256 fingerprint covering everything that affects the output
 * image: row data, background colour, and the renderer source itself.
 * A change to any of these triggers wallpaper regeneration.
 */
function computeFingerprint(rows, bgHex) {
    const parts = [
        `bg=${bgHex}`,
        `renderer=${getRendererHash()}`,
        ...rows.map((r) => `${r.label}=${r.value}`),
    ];
    return createHash("sha256").update(parts.join("\n")).digest("hex");
}
// ---------------------------------------------------------------------------
// System info helpers
// ---------------------------------------------------------------------------
/**
 * Get the Mac serial number via ioreg.
 */
export function getMacSerialNumber() {
    try {
        return execSync(`ioreg -l | grep IOPlatformSerialNumber | awk -F'"' '{print $4}'`, { encoding: "utf-8", timeout: 10_000 }).trim();
    }
    catch {
        return "";
    }
}
/**
 * Get the macOS hostname via scutil.
 */
export function getMacHostname() {
    try {
        return execSync("scutil --get HostName", { encoding: "utf-8", timeout: 5_000 }).trim();
    }
    catch {
        try {
            return execSync("scutil --get ComputerName", { encoding: "utf-8", timeout: 5_000 }).trim();
        }
        catch {
            return "";
        }
    }
}
/**
 * macOS major version -> marketing codename map.
 * Versions 16–25 are not listed because Apple jumped the internal
 * version counter to 26 for macOS Tahoe (announced WWDC 2025).
 * Entries can be added here as future releases are announced.
 */
const MACOS_CODENAMES = {
    15: "Sequoia",
    14: "Sonoma",
    13: "Ventura",
    12: "Monterey",
    11: "Big Sur",
    26: "Tahoe",
};
/**
 * Get macOS version with codename, e.g. "Sequoia 15.3.2".
 */
export function getMacOSVersion() {
    try {
        const version = execSync("sw_vers -productVersion", { encoding: "utf-8", timeout: 5_000 }).trim();
        const major = parseInt(version.split(".")[0], 10);
        const codename = MACOS_CODENAMES[major];
        return codename ? `${codename} ${version}` : version;
    }
    catch {
        return "";
    }
}
/**
 * Get the marketing hardware model name including chip, e.g. "Mac mini (M4)".
 * Combines "Model Name" and "Chip" fields from system_profiler.
 * Falls back to sysctl hw.model identifier.
 */
export function getMacHardwareModelName() {
    try {
        const output = execSync("system_profiler SPHardwareDataType 2>/dev/null", { encoding: "utf-8", timeout: 10_000 });
        const modelMatch = /Model Name:\s*(.+)$/m.exec(output);
        const chipMatch = /Chip:\s*(.+)$/m.exec(output);
        if (modelMatch) {
            const modelName = modelMatch[1].trim();
            if (chipMatch) {
                // "Apple M4 Max" -> "M4 Max"
                const chip = chipMatch[1].trim().replace(/^Apple\s+/i, "");
                return `${modelName} (${chip})`;
            }
            return modelName;
        }
    }
    catch { /* fall through */ }
    try {
        return execSync("sysctl -n hw.model", { encoding: "utf-8", timeout: 5_000 }).trim();
    }
    catch {
        return "";
    }
}
// ---------------------------------------------------------------------------
// Swift renderer
// ---------------------------------------------------------------------------
/**
 * Swift source that generates a 4K PNG wallpaper.
 * Arguments: <jsonFilePath> <outputPath>
 *
 * JSON payload schema (read from file):
 *   { bgHex: string, rows: [{label: string, value: string}] }
 *
 * Rendering:
 *   - Background filled with bgHex
 *   - Each row: label (dim) right-aligned to max-label-width + " : " + value (bright)
 *   - Uses Helvetica Neue 40pt, drawn top-left with margin
 */
function swiftSource() {
    return `
import Foundation
import CoreGraphics
import CoreText
import ImageIO
import UniformTypeIdentifiers

// ── Parse arguments ──────────────────────────────────────────────────────────
guard CommandLine.arguments.count > 2 else {
  fputs("Usage: wallpaper <jsonFilePath> <outputPath>\\n", stderr)
  exit(1)
}
let jsonFilePath = CommandLine.arguments[1]
let outputPath = CommandLine.arguments[2]

// ── Parse JSON (read from file to avoid ARG_MAX / shell-escaping issues) ─────
struct WallpaperRow: Codable {
  let label: String
  let value: String
}
struct WallpaperPayload: Codable {
  let bgHex: String
  let rows: [WallpaperRow]
}
let payload: WallpaperPayload
do {
  let data = try Data(contentsOf: URL(fileURLWithPath: jsonFilePath))
  payload = try JSONDecoder().decode(WallpaperPayload.self, from: data)
} catch {
  fputs("Failed to read/parse JSON from \\(jsonFilePath): \\(error)\\n", stderr)
  exit(1)
}

// ── Parse hex color ──────────────────────────────────────────────────────────
func hexToComponents(_ hex: String) -> (CGFloat, CGFloat, CGFloat) {
  var s = hex.hasPrefix("#") ? String(hex.dropFirst()) : hex
  if s.count == 3 { s = s.map { "\\($0)\\($0)" }.joined() }
  guard s.count == 6, let val = UInt32(s, radix: 16) else { return (0, 0, 0) }
  return (
    CGFloat((val >> 16) & 0xFF) / 255.0,
    CGFloat((val >> 8) & 0xFF) / 255.0,
    CGFloat(val & 0xFF) / 255.0
  )
}
let (bgR, bgG, bgB) = hexToComponents(payload.bgHex)

// ── Canvas setup ─────────────────────────────────────────────────────────────
let width = 3840
let height = 2160

guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
      let ctx = CGContext(
        data: nil,
        width: width,
        height: height,
        bitsPerComponent: 8,
        bytesPerRow: 0,
        space: colorSpace,
        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
      ) else {
  fputs("Failed to create graphics context\\n", stderr)
  exit(1)
}

// Fill background
ctx.setFillColor(red: bgR, green: bgG, blue: bgB, alpha: 1.0)
ctx.fill(CGRect(x: 0, y: 0, width: width, height: height))

// ── Text styles ───────────────────────────────────────────────────────────────
// Label: dim
let labelColor = CGColor(srgbRed: 0.55, green: 0.65, blue: 0.75, alpha: 1.0)
// Value: bright white
let valueColor = CGColor(srgbRed: 0.92, green: 0.94, blue: 0.96, alpha: 1.0)

let fontSize: CGFloat = 40
let font = CTFontCreateWithName("Helvetica Neue" as CFString, fontSize, nil)

func makeAttrs(_ color: CGColor, _ f: CTFont) -> [String: Any] {
  return [
    kCTFontAttributeName as String: f,
    kCTForegroundColorAttributeName as String: color,
  ]
}

let labelAttrs = makeAttrs(labelColor, font)
let valueAttrs = makeAttrs(valueColor, font)
let colonAttrs = makeAttrs(labelColor, font)

// ── Colon alignment ───────────────────────────────────────────────────────────
func textWidth(_ text: String, _ attrs: [String: Any]) -> CGFloat {
  let attrStr = CFAttributedStringCreate(nil, text as CFString, attrs as CFDictionary)!
  let line = CTLineCreateWithAttributedString(attrStr)
  var asc: CGFloat = 0, desc: CGFloat = 0, lead: CGFloat = 0
  return CTLineGetTypographicBounds(line, &asc, &desc, &lead)
}

let maxLabelWidth = payload.rows.map { textWidth($0.label, labelAttrs) }.max() ?? 0

// ── Draw rows ─────────────────────────────────────────────────────────────────
func lineHeight() -> CGFloat {
  var asc: CGFloat = 0, desc: CGFloat = 0, lead: CGFloat = 0
  let dummy = CFAttributedStringCreate(nil, "Ag" as CFString, labelAttrs as CFDictionary)!
  CTLineGetTypographicBounds(CTLineCreateWithAttributedString(dummy), &asc, &desc, &lead)
  return asc + desc + lead
}

let rowSpacing: CGFloat = 14
let lh = lineHeight()

// ── Centre the text block in the canvas ──────────────────────────────────────
let colonW = textWidth(" : ", colonAttrs)
let maxValueWidth = payload.rows.map { textWidth($0.value, valueAttrs) }.max() ?? 0
let blockWidth  = maxLabelWidth + colonW + maxValueWidth
let blockHeight = Double(payload.rows.count) * lh + Double(payload.rows.count - 1) * rowSpacing
let originX = (Double(width)  - blockWidth)  / 2.0
let originY = (Double(height) + blockHeight) / 2.0 - lh  // top row baseline

var currentY = originY

for row in payload.rows {
  let lw    = textWidth(row.label, labelAttrs)
  let labelX = originX + (maxLabelWidth - lw)

  let labelStr = CFAttributedStringCreate(nil, row.label as CFString, labelAttrs as CFDictionary)!
  ctx.textPosition = CGPoint(x: labelX, y: currentY)
  CTLineDraw(CTLineCreateWithAttributedString(labelStr), ctx)

  let colonStr = CFAttributedStringCreate(nil, " : " as CFString, colonAttrs as CFDictionary)!
  let colonLine = CTLineCreateWithAttributedString(colonStr)
  let colonX = originX + maxLabelWidth
  ctx.textPosition = CGPoint(x: colonX, y: currentY)
  CTLineDraw(colonLine, ctx)

  let valueStr = CFAttributedStringCreate(nil, row.value as CFString, valueAttrs as CFDictionary)!
  ctx.textPosition = CGPoint(x: colonX + colonW, y: currentY)
  CTLineDraw(CTLineCreateWithAttributedString(valueStr), ctx)

  currentY -= (lh + rowSpacing)
}

// ── Write PNG ─────────────────────────────────────────────────────────────────
guard let image = ctx.makeImage() else {
  fputs("Failed to create image\\n", stderr)
  exit(1)
}
let url = URL(fileURLWithPath: outputPath)
guard let dest = CGImageDestinationCreateWithURL(
  url as CFURL,
  UTType.png.identifier as CFString,
  1,
  nil
) else {
  fputs("Failed to create image destination\\n", stderr)
  exit(1)
}
CGImageDestinationAddImage(dest, image, nil)
if !CGImageDestinationFinalize(dest) {
  fputs("Failed to write PNG\\n", stderr)
  exit(1)
}
`;
}
// ---------------------------------------------------------------------------
// Compile + run
// ---------------------------------------------------------------------------
/**
 * Compile and run the Swift wallpaper generator.
 * The compiled binary is cached in /tmp and keyed by a hash of the Swift
 * source, so a VisionClaw upgrade that changes the renderer automatically
 * triggers recompilation.
 */
function runWallpaperGenerator(data, outputPath) {
    const rendererHash = getRendererHash();
    const binFile = path.join(os.tmpdir(), `visionclaw-wallpaper-${rendererHash}`);
    // Recompile only when the binary for this source version is missing
    if (!fs.existsSync(binFile)) {
        const srcFile = path.join(os.tmpdir(), `visionclaw-wallpaper-${rendererHash}.swift`);
        fs.writeFileSync(srcFile, swiftSource(), "utf-8");
        const compile = spawnSync("swiftc", ["-O", "-o", binFile, srcFile], {
            encoding: "utf-8",
            timeout: 60_000,
            stdio: ["pipe", "pipe", "pipe"],
        });
        try {
            fs.unlinkSync(srcFile);
        }
        catch { /* cleanup */ }
        if (compile.status !== 0) {
            throw new Error(`Swift compilation failed: ${compile.stderr}`);
        }
    }
    // Write JSON payload to a temp file to avoid ARG_MAX / shell-escaping issues
    const jsonFile = path.join(os.tmpdir(), `visionclaw-wallpaper-payload-${process.pid}.json`);
    fs.writeFileSync(jsonFile, JSON.stringify(data), "utf-8");
    try {
        const run = spawnSync(binFile, [jsonFile, outputPath], {
            encoding: "utf-8",
            timeout: 30_000,
            stdio: ["pipe", "pipe", "pipe"],
        });
        if (run.status !== 0) {
            throw new Error(`Wallpaper generation failed: ${run.stderr}`);
        }
    }
    finally {
        try {
            fs.unlinkSync(jsonFile);
        }
        catch { /* cleanup */ }
    }
}
// ---------------------------------------------------------------------------
// Shared data builder
// ---------------------------------------------------------------------------
/**
 * Build the base rows common to all wallpaper stages.
 * Omits empty values so the layout stays clean.
 */
function buildBaseRows() {
    const rows = [];
    const model = getMacHardwareModelName();
    if (model)
        rows.push({ label: "Model", value: model });
    const serial = getMacSerialNumber();
    if (serial)
        rows.push({ label: "Serial", value: serial });
    const macos = getMacOSVersion();
    if (macos)
        rows.push({ label: "macOS", value: macos });
    const hostname = getMacHostname();
    if (hostname)
        rows.push({ label: "Hostname", value: hostname });
    const version = getCurrentVersion();
    if (version)
        rows.push({ label: "VisionClaw", value: version });
    return rows;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Resolve the background colour and row data for a wallpaper.
 * When agentName is provided → green (running); otherwise → blue-grey (setup).
 */
function buildWallpaperData(agentName) {
    const rows = buildBaseRows();
    const hasAgent = agentName !== undefined && agentName !== "";
    if (hasAgent)
        rows.push({ label: "Agent", value: agentName });
    return { bgHex: hasAgent ? BG_RUNNING : BG_SETUP, rows };
}
/**
 * Generate a wallpaper image.
 *   - With agent name → green bg, device info + agent name
 *   - Without agent name → blue-grey bg, device info only
 * Returns the path to the generated PNG.
 */
export function generateWallpaper(agentName, outputPath) {
    const data = buildWallpaperData(agentName);
    const fingerprint = computeFingerprint(data.rows, data.bgHex);
    const dest = outputPath ?? wallpaperPathForFingerprint(fingerprint);
    fs.mkdirSync(path.dirname(dest), { recursive: true });
    runWallpaperGenerator(data, dest);
    return dest;
}
/**
 * Generate a wallpaper, set it as the desktop background, and persist
 * metadata (agent name + content fingerprint) for change detection by
 * `ensureWallpaper()`.
 *
 * Uses a fingerprint-specific filename so macOS is forced to reload
 * the image when content changes (Finder caches by path, not by mtime).
 * Old wallpaper-*.png files are cleaned up after the new one is applied.
 *
 * Returns the path to the generated wallpaper.
 */
export function generateAndSetWallpaper(agentName) {
    const data = buildWallpaperData(agentName);
    const fingerprint = computeFingerprint(data.rows, data.bgHex);
    const dest = wallpaperPathForFingerprint(fingerprint);
    fs.mkdirSync(path.dirname(dest), { recursive: true });
    runWallpaperGenerator(data, dest);
    setDesktopWallpaper(dest);
    writeWallpaperMeta({
        agentName: agentName ?? "",
        fingerprint,
        wallpaperPath: dest,
    });
    cleanupOldWallpapers(dest);
    return dest;
}
/**
 * Set a PNG file as the macOS desktop wallpaper using AppleScript.
 */
export function setDesktopWallpaper(imagePath) {
    if (process.platform !== "darwin")
        return;
    execSync(`osascript -e 'tell application "Finder" to set desktop picture to POSIX file "${imagePath}"'`, { stdio: "ignore", timeout: 15_000 });
}
/**
 * Get the current macOS desktop wallpaper path via AppleScript.
 * Returns the POSIX path string, or "" on failure.
 */
export function getCurrentDesktopWallpaper() {
    if (process.platform !== "darwin")
        return "";
    try {
        return execSync(`osascript -e 'tell application "Finder" to get POSIX path of (desktop picture as alias)'`, { encoding: "utf-8", timeout: 15_000, stdio: ["pipe", "pipe", "pipe"] }).trim();
    }
    catch {
        return "";
    }
}
/**
 * Ensure the desktop wallpaper is set to the VisionClaw wallpaper.
 * Regenerates when:
 *   - the wallpaper file is missing
 *   - the agent name has changed (e.g. after reconfigure rename)
 *   - any system info has changed (version upgrade, hostname change, macOS update)
 *   - the renderer source has changed (VisionClaw upgrade)
 * Re-applies (without regeneration) when macOS is no longer using it
 * (manual change / reboot).
 *
 * Returns true if the wallpaper was (re-)applied, false if already correct.
 */
export function ensureWallpaper(agentName) {
    const meta = readWallpaperMeta();
    // Compute the expected fingerprint from current state
    const data = buildWallpaperData(agentName);
    const currentFingerprint = computeFingerprint(data.rows, data.bgHex);
    // Regenerate if agent name changed, content fingerprint drifted,
    // or the expected wallpaper file was deleted
    const expectedPath = wallpaperPathForFingerprint(currentFingerprint);
    const needsRegenerate = meta?.agentName !== agentName ||
        meta.fingerprint !== currentFingerprint ||
        !fs.existsSync(expectedPath);
    if (needsRegenerate) {
        generateAndSetWallpaper(agentName);
        return true;
    }
    // File is current — ensure macOS is actually using it
    const currentWallpaper = getCurrentDesktopWallpaper();
    if (currentWallpaper !== expectedPath) {
        setDesktopWallpaper(expectedPath);
        return true;
    }
    return false;
}
//# sourceMappingURL=generate-wallpaper.js.map