#!/usr/bin/env bash
# incusos-iso - Build IncusOS ISO/IMG images using the official flasher-tool
#
# Wrapper around the IncusOS flasher-tool that adds cross-architecture support,
# automatic seed generation, client certificate injection, and batch builds.
#
# Usage: incusos-iso [OPTIONS]
# Run 'incusos-iso --help' for full usage information.

set -euo pipefail

readonly VERSION="0.1.0"
readonly SCRIPT_NAME="incusos-iso"
readonly CDN_INDEX="https://images.linuxcontainers.org/os/index.json"
readonly CDN_BASE="https://images.linuxcontainers.org/os"

# ---------------------------------------------------------------------------
# Color and formatting
# ---------------------------------------------------------------------------

setup_colors() {
    if [[ -t 1 ]] && [[ "${NO_COLOR:-}" != "1" ]] && [[ "${TERM:-}" != "dumb" ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    else
        RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' RESET=''
    fi
}

info()    { echo -e "${BLUE}[info]${RESET}    $*"; }
success() { echo -e "${GREEN}[ok]${RESET}      $*"; }
warn()    { echo -e "${YELLOW}[warn]${RESET}    $*" >&2; }
error()   { echo -e "${RED}[error]${RESET}   $*" >&2; }
step()    { echo -e "${CYAN}[step]${RESET}    ${BOLD}$*${RESET}"; }
detail()  { echo -e "${DIM}          $*${RESET}"; }

# ---------------------------------------------------------------------------
# Help
# ---------------------------------------------------------------------------

usage() {
    cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} v${VERSION} - Build IncusOS ISO/IMG images

${BOLD}USAGE${RESET}
    ${SCRIPT_NAME} [OPTIONS]

${BOLD}OPTIONS${RESET}
    ${BOLD}-a, --arch${RESET} ARCH          Target architecture: ${CYAN}x86_64${RESET}, ${CYAN}aarch64${RESET}
                             (default: auto-detect current system)
    ${BOLD}-A, --app${RESET} APP            Application: ${CYAN}incus${RESET}, ${CYAN}operations-center${RESET}
                             (default: incus)
    ${BOLD}-d, --defaults${RESET}           Apply default configuration (ZFS pool, bridge, port 8443)
    ${BOLD}-c, --cert${RESET} FILE          Path to client certificate PEM file
                             (default: auto-detect from local Incus installation)
        ${BOLD}--no-cert${RESET}            Do not inject any client certificate
    ${BOLD}-F, --format${RESET} FORMAT      Image format: ${CYAN}iso${RESET}, ${CYAN}img${RESET} (default: iso)
    ${BOLD}-o, --output${RESET} FILE        Output filename (default: auto-generated)
    ${BOLD}-C, --channel${RESET} CHANNEL    Update channel: ${CYAN}stable${RESET}, ${CYAN}testing${RESET} (default: stable)

  ${BOLD}Seed options:${RESET}
    ${BOLD}-s, --seed${RESET} FILE          Use a pre-built seed tar archive (skip seed generation)
        ${BOLD}--disk-target${RESET} ID     Target disk ID pattern (e.g. "virtio-*")
        ${BOLD}--force-install${RESET}      Overwrite existing installation without prompting
        ${BOLD}--force-reboot${RESET}       Automatically reboot after installation
        ${BOLD}--missing-tpm${RESET}        Allow installation without TPM 2.0
        ${BOLD}--missing-secureboot${RESET} Allow installation without Secure Boot
        ${BOLD}--hostname${RESET} NAME      Set system hostname

  ${BOLD}Image source:${RESET}
    ${BOLD}-i, --image${RESET} FILE         Use a local base image (skip CDN download)

  ${BOLD}Batch mode:${RESET}
    ${BOLD}-b, --batch${RESET}              Generate all common ISO variants
        ${BOLD}--batch-arches${RESET} LIST  Architectures for batch mode (default: "x86_64,aarch64")
        ${BOLD}--batch-apps${RESET} LIST    Applications for batch mode (default: "incus,operations-center")
        ${BOLD}--output-dir${RESET} DIR     Output directory for batch mode (default: current directory)

  ${BOLD}General:${RESET}
        ${BOLD}--dry-run${RESET}            Show what would be done without executing
    ${BOLD}-q, --quiet${RESET}              Suppress informational output
    ${BOLD}-h, --help${RESET}               Show this help message
    ${BOLD}-V, --version${RESET}            Show version

${BOLD}EXAMPLES${RESET}
    # Build default ISO for current architecture (Incus, no defaults)
    ${SCRIPT_NAME}

    # Incus with defaults, for x86_64
    ${SCRIPT_NAME} --arch x86_64 --defaults

    # Operations Center for aarch64
    ${SCRIPT_NAME} --arch aarch64 --app operations-center --defaults

    # Use a pre-built seed archive
    ${SCRIPT_NAME} --seed my-config.tar --arch x86_64

    # Build USB image instead of ISO
    ${SCRIPT_NAME} --format img --defaults

    # Generate all common variants
    ${SCRIPT_NAME} --batch

    # Preview what would be built
    ${SCRIPT_NAME} --batch --dry-run

${BOLD}PREREQUISITES${RESET}
    The flasher-tool must be installed. Install with:
        go install github.com/lxc/incus-os/incus-osd/cmd/flasher-tool@latest

    Ensure \$GOPATH/bin (typically ~/go/bin) is in your PATH.

EOF
    exit 0
}

# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------

# Defaults
ARCH=""
APP="incus"
APPLY_DEFAULTS=false
CERT_FILE=""
NO_CERT=false
IMG_FORMAT="iso"
OUTPUT=""
CHANNEL="stable"
SEED_FILE=""
LOCAL_IMAGE=""
DISK_TARGET=""
FORCE_INSTALL=false
FORCE_REBOOT=false
MISSING_TPM=false
MISSING_SECUREBOOT=false
HOSTNAME_OPT=""
BATCH=false
BATCH_ARCHES="x86_64,aarch64"
BATCH_APPS="incus,operations-center"
OUTPUT_DIR="."
DRY_RUN=false
QUIET=false

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -a|--arch)
                ARCH="${2:?'--arch requires a value'}"
                shift 2
                ;;
            -A|--app)
                APP="${2:?'--app requires a value'}"
                shift 2
                ;;
            -d|--defaults)
                APPLY_DEFAULTS=true
                shift
                ;;
            -c|--cert)
                CERT_FILE="${2:?'--cert requires a file path'}"
                shift 2
                ;;
            --no-cert)
                NO_CERT=true
                shift
                ;;
            -F|--format)
                IMG_FORMAT="${2:?'--format requires a value'}"
                shift 2
                ;;
            -o|--output)
                OUTPUT="${2:?'--output requires a file path'}"
                shift 2
                ;;
            -C|--channel)
                CHANNEL="${2:?'--channel requires a value'}"
                shift 2
                ;;
            -s|--seed)
                SEED_FILE="${2:?'--seed requires a file path'}"
                shift 2
                ;;
            -i|--image)
                LOCAL_IMAGE="${2:?'--image requires a file path'}"
                shift 2
                ;;
            --disk-target)
                DISK_TARGET="${2:?'--disk-target requires a value'}"
                shift 2
                ;;
            --force-install)
                FORCE_INSTALL=true
                shift
                ;;
            --force-reboot)
                FORCE_REBOOT=true
                shift
                ;;
            --missing-tpm)
                MISSING_TPM=true
                shift
                ;;
            --missing-secureboot)
                MISSING_SECUREBOOT=true
                shift
                ;;
            --hostname)
                HOSTNAME_OPT="${2:?'--hostname requires a value'}"
                shift 2
                ;;
            -b|--batch)
                BATCH=true
                shift
                ;;
            --batch-arches)
                BATCH_ARCHES="${2:?'--batch-arches requires a value'}"
                shift 2
                ;;
            --batch-apps)
                BATCH_APPS="${2:?'--batch-apps requires a value'}"
                shift 2
                ;;
            --output-dir)
                OUTPUT_DIR="${2:?'--output-dir requires a value'}"
                shift 2
                ;;
            --dry-run)
                DRY_RUN=true
                shift
                ;;
            -q|--quiet)
                QUIET=true
                shift
                ;;
            -h|--help)
                usage
                ;;
            -V|--version)
                echo "${SCRIPT_NAME} v${VERSION}"
                exit 0
                ;;
            -*)
                error "Unknown option: $1"
                echo "Run '${SCRIPT_NAME} --help' for usage information." >&2
                exit 1
                ;;
            *)
                error "Unexpected argument: $1"
                echo "Run '${SCRIPT_NAME} --help' for usage information." >&2
                exit 1
                ;;
        esac
    done
}

# ---------------------------------------------------------------------------
# Validation
# ---------------------------------------------------------------------------

detect_arch() {
    local machine
    machine=$(uname -m)
    case "$machine" in
        x86_64)  echo "x86_64"  ;;
        aarch64) echo "aarch64" ;;
        arm64)   echo "aarch64" ;;
        *)
            error "Unsupported architecture: ${machine}"
            error "IncusOS supports x86_64 and aarch64 only"
            exit 1
            ;;
    esac
}

validate_args() {
    # Auto-detect architecture if not specified
    if [[ -z "$ARCH" ]]; then
        ARCH=$(detect_arch)
        [[ "$QUIET" != true ]] && info "Auto-detected architecture: ${ARCH}"
    fi

    case "$ARCH" in
        x86_64|aarch64) ;;
        *)
            error "Invalid architecture: ${ARCH}"
            error "Supported: x86_64, aarch64"
            exit 1
            ;;
    esac

    case "$APP" in
        incus|operations-center) ;;
        *)
            error "Invalid application: ${APP}"
            error "Supported: incus, operations-center"
            exit 1
            ;;
    esac

    case "$IMG_FORMAT" in
        iso|img) ;;
        *)
            error "Invalid image format: ${IMG_FORMAT}"
            error "Supported: iso, img"
            exit 1
            ;;
    esac

    case "$CHANNEL" in
        stable|testing) ;;
        *)
            error "Invalid channel: ${CHANNEL}"
            error "Supported: stable, testing"
            exit 1
            ;;
    esac

    if [[ -n "$CERT_FILE" ]] && [[ "$NO_CERT" == true ]]; then
        error "Cannot use both --cert and --no-cert"
        exit 1
    fi

    if [[ -n "$CERT_FILE" ]] && [[ ! -f "$CERT_FILE" ]]; then
        error "Certificate file not found: ${CERT_FILE}"
        exit 1
    fi

    if [[ -n "$SEED_FILE" ]] && [[ ! -f "$SEED_FILE" ]]; then
        error "Seed file not found: ${SEED_FILE}"
        exit 1
    fi

    if [[ -n "$LOCAL_IMAGE" ]] && [[ ! -f "$LOCAL_IMAGE" ]]; then
        error "Local image not found: ${LOCAL_IMAGE}"
        exit 1
    fi
}

# ---------------------------------------------------------------------------
# Dependency checks
# ---------------------------------------------------------------------------

FLASHER_TOOL=""

find_flasher_tool() {
    # Check common locations
    local candidates=(
        "flasher-tool"
        "${GOPATH:-${HOME}/go}/bin/flasher-tool"
        "${HOME}/go/bin/flasher-tool"
        "/usr/local/bin/flasher-tool"
    )

    for candidate in "${candidates[@]}"; do
        if command -v "$candidate" &>/dev/null; then
            FLASHER_TOOL="$candidate"
            return
        fi
        if [[ -x "$candidate" ]]; then
            FLASHER_TOOL="$candidate"
            return
        fi
    done
}

check_dependencies() {
    step "Checking dependencies"

    # flasher-tool
    find_flasher_tool
    if [[ -z "$FLASHER_TOOL" ]]; then
        error "flasher-tool not found"
        echo "" >&2
        echo "  Install the flasher-tool with:" >&2
        echo "    go install github.com/lxc/incus-os/incus-osd/cmd/flasher-tool@latest" >&2
        echo "" >&2
        echo "  Make sure \$GOPATH/bin is in your PATH:" >&2
        echo "    export PATH=\"\${GOPATH:-\$HOME/go}/bin:\$PATH\"" >&2
        echo "" >&2

        # Offer to install if Go is available
        if command -v go &>/dev/null; then
            echo -n "  Go is installed. Install flasher-tool now? [Y/n] " >&2
            local reply
            read -r reply
            if [[ "$reply" =~ ^[Yy]?$ ]]; then
                install_flasher_tool
            else
                exit 1
            fi
        else
            error "Go is not installed. Install Go 1.21+ first, then the flasher-tool."
            exit 1
        fi
    else
        success "flasher-tool: ${FLASHER_TOOL}"
    fi

    # curl (needed for cross-arch downloads)
    if ! command -v curl &>/dev/null; then
        warn "curl not found -- cross-architecture downloads will not be available"
    else
        detail "curl: $(command -v curl)"
    fi

    # gzip
    if ! command -v gzip &>/dev/null; then
        warn "gzip not found -- image decompression may fail"
    fi

    # tar
    if ! command -v tar &>/dev/null; then
        error "tar is required but not found"
        exit 1
    fi
}

install_flasher_tool() {
    step "Installing flasher-tool"
    info "Running: go install github.com/lxc/incus-os/incus-osd/cmd/flasher-tool@latest"

    if go install github.com/lxc/incus-os/incus-osd/cmd/flasher-tool@latest; then
        find_flasher_tool
        if [[ -n "$FLASHER_TOOL" ]]; then
            success "flasher-tool installed: ${FLASHER_TOOL}"
        else
            # Try adding GOPATH/bin to PATH for this session
            export PATH="${GOPATH:-${HOME}/go}/bin:${PATH}"
            find_flasher_tool
            if [[ -n "$FLASHER_TOOL" ]]; then
                success "flasher-tool installed: ${FLASHER_TOOL}"
            else
                error "flasher-tool was installed but could not be found in PATH"
                exit 1
            fi
        fi
    else
        error "Failed to install flasher-tool"
        exit 1
    fi
}

# ---------------------------------------------------------------------------
# Certificate detection
# ---------------------------------------------------------------------------

CERT_CONTENT=""

detect_client_cert() {
    if [[ "$NO_CERT" == true ]]; then
        [[ "$QUIET" != true ]] && info "Skipping certificate injection (--no-cert)"
        return
    fi

    if [[ -n "$CERT_FILE" ]]; then
        step "Loading client certificate from ${CERT_FILE}"
        CERT_CONTENT=$(<"$CERT_FILE")
        if [[ "$CERT_CONTENT" != *"BEGIN CERTIFICATE"* ]]; then
            error "File does not appear to be a PEM certificate: ${CERT_FILE}"
            exit 1
        fi
        success "Certificate loaded"
        return
    fi

    # Auto-detection
    step "Auto-detecting client certificate"

    # Try incus CLI
    if command -v incus &>/dev/null; then
        local cert
        if cert=$(incus remote get-client-certificate 2>/dev/null); then
            CERT_CONTENT="$cert"
            success "Certificate obtained from Incus CLI"
            return
        fi
    fi

    # Try common file locations
    local cert_paths=(
        "${HOME}/.config/incus/client.crt"
        "${HOME}/snap/incus/common/config/client.crt"
        "${HOME}/.config/lxc/client.crt"
    )

    for path in "${cert_paths[@]}"; do
        if [[ -f "$path" ]]; then
            CERT_CONTENT=$(<"$path")
            success "Certificate loaded from ${path}"
            return
        fi
    done

    warn "No client certificate found -- image will require manual certificate setup"
}

# ---------------------------------------------------------------------------
# CDN operations
# ---------------------------------------------------------------------------

fetch_latest_version() {
    local channel="$1"

    step "Fetching latest IncusOS version (channel: ${channel})"

    local index_file
    index_file=$(mktemp "${TMPDIR:-/tmp}/incusos-index-XXXXXX.json")

    if ! curl -fsSL "$CDN_INDEX" -o "$index_file" 2>/dev/null; then
        error "Failed to fetch CDN index from ${CDN_INDEX}"
        rm -f "$index_file"
        exit 1
    fi

    local version=""

    # Parse with jq if available, otherwise use python3
    if command -v jq &>/dev/null; then
        version=$(jq -r \
            --arg ch "$channel" \
            '[.updates[] | select(.channels[] == $ch)] | sort_by(.version) | last | .version // empty' \
            "$index_file")
    elif command -v python3 &>/dev/null; then
        version=$(python3 -c "
import json, sys
data = json.load(open('$index_file'))
versions = [u['version'] for u in data.get('updates', []) if '$channel' in u.get('channels', [])]
if versions:
    print(sorted(versions)[-1])
" 2>/dev/null)
    else
        error "Either jq or python3 is required to parse the CDN index"
        rm -f "$index_file"
        exit 1
    fi

    rm -f "$index_file"

    if [[ -z "$version" ]]; then
        error "No version found for channel '${channel}'"
        exit 1
    fi

    success "Latest version: ${version}"
    echo "$version"
}

download_image() {
    local version="$1"
    local arch="$2"
    local format="$3"
    local output_path="$4"

    local url="${CDN_BASE}/${version}/${arch}/IncusOS_${version}.${format}.gz"
    local gz_path="${output_path}.gz"

    step "Downloading IncusOS image"
    detail "URL: ${url}"
    detail "Target: ${output_path}"

    if ! curl -fL --progress-bar "$url" -o "$gz_path"; then
        error "Failed to download image from ${url}"
        rm -f "$gz_path"
        exit 1
    fi

    step "Decompressing image"
    if ! gzip -d "$gz_path"; then
        error "Failed to decompress image"
        rm -f "$gz_path"
        exit 1
    fi

    success "Image ready: ${output_path}"
}

# ---------------------------------------------------------------------------
# Seed generation (inline, for when incusos-seed is not used separately)
# ---------------------------------------------------------------------------

generate_seed_tar() {
    local output_tar="$1"
    local workdir
    workdir=$(mktemp -d "${TMPDIR:-/tmp}/incusos-seed-XXXXXX")

    step "Generating seed archive"

    # install.yaml
    {
        echo "# Generated by ${SCRIPT_NAME} v${VERSION}"
        [[ "$FORCE_INSTALL" == true ]] && echo "force_install: true"
        [[ "$FORCE_REBOOT" == true ]] && echo "force_reboot: true"
        if [[ -n "$DISK_TARGET" ]]; then
            echo "target:"
            echo "  id: \"${DISK_TARGET}\""
        fi
        if [[ "$MISSING_TPM" == true ]] || [[ "$MISSING_SECUREBOOT" == true ]]; then
            echo "security:"
            [[ "$MISSING_TPM" == true ]]        && echo "  missing_tpm: true"
            [[ "$MISSING_SECUREBOOT" == true ]] && echo "  missing_secure_boot: true"
        fi
    } > "${workdir}/install.yaml"
    detail "install.yaml"

    # applications.yaml
    {
        echo "applications:"
        echo "  - name: ${APP}"
    } > "${workdir}/applications.yaml"
    detail "applications.yaml (${APP})"

    # App-specific seed
    case "$APP" in
        incus)
            {
                echo "apply_defaults: ${APPLY_DEFAULTS}"
                if [[ -n "$CERT_CONTENT" ]]; then
                    echo "preseed:"
                    echo "  server:"
                    echo "    certificates:"
                    echo "      - name: \"auto-injected-client\""
                    echo "        type: \"client\""
                    echo "        certificate: |"
                    while IFS= read -r line; do
                        echo "          ${line}"
                    done <<< "$CERT_CONTENT"
                fi
            } > "${workdir}/incus.yaml"
            detail "incus.yaml (defaults=${APPLY_DEFAULTS})"
            ;;
        operations-center)
            {
                echo "apply_defaults: ${APPLY_DEFAULTS}"
                if [[ -n "$CERT_CONTENT" ]]; then
                    echo "trusted_client_certificates:"
                    echo "  - |"
                    while IFS= read -r line; do
                        echo "    ${line}"
                    done <<< "$CERT_CONTENT"
                fi
            } > "${workdir}/operations-center.yaml"
            detail "operations-center.yaml (defaults=${APPLY_DEFAULTS})"
            ;;
    esac

    # network.yaml (optional)
    if [[ -n "$HOSTNAME_OPT" ]]; then
        {
            echo "dns:"
            echo "  hostname: \"${HOSTNAME_OPT}\""
        } > "${workdir}/network.yaml"
        detail "network.yaml (hostname=${HOSTNAME_OPT})"
    fi

    # update.yaml (optional)
    if [[ "$CHANNEL" != "stable" ]]; then
        {
            echo "channel: \"${CHANNEL}\""
            echo "auto_reboot: false"
            echo "check_frequency: \"6h\""
        } > "${workdir}/update.yaml"
        detail "update.yaml (channel=${CHANNEL})"
    fi

    # Create tar
    tar -cf "$output_tar" -C "$workdir" .
    rm -rf "$workdir"

    success "Seed archive: ${output_tar}"
}

# ---------------------------------------------------------------------------
# ISO/IMG building
# ---------------------------------------------------------------------------

generate_output_name() {
    local arch="$1"
    local app="$2"
    local defaults="$3"
    local format="$4"
    local dir="$5"

    local defaults_tag
    if [[ "$defaults" == true ]]; then
        defaults_tag="defaults"
    else
        defaults_tag="bare"
    fi

    local app_tag
    case "$app" in
        incus)              app_tag="incus" ;;
        operations-center)  app_tag="ops-center" ;;
    esac

    echo "${dir}/incusos-${app_tag}-${defaults_tag}-${arch}.${format}"
}

build_single() {
    local arch="$1"
    local app="$2"
    local defaults="$3"
    local format="$4"
    local output="$5"
    local seed="$6"
    local image="$7"

    echo ""
    echo -e "${BOLD}Building: ${CYAN}${output}${RESET}"
    echo -e "  Arch: ${arch}  App: ${app}  Defaults: ${defaults}  Format: ${format}"
    echo ""

    if [[ "$DRY_RUN" == true ]]; then
        info "Dry run -- skipping build"
        return
    fi

    local tmpdir
    tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/incusos-build-XXXXXX")
    trap 'rm -rf "$tmpdir"' RETURN

    # Generate seed if not provided
    local seed_tar="$seed"
    if [[ -z "$seed_tar" ]]; then
        seed_tar="${tmpdir}/seed.tar"

        # Save and set state for seed generation
        local save_app="$APP" save_defaults="$APPLY_DEFAULTS"
        APP="$app"
        APPLY_DEFAULTS="$defaults"

        generate_seed_tar "$seed_tar"

        APP="$save_app"
        APPLY_DEFAULTS="$save_defaults"
    fi

    # Determine image path
    local image_path="$image"
    local needs_download=false

    if [[ -z "$image_path" ]]; then
        # Check if we need a cross-architecture download
        local current_arch
        current_arch=$(detect_arch)

        if [[ "$arch" != "$current_arch" ]]; then
            # Cross-architecture: must download manually
            needs_download=true
        fi
        # Same architecture: flasher-tool can download itself
    fi

    # Build flasher-tool command
    local cmd=("$FLASHER_TOOL")
    cmd+=(--format "$format")
    cmd+=(--seed-tar "$seed_tar")
    cmd+=(--channel "$CHANNEL")

    if [[ "$needs_download" == true ]]; then
        # Download cross-arch image
        local version
        version=$(fetch_latest_version "$CHANNEL" | tail -1)
        image_path="${tmpdir}/IncusOS_${version}.${format}"
        download_image "$version" "$arch" "$format" "$image_path"
        cmd+=(--image "$image_path")
    elif [[ -n "$image_path" ]]; then
        cmd+=(--image "$image_path")
    fi

    step "Running flasher-tool"
    detail "${cmd[*]}"

    # The flasher-tool writes to the image in place when --image is given,
    # or downloads and writes when --image is not given.
    # We need to handle the output file.
    if [[ -n "$image_path" ]]; then
        # flasher-tool modifies the image in place
        "${cmd[@]}"

        # Move to final output location
        if [[ "$image_path" != "$output" ]]; then
            mv "$image_path" "$output"
        fi
    else
        # flasher-tool will download and create output interactively
        # We run it and let it produce the file, then rename
        "${cmd[@]}"

        # Find the produced file (flasher-tool outputs to current directory)
        local produced
        produced=$(ls -t IncusOS_*.${format} 2>/dev/null | head -1)
        if [[ -n "$produced" ]] && [[ "$produced" != "$output" ]]; then
            mv "$produced" "$output"
        fi
    fi

    echo ""
    success "Image built: ${output}"

    local size
    if [[ -f "$output" ]]; then
        size=$(du -h "$output" | cut -f1)
        detail "Size: ${size}"
    fi
}

# ---------------------------------------------------------------------------
# Batch mode
# ---------------------------------------------------------------------------

batch_build() {
    IFS=',' read -ra arches <<< "$BATCH_ARCHES"
    IFS=',' read -ra apps <<< "$BATCH_APPS"

    local total=0
    local variants=()

    # Build matrix: arch x app x defaults(true/false)
    for arch in "${arches[@]}"; do
        for app in "${apps[@]}"; do
            for defaults in true false; do
                local outfile
                outfile=$(generate_output_name "$arch" "$app" "$defaults" "$IMG_FORMAT" "$OUTPUT_DIR")
                variants+=("${arch}|${app}|${defaults}|${outfile}")
                total=$((total + 1))
            done
        done
    done

    echo -e "${BOLD}Batch build: ${total} variants${RESET}"
    echo ""

    local i=0
    for variant in "${variants[@]}"; do
        IFS='|' read -r arch app defaults outfile <<< "$variant"
        i=$((i + 1))
        echo -e "${BOLD}[${i}/${total}]${RESET} ${outfile}"

        if [[ "$DRY_RUN" == true ]]; then
            detail "arch=${arch} app=${app} defaults=${defaults} format=${IMG_FORMAT}"
            continue
        fi

        build_single "$arch" "$app" "$defaults" "$IMG_FORMAT" "$outfile" "$SEED_FILE" "$LOCAL_IMAGE"
    done

    echo ""
    if [[ "$DRY_RUN" == true ]]; then
        info "Dry run complete -- no images were built"
    else
        success "Batch build complete: ${total} images generated"
    fi
}

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------

print_summary() {
    echo ""
    echo -e "${BOLD}Build summary${RESET}"
    echo -e "  Architecture:  ${CYAN}${ARCH}${RESET}"
    echo -e "  Application:   ${CYAN}${APP}${RESET}"
    echo -e "  Defaults:      $(if [[ "$APPLY_DEFAULTS" == true ]]; then echo "${GREEN}yes${RESET}"; else echo "${YELLOW}no${RESET}"; fi)"
    echo -e "  Certificate:   $(if [[ -n "$CERT_CONTENT" ]]; then echo "${GREEN}injected${RESET}"; else echo "${DIM}none${RESET}"; fi)"
    echo -e "  Format:        ${IMG_FORMAT}"
    echo -e "  Channel:       ${CHANNEL}"
    echo -e "  Output:        ${BOLD}${OUTPUT}${RESET}"

    if [[ "$MISSING_TPM" == true ]]; then
        echo -e "  Security:      ${YELLOW}TPM not required${RESET}"
    fi

    echo ""

    if [[ -n "$CERT_CONTENT" ]]; then
        echo -e "${DIM}  The client certificate has been injected into the image.${RESET}"
        echo -e "${DIM}  After installation, add the remote with:${RESET}"
        echo -e "    incus remote add <name> <ip-address>"
    else
        echo -e "${DIM}  No certificate was injected. After installation:${RESET}"
        echo -e "    incus remote add <name> <ip-address> --accept-certificate"
    fi
    echo ""
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

main() {
    setup_colors
    parse_args "$@"
    validate_args

    if [[ "$QUIET" != true ]]; then
        echo -e "${BOLD}${SCRIPT_NAME}${RESET} v${VERSION}"
        echo ""
    fi

    if [[ "$DRY_RUN" != true ]]; then
        check_dependencies
    else
        find_flasher_tool
        if [[ -z "$FLASHER_TOOL" ]]; then
            FLASHER_TOOL="flasher-tool (not installed)"
        fi
    fi
    echo ""

    if [[ "$BATCH" == true ]]; then
        # In batch mode, cert detection applies to all builds
        if [[ -z "$SEED_FILE" ]]; then
            detect_client_cert
        fi
        echo ""
        batch_build
        return
    fi

    # Single build mode
    if [[ -z "$SEED_FILE" ]]; then
        detect_client_cert
    fi

    # Set default output name
    if [[ -z "$OUTPUT" ]]; then
        OUTPUT=$(generate_output_name "$ARCH" "$APP" "$APPLY_DEFAULTS" "$IMG_FORMAT" "$OUTPUT_DIR")
    fi

    build_single "$ARCH" "$APP" "$APPLY_DEFAULTS" "$IMG_FORMAT" "$OUTPUT" "$SEED_FILE" "$LOCAL_IMAGE"

    if [[ "$QUIET" != true ]] && [[ "$DRY_RUN" != true ]]; then
        print_summary
    fi
}

main "$@"
