#!/usr/bin/env nu

# Content Image Copier
#
# Copies images/ directories from page-bundle posts to the public static tree
# so they are served over HTTP.
#
# Page-bundle layout (source):
#   {content_root}/{type}/{lang}/{category}/{slug}/
#   ├── index.md
#   ├── index.ncl
#   └── images/
#       └── *.{png,jpg,webp,svg,gif,avif}
#
# After copy (destination):
#   {public_root}/content/{type}/{lang}/{category}/{slug}/images/
#
# Markdown references use absolute paths:
#   ![alt](/content/{type}/{lang}/{category}/{slug}/images/file.png)
#
# Usage:
#   nu copy-content-images.nu [--content-dir PATH] [--public-dir PATH]
#                             [--type TYPE] [--lang LANG] [--dry-run] [--verbose]
#
# Environment:
#   SITE_CONTENT_PATH  — overrides --content-dir default
#   SITE_PUBLIC_PATH   — overrides --public-dir default

def main [
    --content-dir: string = ""   # Content root (default: site/content)
    --public-dir:  string = ""   # Public assets root (default: site/public)
    --type:        string = ""   # Filter to one content type
    --lang:        string = ""   # Filter to one language
    --dry-run                    # Print what would be copied without writing
    --verbose                    # Print per-file details
] {
    let content_root = resolve_env "SITE_CONTENT_PATH" $content_dir "site/content"
    let public_root  = resolve_env "SITE_PUBLIC_PATH"  $public_dir  "site/public"

    if not ($content_root | path exists) {
        error make { msg: $"Content directory not found: ($content_root)" }
    }

    print $"(ansi cyan)Content Image Copier(ansi reset)"
    print $"  content : ($content_root)"
    print $"  public  : ($public_root)/content"
    if $dry_run { print $"(ansi yellow)[dry-run](ansi reset)" }

    let types = discover_dirs $content_root $type
    if ($types | is-empty) { print "No content types found."; return }

    mut total_copied  = 0
    mut total_skipped = 0
    mut total_errors  = 0

    for ct in $types {
        let langs = discover_dirs $"($content_root)/($ct)" $lang
        for language in $langs {
            let lang_dir = $"($content_root)/($ct)/($language)"
            let subdirs = discover_dirs $lang_dir ""
            for sub in $subdirs {
                let sub_dir = $"($lang_dir)/($sub)"
                # Check if this subdir is itself a page-bundle (flat layout: no category)
                if ($"($sub_dir)/index.md" | path exists) and ($"($sub_dir)/images" | path exists) {
                    # Flat page-bundle: treat as cat="" slug=$sub
                    let result = copy_category_images $lang_dir $ct $language "" $public_root $dry_run $verbose
                    $total_copied  = $total_copied  + $result.copied
                    $total_skipped = $total_skipped + $result.skipped
                    $total_errors  = $total_errors  + $result.errors
                    # Flat bundles handled at lang_dir level — break to avoid duplicate processing
                    break
                } else {
                    # Category layout: subdir contains page-bundles
                    let result = copy_category_images $sub_dir $ct $language $sub $public_root $dry_run $verbose
                    $total_copied  = $total_copied  + $result.copied
                    $total_skipped = $total_skipped + $result.skipped
                    $total_errors  = $total_errors  + $result.errors
                }
            }
        }

        # Copy language-neutral shared images from {ct}/_images/
        let shared = copy_shared_images $ct $content_root $public_root $dry_run $verbose
        $total_copied  = $total_copied  + $shared.copied
        $total_skipped = $total_skipped + $shared.skipped
        $total_errors  = $total_errors  + $shared.errors
    }

    print ""
    print $"(ansi green)Done(ansi reset) — copied: ($total_copied)  skipped: ($total_skipped)  errors: ($total_errors)"

    if $total_errors > 0 { exit 1 }
}

# Copy images/ from one category directory
def copy_category_images [
    cat_dir:     string,
    ct:          string,
    lang:        string,
    cat:         string,
    public_root: string,
    dry_run:     bool,
    verbose:     bool,
] {
    if not ($cat_dir | path exists) { return { copied: 0, skipped: 0, errors: 0 } }

    # Find slug dirs that have an images/ subdir.
    # Two layouts:
    #   page-bundle: {slug}/index.md exists  (projects, activities)
    #   flat-file:   {slug}.md exists in cat_dir (blog, recipes)
    let bundles = (ls $cat_dir)
        | where type == "dir"
        | get name
        | each { |p| $p | path basename }
        | where { |d| not ($d | str starts-with ".") }
        | where { |d|
            ($"($cat_dir)/($d)/images" | path exists) and (
                ($"($cat_dir)/($d)/index.md" | path exists) or
                ($"($cat_dir)/($d).md"       | path exists)
            )
        }

    if ($bundles | is-empty) { return { copied: 0, skipped: 0, errors: 0 } }

    mut copied  = 0
    mut skipped = 0
    mut errors  = 0

    for slug in $bundles {
        let src_images = $"($cat_dir)/($slug)/images"
        let dst_images = if $cat == "" {
            $"($public_root)/content/($ct)/($lang)/($slug)/images"
        } else {
            $"($public_root)/content/($ct)/($lang)/($cat)/($slug)/images"
        }

        # Collect image files
        let img_files = try {
            (ls $src_images)
                | where type == "file"
                | where { |e|
                    let ext = ($e.name | path parse | get extension | str downcase)
                    ($ext in ["png", "jpg", "jpeg", "webp", "svg", "gif", "avif"])
                }
                | get name
        } catch { [] }

        if ($img_files | is-empty) {
            if $verbose { print $"    skip ($ct)/($lang)/($cat)/($slug)/images — no image files" }
            continue
        }

        if $verbose { print $"  ($ct)/($lang)/($cat)/($slug): ($img_files | length) images" }

        for img in $img_files {
            let dst_file = $"($dst_images)/($img | path basename)"

            # Skip if destination is identical (same size)
            if ($dst_file | path exists) {
                let src_size = (ls $img | get size | first)
                let dst_size = (ls $dst_file | get size | first)
                if $src_size == $dst_size {
                    if $verbose { print $"    skip ($img | path basename) — unchanged" }
                    $skipped = $skipped + 1
                    continue
                }
            }

            if $dry_run {
                print $"    [dry-run] ($img | path basename) → ($dst_file)"
                $copied = $copied + 1
                continue
            }

            let result = try {
                mkdir $dst_images
                cp $img $dst_file
                "ok"
            } catch { |e|
                print $"    (ansi red)ERROR(ansi reset) ($img | path basename): ($e.msg)"
                "error"
            }

            if $result == "ok" {
                if $verbose { print $"    (ansi green)copy(ansi reset) ($img | path basename)" }
                $copied = $copied + 1
            } else {
                $errors = $errors + 1
            }
        }
    }

    { copied: $copied, skipped: $skipped, errors: $errors }
}

# Copy {ct}/_images/ tree to public — language-neutral shared images
def copy_shared_images [
    ct:          string,
    content_root: string,
    public_root:  string,
    dry_run:      bool,
    verbose:      bool,
] {
    let src_root = $"($content_root)/($ct)/_images"
    let dst_root = $"($public_root)/content/($ct)/_images"
    if not ($src_root | path exists) { return { copied: 0, skipped: 0, errors: 0 } }

    mut copied = 0; mut skipped = 0; mut errors = 0

    # Walk cat/slug dirs
    let cats = (ls $src_root) | where type == "dir" | get name | each { |p| $p | path basename }
    for cat in $cats {
        let cat_dir = $"($src_root)/($cat)"
        let slugs   = (ls $cat_dir) | where type == "dir" | get name | each { |p| $p | path basename }
            | where { |d| $d != "pending" }
        for slug in $slugs {
            let slug_dir  = $"($cat_dir)/($slug)"
            let dst_slug  = $"($dst_root)/($cat)/($slug)"

            let img_files = try {
                (ls $slug_dir)
                    | where type == "file"
                    | where { |e|
                        let ext = ($e.name | path parse | get extension | str downcase)
                        ($ext in ["png", "jpg", "jpeg", "webp", "svg", "gif", "avif"])
                    }
                    | get name
            } catch { [] }

            for img in $img_files {
                let dst_file = $"($dst_slug)/($img | path basename)"
                if ($dst_file | path exists) {
                    let ss = (ls $img | get size | first)
                    let ds = (ls $dst_file | get size | first)
                    if $ss == $ds { $skipped = $skipped + 1; continue }
                }
                if $dry_run {
                    if $verbose { print $"    [dry-run] ($img | path basename) → ($dst_file)" }
                    $copied = $copied + 1; continue
                }
                let result = try { mkdir $dst_slug; cp $img $dst_file; "ok" } catch { |e|
                    print $"    (ansi red)ERROR(ansi reset) ($img | path basename): ($e.msg)"; "error"
                }
                if $result == "ok" {
                    if $verbose { print $"    (ansi green)copy(ansi reset) ($img | path basename)" }
                    $copied = $copied + 1
                } else { $errors = $errors + 1 }
            }
        }
    }

    { copied: $copied, skipped: $skipped, errors: $errors }
}

def resolve_env [var_name: string, cli_arg: string, fallback: string] {
    if $cli_arg != "" { return $cli_arg }
    try { $env | get $var_name } catch { $fallback }
}

def discover_dirs [parent: string, filter: string] {
    if not ($parent | path exists) { return [] }
    let all = (ls $parent)
        | where type == "dir"
        | get name
        | each { |p| $p | path basename }
        | where { |d| not ($d | str starts-with ".") }
        | where { |d| not ($d | str starts-with "_") }  # exclude _images, _shared, etc.
    if $filter != "" { $all | where { |d| $d == $filter } } else { $all }
}
