package App::Tarotplane::UI;
our $VERSION = '2.02';
use 5.016;
use strict;
use warnings;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(%KEY_BINDINGS);

use Text::Wrap qw(wrap $columns);

use Curses;

# Tells whether Curses is running or not, so that multiple objects don't try to
# run Curses.
my $Cursed = 0;

our %KEY_BINDINGS = (
    Next  => [ 'l', KEY_RIGHT, ],
    Prev  => [ 'h', KEY_LEFT, ],
    Flip  => [ 'j', 'k', ' ', KEY_UP, KEY_DOWN, ],
    First => [ KEY_NPAGE, KEY_END, ],
    Last  => [ KEY_PPAGE, KEY_HOME, ],
    Quit  => [ 'q', ],
    Help  => [ '?', ],
);

my $CONTROLS_HELP = <<END;
Next:      right, l
Previous:  left, h
Flip:      up/down, j/k, space
First      card: page up, home
Last card: page down, end
Quit:      q
Help:      ?
END

sub init {

    my $class = shift;
    my $self = {
        MainWin => undef,
        CardWin => undef,
        InfoWin => undef,
        CardStr => '',
        InfoStr => '',
    };

    if ($Cursed) {
        die "Curses is already running";
    }

    bless $self, $class;

    initscr();

    curs_set(0);
    cbreak();
    noecho();
    keypad(1);

    $self->{MainWin} = $stdscr;

    $self->{InfoWin} = newwin(1, $COLS, $LINES - 1, 0);
    $self->{CardWin} = newwin($LINES - 3, $COLS - 2, 1, 1);

    $Cursed = 1;

    return $self;

}

sub wipe {

    my $self = shift;

    erase($self->{MainWin});
    noutrefresh($self->{MainWin});

}

sub update {

    my $self = shift;

    doupdate();

}

sub draw_card {

    my $self = shift;
    my $str  = shift;
    my $bold = shift;

    # Make room for the box borders and a single whitespace on each side.
    my $linemax = getmaxx($self->{CardWin}) - 4;

    $self->{CardStr} = $str if defined $str;

    $columns = $linemax;
    my $text = wrap('', '', $self->{CardStr});

    if (defined $bold) {
        attrset($self->{CardWin}, $bold ? A_BOLD : A_NORMAL);
    }

    erase($self->{CardWin});
    box($self->{CardWin}, 0, 0);

    my $ypos = (getmaxy($self->{CardWin}) / 2) - (($text =~ tr/\n//) / 2);

    foreach my $l (split /\n/, $text) {
        $l =~ s/^\s+|\s+$//; # Trim leading/trailing whitespace.
        $l =~ s/\s+/ /g;     # Truncate space.
        addstr($self->{CardWin}, $ypos++, (($linemax + 4) - length($l)) / 2, $l);
    }

    noutrefresh($self->{CardWin});

}

sub draw_info {

    my $self = shift;
    my $info = shift;

    $self->{InfoStr} = $info if defined $info;

    erase($self->{InfoWin});

    addstr($self->{InfoWin}, $self->{InfoStr});

    noutrefresh($self->{InfoWin});

}

sub draw_help {

    my $self = shift;

    my $y = 0;
    foreach my $l (split /\n/, $CONTROLS_HELP) {
        addstr($self->{MainWin}, $y++, 0, $l);
    }

    noutrefresh($self->{MainWin});

}

sub update_size {

    my $self = shift;

    $self->wipe();

    resize($self->{InfoWin}, 1, $COLS);
    mvwin($self->{InfoWin}, $LINES - 1, 0);

    resize($self->{CardWin}, $LINES - 3, $COLS - 2);
    mvwin($self->{CardWin}, 1, 1);

    $self->draw_card();
    $self->draw_info();

    $self->update();

}

sub poll {

    my $self = shift;

    while (my $in = getch()) {

        if ($in eq KEY_RESIZE) {
            $self->update_size();
            next;
        }

        foreach my $k (keys %KEY_BINDINGS) {
            return $k if grep { $in eq $_ } @{$KEY_BINDINGS{$k}};
        }

        return undef;

    }

}

sub end {

    my $self = shift;

    endwin();

    foreach my $win (grep { /Win$/ } keys %{$self}) {
        $self->{$win} = undef;
    }

    $Cursed = 0;

}

DESTROY {

    my $self = shift;

    $self->end();

}

1;



=head1 NAME

App::Tarotplane::UI - tarotplane TUI

=head1 SYNOPSIS

  use App::Tarotplane::UI;

  $ui = App::Tarotplane::UI->init();

  $ui->draw_card($cardstr);
  $ui->draw_info($infostr);
  $ui->update();

  $cmd = $ui->poll();

  $ui->wipe();
  $ui->update();

  $ui->end();

=head1 DESCRIPTION

App::Tarotplane::UI is the component of L<tarotplane> that handles the TUI
(text-user interface). It accomplishes this through the L<Curses> module. If
you're looking for documentation for L<tarotplane>, consult its manual page.

=head1 Object Methods

=head2 App::Tarotplane::UI->init()

Initializes L<Curses> and returns an App::Tarotplane::UI object. Only one
App::Tarotplane::UI object can be initialized at a single time.

=head2 $ui->wipe()

Wipes screen. Should be called before any re-drawing occurs.

=head2 $ui->update()

Updates screen with any new drawings. Should be called to display the results
of any draw method.

=head2 $ui->draw_card([$str, $bold])

Draws a new card displaying $str, automatically performing any text wrapping
necessary to fit in the card. If $bold is supplied and true, the drawn card
will be bold.

update() needs to be called to push the drawing to the screen.

If $str and/or $bold are not supplied, draw_card() 
will use whatever was used on a previous call to draw_card().

=head2 $ui->draw_info([$str])

Draws an info bar at the bottom of the screen containing $str.

update() needs to be called to push the drawing to the screen.

If $str is not supplied, uses the last string supplied by a previous
draw_info() call.

=head2 $ui->draw_help()

Wipes screen and draws help message.

update() needs to be called to push the drawing to the screen.

=head2 $ui->update_size()

Resizes drawings to fit screen, automatically performing necessary wiping and
updating.

=head2 $ui->poll()

Returns command from user input, or undef if the command does not exist. See
the documentation for %KEY_BINDINGS for a list of valid commands.

=head2 $ui->end()

Ends L<Curses>.

=head1 Global Variables

=over 4

=item %KEY_BINDINGS

Hash map of commands and their respective key bindings.

=back

=head1 AUTHOR

Written by Samuel Young E<lt>L<samyoung12788@gmail.com>E<gt>.

=head1 COPYRIGHT

Copyright 2024-2026, Samuel Young

This library is free software; you may redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<tarotplane>, L<Curses>

=cut
