#!/usr/bin/env raku
use v6.e.PREVIEW;

use DB;
use DB::BlogMeta;
use DB::Series;
use DB::MarkdownPost;
use DB::IdrisPost;

use Pretty::Table;

my %*SUB-MAIN-OPTS =
    :named-anywhere,
    :bundling,
;

#| The directory this script is located in
my IO::Path:D $default-blog-dir = $*PROGRAM.parent;

#| Default database directory
my IO::Path:D $default-db-dir =
    do if %*ENV<BLOG_TEST> {
        $default-blog-dir.add('test-db/')
    } else {
        $default-blog-dir.add('db/')
    };

#| The default output directory
my IO::Path:D $default-site-dir = $default-blog-dir.add('site/');

#| The default idris ipkg
my IO::Path:D $default-ipkg = $default-blog-dir.add('projects/Idris/Idris.ipkg');

#| Initalize the database
multi MAIN(
    "db",
    "init",
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| Overwrite an already existing database file
    Bool :$force
) {
    die "Database folder already exists, use --force to overwrite: {$db-dir.Str}"
    if $db-dir.e && !$force;

    print "Blog Title: ";
    my $title = get;
    print "Tagline: ";
    my $tagline = get;
    print "Base URL: ";
    my $base-url = get;

    my $meta =
        BlogMeta.new:
        title => $title, tagline => $tagline, base-url => $base-url;

    my $db = DB::PostDB.init: $meta;

    $db.write: $db-dir;
}

#| Ensure that the database loads, erroring otherwise
multi MAIN(
    "db",
    "verify",
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    $db.write: $db-dir;
    say "Database OK";
}

#| Create a new markdown post
multi MAIN(
    "post",
    "new",
    "markdown",
    #| The path to the markdown file
    IO::Path(Str) $source,
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The date/time the post should be recorded as posted at
    DateTime(Str) :$posted-at = DateTime.now,
    #| Should the post be hidden from the archive?
    Bool :$hidden = False,
) {
    my $db = read-db $db-dir;
    my $id =
        $db.insert-post:
        MarkdownPost.new(
            source => $source.absolute.IO,
            posted-at => $posted-at,
            hidden => $hidden,
        );
    $db.write: $db-dir;
    say 'Post inserted with id ', $id;
    say 'Post has slugs: ', $db.posts{$id}.all-slugs;
}

#| Create a new idris post
multi MAIN(
    "post",
    "new",
    "idris",
    #| The path to the idris file
    IO::Path(Str) $source,
    #| The path to the ipkg file
    IO::Path(Str) :$ipkg = $default-ipkg,
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The date/time the post should be recorded as posted at
    DateTime(Str) :$posted-at = DateTime.now,
    #| Should the post be hidden from the archive?
    Bool :$hidden = False,
) {
    my $db = read-db $db-dir;
    my $id =
        $db.insert-post:
        IdrisPost.new(
            source => $source.absolute.IO,
            posted-at => $posted-at,
            hidden => $hidden,
            ipkg => $ipkg.absolute.IO,
        );
    $db.write: $db-dir;
    say 'Post inserted with id ', $id;
    say 'Post has slugs: ', $db.posts{$id}.all-slugs;
}

#| Update the last editied time on a post
multi MAIN(
    "post",
    "edit",
    #| The post id to touch
    Int:D $id,
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The date/time the post should be recorded as laste edited at
    DateTime(Str) :$edited-at = DateTime.now,
) {
    my $db = read-db $db-dir;
    my $post = $db.posts{$id.Int};
    $post.edited-at.push: $edited-at;
    $db.write: $db-dir;
}

#| Render the blog to html
multi MAIN(
   "render",
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The path of the output directory
    IO::Path(Str) :$site-dir = $default-site-dir,
) {
    my $db = read-db $db-dir;
    $db.render: $site-dir;
}

#| Provide a table of posts, in newest to oldest order
multi MAIN(
    "post",
    "list",
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The number of posts to show on a single page
    Int :$per-page = 10;
    #| The page number to show
    Int :$page = 1;
) {
    my $db = read-db $db-dir;
    my @pages =
        $db.sorted-posts.rotor($per-page, :partial);
    my @page = @pages[$page - 1].flat;
    my $table = Pretty::Table.new:
        title => "Posts (page $page/{@pages.elems})",
        field-names => ["ID", "Title", "Type"];
    for @page -> $pair {
        my $id = $pair.key;
        my $post = $pair.value;
        # TODO: Terminal aware truncation
        my $title = do if $post.title.chars > 50 {
           "{$post.title.substr(0, 50)}..."
        } else {
            $post.title
        };
        my $type = do given $post {
            when MarkdownPost {
                "md"
            }
            when IdrisPost {
                "idr"
            }
            default {
                ""
            }
        }
        $table.add-row: [$id, $title, $type];
    }
    say $table;
}

#| Display information about a post
multi MAIN(
    "post",
    "info",
    #| The id of the post
    Int $id,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| Display all of the information and not just the primaries
    Bool :$full,
) {
    my $db = read-db $db-dir;
    my $post = $db.posts{$id.Int};
    given $post {
        say 'Title: ', .title;
        say 'Type: ', .WHAT.^name;
        say 'Source: ', .source.relative;
        if .hidden {
            say "Hidden";
        }
        if .all-slugs {
            if $full {
                say 'Slugs: ';
                for .all-slugs -> $slug {
                    say '  * ', $slug;
                }
            } else {
                say 'Primary Slug: ', .all-slugs[*-1];
            }
        } else {
            say 'Slugs: []';
        }
        if .tags {
            say 'Tags:';
            for .tags -> $tag {
                say '  * ', $tag;
            }
        }
        given .posted-at {
            say 'Posted At: ', .Date.Str, ' ', .hh-mm-ss;
        }
        if .edited-at {
            if $full {
                say 'Edits: ';
                for .edited-at.sort.reverse {
                    say '  * ', .Date.Str, ' ', .hh-mm-ss;
                }
            } else {
                given .edited-at.sort[*-1] {
                    say 'Last Edited At: ', .Date.Str, ' ', .hh-mm-ss;
                }
            }
        }
    }
}

#| Add or remove a tag to a post
multi MAIN(
    "post",
    "tag",
    #| The id of the post
    Int $id,
    #| The tag to add/remove
    Str $tag,
    #| remove the tag instead of adding it
    Bool :$remove,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    my $post = $db.posts{$id.Int};
    if $remove {
        die "Post did not have requested tag"
            unless $tag ~~ any($post.tags);
        my $index = $post.tags.first: $tag;
        $post.tags.=grep(* ne $tag);
    } else {
        die "Post already had requested tag"
            if $tag ~~ any($post.tags);
        $post.tags.push: $tag;
    }
    $db.write: $db-dir;
}

#| Update the posted-at time on a post to the current time
multi MAIN(
   "post",
   "now",
    #| The id of the post
    Int $id,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
){
    my $db = read-db $db-dir;
    my $post = $db.posts{$id.Int};
    $post.posted-at = DateTime.now;
    $db.write: $db-dir;
}

#| Set the source code link for a post
multi MAIN(
   "post",
   "source",
    #| The id of the post
    Int $id,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    my $post = $db.posts{$id.Int};

    say "Source Code Link:";
    my $source-code = get;

    $post.source-code = $source-code;

    $db.write: $db-dir;
}

#| Create a new series
multi MAIN(
    "series",
    "new",
    #| The path of the database file
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    say 'Series Title: ';
    my $title = get;
    say 'Series Description: ';
    my $desc = get;

    my $series = Series.new:
        title => $title, desc => $desc;
    my $id = $db.insert-series: $series;
    say 'Series inserted with id ', $id;

    $db.write: $db-dir;
}


#| Provide a table of series
multi MAIN(
    "series",
    "list",
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The number of items to show on a single page
    Int :$per-page = 10;
    #| The page number to show
    Int :$page = 1;
) {
    my $db = read-db $db-dir;
    my @pages =
        $db.series.sort(*.key).rotor($per-page, :partial);
    my @page = @pages[$page - 1].flat;
    my $table = Pretty::Table.new:
        title => "Series (page $page/{@pages.elems})",
        field-names => ["ID", "Title", "Desc"];
    for @page -> $pair {
        my $id = $pair.key;
        my $series = $pair.value;
        # TODO: Terminal aware truncation
        my $title = do if $series.title.chars > 40 {
           "{$series.title.substr(0, 50)}..."
        } else {
            $series.title
        };
        my $desc = do if $series.desc.chars > 40 {
           "{$series.desc.substr(0, 50)}..."
        } else {
            $series.desc
        };
        $table.add-row: [$id, $title, $desc];
    }
    say $table;
}

#| Display the contents of a series
multi MAIN(
    "series",
    "info",
    #| The id of the series to display
    Int $id,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    my $series = $db.series{$id.Int};
    say 'Title: ', $series.title;
    say 'Description:';
    for $series.desc.lines -> $line {
       say '  ', $line;
    }
    say 'Posts:';
    for $series.post-ids -> $post-id {
        my $post = $db.posts{$post-id};
        say '  * ', $post-id, ': ', $post.title;
    }
}

#| Add a post to a series
multi MAIN(
    "series",
    "add",
    #| The id of the series to add to
    Int $series-id,
    #| The id of the post
    Int $post-id,
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
) {
    my $db = read-db $db-dir;
    my $series = $db.series{$series-id.Int};
    $series.post-ids.push: $post-id.Int;
    $db.write: $db-dir;
}

#| Do a clean build and upload the blog to the remote
multi MAIN(
    "upload",
    #| The path of the database directory
    IO::Path(Str) :$db-dir = $default-db-dir,
    #| The path of the output directory
    IO::Path(Str) :$site-dir = $default-site-dir,
) {
    my $db = read-db $db-dir;
    my $site = "{$site-dir.absolute}/";
    # Clean out the site dir
    my $proc = run <rm -rf>, $site;
    die "clean failed" unless $proc;
    # Render the site
    $db.render: $site-dir;
    # Upload it
    $proc = run <rsync -avzhL>, $site, <static.stranger.systems:/var/www/www.stranger.systems>;
    die "rsync failed" unless $proc;
}