<?php

namespace Drupal\reader\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Markup;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\reader\ReaderInterface;
use Drupal\user\UserDataInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class ReaderController extends ControllerBase {

  /**
   * The date formatter service.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected $dateFormatter;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The user data service.
   *
   * @var \Drupal\user\UserDataInterface
   */
  protected $userData;

  /**
   * ReaderController constructor.
   *
   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
   *   The date formatter.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\user\UserDataInterface $user_data
   *   The user data service.
   */
  public function __construct(DateFormatterInterface $date_formatter, EntityFieldManagerInterface $entity_field_manager, UserDataInterface $user_data) {
    $this->dateFormatter = $date_formatter;
    $this->entityFieldManager = $entity_field_manager;
    $this->userData = $user_data;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('date.formatter'),
      $container->get('entity_field.manager'),
      $container->get('user.data')
    );
  }

  /**
   * Returns the manifest.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   */
  public function manifest() {

    $settings = $this->userData->get('reader', $this->currentUser()->id(), 'settings');
    $pwa_display = !empty($settings['pwa_display']) ? $settings['pwa_display'] : 'standalone';
    $url = Url::fromRoute('reader.home')->toString();
    $manifest = [];
    $manifest['name'] = 'Reader';
    $manifest['scope'] = $url;
    $manifest['start_url'] = $url;
    $manifest['display'] = $pwa_display;
    $manifest['icons'][] = (object) [
      'src' => \Drupal::service('file_url_generator')->generateAbsoluteString(\Drupal::service('extension.list.theme')->getPath('reader_theme') . '/images/icon.png'),
      'sizes' => '192x192',
      'type' => 'image/png',
    ];

    $this->moduleHandler()->alter('reader_manifest', $manifest);

    return new JsonResponse($manifest);
  }

  /**
   * Home entry point for the reader.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *
   * @return array
   */
  public function home(Request $request) {
    $build = [];

    $settings = $this->userData->get('reader', $this->currentUser()->id(), 'settings');
    $default_timeline = !empty($settings['default_timeline']) ? $settings['default_timeline'] : '';
    if (!empty($default_timeline)) {
      [$type, $id] = explode(ReaderInterface::SEPARATOR, $default_timeline);
      return $this->timeline($request, $type, $id, TRUE);
    }

    $build['main'] = ['#markup' => '<div class="general-content">' . $this->t('Click on a channel to start reading') . '</div>'];
    $build['#cache']['max-age'] = 0;
    $build['#cached']['tags'] = ['reader'];

    return $build;
  }

  /**
   * Perform a sources action.
   *
   * @param $module
   * @param $op
   *
   * @return array
   */
  public function sources($module, $op) {
    $build = [];

    if ($op == 'list' && $module == '_reader_sources_list_') {
      $sources = [];
      foreach (reader_get_implementators() as $module) {
        $function = $module . '_reader_channels';
        if (function_exists($function)) {
          $data = $function();
          if (!empty($data['channels'])) {
            $sources[] = Link::fromTextAndUrl($data['label'], Url::fromRoute('reader.sources', [
              'module' => strtr($module, '_', '-'),
              'op' => 'list',
            ], ['attributes' => ['class' => ['column-link']]]))->toString();
          }
        }
      }

      return [
        '#theme' => 'reader_sources_list',
        '#sources' => $sources,
        '#title' => $this->t('Manage sources'),
        '#cache' => ['max-age' => 0],
      ];
    }
    else {

      // Replace dashes with underscores.
      $module = strtr($module, '-', '_');

      $function = $module . '_reader_sources';
      if (function_exists($function)) {
        $build = $function($op);
        $build['#prefix'] = '<div class="reader-sources-container">';
        $build['#suffix'] = '</div>';
        $build['#cache']['max-age'] = 0;
      }
    }

    return $build;
  }

  /**
   * Returns the timeline.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   * @param $module
   * @param $id
   * @param $is_home
   * @param $search
   *
   * @return array
   */
  public function timeline(Request $request, $module, $id, $is_home = FALSE, $search = '') {
    $build = [];
    $timeline_response = [];
    $timeline = [];
    $pager = NULL;
    $page = '';
    $title = $this->t('Unknown timeline');

    // Replace dashes with underscores.
    $module = strtr($module, '-', '_');

    $function = $module . '_reader_timeline';
    if (function_exists($function)) {
      $timeline_response = $function($id, $search);

      // Channel name.
      if (empty($search)) {
        $channel_function = $module . '_reader_channels';
        if (function_exists($channel_function)) {
          $channel_data = $channel_function();
          foreach ($channel_data['channels'] as $c) {
            if ($c->uid == $id) {
              $title = $c->name;
              break;
            }
          }
        }
      }
      else {
        $title = $this->t('Search');
      }
    }

    // Loop over the items.
    if (!empty($timeline_response['items'])) {
      $position = 1;
      foreach ($timeline_response['items'] as $item) {
        $audio = $video = NULL;

        $internal_id = !empty($item->_id) ? $item->_id : '';
        $url = !empty($item->url) ? $item->url : '';
        $author = $this->t('Unknown');
        if (!empty($item->author->name)) {
          $author = $item->author->name;
        }
        elseif (!empty($item->author->url)) {
          $author = $item->author->url;
        }
        $author_url = !empty($item->author->url) ? $item->author->url : '#';

        $name = '';
        $content = '';
        $summary = '';
        if (!empty($item->content->html)) {
          $content = $this->filterContent($item->content->html);
        }
        elseif (!empty($item->content->text)) {
          $content = $this->filterContent($item->content->text);
        }
        elseif (!empty($item->summary)) {
          $summary = $this->filterContent($item->summary);
        }

        if (!empty($item->name)) {
          $name = $this->filterContent($item->name);
        }

        // New.
        $new = FALSE;
        if (isset($item->_is_read) && !$item->_is_read) {
          $new = TRUE;
        }

        // Avatar.
        $avatar = !empty($item->author->photo) ? $item->author->photo : '';

        // Date.
        $date = '';
        $datetime = $item->published ?? '';
        if (!empty($datetime)) {
          $date = $this->dateFormatter->format(strtotime($item->published), 'custom', 'd/m/Y H:i');
        }

        // Response type.
        $response_key = '';
        $response_key_value = '';
        $response_type = '';
        foreach ($this->responseTypes() as $key => $label) {
          if (isset($item->{$key})) {
            $response_key = $key;
            $response_key_value_label = NULL;
            if (is_array($item->{$key})) {
              $response_key_value = $item->{$key}[0];
            }
            elseif (is_object($item->{$key})) {
              if (isset($item->{$key}->url)) {
                $response_key_value = $item->{$key}->url;
                if (isset($item->{$key}->name)) {
                  $response_key_value_label = $item->{$key}->name;
                }
              }
            }
            else {
              $response_key_value = $item->{$key};
            }
            if (!isset($response_key_value_label)) {
              $response_key_value_label = $response_key_value;
            }
            $response_type = Markup::create('<div class="response-type">' . $label . ' <a href="' . Html::escape($response_key_value) . '">' . Html::escape($response_key_value_label) . '</a></div>');
          }
        }

        // Images.
        if (!empty($item->photo)) {
          foreach ($item->photo as $p) {
            if (is_array($p)) {
              $images[] = ['url' => $p['url'], 'alt' => $p['alt'] ?? ''];
            }
            else {
              $images[] = ['url' => $p, 'alt' => ''];
            }
          }
        }

        // Context.
        $context = '';
        if (!empty($response_key) && !empty($item->refs) && isset($item->refs->{$response_key_value})) {
          $ref = $item->refs->{$response_key_value};

          // Context content.
          $context_content = '';
          if (isset($ref->content)) {
            if (isset($ref->content->html)) {
              $context_content = $ref->content->html;
            }
            elseif (isset($ref->content->text)) {
              $context_content = $ref->content->text;
            }
          }

          if (!empty($context_content)) {
            if ($response_key == 'repost-of') {
              $content = NULL;
            }
            $context = $this->filterContent($context_content);
          }
          elseif (isset($ref->summary)) {
            $context = $this->filterContent($ref->summary);
          }

          // Context images.
          if (isset($ref->photo)) {
            foreach ($ref->photo as $p) {
              if (is_array($p)) {
                $images[] = ['url' => $p['url'], 'alt' => $p['alt'] ?? ''];
              }
              else {
                $images[] = ['url' => $p, 'alt' => ''];
              }
            }
          }

          // Context audio and video.
          if (!empty($ref->audio)) {
            $audio = $ref->audio[0];
          }
          if (!empty($ref->video)) {
            $video = $ref->video[0];
          }
        }

        // Set content to summary in case nothing was found.
        if (empty($response_type) && empty($content) && !empty($summary)) {
          $content = $summary;
        }

        // Main audio & video.
        if (!empty($item->audio)) {
          $audio = $item->audio[0];
        }
        if (!empty($item->video)) {
          $video = $item->video[0];
        }

        // Get actions.
        $actions = NULL;
        if ($links = $this->getActionLinks($module, $id, $item)) {
          $actions = [
            '#theme' => 'links__reader_post_actions',
            '#links' => $links,
          ];
        }

        $timeline[] = [
          '#theme' => 'reader_article',
          '#position' => $position,
          '#id' => $internal_id,
          '#url' => $url,
          '#response_type' => $response_type,
          '#name' => $name,
          '#content' => $content,
          '#context' => $context,
          '#images' => $images,
          '#datetime' => $datetime,
          '#date' => $date,
          '#avatar' => $avatar,
          '#author' => $author,
          '#author_url' => $author_url,
          '#new' => $new,
          '#audio' => $audio,
          '#video' => $video,
          '#actions' => $actions,
        ];
        $position++;
      }

      // Pager.
      if (isset($timeline_response['paging']->after)) {
        $pager = ['#type' => 'pager'];
      }

      // Page.
      if (!empty($timeline) && !$this->config('reader.settings')->get('use_infinite_scroll')) {
        $page = $this->t('page @page', ['@page' => 1]);
        if ($after = $request->get('page')) {
          $page = $this->t('page @page', ['@page' => $after + 1]);
        }

        $page = ' - ' . $page;
      }

    }

    $build['#theme'] = 'reader_timeline';
    $build['#timeline'] = $timeline;
    $build['#pager'] = $pager;
    $build['#title'] = $is_home ? $this->t('Home') : $title . $page;
    $build['#empty'] = ['#markup' => '<div class="general-content">' . $this->t('No items found') . '</div>'];
    $build['#cache']['max-age'] = 0;
    $build['#cache']['tags'] = ['reader'];

    // Read more.
    $build['#attached']['drupalSettings']['reader']['readMore'] = $this->config('reader.settings')->get('read_more');
    if ($build['#attached']['drupalSettings']['reader']['readMore']) {
      $build['#attached']['drupalSettings']['reader']['readMoreHeight'] = $this->config('reader.settings')->get('read_more_height');
    }

    // Infinite scroll.
    if ($this->config('reader.settings')->get('use_infinite_scroll')) {
      $build['#attached']['library'][] = 'reader_theme/infinite-scroll';
    }

    // Magnific popup.
    if ($this->config('reader.settings')->get('use_magnific_popup')) {
      $build['#attached']['library'][] = 'magnific_popup/magnific_popup';
    }

    return $build;
  }

  /**
   * Routing callback: search.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *
   * @return array
   */
  public function search(Request $request) {
    $module = $request->query->get('module') ?: 'indieweb_microsub';
    return $this->timeline($request, $module, 'internal-search', FALSE, $request->query->get('keyword'));
  }

  /**
   * Routing callback: do a timeline action.
   *
   * @param $module
   * @param $action
   * @param $id
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   */
  public function timelineAction($module, $action, $id) {

    $function = strtr($module, '-', '_') . '_reader_do_timeline_action';
    if (function_exists($function)) {
      $function($action, $id);
    }

    $this->messenger()->addMessage($this->t('Performed the "@action" action', ['@action' => $action]));

    // All actions have a destination.
    return $this->redirect('reader.home');
  }

  /**
   * Routing callback: do a post action.
   *
   * @param $module
   * @param $action
   * @param $id
   * @param $post_id
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   */
  public function postAction($module, $action, $id, $post_id) {

    $function = strtr($module, '-', '_') . '_reader_do_post_action';
    if (function_exists($function)) {
      $function($action, $id, [$post_id]);
    }

    $this->messenger()->addMessage($this->t('Performed the "@action" action', ['@action' => $action]));

    // All actions have a destination.
    return $this->redirect('reader.home');
  }

  /**
   * Routing callback: overview of your content.
   *
   * @return array $build
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function content() {
    $build = $rows = [];

    // Overwrite via ?content=.
    if (!empty($_GET['content']) && in_array($_GET['content'], ['content', 'comment'])) {
      $_SESSION['reader_content_type'] = $_GET['content'];
    }

    // Type depends on the session.
    $type = $_SESSION['reader_content_type'] ?? 'content';
    $entity_type_id = $type == 'content' ? 'node' : 'comment';

    $limit = Settings::get('reader_content_items_per_page', 30);

    $query = $this->entityTypeManager()->getStorage($entity_type_id)
      ->getQuery()
      ->accessCheck()
      ->pager($limit);
    if ($type == 'content') {
      $query->condition('uid', $this->currentUser()->id());
    }
    $query->sort('created', 'DESC');

    if ($type == 'content') {
      $node_hide = $this->config('reader.settings')->get('node_hide');
      if (!empty($node_hide)) {
        $query->condition('type', $node_hide, 'NOT IN');
      }
    }

    $ids = $query->execute();

    $options = [];
    $options['query'] = $this->getDestinationArray();
    $entities = $this->entityTypeManager()->getStorage($entity_type_id)->loadMultiple(array_values($ids));
    foreach ($entities as $entity) {
      $rows[] = [
        $entity->label(),
        $this->dateFormatter->format($entity->getCreatedTime(), 'custom', 'd/m/Y H:i'),
        Link::fromTextAndUrl($this->t('Edit'), Url::fromRoute('reader.' . $entity_type_id . '.edit', [$entity_type_id => $entity->id()], $options)),
      ];
    }

    $build['table'] = [
      '#type' => 'table',
      '#header' => [$this->t('Title'), $this->t('Created'), ''],
      '#rows' => $rows,
      '#empty' => $this->t('You have not created any posts yet.'),
      '#attributes' => ['class' => ['reader-content-table']],
    ];

    $build['pager'] = ['#type' => 'pager'];
    $build['#cache']['max-age'] = 0;
    $build['#title'] = $entity_type_id == 'comment' ? $this->t('Comments') : $this->t('Content');

    return $build;
  }

  /**
   * Filter content.
   *
   * @param $content
   *
   * @return \Drupal\Component\Render\MarkupInterface|string
   */
  protected function filterContent($content) {
    $filter_format_id = $this->config('reader.settings')->get('filter_format');
    return check_markup($content, $filter_format_id);
  }

  /**
   * Returns the response types.
   *
   * @return array
   */
  protected function responseTypes() {
    return [
      'like-of' => $this->t('Liked'),
      'repost-of' => $this->t('Repost of'),
      'quotation-of' => $this->t('Quoted'),
      'in-reply-to' => $this->t('In reply to'),
      'checkin' => $this->t('Checked in at'),
    ];
  }

  /**
   * Returns the action links for this post.
   *
   * @param $module
   *   The current module
   * @param $id
   *   The channel
   * @param $item
   *   The current post
   *
   * @return array $links
   */
  protected function getActionLinks($module, $id, $item) {
    $links = [];
    $actions = $this->config('reader.settings')->get('node_actions');
    $use_prepopulate = $this->config('reader.settings')->get('use_prepopulate');

    if (!empty($actions)) {
      $counter = count($actions);
      foreach ($actions as $delta => $action) {
        $options = [];

        // Add prepopulate.
        if ($use_prepopulate) {
          $query = [];

          // Url.
          if (!empty($item->url)) {
            $fields = $this->entityFieldManager->getFieldDefinitions('node', $action['node_type']);
            foreach ($fields as $field) {
              if ($field->getFieldStorageDefinition()->getType() == 'link' && !$field->isComputed()) {
                $query['edit[' . $field->getName() . '][widget][0][uri]'] = $item->url;
              }

              if ($field->getFieldStorageDefinition()->getName() == 'title') {
                $label = 'Bookmark of';
                if ($action['response_type'] == 'reply') {
                  $label = 'In reply to';
                }
                if ($action['response_type'] == 'repost') {
                  $label = 'Repost of';
                }
                if ($action['response_type'] == 'like') {
                  $label = 'Like of';
                }
                $query['edit[title][widget][0][value]'] = $label . ' ' . $item->url;
              }
            }
          }

          // Author.
          if (!empty($item->author->url)) {
            $query['to'] = $item->author->url;
          }

          if (!empty($query)) {
            $options['query'] = $query;
          }
        }

        $links[$delta] = [
          'title' => $this->getActionLinkLabel($action['response_type']),
          'url' => Url::fromRoute('reader.add', ['node_type' => $action['node_type']], $options),
          'attributes' => ['class' => ['response-action']],
        ];

        if (($delta + 1) == $counter) {
          $links[$delta]['attributes']['class'][] = 'response-action-last';
        }
      }
    }

    $function = strtr($module, '-', '_') . '_reader_post_actions';
    if (function_exists($function)) {
      $response = $function($id, $item);
      if (!empty($response)) {
        foreach ($response as $a) {
          $links[] = [
            'url' => Url::fromRoute('reader.post.action', ['module' => strtr($module, '_', '-'), 'action' => $a['action'], 'id' => $id, 'post_id' => $item->_id], ['query' => $this->getDestinationArray(), 'fragment' => 'post-id-' . $item->_id]),
            'title' => $a['title'],
            'attributes' => ['class' => ['post-action']],
          ];
        }
      }
    }

    $context = ['id' => $id, 'post_id' => $item->_id];
    $this->moduleHandler()->alter('reader_post_actions', $links, $context);

    return $links;
  }

  /**
   * Returns the label for the action link.
   *
   * @param $type
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   */
  protected function getActionLinkLabel($type) {
    $label = $this->t('Unknown');

    switch ($type) {
      case 'reply':
        $label = $this->t('Reply');
        break;

      case 'repost':
        $label = $this->t('Repost');
        break;

      case 'bookmark':
        $label = $this->t('Bookmark');
        break;

      case 'like':
        $label = $this->t('Like');
        break;
    }

    return $label;
  }

}
