#!/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 <<'__POD__'

=encoding utf-8

=head1 NAME

mdee - em·dee, Markdown Easy on the Eyes

=head1 SYNOPSIS

    mdee [ options ] file ...

     -h  --help             show help
         --version          show version
     -d  --debug            debug level (repeatable)
     -x  --[no-]trace       trace mode (set -x)
     -n  --dryrun           dry-run mode
     -s  --style=#          output style (nup/pager/cat/filter/raw)
     -f  --filter           shortcut for --style=filter
     -p  --plain            shortcut for --style=pager
         --[no-]fold        line folding (default: on)
         --[no-]table       table formatting (default: on)
         --[no-]nup         nup paged output (default: on)
         --[no-]rule        use Unicode rules for tables (default: on)
     -w  --width=#          fold width (default: 80)
     -t  --theme=#[,#,...]  color theme(s) (default: hashed)
     -m  --mode=#           light or dark (default: light)
     -B  --base-color=#     override base color of theme
                            (e.g., Ivory, #780043, (120,0,67))
         --show=#           set field visibility (e.g., italic=1)
     -C  --pane=#           number of columns
     -R  --row=#            number of rows
     -G  --grid=#           grid layout (e.g., 2x3)
     -P  --page=#           page height in lines
     -S  --pane-width=#     pane width (default: 85)
    --bs --border-style=#   border style
         --[no-]pager[=#]   pager command

=head1 VERSION

Version 0.17

=cut

__POD__

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

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

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

##############################################################################
# Theme file discovery
##############################################################################

find_share_dir() {
    perl -MFile::Share=dist_dir -e 'print dist_dir("App-mdee")' 2>/dev/null
}

# User theme directory
user_theme_dir="${XDG_CONFIG_HOME:-$HOME/.config}/mdee/theme"

# Share theme directory (development first, then installed)
share_theme_dir=""
_dev_share="$(cd "$(dirname "$0")/../share" 2>/dev/null && pwd)" || :
if [[ ${_dev_share:-} && -d "$_dev_share/theme" ]]; then
    share_theme_dir="$_dev_share/theme"
else
    _share_dir=$(find_share_dir) || :
    [[ $_share_dir ]] && share_theme_dir="$_share_dir/theme"
fi
unset _share_dir _dev_share

# Print unique elements, preserving order
uniq_array() {
    local -A _seen=()
    local _e
    for _e in "$@"; do
        [[ ${_seen[$_e]:-} ]] && continue
        _seen[$_e]=1
        printf '%s\n' "$_e"
    done
}

# Load a theme file by name
# Searches user_theme_dir then share_theme_dir, trying .sh and .bash
load_theme_file() {
    local name=$1
    local file
    for dir in "$user_theme_dir" "$share_theme_dir"; do
        [[ $dir ]] || continue
        for ext in sh bash; do
            file="$dir/${name}.${ext}"
            if [[ -f "$file" ]]; then
                . "$file"
                return 0
            fi
        done
    done
    return 1
}

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

define USAGE <<END
mdee - em·dee, Markdown Easy on the Eyes

Usage: $myname [ options ] file ...
END

declare -a nup_opts=(--document --no-filename)
declare -A show=()
declare -A default=()

declare -A adjust=([light]='=y25' [dark]='=y80')
declare -A OPTS=(
    [&REQUIRE]=0.7.2 [&USAGE]="$USAGE"
    [         help | h   !         # show help             ]=
    [      version |     !         # show version          ]=
    [        debug | d  +          # debug level           ]=0
    [        trace | x   !         # trace mode            ]=
    [       dryrun | n             # dry-run mode          ]=
    [        style | s  :          # output style          ]=
    [       filter | f   !         # filter mode           ]=
    [        plain | p   !         # plain mode            ]=
    [         fold |               # line folding          ]=_
    [        table |               # table formatting      ]=_
    [          nup |               # use nup               ]=_
    [         rule |               # table rule lines      ]=_
    [        width | w  :=i        # fold width            ]=0
    [        theme | t  @          # color theme           ]=hashed
    [         mode | m  :          # light or dark         ]=
    [   base-color | B  :          # base color            ]=
    [       adjust |    %          # color adjustment      ]=
    [         show |    %!         # field visibility      ]=
    [        pager |    ?!         # pager command         ]=
    [         pane | C  :=i        # number of columns     ]=0
    [   pane-width | S  :=i        # pane width            ]=85
    [         grid | G  :>nup_opts # grid layout           ]=
    [          row | R  :>nup_opts # number of rows        ]=
    [         page | P  :>nup_opts # page height           ]=
    [ border-style | bs :>nup_opts # border style          ]=
)

##############################################################################
# Theme base colors
##############################################################################

declare -A theme_light=([base]='<RoyalBlue>=y25')
declare -A theme_dark=([base]='<RoyalBlue>=y80')
declare -a md_config=()
declare -a show_fields=(
    comment bold strike italic link image image_link
    h1 h2 h3 h4 h5 h6 blockquote horizontal_rule
    code_mark code_info code_block code_inline
)

# Patterns (name-pattern pairs)
# Highlighting patterns are handled by App::Greple::md module.
# These are used by the run_fold() pipeline stage and table exclusion.
declare -a patterns_default=(
    comment      '^<!--(?![->])(?s:.+?)-->'
    code_block   '^ {0,3}(?<bt>`{3,}+|~{3,}+)(.*)\n((?s:.*?))^ {0,3}(\g{bt})'
    table        '^ {0,3}([│|├].+[│|┤]\n){3,}'
    item_prefix  '^\h*(?:[*-]|(?:\d+|#)[.)])\h+'
    def_pattern  '(?:\A|\G\n|\n\n).+\n\n?(:\h+.*\n)'
    autoindent   '^\h*(?:[*-]|(?:\d+|#)[.)]|:)\h+|^\h+'
)

# Build pattern associative array from patterns_default
# (built before theme loading so themes can modify patterns)
declare -A pattern
for ((_i=0; _i<${#patterns_default[@]}; _i+=2)); do
    _name=${patterns_default[_i]}
    [[ -v pattern[$_name] ]] && pattern[$_name]+='|'
    pattern[$_name]+=${patterns_default[_i+1]}
done

# User config file path
config_file="${XDG_CONFIG_HOME:-$HOME/.config}/mdee/config.sh"

# Detect mode from terminal background luminance
# Returns "dark" if luminance < 50, "light" otherwise
# Returns empty string if luminance cannot be determined
detect_terminal_mode() {
    local lum
    lum=$(perl -MGetopt::EX::termcolor=luminance -e luminance 2>/dev/null) || return
    [[ $lum ]] || return
    (( lum < 50 )) && echo dark || echo light
}

##############################################################################
# Option callbacks
##############################################################################

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

version() {
    echo "$my_version"
    exit 0
}

pager() {
    [[ $pager ]] && nup_opts+=("--pager=$pager") || nup_opts+=("--no-pager")
}

filter() {
    style=filter
}

plain() {
    [[ $plain ]] && style=pager || style=nup
}

trace() {
    [[ $trace ]] && set -x || set +x
}

_theme_specified=
theme() { _theme_specified=1; }

show() {
    local arg=$2 key val
    if [[ $arg == *=* ]]; then
        key=${arg%%=*} val=${arg#*=}
    else
        key=$arg val=1
    fi
    if [[ $key == all ]]; then
        for k in "${show_fields[@]}"; do
            show[$k]=$val
        done
    fi
    # Individual key=value is handled by getoptlong.sh
}

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

. getoptlong.sh OPTS "$@"

##############################################################################
# Apply config defaults and resolve options
##############################################################################

if (( $# == 0 )) && [[ -t 0 ]] && [[ ${theme[0]:-} != '?' ]]; then
    help
fi

# Load user config (may set default[theme], default[mode], default[style], etc.)
[[ -f "$config_file" ]] && . "$config_file" || :

# Apply config defaults if theme not explicitly specified
if [[ ! $_theme_specified ]] && [[ ${default[theme]:-} ]]; then
    IFS=, read -ra theme <<< "${default[theme]}"
fi

# Set mode: explicit option > config default > terminal detection > "light"
: ${mode:=${default[mode]:-$(detect_terminal_mode)}}
: ${mode:=light}

# Apply config defaults for pane/pane-width
(( pane_width == 85 )) && pane_width=${default[pane_width]:-85}
(( pane == 0 ))        && pane=${default[pane]:-0}

# Calculate columns from terminal width and pane width
: ${COLUMNS:=$(tput cols)}
(( debug > 0 )) && echo "debug: COLUMNS=${COLUMNS:-unset} pane=$pane pane_width=$pane_width" >&2
if (( pane > 0 )); then
    _cols=$pane
elif [[ ${COLUMNS:-} ]]; then
    _cols=$(( COLUMNS / pane_width ))
    (( _cols < 1 )) && _cols=1
else
    _cols=0  # unknown
fi

# Apply style default: explicit option > config default > "nup"
: ${style:=${default[style]:-nup}}

# Apply width default: explicit option (non-zero) > config default > (actual pane width - 5)
# Actual pane width = COLUMNS / _cols (varies with column count)
if (( width == 0 )); then
    if [[ ${default[width]:-} ]]; then
        width=${default[width]}
    elif (( _cols > 0 )); then
        width=$(( ${COLUMNS:-80} / _cols - 5 ))
    else
        width=$(( pane_width - 5 ))
    fi
fi

# Style determines default values for fold, table, nup.
# Sentinel "_" means not explicitly set by user.
declare -A style_defaults
case $style in
    nup)    style_defaults=([fold]=1 [table]=1 [nup]=1 [rule]=1) ;;
    pager)  style_defaults=([fold]=1 [table]=1 [nup]=  [rule]=1) ;;
    cat)    style_defaults=([fold]=1 [table]=1 [nup]=  [rule]=1) ;;
    filter) style_defaults=([fold]=  [table]=1 [nup]=  [rule]=1) ;;
    raw)    style_defaults=([fold]=  [table]=  [nup]=  [rule]=)  ;;
    *)      die "unknown style: $style" ;;
esac
[[ $fold  == _ ]] && fold=${style_defaults[fold]}
[[ $table == _ ]] && table=${style_defaults[table]}
[[ $nup   != _ ]] && _nup_explicit=1
[[ $nup   == _ ]] && nup=${style_defaults[nup]}
[[ $rule  == _ ]] && rule=${style_defaults[rule]}
[[ ${rule:-} ]] && rule='│'

# Load theme files: each modifies theme_light/theme_dark/theme_cm arrays
while IFS= read -r _t; do
    if [[ -f "$_t" ]]; then
        . "$_t"
    else
        load_theme_file "$_t" || die "theme not found: $_t"
    fi
done < <(uniq_array "${theme[@]}")

# Override base color if specified via --base-color or default[base_color]
: ${base_color:=${default[base_color]:-}}

# Validate --show field names
is_show_field() {
    local name=$1
    for f in "${show_fields[@]}"; do [[ $f == "$name" ]] && return 0; done
    return 1
}
for name in "${!show[@]}"; do
    [[ $name == all ]] || is_show_field "$name" || die "unknown field: $name"
done

(( debug > 0 )) && {
    for _mode in light dark; do
        declare -n _ref="theme_${_mode}"
        for key in "${!_ref[@]}"; do
            printf "theme_%s[%s]='%s'\n" "$_mode" "$key" "${_ref[$key]//\'/\'\\\'\'}"
        done | sort >&2
    done
    for _e in "${md_config[@]}"; do
        printf "md_config: '%s'\n" "$_e" >&2
    done
    (( debug > 1 )) && {
        for key in "${!pattern[@]}"; do
            printf "pattern[%s]='%s'\n" "$key" "${pattern[$key]//\'/\'\\\'\'}"
        done | sort >&2
    }
}

##############################################################################
# Build command pipeline
##############################################################################

# Process files
(( $# == 0 )) && [[ $style == nup ]] && die "stdin mode not supported without -f option"
for file in "$@"; do
    [[ -e "$file" ]] || die "$file: No such file or directory"
done

# Define pipeline stage functions
invoke() {
    (( debug > 1 )) && echo "debug: $(printf '%q ' "$@")" >&2
    [[ ${dryrun:-} ]] && return
    "$@"
}

run_greple() {
    local -a md_opts=()

    # Pass mode and base_color via config
    local -a config_params=("mode=${mode}")

    # Determine effective base color
    # Priority: --base-color > theme's base > md module default
    if [[ $base_color ]]; then
        # Apply adjust for simple color names (md module handles full specs as-is)
        if [[ $base_color =~ ^[A-Za-z][A-Za-z0-9_]*$ ]]; then
            config_params+=("base_color=<${base_color}>${adjust[$mode]}")
        else
            config_params+=("base_color=${base_color}")
        fi
    else
        local -n _theme_ref="theme_${mode}"
        local _default_base
        [[ $mode == dark ]] && _default_base='<RoyalBlue>=y80' || _default_base='<RoyalBlue>=y25'
        if [[ ${_theme_ref[base]} != "$_default_base" ]]; then
            config_params+=("base_color=${_theme_ref[base]}")
        fi
    fi

    # Add table/rule params
    [[ $table ]] && config_params+=("table=1") || config_params+=("table=0")
    [[ $rule  ]] && config_params+=("rule=1")  || config_params+=("rule=0")

    # Add md_config entries from themes
    config_params+=("${md_config[@]}")

    local IFS=','
    md_opts+=("-Mmd::config(${config_params[*]})")
    unset IFS

    # Pass show visibility
    local name
    for name in "${!show[@]}"; do
        [[ $name == all ]] && continue
        md_opts+=(--show "${name}=${show[$name]}")
    done

    invoke greple "${md_opts[@]}" -- \
        --filestyle=once --color=always \
        "$@"
}

run_fold() {
    invoke greple \
        -Mtee "&ansifold" --crmode --autoindent="${pattern[autoindent]}" -sw${width} -- \
        --exclude "${pattern[code_block]}" \
        --exclude "${pattern[comment]}" \
        --exclude "${pattern[table]}" \
        -G -E "${pattern[item_prefix]}.*\\n" -E "${pattern[def_pattern]}" \
        --crmode --all --need=0 --no-color
}

run_nup()    { invoke nup "${nup_opts[@]}"; }
run_column() { invoke nup "${nup_opts[@]}" --no-pager --fold --pane=1; }
run_pager()  { invoke ${PAGER:-less}; }

# Set defaults for less environment
export LESS="${LESS:--R}"
export LESSANSIENDCHARS="${LESSANSIENDCHARS:-mK}"

# Add pane/pane-width to nup_opts
(( pane_width != 85 )) && nup_opts+=("--pane-width=$pane_width")
(( pane != 0 ))        && nup_opts+=("--pane=$pane")

# Collect pipeline stages
declare -a stages=(run_greple)
[[ $fold  ]] && stages+=(run_fold)
if [[ $nup ]]; then
    if (( _cols == 1 )) && [[ ! ${_nup_explicit:-} ]]; then
        stages+=(run_column run_pager)
    else
        stages+=(run_nup)
    fi
fi
[[ $style == pager ]] && stages+=(run_pager)

# Build and execute pipeline
# run_greple needs "$@" for file arguments; other stages read from stdin
pipeline='run_greple "$@"'
for stage in "${stages[@]:1}"; do
    pipeline+=" | $stage"
done

(( debug > 0 )) && echo "debug: stages: ${stages[*]}" >&2

if [[ ${dryrun:-} ]]; then
    if (( debug > 1 )); then
        for stage in "${stages[@]}"; do
            $stage "$@"
        done
    else
        echo "$pipeline"
    fi
else
    eval "$pipeline"
fi

: <<'__POD__'

=head1 DESCRIPTION

B<em·dee> (command: C<mdee>) is a terminal-based multi-column
Markdown text viewer with syntax highlighting, particularly
useful for reading documents generated by LLMs.

It displays Markdown text as-is with minimal formatting: syntax
highlighting, line folding for long list items, and table column
alignment.  Markup characters (C<**>, C<`>, C<#>, etc.) are
preserved in the output.  By default, closing hashes are appended
to h3-h6 headers (via the built-in C<hashed> theme) to make
heading levels visually distinct.  Link URLs are removed from the
display, showing only the link text (e.g., C<[text](url)> becomes
C<[text]>).  On terminals supporting OSC 8 hyperlinks, the link
text remains clickable.

It does not render Markdown into formatted output or reflow
paragraphs.  For full Markdown rendering, many other viewers are
available.  Combine them with L<nup(1)|App::nup> for similar paged output
(e.g., C<nup glow README.md>).

The pipeline combines L<greple(1)|App::Greple> for colorization and
L<nup(1)|App::nup> for multi-column paged output.

Supported elements: headers (h1-h6), bold, italic, strikethrough,
inline code, code blocks, HTML comments, tables, and list items.

=head2 Multi-column Layout and Pagination

By default, B<mdee> calculates the number of display columns by
dividing the terminal width by the pane width (default 85
characters).  This determines how the output is laid out and
paginated.

When two or more columns fit, L<nup(1)|App::nup> arranges output
in multi-column layout with page-by-page pagination — content is
split into terminal-height pages viewed through a pager.

When only one column fits, B<mdee> still uses nup for formatting
(borders, document layout) but disables page-by-page splitting,
so the content scrolls continuously in the pager.  This avoids
wasted space from page breaks on narrow terminals while
maintaining the same visual appearance.

To force page-by-page pagination even in single-column layout,
specify C<--nup> explicitly.  The C<--pane-width> (C<-S>) option
adjusts the column width used for this calculation, and
C<--pane> (C<-C>) sets the number of columns directly.

Use C<-p> (C<--style=pager>) for a simpler view without nup
formatting — highlighted output is piped directly through a
pager.  Use C<-f> (C<--style=filter>) to write highlighted
output to stdout without a pager, suitable for piping into
other commands.

=begin html

<p><img width="750" src="https://raw.githubusercontent.com/tecolicom/App-mdee/main/images/3-column.png">

=end html

=head1 INSTALLATION

=head2 Homebrew

Use L<tecolicom/tap|https://github.com/tecolicom/homebrew-tap>:

    brew tap tecolicom/tap
    brew install app-mdee

=head2 CPAN

    cpanm -n App::mdee

=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>

Set debug level.  Can be repeated for increasing verbosity.

=over 4

=item C<-d>

Show theme values (C<theme_light[]>/C<theme_dark[]>, C<md_config[]>)
and pipeline stage names.

=item C<-dd>

Above, plus pattern definitions (C<pattern[]>) and full command lines
for each pipeline stage.

=back

=item B<-x>, B<--trace>, B<--no-trace>

Enable or disable shell trace mode (C<set -x>).  Useful for
debugging script execution.  Can be toggled: C<-x --no-x> enables
then disables tracing.

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

Dry-run mode. Show the pipeline without executing.
With C<-dd>, shows expanded command lines for each stage instead
of function names.

=back

=head2 Processing Options

=over 4

=item B<-s> I<STYLE>, B<--style>=I<STYLE>

Set the output style.  Default is C<nup>.  Available styles:

=over 4

=item C<nup> - Full pipeline: fold + table + nup paged output (default)

=item C<pager> - Fold + table, output to pager (C<$PAGER> or C<less>)

=item C<cat> - Fold + table, output to stdout

=item C<filter> - Table only (no fold, no nup), suitable for piping

=item C<raw> - Highlight only (no fold, no table, no nup)

=back

The style sets the initial state of C<--fold>, C<--table>, and
C<--nup> options.  Subsequent options can override individual
settings:

    mdee -s filter --fold file.md    # filter + fold
    mdee -p --no-fold file.md        # pager without fold

=item B<-f>, B<--filter>

Shortcut for C<--style=filter>.  Reads from stdin (or files) and
outputs highlighted Markdown to stdout.  Disables line folding and
nup paged output, but keeps table formatting enabled.  Useful for
piping Markdown content through mdee for syntax highlighting.

=item B<-p>, B<--plain>

Shortcut for C<--style=pager>.  Enables fold and table formatting,
outputs through a pager (C<$PAGER> or C<less>) instead of nup.

=item B<--[no-]fold>

Enable or disable line folding for list items.  When enabled, long
lines in list items are wrapped with proper indentation using
L<ansifold(1)|App::ansifold>.  Default is enabled.

Supported list markers: C<*>, C<->, C<1.>, C<1)>, C<#.>, C<#)>.
The C<#.> and C<#)> forms are Pandoc's auto-numbered list syntax.

=item B<--[no-]table>

Enable or disable table formatting.  When enabled, Markdown tables
are formatted using L<ansicolumn(1)|App::ansicolumn> for aligned column display.
Default is enabled.

=item B<--[no-]nup>

Enable or disable L<nup(1)|App::nup> for multi-column paged output.
When disabled, output goes directly to stdout without formatting.
Default is enabled.  When explicitly specified, forces nup's
native pagination even in single-column layout (which normally
uses the pager instead).

=item B<--[no-]rule>

Enable or disable Unicode rule characters for table borders.
When enabled, ASCII pipe characters (C<|>) are replaced with
Unicode box-drawing characters (C<│>, C<├>, C<┤>, C<┼>) and
dashes (C<->) in separator lines are replaced with horizontal
rules (C<─>).  Default is enabled.

=item B<-w> I<N>, B<--width>=I<N>

Set the fold width for text wrapping.  Default is calculated from
C<--pane-width> minus 5 (margin for borders and padding), which
gives 80 when pane-width is 85.  Only effective when C<--fold> is
enabled.

=back

=head2 Theme Options

B<em·dee> supports color themes for customizing syntax highlighting.
Themes define colors for various Markdown elements (headers, code blocks,
bold text, etc.).

=over 4

=item B<-t> I<NAME>, B<--theme>=I<NAME>[,I<NAME>,...]

Select color themes.  The default is C<hashed>, which appends
closing hashes to h3-h6 headers.

Themes specified with C<--theme> are added to the default, not
replacing it.  Multiple themes can be specified as comma-separated
names or by repeating the option.  Each theme is applied in order
as a transformation:

    mdee --theme=warm file.md         # hashed (default) + warm
    mdee --theme=warm,hashed file.md  # same (hashed already in default)
    mdee --no-theme --theme=warm      # warm only (clear default first)
    mdee --no-theme file.md           # no theme

Use C<--no-theme> to clear the default theme.  It can be combined
with C<--theme> to start fresh.

Theme files are searched in the following order:

=over 4

=item 1. User theme directory: C<${XDG_CONFIG_HOME:-~/.config}/mdee/theme/NAME.sh>

=item 2. Share theme directory: installed with the distribution under C<auto/share/dist/App-mdee/theme/>

=back

Theme files are Bash scripts that can modify C<theme_light[base]>,
C<theme_dark[base]>, C<md_config[]>, and/or C<pattern[]>:

    # theme/warm.sh — change base color
    theme_light[base]='<Coral>=y25'
    theme_dark[base]='<Coral>=y80'

    # theme/hashed.sh — enable closing hashes on h3-h6
    md_config+=(hashed.h3=1 hashed.h4=1 hashed.h5=1 hashed.h6=1)

    # modify matching pattern
    pattern[link]='...'

Use C<-d> to dump current theme values in sourceable format.

=item B<-m> I<MODE>, B<--mode>=I<MODE>

Select light or dark mode.  Default is C<light>.

If the terminal supports background color detection (via
L<Getopt::EX::termcolor>), the mode is automatically selected based on
terminal luminance.

Each theme has light and dark variants optimized for different terminal
backgrounds.  The built-in C<default> theme uses RoyalBlue as the base
color with automatic luminance adjustment:

=over 4

=item C<light> - RoyalBlue with luminance 25 (dark text for light backgrounds)

=item C<dark> - RoyalBlue with luminance 80 (bright text for dark backgrounds)

=back

=item B<-B> I<COLOR>, B<--base-color>=I<COLOR>

Override the theme's base color.  The base color determines the overall
color scheme because all heading colors (h1, h2, h3, etc.) are derived
from it by adjusting luminance.  For example, h2 might be C<${base}+y20>
(base color with luminance increased by 20).

B<Simple color name> - luminance is adjusted automatically:

When you specify just a color name (without C<E<lt>E<gt>> brackets or
other syntax), the default luminance adjustment is applied based on
the current mode:

    -B RoyalBlue          # becomes <RoyalBlue>=y25 in light mode
                          # becomes <RoyalBlue>=y80 in dark mode

This makes it easy to try different colors without worrying about
luminance values.  The default adjustments (C<=y25> for light, C<=y80>
for dark) are designed to provide good contrast against typical terminal
backgrounds.

B<Full color specification> - used exactly as specified:

If you include C<E<lt>E<gt>> brackets, luminance modifiers, or use RGB
notation, the value is used as-is without any automatic adjustment:

    -B '<Ivory>'               # original color, no adjustment
    -B '<Ivory>=y50'           # explicit luminance 50
    -B '#780043'               # burgundy in hex
    -B '(120,0,67)'            # same color in RGB decimal

B<Customizing the default adjustment>:

The automatic luminance adjustment values can be customized with the
C<--adjust> option:

    --adjust light='=y30'      # use =y30 instead of =y25 for light mode
    --adjust dark='=y70'       # use =y70 instead of =y80 for dark mode

B<Note>: Basic ANSI color codes (C<R>, C<G>, C<B>, etc.) cannot be used
because heading variations require luminance adjustment, which only works
with full color specifications (X11 names, RGB hex, or RGB decimal).

=back

=head2 Highlight Options

=over 4

=item B<--show>=I<FIELD>[=I<VALUE>],...

Control field visibility for highlighting.  Empty value or C<0> disables
the field; any other value (including C<1>) enables it.

    --show italic           # enable italic
    --show bold=0           # disable bold
    --show all              # enable all fields
    --show all= --show bold # disable all, then enable only bold

Multiple fields can be specified with commas or by repeating the option.
The special field C<all> affects all fields and is processed first.

Available fields: C<comment>, C<bold>, C<italic>, C<strike>, C<h1>,
C<h2>, C<h3>, C<h4>, C<h5>, C<h6>, C<code_mark>, C<code_info>, C<code_block>,
C<code_inline>, C<link>, C<image>, C<image_link>.

All fields are enabled by default.

=back

=head2 Layout Options (passed to nup)

=over 4

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

Set the number of columns (panes).  When not specified (or 0),
the number of columns is calculated from the terminal width and
C<--pane-width>.  If only 1 column fits, nup formatting (borders
etc.) is preserved but pagination is handled by the pager instead.
Use C<--nup> to force nup's native pagination even in single column.

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

Set the number of rows.

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

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

=item B<-P> I<N>, B<--page>=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.  This value is
used to calculate the number of columns and the default fold
width.

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

Set the border style.

=back

=head2 Pager Options

=over 4

=item B<--[no-]pager>[=I<COMMAND>]

Set the pager command.  Use C<--pager=less> to specify a pager,
or C<--no-pager> to disable paging.

=back

=head1 CONFIGURATION

User configuration is loaded from:

    ${XDG_CONFIG_HOME:-~/.config}/mdee/config.sh

This is a shell script that can set defaults and override colors:

    # ~/.config/mdee/config.sh
    default[mode]='dark'             # set default mode
    default[theme]='warm'            # set default theme(s)
    default[style]='pager'           # set default style
    default[width]=100               # set default fold width
    default[base_color]='DarkCyan'   # set default base color

The C<default> associative array supports the following keys:

=over 4

=item C<default[mode]> - Corresponds to C<--mode> (e.g., C<dark>, C<light>)

=item C<default[theme]> - Corresponds to C<--theme> (e.g., C<warm>, C<warm,hashed>)

=item C<default[style]> - Corresponds to C<--style> (e.g., C<pager>, C<cat>)

=item C<default[width]> - Corresponds to C<--width> (e.g., C<100>)

=item C<default[pane_width]> - Corresponds to C<--pane-width> (e.g., C<100>)

=item C<default[pane]> - Corresponds to C<--pane> (e.g., C<2>)

=item C<default[base_color]> - Corresponds to C<--base-color> (e.g., C<DarkCyan>)

=back

B<Overriding theme colors and patterns>

Config.sh can modify theme variables and patterns directly, using the
same mechanism as theme files:

    # Change base color for both modes
    theme_light[base]='<DarkCyan>=y25'
    theme_dark[base]='<DarkCyan>=y80'

    # Enable md module features
    md_config+=(hashed.h3=1 hashed.h4=1 hashed.h5=1 hashed.h6=1)

    # Modify matching patterns
    pattern[link]='...'

Changing the base color automatically affects all derived colors
(h1, h2, bold, etc.) because the md module expands C<${base}>
references.

Use C<-d> to dump current theme and pattern values in sourceable format.

B<Color specification format>

Color specifications use L<Term::ANSIColor::Concise> format.
The C<FG/BG> notation specifies foreground and background colors
(e.g., C<L25DE/${base}> means gray foreground on base-colored background).
The C<${base}> string is expanded to the base color value after loading.

=head1 EXAMPLES

    mdee README.md              # view markdown file
    mdee -C2 document.md        # 2-column view
    mdee -G2x2 manual.md        # 2x2 grid (4-up)
    mdee -w60 narrow.md         # narrower text width
    mdee --no-pager file.md     # without pager
    mdee --no-nup file.md       # output to stdout without nup
    mdee --no-fold file.md      # disable line folding
    mdee --no-table file.md     # disable table formatting

    # Output styles
    mdee -s pager file.md       # fold + table, output to pager
    mdee -s cat file.md         # fold + table, output to stdout
    mdee -s filter file.md      # table only, no fold/nup
    mdee -s raw file.md         # highlight only

    # Style shortcuts
    mdee -p file.md             # same as --style=pager
    cat file.md | mdee -f       # highlight stdin (filter mode)
    mdee -f file.md             # highlight only (no paging)

    # Override individual settings
    mdee -f --fold file.md      # filter + fold
    mdee -p --no-fold file.md   # pager without fold

    # Theme examples
    mdee --mode=dark file.md               # use dark mode
    mdee --mode=light file.md              # use light mode
    mdee -B Ivory file.md                  # override base color
    mdee --mode=dark -B '#780043' file.md  # dark mode with burgundy
    mdee --theme=warm file.md              # warm (Coral) base color
    mdee --theme=warm,hashed file.md      # warm + closing hashes

=head1 DEPENDENCIES

This command requires the following:

=over 4

=item * L<App::Greple> - pattern matching and highlighting

=item * L<App::Greple::tee> - filter integration

=item * L<App::ansifold> - ANSI-aware text folding

=item * L<App::ansicolumn> - ANSI-aware column formatting

=item * L<App::nup> - N-up multi-column paged output

=item * L<App::ansiecho> - ANSI color output

=item * L<Getopt::Long::Bash> - bash option parsing

=item * L<Getopt::EX::termcolor> - terminal background detection

=back

=head1 IMPLEMENTATION

B<em·dee> is implemented as a Bash script that orchestrates multiple
specialized tools into a unified pipeline.  The architecture follows
Unix philosophy: each tool does one thing well, and they communicate
through standard streams.

The overall data flow is:

    Input File
        |
        v
    [greple -Mmd] --- Syntax Highlighting + Table Formatting
        |
        v
    [ansifold] --- Text Folding (optional)
        |
        v
    [nup] --- Paged Output (nup style)
        |         or
    [pager] --- Pager Output (pager style)
        |
        v
    Terminal

=head2 Pipeline Architecture

B<em·dee> dynamically constructs a pipeline based on enabled options.
Each stage is defined as a Bash function (e.g., C<run_greple>,
C<run_fold>).  The C<--dryrun> option displays the function-based
pipeline without execution.

=head3 Processing Stages

The pipeline consists of configurable stages.  Each stage can be
enabled or disabled independently using C<--[no-]fold>, C<--[no-]table>,
and C<--[no-]nup> options.

=head4 Syntax Highlighting

The first stage uses L<greple(1)|App::Greple> with the C<-G> (grep mode) and
C<--ci=G> (capture index) options to apply different colors to each
captured group in regular expressions.

Supported Markdown elements:

=over 4

=item * Headers (C<# h1> through C<###### h6>)

=item * Bold text (C<**bold**> or C<__bold__>)

=item * Italic text (C<*italic*> or C<_italic_>)

=item * Inline code (C<`code`>)

=item * Code blocks (fenced with C<```> or C<~~~>)

=item * HTML comments (C<< <!-- comment --> >>)

=back

Code block detection follows the CommonMark specification:

=over 4

=item * Opening fence: 0-3 spaces indentation, then 3+ backticks or tildes

=item * Closing fence: 0-3 spaces indentation, same character, same or more count

=item * Backticks and tildes cannot be mixed (C<```> must close with C<```>)

=back

=head4 Text Folding

The second stage wraps long lines in list items using L<ansifold(1)|App::ansifold>
via L<Greple::tee>.  It preserves ANSI escape sequences and maintains
proper indentation for nested lists.

Recognized list markers include C<*>, C<->, C<1.>, C<1)>, C<#.>,
and C<#)>.  The C<#> marker is Pandoc's auto-numbered list syntax.

The folding width is controlled by C<--width> option (default: 80).

=head4 Table Formatting

Table formatting is handled within the L<App::Greple::md> module
(after syntax highlighting, before output).  Markdown tables are
detected by the pattern C<^ {0,3}(\|.+\|\n){3,}> and formatted with
aligned columns using L<ansicolumn(1)|App::ansicolumn> while
preserving ANSI colors.  When C<--rule> is enabled (default),
ASCII pipe characters are replaced with Unicode box-drawing
characters (C<│>, C<├>, C<┤>, C<┼>, C<─>).

=head3 Output Stage

The final stage uses L<nup(1)|App::nup> to provide multi-column paged output.
Layout options (C<--pane>, C<--row>, C<--grid>, C<--page>) are passed
directly to nup.

=head2 Theme System

B<em·dee> implements a theme system where themes are transformations
applied to the built-in default theme.  Multiple themes can be
chained via C<--theme=NAME1,NAME2,...>.

=head3 Theme Structure

Color definitions are managed by the L<App::Greple::md> module.
The C<theme_light> and C<theme_dark> arrays contain only the base
color.  Theme files can modify the base color, pass configuration
to the md module via C<md_config[]>, and modify C<pattern[]>:

    # theme/warm.sh — change base color
    theme_light[base]='<Coral>=y25'
    theme_dark[base]='<Coral>=y80'

    # theme/hashed.sh — enable closing hashes
    md_config+=(hashed.h3=1 hashed.h4=1 hashed.h5=1 hashed.h6=1)

    # modify matching patterns
    pattern[link]='...'

The C<md_config[]> entries are passed as config parameters to the
L<App::Greple::md> module.

=head4 Base Color Expansion

The C<${base}> placeholder in color values is expanded by the md
module.  The base color is determined by C<--base-color> option
(default: RoyalBlue) with automatic luminance adjustment based on
mode (C<=y25> for light, C<=y80> for dark).

=head3 Color Specifications

Colors are specified using L<Term::ANSIColor::Concise> format.
The C<--cm> option maps colors to captured groups.  For example,
C<L00DE/${base}> specifies gray foreground on base-colored background.

The color specification supports modifiers:

=over 4

=item * C<+y10> / C<-y10>: Adjust luminance by percentage

=item * C<=y50>: Set absolute luminance

=item * C<D>: Bold, C<U>: Underline, C<E>: Erase line

=back

=head3 Terminal Mode Detection

B<em·dee> uses L<Getopt::EX::termcolor> to detect terminal background
luminance.  If luminance is below 50%, dark mode is automatically
selected.

=head1 LIMITATIONS

=head2 HTML Comments

Only HTML comments starting at the beginning of a line are highlighted.
Inline comments are not matched to avoid conflicts with inline code
containing comment-like text (e.g., C<< `<!-->` >>).

=head2 Emphasis

Emphasis patterns (bold and italic) do not span multiple lines.
Multi-line emphasis text is not supported.

=head2 Links

Link patterns do not span multiple lines.  The link text and URL must
be on the same line.

Links inside other highlighted elements (such as headings or bold
text) are not processed.

Reference-style links (C<[text][ref]> with C<[ref]: url> elsewhere)
are not supported.

=head2 OSC 8 Hyperlinks

Links are converted to OSC 8 terminal hyperlinks for clickable URLs:

=over 4

=item C<[text](url)> - C<[text]> links to url

=item C<![alt](url)> - C<![alt]> links to url (image)

=item C<[![alt](img)](url)> - C<!> links to img, C<[alt]> links to url

=back

This requires terminal support.  Compatible terminals include iTerm2,
Kitty, WezTerm, Ghostty, and recent versions of GNOME Terminal.
Apple's default Terminal.app does not support OSC 8.

When using C<less> as pager, version 566 or later is required with
C<-R> option.

For OSC 8 specification, see:
L<https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>

=head2 Less Environment Variables

When C<less> is used as pager (either directly via C<--style=pager> or
through C<nup>), the following environment variables affect behavior.
B<mdee> sets defaults for these when they are not already defined:

=over 4

=item C<LESS>

Default: C<-R>.  The C<-R> option is required for ANSI color
sequences to be displayed correctly.

=item C<LESSANSIENDCHARS>

Default: C<mK>.  This tells C<less> to recognize ANSI sequences
ending with C<m> (SGR color) and C<K> (erase line).  The erase line
sequence is used for background color rendering.

=back

If you already have these variables set in your environment, B<mdee>
does not override them.

=head1 SEE ALSO

L<nup(1)|App::nup>, L<greple(1)|App::Greple>, L<ansifold(1)|App::ansifold>, L<ansicolumn(1)|App::ansicolumn>

=head1 AUTHOR

Kazumasa Utashiro

=head1 LICENSE

Copyright 2026 Kazumasa Utashiro.

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

=cut

__POD__