<?php
/**
 * Tam Rank™ frontend output — Gutenberg blocks
 *
 * Two dynamic blocks, [tamrank_sitemap] and [tamrank_breadcrumb] in block form.
 *
 * "Dynamic" is the whole point: neither block saves markup. They save nothing
 * at all, and a render_callback asks the SAME PHP method the shortcode asks —
 * TamRank_HTML_Sitemap::render() and TamRank_Breadcrumbs::render(). A block that
 * built its own markup would be a second renderer, and a second renderer drifts:
 * the sitemap would start listing noindex pages, or the breadcrumb would stop
 * matching the BreadcrumbList JSON-LD. Both were prevented once already (see the
 * file headers of those two classes); do not reintroduce them here.
 *
 * The block attributes are not declared here either. They are read from
 * get_defaults() on those same classes, so a shortcode attribute and a block
 * attribute cannot get out of step. Every value still goes through the render
 * method's own sanitising, because a block attribute is visitor-editable input,
 * exactly like a shortcode attribute.
 *
 * The editor script is handwritten ES5 against the wp.* globals. There is no
 * npm, no webpack, no src/ directory and no compiled bundle — see
 * assets/js/tamrank-frontend-blocks.js for why that matters for the wp.org zip.
 *
 * @package TamRank
 * @since 1.3.7
 */

if (!defined('ABSPATH')) {
    exit;
}

class TamRank_Frontend_Blocks
{
    /** Script handle for the editor script. */
    const EDITOR_HANDLE = 'tamrank-frontend-blocks';

    public function __construct()
    {
        // init_components() already runs on `init`, so the registry is open.
        $this->register_blocks();
    }

    /**
     * Register both dynamic blocks.
     *
     * Everything the inserter needs (title, description, icon, attributes) is
     * declared server-side. WordPress hands those definitions to the editor via
     * get_block_editor_server_block_settings(), so the JavaScript only has to
     * supply edit() and save() — nothing is declared twice.
     */
    private function register_blocks()
    {
        if (!function_exists('register_block_type')) {
            return;
        }

        $this->register_editor_script();

        register_block_type('tamrank/sitemap', array(
            'api_version'     => 3,
            'title'           => tamrank__('block_sitemap_name', 'HTML sitemap'),
            'description'     => tamrank__('block_sitemap_desc', 'The same links as the XML sitemap, as a page visitors can read.'),
            'category'        => 'widgets',
            'icon'            => 'list-view',
            'keywords'        => array('sitemap', 'tamrank', 'seo'),
            'editor_script'   => self::EDITOR_HANDLE,
            'supports'        => self::supports(),
            'attributes'      => self::attributes_from_defaults(TamRank_HTML_Sitemap::get_defaults()),
            'render_callback' => array(__CLASS__, 'render_sitemap'),
        ));

        register_block_type('tamrank/breadcrumb', array(
            'api_version'     => 3,
            'title'           => tamrank__('block_breadcrumb_name', 'Breadcrumb'),
            'description'     => tamrank__('block_breadcrumb_desc', 'The trail from the homepage to this page, built from the same trail as the BreadcrumbList schema.'),
            'category'        => 'widgets',
            'icon'            => 'arrow-right-alt2',
            'keywords'        => array('breadcrumb', 'tamrank', 'seo'),
            'editor_script'   => self::EDITOR_HANDLE,
            'supports'        => self::supports(),
            'attributes'      => self::attributes_from_defaults(TamRank_Breadcrumbs::get_defaults()),
            'render_callback' => array(__CLASS__, 'render_breadcrumb'),
        ));
    }

    /**
     * Block supports, identical for both blocks.
     *
     * className/customClassName are switched OFF on purpose. They add a second
     * class channel that only reaches the markup when the render callback calls
     * get_block_wrapper_attributes() — which ours does not, because the wrapper
     * has to stay byte-identical to the shortcode's. Leaving them on would put
     * an "Additional CSS class(es)" field in the sidebar that silently does
     * nothing. Both blocks carry their own `class` attribute instead, and that
     * one lands on the same wrapper as the shortcode's `class`.
     *
     * html:false hides "Edit as HTML": there is no saved markup to edit.
     *
     * @return array
     */
    private static function supports()
    {
        return array(
            'html'             => false,
            'className'        => false,
            'customClassName'  => false,
        );
    }

    /**
     * Turn a shortcode defaults array into a block attribute schema.
     *
     * Every shortcode attribute is a string, and shortcode_atts() in the render
     * method fills in the same defaults again, so this stays a straight
     * translation. Deriving it means a new shortcode attribute is a block
     * attribute the moment it exists.
     *
     * @param array $defaults Output of a get_defaults() method.
     * @return array Block attribute schema.
     */
    private static function attributes_from_defaults($defaults)
    {
        $attributes = array();
        foreach ((array) $defaults as $key => $default) {
            $attributes[$key] = array(
                'type'    => 'string',
                'default' => (string) $default,
            );
        }

        return $attributes;
    }

    /**
     * Register + localise the editor script.
     *
     * The strings for the sidebar live here rather than in the JavaScript,
     * because the plugin translates through tamrank__() and not through .mo /
     * .json files — wp_set_script_translations() would find nothing to load.
     */
    private function register_editor_script()
    {
        wp_register_script(
            self::EDITOR_HANDLE,
            TAM_RANK_PLUGIN_URL . 'assets/js/tamrank-frontend-blocks.js',
            array('wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-server-side-render'),
            TAM_RANK_VERSION,
            true
        );

        wp_localize_script(self::EDITOR_HANDLE, 'tamrankFrontendBlocks', array(
            'panelTitle' => tamrank__('block_panel_title', 'Display'),
            'controls'   => array(
                'tamrank/sitemap'    => self::sitemap_controls(),
                'tamrank/breadcrumb' => self::breadcrumb_controls(),
            ),
        ));
    }

    /**
     * Sidebar controls for the sitemap block, one per shortcode attribute.
     *
     * @return array
     */
    private static function sitemap_controls()
    {
        $heading_options = array();
        foreach (TamRank_HTML_Sitemap::ALLOWED_HEADING_TAGS as $tag) {
            $heading_options[] = array(
                'value' => $tag,
                'label' => strtoupper($tag),
            );
        }

        return array(
            array(
                'key'   => 'post_types',
                'type'  => 'text',
                'label' => tamrank__('block_post_types_label', 'Post types'),
                'help'  => tamrank__('block_post_types_help', 'Comma-separated slugs, for example page,post. Leave empty for every type the XML sitemap publishes. A type that is switched off there stays hidden either way.'),
            ),
            array(
                'key'   => 'taxonomies',
                'type'  => 'text',
                'label' => tamrank__('block_taxonomies_label', 'Taxonomies'),
                'help'  => tamrank__('block_taxonomies_help', 'Comma-separated slugs, for example category. Leave empty for every taxonomy the XML sitemap publishes.'),
            ),
            array(
                'key'        => 'home',
                'type'       => 'toggle',
                'label'      => tamrank__('block_home_label', 'Show the homepage'),
                'onValue'    => 'yes',
                'offValue'   => 'no',
                // Mirrors TamRank_HTML_Sitemap::NO_VALUES, so the toggle reads a
                // hand-typed shortcode value the same way the renderer does.
                'noValues'   => TamRank_HTML_Sitemap::NO_VALUES,
            ),
            array(
                'key'     => 'heading_tag',
                'type'    => 'select',
                'label'   => tamrank__('block_heading_tag_label', 'Heading level'),
                'options' => $heading_options,
            ),
            array(
                'key'   => 'class',
                'type'  => 'text',
                'label' => tamrank__('block_class_label', 'Extra CSS class'),
                'help'  => tamrank__('block_class_help', 'Added to the wrapper so your theme can style it. The plugin ships no stylesheet of its own.'),
            ),
        );
    }

    /**
     * Sidebar controls for the breadcrumb block.
     *
     * @return array
     */
    private static function breadcrumb_controls()
    {
        return array(
            array(
                'key'   => 'separator',
                'type'  => 'text',
                'label' => tamrank__('block_separator_label', 'Separator'),
                'help'  => tamrank__('block_separator_help', 'Shown between two crumbs. Hidden from screen readers and never part of the schema.'),
            ),
            array(
                'key'   => 'class',
                'type'  => 'text',
                'label' => tamrank__('block_class_label', 'Extra CSS class'),
                'help'  => tamrank__('block_class_help', 'Added to the wrapper so your theme can style it. The plugin ships no stylesheet of its own.'),
            ),
        );
    }

    /* ==================================================================
     * Render callbacks — one line each, on purpose
     * ================================================================*/

    /**
     * Render the sitemap block.
     *
     * @param array $attributes Block attributes.
     * @return string HTML
     */
    public static function render_sitemap($attributes = array())
    {
        return TamRank_HTML_Sitemap::render(is_array($attributes) ? $attributes : array());
    }

    /**
     * Render the breadcrumb block.
     *
     * @param array $attributes Block attributes.
     * @return string HTML
     */
    public static function render_breadcrumb($attributes = array())
    {
        return TamRank_Breadcrumbs::render(is_array($attributes) ? $attributes : array());
    }
}
