<?php

namespace Drupal\nodehive_sitemap_visualizer\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\nodehive_core\SpaceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Controller for the sitemap visualization.
 */
class SitemapController extends ControllerBase {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The menu link tree service.
   *
   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
   */
  protected $menuTree;

  /**
   * Constructs a SitemapController object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
   *   The menu link tree service.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, MenuLinkTreeInterface $menu_tree) {
    $this->entityTypeManager = $entity_type_manager;
    $this->menuTree = $menu_tree;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('menu.link_tree')
    );
  }

  /**
   * Displays the sitemap for a space.
   *
   * @param \Drupal\nodehive_core\SpaceInterface $nodehive_space
   *   The space entity.
   *
   * @return array
   *   A render array.
   */
  public function sitemap(SpaceInterface $nodehive_space) {
    $build = [
      '#theme' => 'nodehive_sitemap',
      '#space' => $nodehive_space,
      '#sitemap_data' => $this->buildSitemapData($nodehive_space),
      '#attached' => [
        'library' => ['nodehive_sitemap_visualizer/sitemap'],
      ],
    ];

    return $build;
  }

  /**
   * Builds the sitemap data structure.
   *
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return array
   *   The sitemap data structure.
   */
  protected function buildSitemapData(SpaceInterface $space) {
    $sitemap_data = [];

    // Get all menus that reference this space.
    $menus = $this->getMenusForSpace($space);

    // First, add all menu sections.
    foreach ($menus as $menu) {
      $menu_tree = $this->buildMenuTree($menu, $space);
      // Always add menu to show complete structure.
      $menu_data = [
        'id' => $menu->id(),
        'label' => $menu->label(),
        'type' => 'menu',
        'children' => $menu_tree,
      ];
      $sitemap_data[] = $menu_data;
    }

    // Add a message if no menus are configured.
    if (empty($menus)) {
      $space_content_count = (int) $this->entityTypeManager->getStorage('node')
        ->getQuery()
        ->accessCheck(FALSE)
        ->condition('nodehive_space', $space->id())
        ->count()
        ->execute();
      $no_menus_data = [
        'id' => 'no_menus',
        'label' => $this->t('No Menus Configured'),
        'type' => 'info',
        'children' => [],
        'message' => $this->t('This space has no menus configured but contains @count content items. To organize content by menu structure, please go to the <a href="@menus_url">Menus tab</a> and assign menus to this space.', [
          '@count' => $space_content_count,
          '@menus_url' => '/space/' . $space->id() . '/menus',
        ]),
      ];
      $sitemap_data[] = $no_menus_data;
    }

    // Then, add orphaned nodes at the bottom (nodes not in any menu).
    $orphaned_nodes = $this->getOrphanedNodes($space, $menus);
    if (!empty($orphaned_nodes)) {
      $orphaned_data = [
        'id' => 'orphaned',
        'label' => empty($menus) ? $this->t('All Content') : $this->t('Orphaned Content'),
        'type' => 'orphaned',
        'children' => $orphaned_nodes,
      ];
      $sitemap_data[] = $orphaned_data;
    }

    return $sitemap_data;
  }

  /**
   * Builds a menu tree with node information.
   *
   * @param \Drupal\system\MenuInterface $menu
   *   The menu entity.
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return array
   *   The menu tree data.
   */
  protected function buildMenuTree($menu, SpaceInterface $space) {
    $menu_tree_service = $this->menuTree;
    $menu_name = $menu->id();

    // Load the complete menu tree (all levels)
    $parameters = new MenuTreeParameters();
    // Set a reasonable max depth instead of 0.
    $parameters->setMaxDepth(10);
    $parameters->setMinDepth(1);
    $tree = $menu_tree_service->load($menu_name, $parameters);

    // Apply transformations to check access and sort.
    $manipulators = [
      ['callable' => 'menu.default_tree_manipulators:checkAccess'],
      ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
    ];
    $tree = $menu_tree_service->transform($tree, $manipulators);

    return $this->processMenuTree($tree, $space);
  }

  /**
   * Processes the menu tree and extracts node information.
   *
   * @param array $tree
   *   The menu tree.
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return array
   *   Processed menu tree data.
   */
  protected function processMenuTree(array $tree, SpaceInterface $space) {
    $processed = [];

    foreach ($tree as $element) {
      $link = $element->link;

      // Skip disabled menu items.
      if (!$link->isEnabled()) {
        continue;
      }

      $item_data = [
        'id' => $link->getPluginId(),
        'title' => $link->getTitle(),
        'url' => '',
        'type' => 'menu_item',
        'children' => [],
        'is_node' => FALSE,
        'content_type' => 'Menu Item',
        'content_type_machine' => 'menu_item',
        'status' => 'published',
      ];

      // Check if this menu item links to a node.
      if ($link->getRouteName() === 'entity.node.canonical') {
        $route_parameters = $link->getRouteParameters();
        if (isset($route_parameters['node'])) {
          $node_id = $route_parameters['node'];
          $node = $this->entityTypeManager->getStorage('node')->load($node_id);

          // Always show nodes linked from menus.
          if ($node) {
            $node_data = $this->getNodeData($node);
            // Preserve the menu title, but use node data for other fields.
            $menu_title = $item_data['title'];
            $item_data = array_merge($item_data, $node_data);
            // Keep the menu title.
            $item_data['title'] = $menu_title;
            // Store node title separately.
            $item_data['node_title'] = $node_data['title'];
            $item_data['type'] = 'node';
            $item_data['is_node'] = TRUE;

            // Check if node belongs to space for visual indication.
            if ($this->isNodeInSpace($node, $space)) {
              $item_data['in_space'] = TRUE;
            }
            else {
              $item_data['in_space'] = FALSE;
              $item_data['content_type'] = 'External Node';
              $item_data['content_type_machine'] = 'external_node';
            }
          }
          else {
            // Node not found, show as broken link.
            $item_data['content_type'] = 'Broken Link';
            $item_data['content_type_machine'] = 'broken_link';
            $item_data['status'] = 'broken';
            $item_data['url'] = '#';
          }
        }
      }
      else {
        // For non-node menu items, get the URL.
        try {
          $item_data['url'] = $link->getUrlObject()->toString();
        }
        catch (\Exception $e) {
          $item_data['url'] = '#';
        }
      }

      // Process children recursively.
      if ($element->hasChildren) {
        $children = $this->processMenuTree($element->subtree, $space);
        $item_data['children'] = $children;
      }

      // Always include menu items to preserve hierarchy.
      $processed[] = $item_data;
    }

    return $processed;
  }

  /**
   * Gets orphaned nodes (not in any menu) grouped by content type.
   *
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   * @param array $menus
   *   Array of menu entities.
   *
   * @return array
   *   Array of orphaned node data grouped by content type.
   */
  protected function getOrphanedNodes(SpaceInterface $space, array $menus) {
    // Get all nodes that belong to this space.
    $node_ids = $this->entityTypeManager->getStorage('node')
      ->getQuery()
      ->accessCheck(FALSE)
      ->condition('nodehive_space', $space->id())
      ->execute();
    $space_nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($node_ids);

    // Get all node IDs that are referenced in the space's menus.
    $menu_node_ids = $this->getMenuNodeIds($menus, $space);

    $orphaned_by_type = [];

    foreach ($space_nodes as $node) {
      // Include all nodes (published and unpublished) that are not in any menu.
      if (!in_array($node->id(), $menu_node_ids)) {
        $node_data = $this->getNodeData($node);
        $content_type = $node->getType();

        if (!isset($orphaned_by_type[$content_type])) {
          $orphaned_by_type[$content_type] = [
            'label' => $node_data['content_type'],
            'machine_name' => $content_type,
            'nodes' => [],
          ];
        }

        $orphaned_by_type[$content_type]['nodes'][] = $node_data;
      }
    }

    // Sort nodes within each content type by created date (newest first)
    foreach ($orphaned_by_type as &$type_data) {
      usort($type_data['nodes'], function ($a, $b) {
        $node_a = $this->entityTypeManager->getStorage('node')->load($a['id']);
        $node_b = $this->entityTypeManager->getStorage('node')->load($b['id']);

        if (!$node_a || !$node_b) {
          return 0;
        }

        return $node_b->getCreatedTime() - $node_a->getCreatedTime();
      });
    }

    return $orphaned_by_type;
  }

  /**
   * Gets all node IDs referenced in menus that belong to the space.
   *
   * @param array $menus
   *   Array of menu entities.
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return array
   *   Array of node IDs.
   */
  protected function getMenuNodeIds(array $menus, SpaceInterface $space) {
    $node_ids = [];

    foreach ($menus as $menu) {
      $menu_tree_service = $this->menuTree;
      $menu_name = $menu->id();

      $parameters = $menu_tree_service->getCurrentRouteMenuTreeParameters($menu_name);
      $tree = $menu_tree_service->load($menu_name, $parameters);

      $this->extractNodeIdsFromTree($tree, $node_ids, $space);
    }

    return array_unique($node_ids);
  }

  /**
   * Extracts node IDs from menu tree recursively, filtering by space.
   *
   * @param array $tree
   *   The menu tree.
   * @param array &$node_ids
   *   Array to collect node IDs.
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   */
  protected function extractNodeIdsFromTree(array $tree, array &$node_ids, SpaceInterface $space) {
    foreach ($tree as $element) {
      $link = $element->link;

      if ($link->getRouteName() === 'entity.node.canonical') {
        $route_parameters = $link->getRouteParameters();
        if (isset($route_parameters['node'])) {
          $node_id = $route_parameters['node'];
          $node = $this->entityTypeManager->getStorage('node')->load($node_id);

          // Only include nodes that belong to this space.
          if ($node && $this->isNodeInSpace($node, $space)) {
            $node_ids[] = $node_id;
          }
        }
      }

      if ($element->hasChildren) {
        $this->extractNodeIdsFromTree($element->subtree, $node_ids, $space);
      }
    }
  }

  /**
   * Checks if a node belongs to the given space.
   *
   * @param \Drupal\node\NodeInterface $node
   *   The node entity.
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return bool
   *   TRUE if the node belongs to the space.
   */
  protected function isNodeInSpace($node, SpaceInterface $space) {
    if (!$node->hasField('nodehive_space')) {
      return FALSE;
    }

    $node_spaces = $node->get('nodehive_space')->referencedEntities();
    foreach ($node_spaces as $node_space) {
      if ($node_space->id() === $space->id()) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Gets all menus that reference the given space.
   *
   * @param \Drupal\nodehive_core\SpaceInterface $space
   *   The space entity.
   *
   * @return array
   *   Array of menu entities that reference this space.
   */
  protected function getMenusForSpace(SpaceInterface $space) {
    $all_menus = $this->entityTypeManager->getStorage('menu')->loadMultiple();
    $space_menus = [];

    foreach ($all_menus as $menu) {
      // Get the space field value from the menu's third-party settings.
      $referenced_spaces = $menu->getThirdPartySetting('nodehive_core', 'nodehive_space_field', []);

      if (in_array($space->id(), $referenced_spaces)) {
        $space_menus[$menu->id()] = $menu;
      }
    }

    return $space_menus;
  }

  /**
   * Gets formatted node data for display.
   *
   * @param \Drupal\node\NodeInterface $node
   *   The node entity.
   *
   * @return array
   *   Formatted node data.
   */
  protected function getNodeData($node) {
    $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->getType());

    return [
      'id' => $node->id(),
      'title' => $node->getTitle(),
      'content_type' => $node_type ? $node_type->label() : $node->getType(),
      'content_type_machine' => $node->getType(),
      'slug' => $node->toUrl()->toString(),
      'status' => $node->isPublished() ? 'published' : 'unpublished',
      'type' => 'node',
      'edit_url' => $node->toUrl('edit-form')->toString(),
      'view_url' => $node->toUrl()->toString(),
    ];
  }

}
