package Spike::Config;

use strict;
use warnings;

use feature 'state';

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;

    my %args = @_;

    my $handler = sub {
        state $config ||= bless { %args }, "${class}::Section";

        if (@_) {
            my $section = shift;
            return $config->$section(@_);
        }

        return $config;
    };

    return bless $handler, "${class}::Accessor";
}

package Spike::Config::Parse;

sub set         { $_[0] = $_[1] }

sub defined     { $_[0] // $_[1] // '' }
sub def         { $_[0] // $_[1] // '' }

sub integer     { no warnings 'numeric'; CORE::int($_[0] // $_[1] // 0) }
sub int         { no warnings 'numeric'; CORE::int($_[0] // $_[1] // 0) }

sub number      { no warnings 'numeric'; 0 + ($_[0] // $_[1] // 0) }
sub num         { no warnings 'numeric'; 0 + ($_[0] // $_[1] // 0) }

sub boolean     { !!($_[0] // $_[1]) }
sub bool        { !!($_[0] // $_[1]) }

sub string      { ''.($_[0] // $_[1] // '') }
sub str         { ''.($_[0] // $_[1] // '') }

package Spike::Config::Accessor;

our $AUTOLOAD;

sub AUTOLOAD {
    my $self = shift;

    (my $key = $AUTOLOAD)   =~ s/^.*:://;
    (my $class = ref $self) =~ s/::[^:]*$//;

    return $self->()->$key(@_);
}

sub DESTROY {}

package Spike::Config::Section;

our $AUTOLOAD;

sub AUTOLOAD {
    my $self = shift;

    (my $key = $AUTOLOAD)   =~ s/^.*:://;
    (my $class = ref $self) =~ s/::[^:]*$//;

    my $section = bless $self->{$key} ||= {}, "${class}::Value";

    if (@_) {
        my $value = shift;
        return $section->$value(@_);
    }

    return $section;
}

sub DESTROY {}

package Spike::Config::Value;

use Carp;

our $AUTOLOAD;

sub AUTOLOAD {
    my $self = shift;

    (my $key = $AUTOLOAD)   =~ s/^.*:://;
    (my $class = ref $self) =~ s/::[^:]*$//;

    if (@_) {
        my $format = shift;
        my $method = "${class}::Parse::${format}";

        if (defined *{$method}) {
            no strict 'refs';
            return $method->($self->{$key}, @_);
        }
        else {
            carp "Unknown format: $format";
        }
    }

    return $self->{$key};
}

sub DESTROY {}

1;