#!/usr/bin/env perl

# Browse this distribution's POD in a web browser. Walks lib/ for .pod
# and .pm files with POD, renders each on demand via Pod::Simple::XHTML.
# Module discovery happens per request, so regenerating docs (e.g. with
# devel/build_docs.pl) shows up immediately on refresh.
#
# Usage:
#     devel/browse-docs                # default: http://127.0.0.1:5000
#     devel/browse-docs --port 8080    # custom port
#     devel/browse-docs --host 0.0.0.0 # listen on all interfaces
#
# Requires Plack (Pod::Simple ships with core Perl). Install with:
#     cpanm --installdeps --with-develop .

use 5.014;
use strict;
use warnings;
use FindBin;
use Getopt::Long qw(GetOptions);
use File::Find  ();
use File::Spec;
use Pod::Simple::XHTML;
use Plack::Request;
use Plack::Runner;

my $port      = 5000;
my $host      = '127.0.0.1';
my $show_help = 0;

GetOptions(
    'port=i' => \$port,
    'host=s' => \$host,
    'help|h' => \$show_help,
) or die "see --help\n";

if ($show_help) {
    print <<'USAGE';
devel/browse-docs - browse this distribution's POD in a web browser

Usage:
    devel/browse-docs [options]

Options:
    --port <N>     listen port (default: 5000)
    --host <addr>  listen address (default: 127.0.0.1; use 0.0.0.0 for LAN)
    -h, --help     this message
USAGE
    exit 0;
}

my $LIB_DIR = File::Spec->rel2abs("$FindBin::Bin/../lib");

sub discover_modules {
    my @entries;
    File::Find::find({
        no_chdir => 1,
        wanted   => sub {
            return unless -f $_ && /\.(pod|pm)$/;
            my $abs = $File::Find::name;

            if ($abs =~ /\.pm$/) {
                open my $fh, '<', $abs or return;
                my $has_pod;
                while (<$fh>) {
                    if (/^=(?:pod|head\d|item|over|begin|for)\b/) {
                        $has_pod = 1;
                        last;
                    }
                }
                return unless $has_pod;
            }

            my $rel = File::Spec->abs2rel($abs, $LIB_DIR);
            (my $module = $rel) =~ s{\.(?:pod|pm)$}{};
            $module =~ s{[/\\]}{::}g;
            push @entries, { module => $module, path => $abs };
        },
    }, $LIB_DIR);

    # Prefer .pod over .pm if both exist for the same module.
    my %seen;
    for my $e (sort { ($a->{path} =~ /\.pod$/) <=> ($b->{path} =~ /\.pod$/) } @entries) {
        $seen{ $e->{module} } = $e;
    }
    return [ sort { $a->{module} cmp $b->{module} } values %seen ];
}

sub module_url {
    my ($name) = @_;
    (my $url = $name) =~ s{::}{/}g;
    return "/$url";
}

sub sidebar_html {
    my ($modules, $current) = @_;
    my $html = qq{<nav class="sidebar"><h2>Modules</h2><ul>};
    for my $m (@$modules) {
        my $name  = $m->{module};
        my $depth = () = $name =~ /::/g;
        my $cls   = 'd' . $depth;
        $cls .= ' current' if defined $current && $current eq $name;
        my $url   = module_url($name);
        $html .= qq{<li class="$cls"><a href="$url">$name</a></li>};
    }
    $html .= q{</ul></nav>};
    return $html;
}

sub render_pod {
    my ($path) = @_;
    my $parser = Pod::Simple::XHTML->new;
    $parser->perldoc_url_prefix('/');
    $parser->perldoc_url_postfix('');
    $parser->html_header('');
    $parser->html_footer('');
    $parser->index(1);
    $parser->output_string(\my $out);
    $parser->parse_file($path);
    return $out;
}

my $CSS = <<'CSS';
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
       margin: 0; display: flex; color: #222; }
.sidebar { width: 320px; height: 100vh; overflow-y: auto;
           padding: 1em; background: #f4f4f4;
           border-right: 1px solid #ddd; box-sizing: border-box;
           position: sticky; top: 0; }
.sidebar h2 { margin: 0 0 0.6em; font-size: 0.8em;
              text-transform: uppercase; color: #666; letter-spacing: 0.06em; }
.sidebar ul { list-style: none; padding: 0; margin: 0;
              font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
              font-size: 0.85em; }
.sidebar li { margin: 0.15em 0; }
.sidebar li.d1 { padding-left: 0.5em; }
.sidebar li.d2 { padding-left: 1em; }
.sidebar li.d3 { padding-left: 1.5em; }
.sidebar li.d4 { padding-left: 2em; }
.sidebar li.d5 { padding-left: 2.5em; }
.sidebar li.current a { font-weight: bold; color: #000; }
.sidebar a { text-decoration: none; color: #06c; }
.sidebar a:hover { text-decoration: underline; }
main { padding: 1em 2em; flex: 1; max-width: 920px; }
main h1 { margin-top: 0.2em; }
pre { background: #f6f8fa; padding: 0.8em; overflow-x: auto; border-radius: 4px; }
code { background: #f6f8fa; padding: 0.1em 0.3em; border-radius: 3px; }
pre code { background: transparent; padding: 0; }
h1, h2, h3 { border-bottom: 1px solid #eee; padding-bottom: 0.2em; }
a { color: #06c; }
CSS

my $app = sub {
    my $req  = Plack::Request->new(shift);
    my $path = $req->path_info;

    if ($path eq '/style.css') {
        return [ 200, [ 'Content-Type' => 'text/css; charset=utf-8' ], [$CSS] ];
    }

    my $modules = discover_modules();
    my %by_module = map { $_->{module} => $_ } @$modules;

    my $module;
    if ($path eq '/' || $path eq '') {
        $module = $by_module{'OpenAPI::Client::OpenAI'}
            ? 'OpenAPI::Client::OpenAI'
            : @$modules ? $modules->[0]{module} : undef;
    }
    else {
        ($module = $path) =~ s{^/}{};
        $module =~ s{/}{::}g;
    }

    if (!$module || !$by_module{$module}) {
        my $sidebar = sidebar_html($modules);
        my $body    = qq{<!doctype html><html><head><meta charset="utf-8">}
                    . qq{<title>Not found</title>}
                    . qq{<link rel="stylesheet" href="/style.css"></head><body>}
                    . qq{$sidebar<main><h1>Not found</h1>}
                    . qq{<p>No POD for <code>$module</code>.</p></main></body></html>};
        return [ 404, [ 'Content-Type' => 'text/html; charset=utf-8' ], [$body] ];
    }

    my $entry   = $by_module{$module};
    my $rendered = render_pod($entry->{path});
    my $sidebar  = sidebar_html($modules, $module);
    my $html = qq{<!doctype html><html><head><meta charset="utf-8">}
             . qq{<title>$module</title>}
             . qq{<link rel="stylesheet" href="/style.css"></head><body>}
             . qq{$sidebar<main><h1>$module</h1>$rendered</main></body></html>};

    return [ 200, [ 'Content-Type' => 'text/html; charset=utf-8' ], [$html] ];
};

say "Browsing $LIB_DIR at http://$host:$port/";
say "(press Ctrl-C to stop)";

my $runner = Plack::Runner->new;
$runner->parse_options( '--port' => $port, '--host' => $host );
$runner->run($app);
