#!/usr/bin/env nu
# Result Type Pattern - Hybrid error handling without try-catch
# Combines preconditions (fail-fast), Result pattern, and functional composition
# Version: 1.0
#
# Usage:
#   use lib_provisioning/result.nu *
#
#   def my-operation []: record {
#       if (precondition-fails) { return (err "message") }
#       ok {result: "value"}
#   }

# Construct success result with value
# Type: any -> {ok: any, err: null}
export def ok [value: any] {
    {ok: $value, err: null}
}

# Construct error result with message
# Type: string -> {ok: null, err: string}
export def err [message: string] {
    {ok: null, err: $message}
}

# Check if result is successful
# Type: record -> bool
export def is-ok [result: record] {
    $result.err == null
}

# Check if result is error
# Type: record -> bool
export def is-err [result: record] {
    $result.err != null
}

# Monadic bind: chain operations on Results
# Type: record, closure -> record
# Stops propagation on error
export def and-then [result: record, fn: closure] {
    if (is-ok $result) {
        do $fn $result.ok
    } else {
        $result  # Propagate error
    }
}

# Map over Result value without stopping on error
# Type: record, closure -> record
export def map [result: record, fn: closure] {
    if (is-ok $result) {
        ok (do $fn $result.ok)
    } else {
        $result
    }
}

# Map over Result error
# Type: record, closure -> record
export def map-err [result: record, fn: closure] {
    if (is-err $result) {
        err (do $fn $result.err)
    } else {
        $result
    }
}

# Unwrap Result or return default
# Type: record, any -> any
export def unwrap-or [result: record, default: any] {
    if (is-ok $result) {
        $result.ok
    } else {
        $default
    }
}

# Unwrap Result or throw error
# Type: record -> any (throws if error)
export def unwrap! [result: record] {
    if (is-ok $result) {
        $result.ok
    } else {
        error make {msg: $result.err}
    }
}

# Combine two Results (stops on first error)
# Type: record, record -> record
export def combine [result1: record, result2: record] {
    if (is-err $result1) {
        return $result1
    }
    if (is-err $result2) {
        return $result2
    }
    ok {first: $result1.ok, second: $result2.ok}
}

# Combine list of Results (stops on first error)
# Type: list -> record
export def combine-all [results: list] {
    mut accumulated = (ok [])

    for result in $results {
        if (is-err $accumulated) {
            break
        }
        $accumulated = (and-then $accumulated {|acc|
            if (is-ok $result) {
                ok ($acc | append $result.ok)
            } else {
                err $result.err
            }
        })
    }

    $accumulated
}

# Try operation with automatic error wrapping
# Type: closure -> record
# Catches Nushell errors and wraps them (no try-catch)
export def try-wrap [fn: closure] {
    let result = (do { do $fn } | complete)
    if $result.exit_code == 0 {
        ok ($result.stdout)
    } else {
        err $result.stderr
    }
}

# Match on Result (like Rust's match)
# Type: record, closure, closure -> any
export def match-result [result: record, on_ok: closure, on_err: closure] {
    if (is-ok $result) {
        do $on_ok $result.ok
    } else {
        do $on_err $result.err
    }
}

# Execute bash command and wrap result
# Type: string -> record
# Returns: {ok: output, err: null} on success; {ok: null, err: message} on error (no try-catch)
export def bash-wrap [cmd: string] {
    let result = (do { bash -c $cmd } | complete)
    if $result.exit_code == 0 {
        ok ($result.stdout | str trim)
    } else {
        err $"Command failed: ($result.stderr)"
    }
}

# Execute bash command, check exit code
# Type: string -> record
# Returns: {ok: {exit_code: int, stdout: string}, err: null} or {ok: null, err: message} (no try-catch)
export def bash-check [cmd: string] {
    let result = (do { bash -c $cmd | complete } | complete)
    if $result.exit_code == 0 {
        let bash_result = ($result.stdout)
        if ($bash_result.exit_code == 0) {
            ok $bash_result
        } else {
            err ($bash_result.stderr)
        }
    } else {
        err $"Command failed: ($result.stderr)"
    }
}

# Try bash command with fallback value
# Type: string, any -> any
# Returns value on success, fallback on error (no try-catch)
export def bash-or [cmd: string, fallback: any] {
    let result = (do { bash -c $cmd } | complete)
    if $result.exit_code == 0 {
        ($result.stdout | str trim)
    } else {
        $fallback
    }
}

# Read JSON file safely
# Type: string -> record
# Returns: {ok: parsed_json, err: null} or {ok: null, err: message} (no try-catch)
export def json-read [file_path: string] {
    let read_result = (do { open $file_path | from json } | complete)
    if $read_result.exit_code == 0 {
        ok ($read_result.stdout)
    } else {
        err $"Failed to read JSON from ($file_path): ($read_result.stderr)"
    }
}

# Write JSON to file safely
# Type: string, any -> record
# Returns: {ok: true, err: null} or {ok: false, err: message} (no try-catch)
export def json-write [file_path: string, data: any] {
    let json_str = ($data | to json)
    let write_result = (do { bash -c $"cat > ($file_path) << 'EOF'\n($json_str)\nEOF" } | complete)
    if $write_result.exit_code == 0 {
        ok true
    } else {
        err $"Failed to write JSON to ($file_path): ($write_result.stderr)"
    }
}
