use Renard::Incunabula::Common::Setup;
package Renard::Incunabula::Outline;
# ABSTRACT: Model that represents a document outline
$Renard::Incunabula::Outline::VERSION = '0.005';
use Moo;
use Renard::Incunabula::Common::Types qw(
	ArrayRef Dict
	PositiveOrZeroInt Str Bool
	InstanceOf
	Optional);
use Renard::Incunabula::Document::Types qw(LaxPageNumber);
use Type::Utils qw( declare as where message );
use Module::Load;

my $Outline = declare
	as ArrayRef[Dict[
			level => PositiveOrZeroInt,
			text => Str,
			open => Optional[Bool],
			page => Optional[LaxPageNumber]
		]];
my $OutlineLevelCheck = declare
	as $Outline,
	where {
		my @outline = @$_;
		for my $idx (0..@outline-2) {
			my $current_level = $outline[$idx]{level};
			my $next_level = $outline[$idx + 1]{level};
			if(  $current_level < $next_level
				and $current_level + 1 != $next_level ) {
				# This is a malformed outline. It should not
				# be possible to go down multiple levels at
				# a time.
				return 0;
			}
		}
		return 1;
	},
	message {
		$Outline->validate($_)
			or "Outline item data has malformed levels";
	};

has items => (
	is => 'rw',
	required => 1,
	isa => $OutlineLevelCheck,
);

has tree_store => (
	is => 'lazy', # _build_tree_store
	isa => InstanceOf['Gtk3::TreeStore'],
);


method _build_tree_store() {
	# load Gtk3 dynamically if used outside Gtk3 context
	load 'Gtk3', '-init';

	my $data = Gtk3::TreeStore->new( 'Glib::String', 'Glib::String', );

	my $outline_items = $self->items;
	my $level = 0;
	my $iter = undef;
	my @parents = ();

	for my $item (@$outline_items) {
		no autovivification;

		# If we need to go up to the parent iterators.
		while( @parents && $item->{level} < @parents ) {
			$iter = pop @parents;
		}

		if( $item->{level} > @parents ) {
			# If we need to go one level down to a child.
			# NOTE : This is not a while(...) loop because the
			# outline should only increase one level at a time.
			push @parents, $iter;
			$iter = $data->append($iter);
			$level++;
		} else {
			# We are still at the same level. Just add a new row to
			# that last parent (or undef if we are at the root).
			$iter = $data->append( $parents[-1] // undef );
		}

		$data->set( $iter,
			0 => $item->{text} // '',
			1 => $item->{page} );
	}

	$data;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Renard::Incunabula::Outline - Model that represents a document outline

=head1 VERSION

version 0.005

=head1 EXTENDS

=over 4

=item * L<Moo::Object>

=back

=head1 ATTRIBUTES

=head2 items

An C<ArrayRef[HashRef]> with a simple representation of an outline where each
item of the ArrayRef represents an item in the list of headings displayed in
order.

Each C<HashRef> element is an element of the outline with the structure:

  {
    # The level in the outline that the item is at. Starts at zero (0).
    level => PositiveOrZeroInt,

    # The textual description of the item.
    text  => Str,

    # The page number that the outline item points to.
    page  => LaxPageNumber,
  }

A complete example is:

  [
    {
      level => 0,
      text  => 'Chapter 1',
      page  => 20,
    },
    {
      level => 1,
      text  => 'Section 1.1',
      page  => 25,
    },
    {
      level => 0,
      text  => 'Chapter 2',
      page  => 30,
    },
  ]

which represents the outline

  Chapter 1 .......... 20
    Section 1.1 ...... 25
  Chapter 2 .......... 30

=head2 tree_store

The L<Gtk3::TreeStore> representation for this outline. It holds tree data of
the heading text and page numbers.

=head1 AUTHOR

Project Renard

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Project Renard.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut