#!/usr/bin/env perl

# The shebang has to name perl, and must not name a server.  morbo starts the
# application as
#
#   perl <this script> daemon
#
# and perl hands a script whose #! line does not mention perl to the
# interpreter that line names (see perlrun).  With "#!/usr/bin/env morbo" that
# interpreter is morbo again, so every worker started another morbo; each
# generation exported the whole of @INC as PERL5LIB, so the environment grew
# with every step until exec failed with "Argument list too long", and every
# generation kept the listening socket, leaving a chain of stale servers.

use strict;
use warnings;

use File::Spec ();
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../lib" }

require Mojo::Loader;
require Mojolicious::Commands;

# True when an argument names something to open, not a Mojolicious command.  A
# command word always wins, even over a file of that name in the current
# directory: morbo restarts the server as "perl <this script> daemon".  A word
# that is neither is left for Mojolicious, which says the command is unknown.
sub _names_a_database {
	my ($arg) = @_;

	return 0 if $arg =~ /\A-/;
	(my $name = $arg) =~ s/-/_/g;
	for my $namespace (@{ Mojolicious::Commands->new->namespaces }, 'Database::BI::Command') {
		my $class = "${namespace}::$name";
		return 0 if $name =~ /\A\w+\z/ && !Mojo::Loader::load_class($class);
	}
	return -e $arg || $arg !~ /\A\w[\w-]*\z/;
}

sub _refuse {
	my ($message) = @_;

	print STDERR "database-bi: $message\n";
	exit 2;
}

# Hands the one database named to the server through the environment, because
# morbo restarts the application with its own arguments.  GET / reads BI_OPEN.
# A remote path, Redis spec or MariaDB spec cannot be checked from here, so /open
# reports it.
sub _open_on_start {
	my ($database, @others) = @_;

	_refuse('only one database can be opened from the command line.  To bring '
		. 'several together, open one and use Merge or Combine in the browser.')
		if @others;

	if ($database !~ m{\A(?:/\.\./|redis:|mariadb:)}i) {
		require Database::BI::Controller::Dashboard;
		_refuse("cannot open $database: no such file") unless -e $database;
		_refuse("cannot open $database: not a file") unless -f _;
		_refuse("cannot open $database: permission denied") unless -r _;
		_refuse("cannot open $database: not a supported file type")
			unless Database::BI::Controller::Dashboard::is_supported_file($database);
		$database = File::Spec->rel2abs($database);
	}

	$ENV{BI_OPEN} = $database;
	return;
}

if (@ARGV && _names_a_database($ARGV[0])) {
	_open_on_start(@ARGV);
	@ARGV = ();
}

# With no arguments this is the development server, restarting when a file
# changes - what "morbo script/database-bi" does.  A command word (daemon,
# routes, get, ...) goes to the application's command line interface, so the
# script can still start a plain server or be used from a test.
#
# A server that loads this file instead of running it (hypnotoad) also leaves
# @ARGV empty, and sets MOJO_APP_LOADER.  It must not get the development server,
# which never returns.  Morbo's run exits, so start_app is reached only otherwise.
if (!@ARGV && !defined $ENV{MOJO_APP_LOADER}) {
	# Morbo opens the port before the application is loaded, so load it once
	# here: its startup reads config listen into MOJO_LISTEN (this machine
	# only, unless the configuration says otherwise), and a configuration
	# that does not start stops here rather than in every restart.
	require Mojo::Server;
	Mojo::Server->new->build_app('Database::BI');
	require Mojo::Server::Morbo;
	Mojo::Server::Morbo->new(silent => 1)
		->run(File::Spec->catfile($FindBin::RealBin, $FindBin::Script));
}

# This must stay the last statement: a loading server takes the file's value as
# the application, and refuses anything else.
Mojolicious::Commands->start_app('Database::BI');

__END__

=head1 NAME

database-bi - start Database::BI, or run one of its commands

=head1 SYNOPSIS

    database-bi                       # development server, 127.0.0.1:3000
    database-bi data/store.db         # the same, and / shows that database
    database-bi password              # print a password_hash line for the config
    hypnotoad database-bi             # production server, 127.0.0.1:8080
    database-bi daemon -l http://127.0.0.1:8080

=head1 DESCRIPTION

Starts the L<Database::BI> web application.  With no arguments it runs the
reloading development server (morbo) on the address in the configuration's
C<listen>, by default C<http://127.0.0.1:3000>.  With one data file it does the
same and the home page opens that file.  Any other first argument is a
Mojolicious command (C<daemon>, C<routes>, C<get>, ...) or C<password>, which
reads a password and prints the C<password_hash> line for F<database_bi.conf>.

Without C<password_hash> the server listens on this machine only: a listen
address that is not loopback refuses to start.  See L<Database::BI> for the
configuration.

=head1 EXIT STATUS

0 when a command succeeds; 2 when the arguments name more than one database or
a file that cannot be opened, before any server starts.

=head1 AUTHOR

Nigel Horne C<< <njh@nigelhorne.com> >>

=head1 LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.
If you use it,
please let me know.

=cut
