<?php

namespace MailPoet\Listing;

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


use MailPoetVendor\Paris\Model;
use MailPoetVendor\Paris\ORMWrapper;

class Handler {
  const DEFAULT_LIMIT_PER_PAGE = 20;

  public function get($modelClass, array $data) {
    $data = $this->processData($data);
    $tableName = $modelClass::$_table;
    $model = Model::factory($modelClass);
    // get groups
    $groups = [];
    $groupsCallback = [$modelClass, 'groups'];
    if (method_exists($modelClass, 'groups') && is_callable($groupsCallback)) {
      $groups = call_user_func_array(
        $groupsCallback,
        [$data]
      );
    }

    // get filters
    $filters = [];
    $filtersCallback = [$modelClass, 'filters'];
    if (method_exists($modelClass, 'filters') && is_callable($filtersCallback)) {
      $filters = call_user_func_array(
        $filtersCallback,
        [$data]
      );
    }

    // get items and total count
    $listingCallback = [$modelClass, 'listingQuery'];
    if (method_exists($modelClass, 'listingQuery') && is_callable($listingCallback)) {
      $customQuery = call_user_func_array(
        $listingCallback,
        [$data]
      );

      $count = $customQuery->count();

      $items = $customQuery
        ->offset($data['offset'])
        ->limit($data['limit'])
        ->{'order_by_' . $data['sort_order']}(
          $tableName . '.' . $data['sort_by']
        )
        ->findMany();
    } else {
      $model = $this->setFilter($model, $data);
      $this->setGroup($model, $data);
      $this->setSearch($model, $data);
      $this->setOrder($model, $data, $tableName);

      $count = $model->count();

      $items = $model
        ->offset($data['offset'])
        ->limit($data['limit'])
        ->findMany();
    }

    return [
      'count' => $count,
      'filters' => $filters,
      'groups' => $groups,
      'items' => $items,
    ];
  }

  public function getListingDefinition(array $data): ListingDefinition {
    $data = $this->processData($data);
    return new ListingDefinition(
      $data['group'],
      $data['filter'] ?? [],
      $data['search'],
      $data['params'] ?? [],
      $data['sort_by'],
      $data['sort_order'],
      $data['offset'],
      $data['limit'],
      $data['selection'] ?? []
    );
  }

  private function setSearch(ORMWrapper $model, array $data) {
    if (empty($data['search'])) {
      return;
    }
    return $model->filter('search', $data['search']);
  }

  private function setOrder(ORMWrapper $model, array $data, $tableName) {
    return $model
      ->{'order_by_' . $data['sort_order']}(
        $tableName . '.' . $data['sort_by']);
  }

  private function setGroup(ORMWrapper $model, array $data) {
    if ($data['group'] === null) {
      return;
    }
    $model->filter('groupBy', $data['group']);
  }

  private function setFilter(ORMWrapper $model, array $data) {
    if ($data['filter'] === null) {
      return $model;
    }
    return $model->filter('filterBy', $data['filter']);
  }

  /**
   * Polyfill for deprecated FILTER_SANITIZE_STRING which was used to sanitize
   * $data['sort_by'].
   */
  private function filterString(string $string): string {
    $str = (string)preg_replace('/\x00|<[^>]*>?/', '', $string);
    return str_replace(["'", '"'], ['&#39;', '&#34;'], $str);
  }

  private function processData(array $data) {
    // check if sort order was specified or default to "asc"
    $sortOrder = (!empty($data['sort_order'])) ? $data['sort_order'] : 'asc';
    // constrain sort order value to either be "asc" or "desc"
    $sortOrder = ($sortOrder === 'asc') ? 'asc' : 'desc';

    // sanitize sort by
    $sortBy = (!empty($data['sort_by']))
      ? $this->filterString($data['sort_by'])
      : '';

    if (empty($sortBy)) {
      $sortBy = 'id';
    }

    $data = [
      // extra parameters
      'params' => (isset($data['params']) ? $data['params'] : []),
      // pagination
      'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
      'limit' => (isset($data['limit'])
        ? (int)$data['limit']
        : PageLimit::DEFAULT_LIMIT_PER_PAGE
      ),
      // searching
      'search' => (isset($data['search']) ? $data['search'] : null),
      // sorting
      'sort_by' => $sortBy,
      'sort_order' => $sortOrder,
      // grouping
      'group' => (isset($data['group']) ? $data['group'] : null),
      // filters
      'filter' => (isset($data['filter']) ? $data['filter'] : null),
      // selection
      'selection' => (isset($data['selection']) ? $data['selection'] : null),
    ];

    return $data;
  }
}
