use v5.10;
use strict;
use warnings;
use feature 'state';

package Context::Singleton::Frame::DB;

our $VERSION = v1.0.5;

use Class::Load;
use Module::Pluggable::Object;
use Ref::Util;

use Context::Singleton::Frame::Builder::Value;
use Context::Singleton::Frame::Builder::Hash;
use Context::Singleton::Frame::Builder::Array;

sub new {
	my ($class) = @_;

	my $self = bless {
		cache => {},
		plugin => {},
	}, $class;

	$self->contrive ('Class::Load', (
		value => 'Class::Load',
	));

	$self->contrive ('class_loader', (
		dep => [ 'Class::Load' ],
		as  => sub { $_[0]->can ('load_class') },
	));

	return $self;
}

sub instance {
	state $instance = __PACKAGE__->new;
	return $instance;
}

sub _contrive_class_loader {
	my ($self, $name) = @_;

	return if exists $self->{cache}{$name};

	$self->contrive ($name, (
		dep => [ 'class_loader' ],
		as => eval "sub { \$_[0]->(q[$name]) && q[$name] }",
	));

	return;
}

sub _guess_builder_class {
	my ($self, $def) = @_;

	return 'Context::Singleton::Frame::Builder::Value' if exists $def->{value};
	return 'Context::Singleton::Frame::Builder::Hash'  if Ref::Util::is_hashref ($def->{dep});
	return 'Context::Singleton::Frame::Builder::Array'
}

sub contrive {
	my ($self, $name, %def) = @_;

	if ($def{class}) {
		$self->_contrive_class_loader ($def{class});
		$def{builder} //= 'new';
	}

	if ($def{class} // $def{deduce}) {
		$def{this} = $def{class} // $def{deduce};
		delete $def{class};
		delete $def{deduce};
	}

	my $builder_class = $self->_guess_builder_class (\%def);
	my $builder = $builder_class->new (%def);

	push @{ $self->{cache}{ $name } }, $builder;

	return;
}

sub trigger {
	my ($self, $name, $code) = @_;

	push @{ $self->{trigger}{ $name } }, $code;

	return;
}

sub find_builder_for {
	my ($self, $name) = @_;

	return @{ $self->{cache}{ $name } // [] };
}

sub find_trigger_for {
	my ($self, $name) = @_;

	return @{ $self->{trigger}{ $name } // [] };
}

sub load_rules {
	my ($self, @packages) = @_;

	for my $package (@packages) {
		$self->{plugins}{ $package } //= do {
			Module::Pluggable::Object->new (
				require => 1,
				search_path => [ $package ],
			)->plugins;
			1;
		};
	}

	return;
}

1;