////
// Shell utilities and helper functions.

import { $ } from "bun"
import { statSync } from "fs"
import { setFatal } from "./fatal"
import { stat } from "fs/promises"

// Convert /Users/$USER or /home/$USER to ~ for simplicity
export function tilde(path: string): string {
    return path.replace(new RegExp(`/(Users|home)/${process.env.USER}`), "~")
}

// Convert ~ to /Users/$USER or /home/$USER for simplicity
export function untilde(path: string): string {
    const prefix = process.platform === 'darwin' ? 'Users' : 'home'
    return path.replace("~", `/${prefix}/${process.env.USER}`)
}

// Cause a fatal error if a directory doesn't exist.
export function expectDir(path: string): boolean {
    if (!isDir(path)) {
        setFatal(`Missing critical directory: ${path}`)
        return false
    }

    return true
}

// Cause a fatal error if a system binary doesn't exist.
export async function expectShellCmd(cmd: string): Promise<boolean> {
    try {
        await $`which ${cmd}`
        return true
    } catch {
        setFatal(`Missing critical dependency: avahi-publish`)
        return false
    }
}

// Is the given `path` a file?
export function isFile(path: string): boolean {
    try {
        const stats = statSync(path)
        return stats.isFile()
    } catch {
        return false
    }
}

// Is the given `path` a directory?
export function isDir(path: string): boolean {
    try {
        const stats = statSync(path)
        return stats.isDirectory()
    } catch {
        return false
    }
}

export async function mtime(path: string): Promise<Date> {
    const { mtime } = await stat(path)
    return mtime
}

export async function mtimeEpoch(path: string): Promise<string> {
    return (await mtime(path)).getTime().toString()
}

// is the given file binary?
export async function isBinaryFile(path: string): Promise<boolean> {
    // Create a stream to read just the beginning
    const file = Bun.file(path)
    const stream = file.stream()
    const reader = stream.getReader()

    try {
        // Read first chunk (typically 16KB, which is more than enough to detect binary)
        const { value } = await reader.read()
        if (!value) return false

        // Check first 512 bytes or less
        const bytes = new Uint8Array(value)
        const checkLength = Math.min(bytes.length, 512)
        for (let i = 0; i < checkLength; i++) {
            const byte = bytes[i]!
            if (byte === 0) return true // null byte
            if (byte < 32 && ![9, 10, 13].includes(byte)) return true // control char
        }

        return false
    } finally {
        reader.releaseLock()
        stream.cancel()
    }
}

const transpiler = new Bun.Transpiler({ loader: 'tsx' })
const transpileCache: Record<string, string> = {}

// Transpile the frontend *.ts file at `path` to JavaScript.
export async function transpile(path: string): Promise<string> {
    const { mtime } = await stat(path)
    const key = `${path}?${mtime}`

    let cached = transpileCache[key]
    if (!cached) {
        const code = await Bun.file(path).text()
        cached = transpiler.transformSync(code)
        cached = cached.replaceAll(/\bjsxDEV_?\w*\(/g, "jsx(")
        cached = cached.replaceAll(/\bFragment_?\w*,/g, "Fragment,")

        transpileCache[key] = cached
    }

    return cached
}
