<?php

declare(strict_types=1);

namespace PpPhp\Core\Events;

/**
 * Bridge between the Designer's Custom Button component and the Events store.
 *
 * A Custom Button carries three handlers on its component props — clientBefore
 * (JS), server (PHP), clientAfter (JS). The Events module keeps every project
 * handler in config/events.json. To let a developer author button logic in EITHER
 * place and have the app record it as ONE event, we mirror the button handlers
 * into an isolated `buttons` subtree of the events state (never touching the
 * auth/table/global event trees), and mirror any edits back onto the component.
 *
 * Both operations are pure functions so they are unit-testable, and callers wrap
 * them in try/catch so a bridge hiccup can never fail a normal save.
 */
final class CustomButtonEvents
{
    /** The isolated events-state key that holds custom-button handlers. */
    public const KEY = 'buttons';

    /**
     * Walk a designer state for every Custom Button and return its handlers,
     * keyed by the button's node id.
     *
     * @return array<string,array{label:string,clientBefore:string,server:string,clientAfter:string}>
     */
    public static function collect(array $designer): array
    {
        $out = [];
        $walk = function ($v) use (&$walk, &$out): void {
            if (!is_array($v)) return;
            if (($v['type'] ?? null) === 'custom-button') {
                $id = preg_replace('/[^A-Za-z0-9_-]/', '', (string) ($v['id'] ?? ''));
                if ($id !== '' && !isset($out[$id])) {
                    $p = (array) ($v['props'] ?? []);
                    $out[$id] = [
                        'label'        => (string) ($p['label'] ?? 'Custom action'),
                        'clientBefore' => (string) ($p['clientBefore'] ?? ''),
                        'server'       => (string) ($p['server'] ?? ''),
                        'clientAfter'  => (string) ($p['clientAfter'] ?? ''),
                    ];
                }
            }
            foreach ($v as $child) {
                if (is_array($child)) $walk($child);
            }
        };
        $walk($designer);
        return $out;
    }

    /**
     * Rebuild the isolated `buttons` subtree of the events state from the designer.
     * Each handler is stored in the same {code, enabled, lang} shape the Events
     * module uses, so it is a first-class event record. Only the `buttons` key is
     * ever replaced — all other events are untouched.
     */
    public static function mirrorIntoEvents(array $designer, array $events): array
    {
        $buttons = [];
        foreach (self::collect($designer) as $id => $b) {
            $buttons[$id] = [
                'label'        => $b['label'],
                'clientBefore' => ['lang' => 'js',  'code' => $b['clientBefore'], 'enabled' => trim($b['clientBefore']) !== ''],
                'server'       => ['lang' => 'php', 'code' => $b['server'],       'enabled' => trim($b['server']) !== ''],
                'clientAfter'  => ['lang' => 'js',  'code' => $b['clientAfter'],  'enabled' => trim($b['clientAfter']) !== ''],
            ];
        }
        // Isolated: replace only this key (drop it entirely when there are none).
        if ($buttons === []) {
            unset($events[self::KEY]);
        } else {
            $events[self::KEY] = $buttons;
        }
        return $events;
    }

    /**
     * Push any button handler code from the events `buttons` subtree back onto the
     * matching Custom Button component props in the designer state. Returns the
     * (possibly) updated designer and whether anything changed.
     *
     * @return array{0:array,1:bool}
     */
    public static function applyEventsToDesigner(array $events, array $designer): array
    {
        $buttons = (array) ($events[self::KEY] ?? []);
        if ($buttons === []) return [$designer, false];

        $changed = false;
        $codeOf = static function ($h): string {
            if (is_array($h)) return (string) ($h['code'] ?? '');
            return (string) $h;
        };
        $walk = function (&$v) use (&$walk, $buttons, $codeOf, &$changed): void {
            if (!is_array($v)) return;
            if (($v['type'] ?? null) === 'custom-button') {
                $id = preg_replace('/[^A-Za-z0-9_-]/', '', (string) ($v['id'] ?? ''));
                if ($id !== '' && isset($buttons[$id])) {
                    $b = (array) $buttons[$id];
                    if (!isset($v['props']) || !is_array($v['props'])) $v['props'] = [];
                    foreach (['clientBefore', 'server', 'clientAfter'] as $k) {
                        if (array_key_exists($k, $b)) {
                            $code = $codeOf($b[$k]);
                            if ((string) ($v['props'][$k] ?? '') !== $code) {
                                $v['props'][$k] = $code;
                                $changed = true;
                            }
                        }
                    }
                }
            }
            foreach ($v as $key => &$child) {
                if (is_array($child)) $walk($child);
            }
            unset($child);
        };
        $walk($designer);
        return [$designer, $changed];
    }
}
