#!/usr/bin/env bash
# vim: filetype=bash :  -*- mode: sh; sh-shell: bash; -*-

set -euo pipefail

define() { IFS='\n' read -r -d '' ${1} || true ; }

myname="${0##*/}"

define pod <<"=cut"

=encoding utf-8

=head1 NAME

nup - N-up output wrapper for optex -Mup

=head1 SYNOPSIS

    nup [ options ] file ...
    nup -e [ options ] command ...

    -h,   --help             show help
          --version          show version
    -d,   --debug            debug mode
    -n,   --dryrun           dry-run mode
    -e,   --exec             execute command mode
    -V,   --parallel         parallel view mode
    -F,   --fold             fold mode (disable page mode)
    -H,   --header           show file headers (default: on)
    -G,   --grid=#           grid layout (e.g., 2x3)
    -C,   --pane=#           number of columns
    -R,   --row=#            number of rows
          --height=#         page height in lines
    -S,   --pane-width=#     pane width (default: 85)
    --bs, --border-style=#   border style (default: heavy-box)
    --ls, --line-style=#     line style (none/truncate/wrap/wordwrap)
          --pager=#          pager command (empty to disable)
          --no-pager         disable pager

=head1 VERSION

Version 0.99

=cut

[[ $pod =~ Version\ +([0-9.]+) ]] && my_version=${BASH_REMATCH[1]}

# -h callback: show help from POD
help() {
    sed -E \
        -e '/^$/N' \
        -e 's/^(\n*)=head[0-9]* */\1/' \
        -e '/^\n*[#=]/d' \
        -e '/Version/q' \
        <<< "$pod"
    exit 0
}

# -v callback: show version
version() {
    echo "$my_version"
    exit 0
}

##############################################################################
# Utility functions
##############################################################################

debug() {
    [[ ${debug:-} ]] && echo "debug: $*" >&2 || true
}

die() {
    echo "$myname: $*" >&2
    exit 1
}

# Check bash version (4.3+ required for getoptlong.sh)
if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 3))); then
    die "bash 4.3+ required (found $BASH_VERSION)"
fi

##############################################################################
# Setup PATH for getoptlong.sh
##############################################################################

basedir="${0%/*/*}"
if [[ -f $basedir/share/getoptlong/getoptlong.sh ]]; then
    PATH="$basedir/share/getoptlong:$PATH"
else
    dist_dir() {
        perl -MFile::Share=:all -E "say dist_dir '$1'" 2>/dev/null || true
    }
    share=$(dist_dir App-nup)
    PATH="${share:+$share/getoptlong:}$PATH"
fi

##############################################################################
# Option definitions
##############################################################################

define USAGE <<END
nup - N-up output wrapper for optex -Mup

Usage: $myname [ options ] file ...
       $myname -e [ options ] command ...
END

declare -A OPTS=(
    [&REQUIRE]=0.4 [&USAGE]="$USAGE" [&PERMUTE]=
    [         help | h  ! # show help         ]=
    [      version |    ! # show version      ]=
    [        debug | d    # debug mode        ]=
    [       dryrun | n    # dry-run mode      ]=
    [         exec | e    # execute command   ]=
    [     parallel | V    # parallel view     ]=
    [         fold | F    # fold mode         ]=
    [       header | H    # show file headers ]=1
    [         grid | G  : # grid layout       ]=
    [         pane | C  : # number of columns ]=
    [          row | R  : # number of rows    ]=
    [       height |    : # page height       ]=
    [   pane-width | S  : # pane width        ]=
    [ border-style | bs : # border style      ]=
    [   line-style | ls : # line style        ]=
    [        pager |    : # pager command     ]=1
)

##############################################################################
# Parse options
##############################################################################

. getoptlong.sh OPTS "$@"

##############################################################################
# Build module options
##############################################################################

declare -a up_opts=()

[[ ${grid:-}         ]] && up_opts+=("--grid=$grid")
[[ ${pane:-}         ]] && up_opts+=("--pane=$pane")
[[ ${row:-}          ]] && up_opts+=("--row=$row")
[[ ${height:-}       ]] && up_opts+=("--height=$height")
[[ ${pane_width:-}   ]] && up_opts+=("--pane-width=$pane_width")
[[ ${border_style:-} ]] && up_opts+=("--border-style=$border_style")
[[ ${line_style:-}   ]] && up_opts+=("--line-style=$line_style")
if [[ $pager != 1 ]]; then
    if [[ -z $pager ]]; then
        up_opts+=("--no-pager")
    else
        up_opts+=("--pager=$pager")
    fi
fi
[[ $fold ]] && up_opts+=("--fold")

##############################################################################
# Main
##############################################################################

# Show help if no arguments and stdin is a terminal
if (( $# == 0 )) && [[ -t 0 ]]; then
    help
fi

# Determine mode: command or file view
# Command mode if: -e specified, OR ($1 is not a file AND $1 is a command)
is_command_mode() {
    [[ ${exec:-} ]] && return 0
    [[ -f ${1:-} ]] && return 1
    command -v "${1:-}" >/dev/null 2>&1
}

if is_command_mode "$@"; then
    # Command mode: use optex -Mup
    declare -a cmd=(optex -Mup "${up_opts[@]}" -- "$@")
else
    # File view mode: use ansicolumn via optex -Mup
    # up.pm adds default options (--bs, -DP, -C, etc.)
    # User-specified options are added here to override defaults
    declare -a ac_opts=()
    [[ ${parallel:-} ]] && ac_opts+=(--parallel)
    [[ $fold ]] && ac_opts+=(--no-page)
    [[ $header ]] && ac_opts+=(--filename)
    [[ ${border_style:-} ]] && ac_opts+=("--border-style=$border_style")
    [[ ${line_style:-}   ]] && ac_opts+=("--line-style=$line_style")
    declare -a cmd=(optex -Mup "${up_opts[@]}" -- ansicolumn "${ac_opts[@]}" "$@")
fi

debug "command: ${cmd[*]}"

if [[ ${dryrun:-} ]]; then
    echo "${cmd[@]}"
    exit 0
fi

exec "${cmd[@]}"

: <<'=cut'

=head1 DESCRIPTION

B<nup> is a simple wrapper script for C<optex -Mup>.  It provides a
convenient way to view files or run commands with N-up output
formatting using the L<App::optex::up> module.

B<nup> automatically detects the mode based on the first argument:
if it is an existing file, file view mode is used; if it is an
executable command, command mode is used.  Use C<-e> option to
force command mode when needed.

=head1 OPTIONS

=head2 General Options

=over 4

=item B<-h>, B<--help>

Show help message.

=item B<--version>

Show version.

=item B<-d>, B<--debug>

Enable debug mode.

=item B<-n>, B<--dryrun>

Dry-run mode. Show the command without executing.

=item B<-e>, B<--exec>

Force command execution mode. Normally the mode is auto-detected,
but use this option when you want to execute a file as a command.

=item B<-V>, B<--parallel>

Enable parallel view mode for ansicolumn.  In this mode, each file
is displayed in its own column without pagination, similar to
C<--fold>.  Automatically enabled when multiple files are
specified.  Single file or stdin input results in single column
output.

=item B<-F>, B<--fold>

Enable fold mode (disable page mode).  In fold mode, the entire
content is split evenly across columns without pagination.  Page
mode is the default.

=item B<-H>, B<--header>

Show filename headers in file view mode. Enabled by default.
Use C<--no-header> to disable.

=back

=head2 Layout Options

=over 4

=item B<-G> I<CxR>, B<--grid>=I<CxR>

Set grid layout. For example, C<-G2x3> creates 2 columns and 3 rows.

=item B<-C> I<N>, B<--pane>=I<N>

Set the number of columns (panes).

=item B<-R> I<N>, B<--row>=I<N>

Set the number of rows.

=item B<--height>=I<N>

Set the page height in lines.

=item B<-S> I<N>, B<--pane-width>=I<N>

Set the pane width in characters. Default is 85.

=back

=head2 Style Options

=over 4

=item B<--border-style>=I<STYLE>, B<--bs>=I<STYLE>

Set the border style. Default is C<heavy-box>.

=item B<--line-style>=I<STYLE>, B<--ls>=I<STYLE>

Set the line style. Available: C<none>, C<truncate>, C<wrap>, C<wordwrap>.

=back

=head2 Pager Options

=over 4

=item B<--pager>=I<COMMAND>

Set the pager command. Default is C<$PAGER> or C<less>.
Use C<--pager=> (empty) or C<--no-pager> to disable pager.

=item B<--no-pager>

Disable pager.

=back

=head1 EXAMPLES

    nup man nup                # view manual in multi-column
    nup -C2 man perl           # 2 columns
    nup -G2x2 man perl         # 2x2 grid (4-up)
    nup -F man perl            # fold mode (no pagination)
    nup file1.txt file2.txt    # view files side by side
    nup -e ./script.sh         # force command mode for a file

=head1 INSTALLATION

Using L<cpanminus|https://metacpan.org/pod/App::cpanminus>:

    cpanm -n App::nup

=head1 SEE ALSO

L<App::optex::up>, L<optex>

=head1 AUTHOR

Kazumasa Utashiro

=head1 LICENSE

Copyright 2025 Kazumasa Utashiro.

This software is released under the MIT License.
L<https://opensource.org/licenses/MIT>

=cut