package Yukki::Types;
$Yukki::Types::VERSION = '0.991_007'; # TRIAL
$Yukki::Types::VERSION = '0.991007';
use v5.24;
use utf8;
use warnings;
use Type::Library -base, -declare => qw(
    LoginName AccessLevel
    NavigationLinks NavigationMenuMap
    BaseURL BaseURLEnum BreadcrumbLinks
    RepositoryName RepositoryMap
    PluginConfig PluginList
    PrivilegesMap
    EmailAddress YukkiSettings
    YukkiWebSettings YukkiSettingsAnonymous
);
use Type::Utils qw( declare as where message coerce enum from via class_type );

use Types::Standard qw( Str Int ArrayRef Maybe Map HashRef Dict );
use Types::URI qw( Uri );

use Email::Address;
use List::Util qw( first all );

use namespace::clean;

# ABSTRACT: standard types for use in Yukki


declare LoginName,
    as Str,
    where { /^[a-zA-Z0-9_-]{3,20}$/ },
    message { "login name $_ must only contain letters and numbers" };


enum AccessLevel, [qw( read write none )];


declare NavigationLinks,
    as ArrayRef[
        Dict[
            label => Str,
            href  => Str|Uri,
            sort  => Maybe[Int],
        ],
    ];


declare NavigationMenuMap,
    as HashRef[ NavigationLinks ];


enum BaseURLEnum, [qw( SCRIPT_NAME REWRITE )];

declare BaseURL, as BaseURLEnum|Uri;

coerce BaseURL,
    from Str,
    via {
        $_ !~ /^(?:SCRIPT_NAME|REWRITE)$/
            && URI->new($_)
    };


declare BreadcrumbLinks,
    as ArrayRef[
        Dict[
            label => Str,
            href  => Str,
        ],
    ];


declare RepositoryName,
    as Str,
    where { /^[a-zA-Z0-9_.-]{3,50}$/ },
    message { "repository name $_ must only contain letters, numbers, and periods" };


my $Repository = class_type 'Yukki::Settings::Repository';
declare RepositoryMap,
    as Map[RepositoryName, $Repository];

coerce RepositoryMap,
    from HashRef,
    via {
        my $source = $_;
        +{
            map { $_ => Yukki::Settings::Repository->new($source->{$_}) }
                keys %$source
        }
    };


my $Privileges = class_type 'Yukki::Settings::Privileges';
declare PrivilegesMap,
    as HashRef[$Privileges];

coerce PrivilegesMap,
    from HashRef,
    via {
        my $source = $_;
        +{
            map { $_ => Yukki::Settings::Privileges->new($source->{$_}) }
                keys %$source
        }
    };


declare PluginConfig,
    as ArrayRef[HashRef],
    where { all { defined $_->{module} } @$_ };


my $Plugin = class_type 'Yukki::Web::Plugin';
declare PluginList,
    as ArrayRef[$Plugin],
    message {
        return 'It is not an array of objects.' unless ref $_ eq 'ARRAY';
        my $bad = first { not blessed $_ or not $_->isa('Yukki::Web::Plugin') }
                        @$_;
        $bad = blessed $bad if blessed $bad;
        return "It contains $bad, which is not a Yukki::Web::Plugin.";
    };


class_type EmailAddress, { class => 'Email::Address' };
coerce EmailAddress,
    from Str,
    via { (Email::Address->parse($_))[0] };


class_type YukkiSettings, { class => 'Yukki::Settings' };
coerce YukkiSettings,
    from HashRef,
    via { Yukki::Settings->new($_) };


class_type YukkiWebSettings, { class => 'Yukki::Web::Settings' };
coerce YukkiWebSettings,
    from HashRef,
    via { Yukki::Web::Settings->new($_) };


class_type YukkiSettingsAnonymous, { class => 'Yukki::Settings::Anonymous' };
coerce YukkiSettingsAnonymous,
    from HashRef,
    via { Yukki::Settings::Anonymous->new($_) };

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Yukki::Types - standard types for use in Yukki

=head1 VERSION

version 0.991_007

=head1 SYNOPSIS

  use Yukki::Types qw( LoginName AccessLevel );

  has login_name => ( isa => LoginName );
  has access_level => ( isa => AccessLevel );

=head1 DESCRIPTION

A standard type library for Yukki.

=head1 TYPES

=head2 LoginName

This is a valid login name. Login names may only contain letters and numbers, as of this writing.

=head2 AccessLevel

This is a valid access level. This includes any of the following values:

  read
  write
  none

=head2 NavigationLinks

This is an array of hashes formatted like:

  {
      label => 'Label',
      href  => '/link/to/somewhere',
      sort  => 40,
  }

=head2 NavigationMenuMap

This is a hash of L</NavigationLinks>.

=head2 BaseURL

This is either an absolute URL or the words C<SCRIPT_NAME> or C<REWRITE>.

=head2 BreadcrumbLinks

This is an array of hashes formatted like:

  {
      label => 'Label',
      href  => '/link/to/somewhere',
  }

=head2 RepositoryName

This limits repository names to a limited subset of ASCII.

=head2 RepositoryMap

This is a hash of L<Yukki::Settings::Repository> objects.

=head2 PrivilegeMap

This is a hash of L<Yukki::Settings::Privileges> objects.

=head2 PluginConfig

A plugin configuration is an array of hashes. Each hash must have at least one key named "module" defined.

=head2 PluginList

A plugin list is a loaded set of plugin objects.

=head1 COERCIONS

In addition to the types above, these coercions are provided for other types.

=head2 EmailAddress

Coerces a C<Str> into an L<Email::Address>.

=head2 YukkiSettings

Coerces a C<HashRef> into this object by passing the value to the constructor.

=head2 YukkiWebSettings

Coerces a C<HashRef> into a L<Yukki::Web::Settings>.

=head2 YukkiSettingsAnonymous

Coerces a C<HashRef> into this object by passing the value to the constructor.

=head1 AUTHOR

Andrew Sterling Hanenkamp <hanenkamp@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Qubling Software LLC.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
