<?php
/**
 * Tam Rank™ frontend output — widgets
 *
 * The third route to the same two renderers. [tamrank_sitemap] is a shortcode,
 * a block and — here — a classic WP_Widget; likewise [tamrank_breadcrumb].
 *
 * Classic WP_Widget rather than a block-only widget on purpose: a WP_Widget
 * still works in a classic widget area (a large part of the theme population,
 * Divi included) and shows up in the block-based widget screen as a Legacy
 * Widget, so one class covers both editors.
 *
 * Same rule as the block: no markup is built here. The widget collects fields
 * that map one-to-one onto the shortcode attributes and hands them to
 * TamRank_HTML_Sitemap::render() / TamRank_Breadcrumbs::render(). The only thing
 * this file adds to the output is the widget title and the theme's own
 * before_widget / after_widget wrappers, which every widget gets.
 *
 * @package TamRank
 * @since 1.3.7
 */

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

/**
 * Shared plumbing for both widgets.
 *
 * Holds everything that would otherwise be copied twice: reading the instance
 * back into shortcode attributes, rendering the title, and the field markup.
 */
abstract class TamRank_Frontend_Widget extends WP_Widget
{
    /**
     * The attribute defaults this widget's fields mirror.
     *
     * @return array Same shape as the renderer's get_defaults().
     */
    abstract protected function attribute_defaults();

    /**
     * Render the feature for the given attributes.
     *
     * @param array $atts Attributes.
     * @return string HTML
     */
    abstract protected function render_feature($atts);

    /**
     * Output the widget.
     *
     * @param array $args     Theme's sidebar arguments.
     * @param array $instance Saved widget settings.
     */
    public function widget($args, $instance)
    {
        $html = $this->render_feature($this->instance_to_atts($instance));

        // An empty render means there is nothing to show — the breadcrumb on the
        // homepage, or a sitemap with no published content. Printing the theme's
        // widget chrome around nothing leaves an empty box in the sidebar, so
        // skip the whole widget instead.
        if ($html === '') {
            return;
        }

        $before_widget = isset($args['before_widget']) ? $args['before_widget'] : '';
        $after_widget  = isset($args['after_widget']) ? $args['after_widget'] : '';
        $title_html    = '';

        $title = isset($instance['title']) ? trim((string) $instance['title']) : '';
        if ($title !== '') {
            $title      = apply_filters('widget_title', $title, $instance, $this->id_base);
            $title_html = (isset($args['before_title']) ? $args['before_title'] : '')
                . esc_html($title)
                . (isset($args['after_title']) ? $args['after_title'] : '');
        }

        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $before_widget/$after_widget are the theme's own sidebar markup, passed through unchanged as every widget does; the title is esc_html()'d above; $html is assembled by the shared renderer, where every URL goes through esc_url() and every label through esc_html().
        echo $before_widget . $title_html . $html . $after_widget;
    }

    /**
     * Save the form.
     *
     * Only the shape is enforced here — the values keep their raw text, exactly
     * as a shortcode attribute does. The renderer sanitises them for real:
     * sanitize_key() per slug, sanitize_html_class() on the class, a whitelist on
     * the heading tag and esc_html() on the separator. Cleaning them twice, in
     * two different ways, is how a widget and a shortcode start disagreeing.
     *
     * @param array $new_instance Submitted values.
     * @param array $old_instance Previously saved values.
     * @return array
     */
    public function update($new_instance, $old_instance)
    {
        $instance = array(
            'title' => isset($new_instance['title']) ? sanitize_text_field($new_instance['title']) : '',
        );

        foreach ($this->attribute_defaults() as $key => $default) {
            $instance[$key] = isset($new_instance[$key])
                ? sanitize_text_field($new_instance[$key])
                : (string) $default;
        }

        return $instance;
    }

    /**
     * The saved instance as renderer attributes.
     *
     * @param array $instance Saved widget settings.
     * @return array
     */
    protected function instance_to_atts($instance)
    {
        $atts = array();
        foreach ($this->attribute_defaults() as $key => $default) {
            $atts[$key] = isset($instance[$key]) ? $instance[$key] : $default;
        }

        return $atts;
    }

    /**
     * Merge a saved instance with the defaults for the form.
     *
     * @param array $instance Saved widget settings.
     * @return array
     */
    protected function form_values($instance)
    {
        $values = array('title' => '');
        foreach ($this->attribute_defaults() as $key => $default) {
            $values[$key] = (string) $default;
        }

        foreach ($values as $key => $default) {
            if (isset($instance[$key])) {
                $values[$key] = (string) $instance[$key];
            }
        }

        return $values;
    }

    /**
     * A plain text field.
     *
     * @param string $key   Field key.
     * @param string $label Field label.
     * @param string $value Current value.
     * @param string $help  Optional description under the field.
     */
    protected function text_field($key, $label, $value, $help = '')
    {
        ?>
        <p>
            <label for="<?php echo esc_attr($this->get_field_id($key)); ?>"><?php echo esc_html($label); ?></label>
            <input class="widefat"
                   type="text"
                   id="<?php echo esc_attr($this->get_field_id($key)); ?>"
                   name="<?php echo esc_attr($this->get_field_name($key)); ?>"
                   value="<?php echo esc_attr($value); ?>">
            <?php if ($help !== '') : ?>
                <small><?php echo esc_html($help); ?></small>
            <?php endif; ?>
        </p>
        <?php
    }

    /**
     * A checkbox that stores the shortcode's yes/no wording.
     *
     * @param string $key   Field key.
     * @param string $label Field label.
     * @param string $value Current value.
     */
    protected function yes_no_field($key, $label, $value)
    {
        $checked = !in_array(strtolower(trim($value)), TamRank_HTML_Sitemap::NO_VALUES, true);
        ?>
        <p>
            <input type="hidden"
                   name="<?php echo esc_attr($this->get_field_name($key)); ?>"
                   value="no">
            <input type="checkbox"
                   id="<?php echo esc_attr($this->get_field_id($key)); ?>"
                   name="<?php echo esc_attr($this->get_field_name($key)); ?>"
                   value="yes"
                   <?php checked($checked); ?>>
            <label for="<?php echo esc_attr($this->get_field_id($key)); ?>"><?php echo esc_html($label); ?></label>
        </p>
        <?php
    }

    /**
     * A dropdown.
     *
     * @param string   $key     Field key.
     * @param string   $label   Field label.
     * @param string   $value   Current value.
     * @param string[] $options Allowed values.
     */
    protected function select_field($key, $label, $value, $options)
    {
        ?>
        <p>
            <label for="<?php echo esc_attr($this->get_field_id($key)); ?>"><?php echo esc_html($label); ?></label>
            <select class="widefat"
                    id="<?php echo esc_attr($this->get_field_id($key)); ?>"
                    name="<?php echo esc_attr($this->get_field_name($key)); ?>">
                <?php foreach ($options as $option) : ?>
                    <option value="<?php echo esc_attr($option); ?>" <?php selected($value, $option); ?>>
                        <?php echo esc_html(strtoupper($option)); ?>
                    </option>
                <?php endforeach; ?>
            </select>
        </p>
        <?php
    }

    /**
     * The title field every widget shares.
     *
     * @param string $value Current value.
     */
    protected function title_field($value)
    {
        $this->text_field('title', tamrank__('widget_title_label', 'Title'), $value);
    }
}

/**
 * The HTML sitemap as a widget.
 */
class TamRank_Sitemap_Widget extends TamRank_Frontend_Widget
{
    public function __construct()
    {
        parent::__construct(
            'tamrank_sitemap',
            tamrank__('widget_sitemap_name', 'TamRank — HTML sitemap'),
            array(
                'description'                 => tamrank__('widget_sitemap_desc', 'The same links as the XML sitemap, in a widget area.'),
                'classname'                   => 'tamrank-sitemap-widget',
                'customize_selective_refresh' => true,
            )
        );
    }

    protected function attribute_defaults()
    {
        return TamRank_HTML_Sitemap::get_defaults();
    }

    protected function render_feature($atts)
    {
        return TamRank_HTML_Sitemap::render($atts);
    }

    /**
     * The settings form.
     *
     * @param array $instance Saved widget settings.
     */
    public function form($instance)
    {
        $values = $this->form_values($instance);

        $this->title_field($values['title']);
        $this->text_field(
            'post_types',
            tamrank__('block_post_types_label', 'Post types'),
            $values['post_types'],
            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.')
        );
        $this->text_field(
            'taxonomies',
            tamrank__('block_taxonomies_label', 'Taxonomies'),
            $values['taxonomies'],
            tamrank__('block_taxonomies_help', 'Comma-separated slugs, for example category. Leave empty for every taxonomy the XML sitemap publishes.')
        );
        $this->yes_no_field('home', tamrank__('block_home_label', 'Show the homepage'), $values['home']);
        $this->select_field(
            'heading_tag',
            tamrank__('block_heading_tag_label', 'Heading level'),
            $values['heading_tag'],
            TamRank_HTML_Sitemap::ALLOWED_HEADING_TAGS
        );
        $this->text_field(
            'class',
            tamrank__('block_class_label', 'Extra CSS class'),
            $values['class'],
            tamrank__('block_class_help', 'Added to the wrapper so your theme can style it. The plugin ships no stylesheet of its own.')
        );
    }
}

/**
 * The breadcrumb as a widget.
 */
class TamRank_Breadcrumb_Widget extends TamRank_Frontend_Widget
{
    public function __construct()
    {
        parent::__construct(
            'tamrank_breadcrumb',
            tamrank__('widget_breadcrumb_name', 'TamRank — Breadcrumb'),
            array(
                'description'                 => tamrank__('widget_breadcrumb_desc', 'The trail from the homepage to the page being viewed.'),
                'classname'                   => 'tamrank-breadcrumb-widget',
                'customize_selective_refresh' => true,
            )
        );
    }

    protected function attribute_defaults()
    {
        return TamRank_Breadcrumbs::get_defaults();
    }

    protected function render_feature($atts)
    {
        return TamRank_Breadcrumbs::render($atts);
    }

    /**
     * The settings form.
     *
     * @param array $instance Saved widget settings.
     */
    public function form($instance)
    {
        $values = $this->form_values($instance);

        $this->title_field($values['title']);
        $this->text_field(
            'separator',
            tamrank__('block_separator_label', 'Separator'),
            $values['separator'],
            tamrank__('block_separator_help', 'Shown between two crumbs. Hidden from screen readers and never part of the schema.')
        );
        $this->text_field(
            'class',
            tamrank__('block_class_label', 'Extra CSS class'),
            $values['class'],
            tamrank__('block_class_help', 'Added to the wrapper so your theme can style it. The plugin ships no stylesheet of its own.')
        );
    }
}

/**
 * Register both widgets.
 *
 * @since 1.3.7
 */
function tamrank_register_frontend_widgets()
{
    register_widget('TamRank_Sitemap_Widget');
    register_widget('TamRank_Breadcrumb_Widget');
}
add_action('widgets_init', 'tamrank_register_frontend_widgets');
