<?php

declare(strict_types=1);

namespace PpPhp\Core;

use PpPhp\Core\Html\Sanitizer;

/**
 * Shared helpers for field-control plugins (Edit / View / Filter).
 *
 * `use PluginSupport;` in a plugin class to get consistent, null-safe HTML
 * escaping and URL validation, so every plugin escapes the same way and none
 * trips the PHP 8.1+ "passing null to htmlspecialchars()" deprecation (which has
 * corrupted generated-app output before — see buglog bug-225).
 */
trait PluginSupport
{
    /** HTML-escape a value for text or a double-quoted attribute. Null-safe. */
    protected static function e(mixed $v): string
    {
        return htmlspecialchars((string) ($v ?? ''), ENT_QUOTES, 'UTF-8');
    }

    /**
     * Validate a user-supplied URL for an href/src. Returns the (escaped) URL when
     * safe, or '' when it must be rejected (javascript:/data:/vbscript:/obfuscated).
     * Pass $isImage=true for image/media src (rejects mailto).
     */
    protected static function safeUrl(mixed $v, bool $isImage = false): string
    {
        $url = Sanitizer::url((string) ($v ?? ''), $isImage);
        return $url === null ? '' : htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
    }

    /**
     * Format a stored date/datetime for display — null/blank-safe, invalid-safe,
     * and on the app's configured timezone (the platform runs on one clock; see the
     * PHP↔MySQL clock-skew note). $default is a PHP date() format; a non-empty
     * `format` setting overrides it. Returns '' for empty/unparseable input.
     */
    protected static function formatDate(mixed $value, string $default, array $settings = []): string
    {
        $raw = trim((string) ($value ?? ''));
        if ($raw === '') return '';
        $ts = strtotime($raw);
        if ($ts === false) return self::e($raw);   // not a date → show as-is, escaped
        $fmt = trim((string) ($settings['format'] ?? '')) ?: $default;
        try {
            $tz = defined('PP_TIMEZONE') ? (string) constant('PP_TIMEZONE') : date_default_timezone_get();
            $dt = new \DateTime('@' . $ts);
            $dt->setTimezone(new \DateTimeZone($tz ?: 'UTC'));
            return self::e($dt->format($fmt));
        } catch (\Throwable $e) {
            return self::e(date($fmt, $ts));
        }
    }

    /**
     * Distinct non-empty values of the source column (for auto-populating filter
     * options). Reads `__table`/`__column` injected by the build; identifiers are
     * sanitised to `[A-Za-z0-9_]`, results capped. Returns [] if unavailable (e.g.
     * no DB, or the source wasn't supplied), so callers fall back to manual options.
     *
     * @return array<int,string>
     */
    protected static function distinctOptions(array $settings, int $limit = 300): array
    {
        $t = preg_replace('/[^A-Za-z0-9_]/', '', (string) ($settings['__table'] ?? ''));
        $c = preg_replace('/[^A-Za-z0-9_]/', '', (string) ($settings['__column'] ?? ''));
        if ($t === '' || $c === '' || !class_exists('\PpPhp\Core\DB')) return [];
        $limit = max(1, min(1000, $limit));
        try {
            $rows = \PpPhp\Core\DB::FetchAll("SELECT DISTINCT `$c` AS v FROM `$t` WHERE `$c` IS NOT NULL AND `$c` <> '' ORDER BY `$c` ASC LIMIT " . $limit) ?: [];
        } catch (\Throwable $e) {
            return [];
        }
        $out = [];
        foreach ($rows as $r) { $v = (string) ($r['v'] ?? ''); if ($v !== '') $out[] = $v; }
        return $out;
    }
}
