package Silki::Markdent::Role::WikiLinkResolver;
BEGIN {
  $Silki::Markdent::Role::WikiLinkResolver::VERSION = '0.27';
}

use strict;
use warnings;
use namespace::autoclean;

use Digest::SHA qw( sha1_hex );
use List::AllUtils qw( all );
use Silki::I18N qw( loc );
use Silki::Markdent::Event::Placeholder;
use Silki::Types qw( HashRef Str );

use Moose::Role;
use MooseX::Params::Validate qw( validated_list );

requires qw( _replace_placeholder );

has _wiki => (
    is       => 'ro',
    isa      => 'Silki::Schema::Wiki',
    required => 1,
    init_arg => 'wiki',
);

has _page => (
    is       => 'ro',
    isa      => 'Silki::Schema::Page',
    init_arg => 'page',
);

has _cached_wikis => (
    is       => 'ro',
    isa      => HashRef ['Silki::Schema::Wiki'],
    default  => sub { {} },
    init_arg => undef,
);

has _page_links => (
    traits   => ['Hash'],
    is       => 'ro',
    isa      => HashRef [HashRef],
    init_arg => undef,
    default  => sub { {} },
    handles  => {
        _save_page_link => 'set',
    },
);

around handle_event => sub {
    my $orig  = shift;
    my $self  = shift;
    my $event = shift;

    if ( $event->isa('Silki::Markdent::Event::WikiLink') ) {
        $self->wiki_link( $event->kv_pairs_for_attributes() );
    }
    elsif ( $event->isa('Markdent::Event::EndDocument') ) {
        $self->$orig($event);
        $self->_replace_all_placeholders();
    }
    elsif ($orig) {
        $self->$orig($event);
    }
};

sub wiki_link {
    my $self = shift;
    my ( $link_text, $display_text ) = validated_list(
        \@_,
        link_text    => { isa => Str },
        display_text => { isa => Str, optional => 1 },
    );

    my ( $wiki, $page_title )
        = $self->_wiki_and_page_title_from_link_text($link_text);

    my $id = sha1_hex( ( $wiki ? $wiki->wiki_id() : -1 ), lc $page_title );

    $self->_save_page_link(
        $id => {
            wiki         => $wiki,
            page_title   => $page_title,
            display_text => $display_text,
            link_text    => $link_text,
        }
    );

    $self->handle_event(
        Silki::Markdent::Event::Placeholder->new( id => $id ) );

    return;
}

sub _wiki_and_page_title_from_link_text {
    my $self      = shift;
    my $link_text = shift;

    my $wiki       = $self->_wiki();
    my $page_title = $link_text;

    if ( $link_text =~ m{^([^/]+)/([^/]+)$} ) {
        $wiki       = $self->_wiki_from_string($1);
        $page_title = $2;
    }

    $page_title =~ s/^\s+|\s+$//g;

    return ( $wiki, $page_title );
}

sub _wiki_from_string {
    my $self   = shift;
    my $string = shift;

    my $wiki_cache = $self->_cached_wikis();

    $string =~ s/^\s+|\s+$//g;

    if ( exists $wiki_cache->{$string} ) {
        return $wiki_cache->{$string};
    }
    else {
        my $wiki = Silki::Schema::Wiki->new( title => $string )
            || Silki::Schema::Wiki->new( short_name => $string );

        if ($wiki) {
            $wiki_cache->{ $wiki->title() }
                = $wiki_cache->{ $wiki->short_name() } = $wiki;
        }
        else {
            $wiki_cache->{$string} = undef;
        }

        return $wiki;
    }
}

sub _replace_all_placeholders {
    my $self = shift;
    my $html = shift;

    $self->_replace_bad_wiki_links();

    $self->_replace_good_wiki_links();

    $self->_replace_nonexistent_page_links();

    return;
}

sub _replace_bad_wiki_links {
    my $self = shift;

    my $links = $self->_page_links();

    for my $id (
        grep { !$links->{$_}{wiki} }
        keys %{$links}
        ) {

        my $link = delete $links->{$id};

        $self->_replace_placeholder(
            $id => {
                text => loc(
                    '(link to a non-existent wiki in a page link - %1)',
                    $link->{link_text}
                )
            },
        );
    }
}

sub _replace_good_wiki_links {
    my $self = shift;

    my $links = $self->_page_links();


    my %titles;
    for my $link ( values %{$links} ) {
        push @{ $titles{ $link->{wiki}->wiki_id() } }, $link->{page_title};
    }

    return unless keys %titles;

    my $pages = Silki::Schema::Page->PagesByWikiAndTitle( \%titles );

    while ( my $page = $pages->next() ) {
        my $id = sha1_hex( $page->wiki_id(), lc $page->title() );

        my $link = delete $links->{$id};

        $link->{display_text} //= $self->_display_text_for_page(
            $link->{wiki},
            $page->title(),
        );

        $self->_replace_placeholder(
            $id => {
                page  => $page,
                title => $page->title(),
                text  => $link->{display_text},
                wiki  => $link->{wiki},
            }
        );
    }
}

sub _replace_nonexistent_page_links {
    my $self = shift;

    my $links = $self->_page_links();

    for my $id ( keys %{$links} ) {

        my $text;

        if ( $links->{$id}{display_text} ) {
            $text = $links->{$id}{display_text};
        }
        else {
            $text = $links->{$id}{page_title};

            my $wiki = $links->{$id}{wiki};

            $text .= ' (' . $wiki->title() . ')'
                unless $wiki->wiki_id() == $self->_wiki()->wiki_id();
        }

        $self->_replace_placeholder(
            $id => {
                page  => undef,
                title => $links->{$id}{page_title},
                text  => $text,
                wiki  => $links->{$id}{wiki},
            }
        );
    }
}

sub _display_text_for_page {
    my $self       = shift;
    my $wiki       = shift;
    my $page_title = shift;

    my $text = $page_title;

    $text .= ' (' . $wiki->title() . ')'
        unless $wiki->wiki_id() == $self->_wiki()->wiki_id();

    return $text;
}

sub _resolve_file_link {
    my $self         = shift;
    my $link_text    = shift;
    my $display_text = shift;

    my $wiki = $self->_wiki();

    return unless $link_text =~ m{^(?:([^/]+)/)?(?:([^/]+)/)?([^/]+)$};

    my $filename = $3;

    my $wiki_name;
    my $page_name;

    if ( all {defined} $1, $2, $3 ) {
        $wiki_name = $1;
        $page_name = $2;
    }
    elsif ( all {defined} $1, $3 ) {
        $page_name = $1;
    }

    if ( defined $wiki_name ) {
        $wiki = Silki::Schema::Wiki->new( title => $wiki_name )
            || Silki::Schema::Wiki->new( short_name => $wiki_name );

        return {
            text => loc(
                '(link to a non-existent wiki in a file link - %1)',
                $link_text
            ),
            }
            unless $wiki;
    }

    my $page = $self->_page();

    if ( defined $page_name ) {
        $page = Silki::Schema::Page->new(
            title   => $page_name,
            wiki_id => $wiki->wiki_id(),
        );

        return {
            text => loc(
                '(link to a non-existent page in a file link - %1)',
                $link_text
            ),
            }
            unless $page;
    }

    my $file = Silki::Schema::File->new(
        page_id  => $page->page_id(),
        filename => $filename,
    );

    unless ( defined $display_text ) {
        $display_text = $self->_link_text_for_file(
            $wiki,
            $file,
            $link_text,
        );
    }

    return {
        file => $file,
        text => $display_text,
        wiki => $wiki,
    };
}

sub _link_text_for_file {
    my $self      = shift;
    my $wiki      = shift;
    my $file      = shift;
    my $link_text = shift;

    return loc( '(link to a non-existent file - %1)', $link_text )
        unless $file;

    my $text = $file->filename();

    $text .= ' (' . $wiki->title() . ')'
        unless $wiki->wiki_id() == $self->_wiki()->wiki_id();

    return $text;
}

# These classes may in turn load other classes which use this role, so they
# need to be loaded after the role is defined.
require Silki::Schema::File;
require Silki::Schema::Page;
require Silki::Schema::Wiki;

1;

# ABSTRACT: A role which resolves page/file/image links from wikitext

__END__
=pod

=head1 NAME

Silki::Markdent::Role::WikiLinkResolver - A role which resolves page/file/image links from wikitext

=head1 VERSION

version 0.27

=head1 AUTHOR

Dave Rolsky <autarch@urth.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2010 by Dave Rolsky.

This is free software, licensed under:

  The GNU Affero General Public License, Version 3, November 2007

=cut