<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-seo-data.php
// Description: SEO data access layer — postmeta read/write abstraction - NUCLEAR+++
// Last Edited: 2026-04-26
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class SeoData
{
    // --------------------------------------------------
    // Meta key prefix — all keys namespaced to avoid collisions
    // --------------------------------------------------
    private const PREFIX = '_amai_seo_';

    // --------------------------------------------------
    // Supported meta keys and their sanitization type
    // --------------------------------------------------
    private const FIELDS = [
        'focus_keyword'    => 'text',
        'meta_title'       => 'text',
        'meta_description' => 'text',
        'canonical_url'    => 'url',
        'robots'           => 'robots',
        'og_title'         => 'text',
        'og_description'   => 'text',
        'og_image_id'      => 'int',
        'schema_type'      => 'schema',
        'score_seo'        => 'int',
        'score_readability' => 'int',
        'analyzed_at'      => 'int',
        'issue_count'      => 'int',
    ];

    // --------------------------------------------------
    // Allowed robots values
    // --------------------------------------------------
    private const ROBOTS_VALUES = [
        'index,follow',
        'noindex,follow',
        'index,nofollow',
        'noindex,nofollow',
    ];

    // --------------------------------------------------
    // Allowed schema types — extend as needed
    // --------------------------------------------------
    private const SCHEMA_TYPES = [
        'Article',
        'BlogPosting',
        'Product',
        'FAQPage',
        'LocalBusiness',
        'WebPage',
        'Service',
    ];

    // --------------------------------------------------
    // Get a single SEO field for a post
    // Returns null if post does not exist or key is invalid
    // --------------------------------------------------
    public static function get(int $post_id, string $field): mixed
    {
        if ($post_id <= 0 || !isset(self::FIELDS[$field])) {
            return null;
        }

        $raw = get_post_meta($post_id, self::PREFIX . $field, true);

        if ($raw === '' || $raw === false) {
            return null;
        }

        return self::cast($raw, self::FIELDS[$field]);
    }

    // --------------------------------------------------
    // Get all SEO fields for a post
    // Returns array keyed by field name — missing fields are null
    // --------------------------------------------------
    public static function getAll(int $post_id): array
    {
        if ($post_id <= 0) {
            return array_fill_keys(array_keys(self::FIELDS), null);
        }

        $result = [];
        foreach (self::FIELDS as $field => $type) {
            $raw = get_post_meta($post_id, self::PREFIX . $field, true);
            $result[$field] = ($raw === '' || $raw === false) ? null : self::cast($raw, $type);
        }

        return $result;
    }

    // --------------------------------------------------
    // Set a single SEO field
    // Returns true on success, false on invalid input
    // --------------------------------------------------
    public static function set(int $post_id, string $field, mixed $value): bool
    {
        if ($post_id <= 0 || !isset(self::FIELDS[$field])) {
            return false;
        }

        $sanitized = self::sanitize($value, self::FIELDS[$field]);

        if ($sanitized === null) {
            return self::delete($post_id, $field);
        }

        $result = update_post_meta($post_id, self::PREFIX . $field, $sanitized);

        return $result !== false;
    }

    // --------------------------------------------------
    // Set multiple SEO fields at once
    // Skips invalid fields silently — returns count of successful writes
    // --------------------------------------------------
    public static function setMany(int $post_id, array $data): int
    {
        if ($post_id <= 0) {
            return 0;
        }

        $count = 0;
        foreach ($data as $field => $value) {
            if (!is_string($field)) {
                continue;
            }
            if (self::set($post_id, $field, $value)) {
                $count++;
            }
        }

        return $count;
    }

    // --------------------------------------------------
    // Delete a single SEO field
    // --------------------------------------------------
    public static function delete(int $post_id, string $field): bool
    {
        if ($post_id <= 0 || !isset(self::FIELDS[$field])) {
            return false;
        }

        return delete_post_meta($post_id, self::PREFIX . $field);
    }

    // --------------------------------------------------
    // Delete all SEO data for a post
    // --------------------------------------------------
    public static function deleteAll(int $post_id): void
    {
        if ($post_id <= 0) {
            return;
        }

        foreach (array_keys(self::FIELDS) as $field) {
            delete_post_meta($post_id, self::PREFIX . $field);
        }
    }

    // --------------------------------------------------
    // Record analysis timestamp — always UTC unix
    // --------------------------------------------------
    public static function touchAnalyzedAt(int $post_id): bool
    {
        return self::set($post_id, 'analyzed_at', time());
    }

    // --------------------------------------------------
    // Sanitize a value for storage
    // Returns null if value is empty or invalid
    // --------------------------------------------------
    private static function sanitize(mixed $value, string $type): string|int|null
    {
        return match ($type) {
            'text'   => self::sanitizeText($value),
            'url'    => self::sanitizeUrl($value),
            'int'    => self::sanitizeInt($value),
            'robots' => self::sanitizeRobots($value),
            'schema' => self::sanitizeSchema($value),
            default  => null,
        };
    }

    // --------------------------------------------------
    // Cast a stored value back to the correct PHP type
    // --------------------------------------------------
    private static function cast(mixed $raw, string $type): string|int|null
    {
        return match ($type) {
            'int', 'robots', 'schema', 'url', 'text' => self::sanitize($raw, $type),
            default => null,
        };
    }

    private static function sanitizeText(mixed $value): ?string
    {
        if (!is_string($value) && !is_int($value) && !is_float($value)) {
            return null;
        }
        $clean = sanitize_text_field((string) $value);
        return $clean !== '' ? $clean : null;
    }

    private static function sanitizeUrl(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $clean = esc_url_raw(trim($value));
        return $clean !== '' ? $clean : null;
    }

    private static function sanitizeInt(mixed $value): ?int
    {
        if (!is_numeric($value)) {
            return null;
        }
        $int = (int) $value;
        return $int > 0 ? $int : null;
    }

    private static function sanitizeRobots(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $clean = strtolower(trim($value));
        return in_array($clean, self::ROBOTS_VALUES, true) ? $clean : null;
    }

    private static function sanitizeSchema(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $clean = trim($value);
        return in_array($clean, self::SCHEMA_TYPES, true) ? $clean : null;
    }
}