use Test2::V0;

# Manifest-driven SSR defaults (issue #1416).
#
# `bf build` embeds an `ssrDefaults` map in every component's manifest
# entry. The Mojolicious plugin reads that map at plugin-register
# time and:
#   1. In a `before_render` hook for top-level components, seeds each
#      template variable into the stash so Perl strict mode doesn't
#      reject `$variant` / `$count` / etc.
#   2. In `register_components_from_manifest`, derives per-child
#      `signal_init`-style seed kvs (caller's props win over the
#      static defaults).
#
# These cover the wiring that used to live in every user's `app.pl`.

use FindBin qw($Bin);
use lib "$Bin/../lib";

use Mojo::File qw(tempdir);
use Mojo::JSON qw(encode_json true false);
use Mojolicious;
use Mojolicious::Plugin::BarefootJS;

use BarefootJS;

subtest '_derive_stash_from_defaults — UI registry child shape' => sub {
    # Manifest entry for a Badge-like component: every template
    # variable carries a `propName` mapping to its source prop, plus a
    # rest-props bag entry. The caller's `$props->{...}` value takes
    # precedence over the static default when supplied.
    my $defaults = {
        variant   => { propName => 'variant',   value => 'default' },
        asChild   => { propName => 'asChild',   value => false      },
        className => { propName => 'className', value => ''         },
        props     => { isRestProps => 1,        value => {}         },
    };

    my %merged = BarefootJS::_derive_stash_from_defaults(
        $defaults,
        { variant => 'destructive', className => 'extra-class' },
    );

    is $merged{variant},   'destructive', 'caller-supplied prop overrides static default';
    is $merged{className}, 'extra-class', 'caller-supplied prop overrides empty string default';
    is $merged{asChild},   false,         'unsupplied prop falls back to static default';
    is ref($merged{props}), 'HASH',        'rest-props bag falls back to empty hash';
};

subtest '_derive_stash_from_defaults — signal/memo defaults (no propName)' => sub {
    # Counter-like component: signal getters / memo names have no
    # `propName` because they're internal to the component, so the
    # caller cannot override them — the static evaluator's result is
    # used verbatim.
    my $defaults = {
        count   => { value => 99  },
        doubled => { value => 198 },
    };

    my %merged = BarefootJS::_derive_stash_from_defaults(
        $defaults,
        { count => 'ignored-by-merge-rule' },  # caller can't override no-propName entries
    );

    is $merged{count},   99,  'signal getter sources from static default';
    is $merged{doubled}, 198, 'memo also sources from static default';
};

subtest 'plugin before_render hook seeds the stash from manifest' => sub {
    # Spin up a Mojolicious app with the plugin, simulate a render of
    # a top-level component, and assert that the SSR template's
    # variables are stashed via the auto-init hook.
    my $dir = tempdir;
    my $manifest = {
        Counter => {
            markedTemplate => 'templates/Counter.html.ep',
            ssrDefaults    => {
                count   => { value => 42 },
                doubled => { value => 84 },
            },
        },
        # A UI registry child entry — these should NOT trigger the
        # auto-init hook (they're invoked via render_child, not as a
        # top-level render target).
        'ui/badge/index' => {
            markedTemplate => 'templates/ui/badge/index.html.ep',
            ssrDefaults    => {
                variant => { propName => 'variant', value => 'default' },
            },
        },
    };
    my $manifest_file = $dir->child('manifest.json');
    $manifest_file->spew(encode_json($manifest));

    my $app = Mojolicious->new;
    $app->plugin('BarefootJS' => { manifest_path => "$manifest_file" });
    my $c = $app->build_controller;

    # Trigger the before_render hook with a top-level component.
    $app->plugins->emit_hook(before_render => $c, { template => 'Counter' });

    is $c->stash('count'),   42, 'static signal default reached the stash';
    is $c->stash('doubled'), 84, 'static memo default reached the stash';
    ok defined($c->bf->_scope_id) && length($c->bf->_scope_id),
        'scope_id was auto-generated by the hook';
};

subtest 'plugin before_render — caller stash wins over manifest defaults' => sub {
    my $dir = tempdir;
    my $manifest_file = $dir->child('manifest.json');
    $manifest_file->spew(encode_json({
        Counter => {
            markedTemplate => 'templates/Counter.html.ep',
            ssrDefaults    => { count => { value => 0 } },
        },
    }));

    my $app = Mojolicious->new;
    $app->plugin('BarefootJS' => { manifest_path => "$manifest_file" });
    my $c = $app->build_controller;

    # Caller pre-stashes a value before the render kicks in.
    $c->stash(count => 10);
    $app->plugins->emit_hook(before_render => $c, { template => 'Counter' });

    is $c->stash('count'), 10, 'caller-supplied stash value preserved';
};

subtest 'plugin before_render — skips child templates' => sub {
    my $dir = tempdir;
    my $manifest_file = $dir->child('manifest.json');
    $manifest_file->spew(encode_json({
        'ui/badge/index' => {
            markedTemplate => 'templates/ui/badge/index.html.ep',
            ssrDefaults    => { variant => { propName => 'variant', value => 'default' } },
        },
    }));

    my $app = Mojolicious->new;
    $app->plugin('BarefootJS' => { manifest_path => "$manifest_file" });
    my $c = $app->build_controller;

    $app->plugins->emit_hook(before_render => $c, { template => 'ui/badge/index' });

    ok !defined($c->stash('variant')), 'child entries do not auto-seed stash on render';
};

done_testing;
