<?php

namespace yndenz\Modules\Navigation;

use stdClass;
use Walker_Nav_Menu;
use WP_Post;

class NavNonClickables extends Walker_Nav_Menu {

    /**
     * @see Walker::start_lvl()
     * @since 3.0.0
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param int $depth Depth of page. Used for padding.
     * @param array $args
     */
    public function start_lvl(&$output, $depth = 0, $args = array()) {
        $indent = str_repeat("\t", $depth);
        $output .= "\n$indent<ul role=\"menu\">\n";
    }

    /**
     * Starts the element output.
     *
     * @since 3.0.0
     * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
     *
     * @see Walker::start_el()
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param WP_Post $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     * @param int $id Current item ID.
     */
    public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        $indent = ($depth) ? str_repeat("\t", $depth) : '';

        $attributes = $this->get_item_attributes($item, $args);
        $output .= $indent . '<li' . $attributes . '>';

        $item_output = '';

        // If the item is a divider or invisible item, the item itself remains empty (except maybe for children)
        if (!$this->item_is_a($item, 'invisible|divider')) {
            $item_output = $this->get_item_output($item, $depth, $args);
        }

        $output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
    }

    /**
     * Get the attributes for the item.
     *
     * @param WP_Post $item Menu item data object.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     *
     * @return string
     */
    protected function get_item_attributes($item, $args) {
        $attributes = '';

        $id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args);
        if ($id) {
            $attributes .= ' id="' . esc_attr($id) . '"';
        }

        if ($this->item_is_a($item, 'non-clickable|invisible|shortcode|divider')) {
            $attributes .= ' role="presentation"';
        }

        $classes = $this->item_get_classes($item, $args);
        if (count($classes) > 0) {
            $attributes .= ' class="' . join(' ', $classes) . '"';
        }

        return $attributes;
    }

    /**
     * Get class names for the item.
     *
     * @param WP_Post $item Menu item data object.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     *
     * @return array
     */
    protected function item_get_classes($item, $args) {
        if (empty($item->classes)) {
            $item->classes = array();
        }

        if (!is_array($item->classes)) {
            $item->classes = (array)$item->classes;
        }

        array_push($item->classes, 'menu-item-' . $item->ID);

        if ($this->item_is_a($item, 'divider')) {
            array_push($item->classes, 'devider');
        }

        if ($this->item_is_a($item, 'non-clickable')) {
            array_push($item->classes, 'disabled');
        }

        if (substr($item->description, 0, 6) === 'image:') {
            array_push($item->classes, 'margin-bottom-15');
        }

        return apply_filters('nav_menu_css_class', array_filter($item->classes), $item, $args);
    }

    /**
     * Determine if the item compares with the given regex.
     *
     * @param WP_Post $item Menu item data object.
     * @param string $regex
     *
     * @return bool
     */
    protected function item_is_a($item, $regex) {
        return preg_match("~(https?://)?{$regex}~i", $item->url) > 0;
    }

    /**
     * Get the item output.
     *
     * @param WP_Post $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     *
     * @return string
     */
    protected function get_item_output($item, $depth, $args) {
        $item->title = apply_filters('the_title', $item->title, $item->ID);

        // If the item is non-clickable, the content should only be a span instead of an anchor
        if ($this->item_is_a($item, 'non-clickable')) {
            return "{$args->before}<span>{$args->link_before}{$item->title}{$args->link_after}</span>{$args->after}";
        }

        $item_output = $args->before . $item->before;

        if ($this->item_is_a($item, 'shortcode')) {
            if ($this->description_includes_link($item)) {
                $item_output .= $this->build_anchor($item, $depth, $args);
            }
            $item_output .= apply_filters('the_content', $item->description);
        } else {
            $item_output .= $this->build_anchor($item, $depth, $args);
        }

        $item_output .= $item->after . $args->after;

        return apply_filters('nav_non_clickable_item_output', $item_output);
    }

    /**
     * Determine if the description includes a link.
     *
     * @param WP_Post $item Menu item data object.
     *
     * @return bool|string The link if it is included in the description, false if not
     */
    protected function description_includes_link(&$item) {
        $link = false;

        if (preg_match('/link\:\s*([^\s]+)\s*(.*)/i', $item->description, $matches) > 0) {
            $link = $matches[1];
            $item->description = $matches[2];
        }

        return $link;
    }

    /**
     * Build the anchor.
     *
     * @param WP_Post $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     *
     * @return string
     */
    protected function build_anchor($item, $depth, $args) {
        $attributes = $this->get_anchor_attributes($item, $args);
        $anchor = '<a' . $attributes . '>';
        $anchor .= $args->link_before;

        // If the description contains an image, use the image instead of the title
        if (preg_match('/image\:\s*(\d+)/i', $item->description, $matches)) {
            $anchor .= wp_get_attachment_image($matches[1], 'medium', false, array('class' => 'img-responsive'));
        } else {
            $anchor .= $item->title;
        }

        $anchor .= $args->link_after;

        // For extra customization options (i.e. a caret for bootstrap dropdowns), we add a link_extra_after
        if (property_exists($item, 'link_extra_after')) {
            $anchor .= $item->link_extra_after;
        }

        $anchor .= '</a>';

        return apply_filters('nav_non_clickable_anchor', $anchor);
    }

    /**
     * Get the attributes for the anchor.
     *
     * @param WP_Post $item Menu item data object.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     *
     * @return string
     */
    protected function get_anchor_attributes($item, $args) {
        $atts = array();
        $atts['title'] = !empty($item->attr_title) ? $item->attr_title : '';
        $atts['target'] = !empty($item->target) ? $item->target : '';
        $atts['rel'] = !empty($item->xfn) ? $item->xfn : '';
        $atts['href'] = !empty($item->url) ? $item->url : '';

        // If the description contains a link, use that as href (used for shortcode items)
        $href = $this->description_includes_link($item);
        if ($href !== false) {
            $atts['href'] = $href;
        }

        // External links should always be opened in a new window/tab
        $host = parse_url($atts['href'], PHP_URL_HOST);
        if (!empty($host) && $host !== parse_url(home_url(), PHP_URL_HOST)) {
            $atts['target'] = '_blank';
        }

        $atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args);
        $attributes = '';
        foreach ($atts as $attr => $value) {
            if (!empty($value)) {
                $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        return apply_filters('nav_non_clickable_anchor_attributes', $attributes);
    }

    /**
     * Traverse elements to create list from elements.
     *
     * Display one element if the element doesn't have any children otherwise,
     * display the element and its children. Will only traverse up to the max
     * depth and no ignore elements under that depth. It is possible to set the
     * max depth to include all depths, see walk() method.
     *
     * This method should not be called directly, use the walk() method instead.
     *
     * @since 2.5.0
     *
     * @param object $element Data object.
     * @param array $children_elements List of elements to continue traversing.
     * @param int $max_depth Max depth to traverse.
     * @param int $depth Depth of current element.
     * @param array $args An array of arguments.
     * @param string $output Passed by reference. Used to append additional content.
     */
    public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
        if (!$element) {
            return;
        }

        $id_field = $this->db_fields['id'];

        // If the item has children, set a boolean
        if (is_object($args[0])) {
            $args[0]->has_children = !empty($children_elements[$element->$id_field]);
        }

        // Display this element.
        parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
    }

}