<?php
// app/http/helpers.php
// English comments: shared HTTP helpers for controllers (no framework dependencies)

declare(strict_types=1);

function read_json_body(): array {
  $raw = file_get_contents('php://input');
  if ($raw === false || trim($raw) === '') {
    return [];
  }
  $data = json_decode($raw, true);
  if (!is_array($data)) return [];
  return normalize_dates_in_array($data);
}

function json_response(array $payload, int $code = 200): void {
  http_response_code($code);
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode(normalize_dates_in_array($payload), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  exit;
}

function require_debug(array $config, bool $dangerous = false): void {
  $enabled = (bool)($config['app']['debug_tools_enabled'] ?? false);
  if (!$enabled) {
    json_response(['ok' => false, 'error' => ['code' => 'FORBIDDEN', 'message' => 'Debug tools disabled']], 403);
  }
  if ($dangerous) {
    $allow = (bool)($config['app']['allow_db_reset'] ?? false);
    if (!$allow) {
      json_response(['ok' => false, 'error' => ['code' => 'FORBIDDEN', 'message' => 'DB reset disabled']], 403);
    }
  }
}

function table_params(int $defaultPerPage = 50, int $maxPerPage = 500): array {
  $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
  $perPage = isset($_GET['per_page']) ? (int)$_GET['per_page'] : $defaultPerPage;
  if ($page < 1) $page = 1;
  if ($perPage < 1) $perPage = $defaultPerPage;
  if ($perPage > $maxPerPage) $perPage = $maxPerPage;
  $sort = isset($_GET['sort']) ? (string)$_GET['sort'] : '';
  $dir = isset($_GET['dir']) ? strtolower((string)$_GET['dir']) : 'asc';
  if (!in_array($dir, ['asc', 'desc'], true)) $dir = 'asc';
  return [$page, $perPage, $sort, $dir];
}

function table_body_params(int $defaultPerPage = 50, int $maxPerPage = 500): array {
  $body = read_json_body();
  $page = isset($body['page']) ? (int)$body['page'] : 1;
  $perPage = isset($body['per_page']) ? (int)$body['per_page'] : $defaultPerPage;
  if ($page < 1) $page = 1;
  if ($perPage < 1) $perPage = $defaultPerPage;
  if ($perPage > $maxPerPage) $perPage = $maxPerPage;
  $sort = isset($body['sort']) ? (string)$body['sort'] : '';
  $dir = isset($body['dir']) ? strtolower((string)$body['dir']) : 'asc';
  if (!in_array($dir, ['asc', 'desc'], true)) $dir = 'asc';
  $filters = is_array($body['filters'] ?? null) ? $body['filters'] : [];
  $params = is_array($body['params'] ?? null) ? $body['params'] : [];
  return [$page, $perPage, $sort, $dir, $filters, $params];
}

function find_filter(array $filters, string $key): ?array {
  foreach ($filters as $filter) {
    if (!is_array($filter)) continue;
    if ((string)($filter['key'] ?? '') === $key) {
      return $filter;
    }
  }
  return null;
}

function filter_value(array $filters, string $key) {
  $filter = find_filter($filters, $key);
  if (!$filter) return null;
  return $filter['value'] ?? null;
}

function filter_op(array $filters, string $key): string {
  $filter = find_filter($filters, $key);
  if (!$filter) return '';
  return (string)($filter['op'] ?? '');
}

function apply_filters_array(array $items, array $filters, array $fieldMap = []): array {
  if (count($filters) === 0) return $items;
  $out = [];
  foreach ($items as $item) {
    if (!is_array($item)) continue;
    $ok = true;
    foreach ($filters as $filter) {
      if (!is_array($filter)) continue;
      $key = (string)($filter['key'] ?? '');
      if ($key === '') continue;
      $op = (string)($filter['op'] ?? 'eq');
      $value = $filter['value'] ?? null;
      $field = $fieldMap[$key] ?? $key;
      $current = null;
      if (is_callable($field)) {
        $current = $field($item);
      } elseif (is_string($field)) {
        $current = $item[$field] ?? null;
      }
      if (!filter_match($current, $op, $value)) {
        $ok = false;
        break;
      }
    }
    if ($ok) $out[] = $item;
  }
  return $out;
}

function filter_match($current, string $op, $value): bool {
  $op = strtolower($op);
  if ($op === 'empty') {
    if (is_array($current)) return count($current) === 0;
    return $current === null || $current === '';
  }
  if ($op === 'in') {
    if (!is_array($value)) return false;
    return in_array($current, $value, true);
  }
  $cur = normalize_filter_scalar($current);
  if ($op === 'like') {
    $needle = strtolower((string)$value);
    return $needle === '' ? true : (str_contains(strtolower((string)$cur), $needle));
  }
  if ($op === 'between') {
    if (!is_array($value) || count($value) < 2) return false;
    [$from, $to] = $value;
    $left = normalize_filter_scalar($from);
    $right = normalize_filter_scalar($to);
    if (is_numeric($cur) && is_numeric($left) && is_numeric($right)) {
      return (float)$cur >= (float)$left && (float)$cur <= (float)$right;
    }
    $curTs = normalize_filter_timestamp($cur);
    $fromTs = normalize_filter_timestamp($left);
    $toTs = normalize_filter_timestamp($right);
    if ($curTs !== null && $fromTs !== null && $toTs !== null) {
      return $curTs >= $fromTs && $curTs <= $toTs;
    }
    return (string)$cur >= (string)$left && (string)$cur <= (string)$right;
  }
  if ($op === 'gt' || $op === 'lt') {
    if (is_numeric($cur) && is_numeric($value)) {
      return $op === 'gt' ? (float)$cur > (float)$value : (float)$cur < (float)$value;
    }
    return $op === 'gt' ? (string)$cur > (string)$value : (string)$cur < (string)$value;
  }
  $needle = normalize_filter_scalar($value);
  if (is_string($cur) || is_string($needle)) {
    return strtolower((string)$cur) === strtolower((string)$needle);
  }
  return $cur === $needle;
}

function normalize_filter_scalar($value) {
  if (is_array($value)) return '';
  if (is_bool($value)) return $value ? '1' : '0';
  return $value;
}

function normalize_filter_timestamp($value): ?int {
  if (!is_string($value)) return null;
  $ts = strtotime($value);
  if ($ts === false) return null;
  return $ts;
}

function paginate_array(array $items, int $page, int $perPage): array {
  $total = count($items);
  $start = ($page - 1) * $perPage;
  $slice = array_slice($items, $start, $perPage);
  return [$slice, $total];
}

function normalize_dates_in_array($data) {
  if (!is_array($data)) return $data;
  $out = [];
  foreach ($data as $key => $value) {
    if (is_array($value)) {
      $out[$key] = normalize_dates_in_array($value);
      continue;
    }
    if (is_string($value) && should_normalize_date_key((string)$key)) {
      $normalized = normalize_datetime_string($value);
      $out[$key] = $normalized ?? $value;
      continue;
    }
    $out[$key] = $value;
  }
  return $out;
}

function should_normalize_date_key(string $key): bool {
  if ($key === 'ts') return true;
  return preg_match('/(_at|_ts)$/', $key) === 1;
}

function normalize_datetime_string(string $value): ?string {
  $raw = trim($value);
  if ($raw === '') return null;

  $tz = new DateTimeZone('UTC');

  if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw)) {
    $raw .= ' 00:00:00';
  } elseif (preg_match('/^\d{2}\.\d{2}\.\d{4}$/', $raw)) {
    $parts = explode('.', $raw);
    $raw = sprintf('%s-%s-%s 00:00:00', $parts[2], $parts[1], $parts[0]);
  } elseif (preg_match('/^\d{2}\.\d{2}\.\d{4}\s+\d{2}:\d{2}:\d{2}$/', $raw)) {
    [$datePart, $timePart] = explode(' ', $raw, 2);
    $parts = explode('.', $datePart);
    $raw = sprintf('%s-%s-%s %s', $parts[2], $parts[1], $parts[0], $timePart);
  }

  try {
    $dt = new DateTimeImmutable($raw, $tz);
  } catch (Throwable $e) {
    return null;
  }
  return $dt->setTimezone($tz)->format('Y-m-d H:i:s');
}
