<?php

namespace FleekDash\Controllers\Schema;

use FleekDash\Controllers\Schema\FieldProviders\VoxelFieldProvider;

if (!\function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

/**
 * Schema Formatter
 *
 * Handles formatting and transformation of schema data
 *
 * @package FleekDash\Controllers\Schema
 */
class SchemaFormatter
{
    private VoxelFieldProvider $voxelProvider;

    public function __construct()
    {
        $this->voxelProvider = new VoxelFieldProvider();
    }

    /**
     * Format post for REST response
     *
     * @param \WP_Post $post Post object
     * @return array<string, mixed> Formatted post data
     */
    public function formatPostForRest($post)
    {
        $formatted = $this->buildRestPostCoreFields($post);
        $formatted['meta'] = $this->collectPublicMetaForRest($post->ID);

        $taxonomies = \get_object_taxonomies($post->post_type);
        foreach ($taxonomies as $taxonomy) {
            $terms = \wp_get_post_terms($post->ID, $taxonomy);
            if (!\is_wp_error($terms)) {
                $formatted[$taxonomy] = \wp_list_pluck($terms, 'term_id');
            }
        }

        return $formatted;
    }

    /**
     * @return array<string, mixed>
     */
    private function buildRestPostCoreFields(\WP_Post $post): array
    {
        return [
            'id' => $post->ID,
            'date' => $post->post_date,
            'date_gmt' => $post->post_date_gmt,
            'guid' => [
                'rendered' => get_permalink($post->ID),
                'raw' => get_permalink($post->ID)
            ],
            'modified' => $post->post_modified,
            'modified_gmt' => $post->post_modified_gmt,
            'password' => $post->post_password,
            'slug' => $post->post_name,
            'status' => $post->post_status,
            'type' => $post->post_type,
            'link' => get_permalink($post->ID),
            'title' => [
                'raw' => $post->post_title,
                'rendered' => get_the_title($post->ID)
            ],
            'content' => [
                'raw' => $post->post_content,
                'rendered' => \apply_filters('the_content', $post->post_content),
                'protected' => !empty($post->post_password)
            ],
            'excerpt' => [
                'raw' => $post->post_excerpt,
                'rendered' => get_the_excerpt($post->ID),
                'protected' => !empty($post->post_password)
            ],
            'author' => $post->post_author,
            'featured_media' => get_post_thumbnail_id($post->ID),
            'comment_status' => $post->comment_status,
            'ping_status' => $post->ping_status,
            'sticky' => is_sticky($post->ID),
            'template' => get_page_template_slug($post->ID),
            'format' => get_post_format($post->ID) ?: 'standard'
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function collectPublicMetaForRest(int $postId): array
    {
        $meta = \get_post_meta($postId);
        $formatted_meta = [];

        foreach ($meta as $key => $values) {
            if (\strpos($key, '_') === 0) {
                continue;
            }

            $meta_value = \count($values) === 1 ? $values[0] : $values;
            $formatted_meta[$key] = $this->decodeMetaStringForRestOutput($key, $meta_value);
        }

        return $formatted_meta;
    }

    /**
     * @param mixed $meta_value
     * @return mixed
     */
    private function decodeMetaStringForRestOutput(string $key, $meta_value)
    {
        $out = $meta_value;

        if (!\is_string($meta_value) || $meta_value === '') {
            return $out;
        }

        $decodeJsonAssoc = static function (string $raw): mixed {
            $decoded = \json_decode($raw, true);

            return ($decoded !== null && \json_last_error() === JSON_ERROR_NONE) ? $decoded : null;
        };

        if (\in_array($key, ['event_date', 'recurring-date'], true)) {
            $decoded = $decodeJsonAssoc($meta_value);
            if ($decoded !== null) {
                fleekdash_log_debug("FleekDash: DECODED VOXEL DATE REPEATER {$key}");
                $out = $decoded;
            }

            return $out;
        }

        $trimmed = \trim($meta_value);
        if ($trimmed !== '' && ($trimmed[0] === '{' || $trimmed[0] === '[')) {
            $decoded = $decodeJsonAssoc($trimmed);
            if ($decoded !== null) {
                $out = $decoded;
            }
        }

        return $out;
    }

    /**
     * Process media/file fields to Voxel-compatible format
     *
     * @param string $field_key Field key
     * @param mixed $value Field value
     * @param string $post_type_slug Post type
     * @return mixed Processed value
     */
    public function processVoxelMediaField($field_key, $value, $post_type_slug)
    {
        fleekdash_log_debug("FleekDash: PROCESSING FIELD {$field_key}");
        fleekdash_log_debug("FleekDash: - Value type: " . \gettype($value));

        if (!\is_array($value) && !\is_object($value)) {
            return $value;
        }

        $arr = \is_object($value) ? (array) $value : $value;

        return $this->transformVoxelArrayValue($field_key, $arr, $post_type_slug);
    }

    /**
     * @param array<string|int, mixed> $value
     * @return mixed
     */
    private function transformVoxelArrayValue(string $field_key, array $value, string $post_type_slug): mixed
    {
        $field_info = $this->voxelProvider->getVoxelFieldInfo($field_key, $post_type_slug);

        $out = $this->encodeKnownVoxelRepeater($field_info, $value);
        if ($out === null) {
            $out = $this->encodeVoxelDateRepeaterIfApplicable($field_key, $value);
        }
        if ($out === null) {
            $out = $this->encodeRepeaterNamePatternIfApplicable($field_key, $value);
        }
        if ($out === null) {
            $out = $this->encodeFileMediaUploadShape($value);
        }
        if ($out === null) {
            $out = $this->transformGenericVoxelArray($value);
        }

        return $out;
    }

    /**
     * @param array<string|int, mixed>|null $field_info
     * @param array<string|int, mixed> $value
     */
    private function encodeKnownVoxelRepeater($field_info, array $value): ?string
    {
        $is_repeater = $field_info && isset($field_info['voxel_type']) && $field_info['voxel_type'] === 'repeater';
        if (!$is_repeater) {
            return null;
        }

        fleekdash_log_debug("FleekDash: DETECTED REPEATER - Saving as JSON");

        return \json_encode($value);
    }

    /**
     * @param array<string|int, mixed> $value
     */
    private function encodeVoxelDateRepeaterIfApplicable(string $field_key, array $value): ?string
    {
        $is_date_key = \in_array($field_key, ['event_date', 'recurring-date'], true);
        if (!$is_date_key || $value === []) {
            return null;
        }

        $converted = $this->convertToVoxelDateFormat($value, $field_key);
        fleekdash_log_debug("FleekDash: CONVERTED TO VOXEL DATE FORMAT");

        return \json_encode($converted);
    }

    /**
     * @param array<string|int, mixed> $value
     */
    private function encodeRepeaterNamePatternIfApplicable(string $field_key, array $value): ?string
    {
        if (\strpos($field_key, 'repeater') === false || $value === []) {
            return null;
        }

        $first = \reset($value);
        if (!\is_array($first) || $first === []) {
            return null;
        }

        return \json_encode($value);
    }

    /**
     * @param array<string|int, mixed> $value
     */
    private function encodeFileMediaUploadShape(array $value): ?string
    {
        if (!isset($value['name'], $value['type'])) {
            return null;
        }

        if (isset($value['attachment_id']) && \is_numeric($value['attachment_id'])) {
            return (string) $value['attachment_id'];
        }

        return '';
    }

    /**
     * @param array<string|int, mixed> $value
     * @return mixed
     */
    private function transformGenericVoxelArray(array $value): mixed
    {
        if ($value === []) {
            return $value;
        }

        foreach ($value as $item) {
            if (!\is_numeric($item)) {
                return SchemaHelpers::encodeHeterogeneousVoxelList($value);
            }
        }

        return \implode(',', $value);
    }

    /**
     * Convert UI date format to Voxel format
     *
     * @param array<int, mixed> $ui_data UI data
     * @param string $field_type Field type
     * @return array<int, array<string, mixed>>
     */
    private function convertToVoxelDateFormat($ui_data, $field_type)
    {
        $converted = [];

        foreach ($ui_data as $item) {
            if (!\is_array($item)) {
                continue;
            }

            $converted[] = $this->mapUiDateItemToVoxel($item, $field_type);
        }

        return $converted;
    }

    /**
     * @param array<string, mixed> $item
     * @return array<string, mixed>
     */
    private function mapUiDateItemToVoxel(array $item, string $field_type): array
    {
        $voxel_item = [
            'start' => $this->buildVoxelDatetime($item, 'start'),
            'end' => $this->buildVoxelDatetime($item, 'end'),
            'multiday' => isset($item['multiday']) ? (bool) $item['multiday'] : false,
            'allday' => isset($item['allday']) ? (bool) $item['allday'] : false,
        ];

        if ($field_type !== 'recurring-date') {
            return $voxel_item;
        }

        if (isset($item['frequency']) && \is_numeric($item['frequency'])) {
            $voxel_item['frequency'] = (int) $item['frequency'];
        }
        if (isset($item['unit']) && !empty($item['unit'])) {
            $voxel_item['unit'] = $item['unit'];
        }
        if (isset($item['until']) && !empty($item['until'])) {
            $voxel_item['until'] = $this->formatVoxelDatetime($item['until']);
        }

        return $voxel_item;
    }

    /**
     * Build Voxel datetime string
     *
     * @param array<string, mixed> $item Item data
     * @param string $type 'start' or 'end'
     * @return string Formatted datetime
     */
    private function buildVoxelDatetime($item, $type)
    {
        if (isset($item[$type])) {
            return $this->formatVoxelDatetime($item[$type]);
        }

        $date = isset($item['date']) ? $item['date'] : '';
        $time = '';

        if ($type === 'start' && isset($item['start_time'])) {
            $time = $item['start_time'];
        } elseif ($type === 'end' && isset($item['end_time'])) {
            $time = $item['end_time'];
        }

        if ($time === '') {
            $time = $type === 'start' ? '09:00' : '10:00';
        }

        $datetime = $date . ' ' . $time . ':00';

        return $this->formatVoxelDatetime($datetime);
    }

    /**
     * Format datetime for Voxel
     *
     * @param string $datetime Input datetime
     * @return string Formatted datetime
     */
    private function formatVoxelDatetime($datetime)
    {
        if (\preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $datetime)) {
            return $datetime;
        }

        try {
            $dt = new \DateTime($datetime);

            return $dt->format('Y-m-d H:i:s');
        } catch (\Exception $e) {
            fleekdash_log_error("FleekDash: Error formatting datetime: " . $e->getMessage());

            return \date('Y-m-d H:i:s');
        }
    }
}
