<?php

namespace BitApps\PiPro\src\Integrations\Airtable\helper;

use Exception;

// Prevent direct script access
if (!\defined('ABSPATH')) {
    exit;
}

/**
 * Comprehensive Airtable field formatter.
 *
 * This class handles proper formatting of field values according to Airtable's field types.
 * It ensures that data sent to Airtable API matches the expected format for each field type.
 */
final class AirtableFieldFormatter
{
    /**
     * Format fields based on their types.
     *
     * @param array $fields      The field data with key-value pairs
     * @param array $fieldTypes  Map of field names to their types
     *
     * @return array Formatted fields ready for Airtable API
     */
    public static function formatFields($fields, $fieldTypes = [])
    {
        if (empty($fields) || !\is_array($fields)) {
            return [];
        }
        $formattedFields = [];

        foreach ($fields as $fieldName => $value) {
            $fieldType = $fieldTypes[$fieldName] ?? null;

            // Skip null or empty field names
            if (empty($fieldName)) {
                continue;
            }

            // Format based on field type
            $formattedValue = self::formatFieldValue($value, $fieldType);

            // Only include field if formatting succeeded
            if ($formattedValue !== null || $fieldType !== null) {
                $formattedFields[$fieldName] = $formattedValue;
            } else {
                // No field type info, keep as is
                $formattedFields[$fieldName] = $value;
            }
        }

        return $formattedFields;
    }

    /**
     * Format a single field value based on its type.
     *
     * @param mixed  $value     The value to format
     * @param string $fieldType The Airtable field type
     *
     * @return mixed Formatted value
     */
    private static function formatFieldValue($value, $fieldType)
    {
        if ($value === null) {
            return;
        }

        // If no field type is provided, try to auto-detect and format
        if ($fieldType === null) {
            return self::autoFormatValue($value);
        }

        switch ($fieldType) {
            // Text fields
            case 'singleLineText':
            case 'multilineText':
            case 'richText':
            case 'email':
            case 'url':
            case 'phoneNumber':
                return self::formatTextField($value);

                // Number fields
            case 'number':
            case 'currency':
            case 'percent':
            case 'duration':
            case 'rating':
            case 'autoNumber':
                return self::formatNumberField($value);

                // Date/Time fields
            case 'date':
            case 'dateTime':
            case 'createdTime':
            case 'lastModifiedTime':
                return self::formatDateField($value);

                // Boolean field
            case 'checkbox':
                return self::formatCheckboxField($value);

                // Select fields
            case 'singleSelect':
                return self::formatSingleSelectField($value);

            case 'multipleSelects':
                return self::formatMultipleSelectsField($value);

                // Linked records
            case 'multipleRecordLinks':
                return self::formatLinkedRecordsField($value);

                // Attachment field
            case 'multipleAttachments':
                return self::formatAttachmentsField($value);

                // Collaborator fields
            case 'singleCollaborator':
                return self::formatCollaboratorField($value, true);

            case 'multipleCollaborators':
            case 'createdBy':
            case 'lastModifiedBy':
                return self::formatCollaboratorField($value, false);

                // Lookup and Rollup (read-only, but handle gracefully)
            case 'lookup':
            case 'rollup':
            case 'count':
            case 'formula':
                // These are computed fields, cannot be set directly
                return;

                // Barcode field
            case 'barcode':
                return self::formatBarcodeField($value);

                // Button field (read-only)
            case 'button':
                return;

                // Default: return as is
            default:
                return self::autoFormatValue($value);
        }
    }

    /**
     * Format text field value.
     *
     * @param mixed $value
     *
     * @return null|string
     */
    private static function formatTextField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Convert to string
        return (string) $value;
    }

    /**
     * Format number field value.
     *
     * @param mixed $value
     *
     * @return null|float|int
     */
    private static function formatNumberField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Remove non-numeric characters except decimal point and minus sign
        if (\is_string($value)) {
            $value = trim($value);
            // Remove currency symbols and spaces
            $value = preg_replace('/[^0-9.\-]/', '', $value);
        }

        // Convert to number
        if (strpos($value, '.') !== false) {
            return (float) $value;
        }

        return (int) $value;
    }

    /**
     * Format date/dateTime field value.
     *
     * @param mixed $value
     *
     * @return null|string
     */
    private static function formatDateField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // If already in ISO 8601 format, return as is
        if (\is_string($value) && preg_match('/^\d{4}-\d{2}-\d{2}/', $value)) {
            return $value;
        }

        // Try to parse and convert to ISO 8601
        try {
            $timestamp = is_numeric($value) ? (int) $value : strtotime($value);
            if ($timestamp === false) {
                return (string) $value;
            }

            return gmdate('Y-m-d\TH:i:s.000\Z', $timestamp);
        } catch (Exception $e) {
            return (string) $value;
        }
    }

    /**
     * Format checkbox field value.
     *
     * @param mixed $value
     *
     * @return null|bool
     */
    private static function formatCheckboxField($value)
    {
        if ($value === null) {
            return;
        }
        // Convert various truthy/falsy values to boolean
        if (\is_string($value)) {
            $value = strtolower(trim($value));
            if (\in_array($value, ['true', '1', 'yes', 'on', 'checked'], true)) {
                return true;
            }
            if (\in_array($value, ['false', '0', 'no', 'off', 'unchecked', ''], true)) {
                return false;
            }
        }

        return $value ? 'true' : 'false';
    }

    /**
     * Format single select field value.
     *
     * @param mixed $value
     *
     * @return null|string
     */
    private static function formatSingleSelectField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Single select expects a string
        if (\is_array($value)) {
            return isset($value[0]) ? (string) $value[0] : null;
        }

        return (string) $value;
    }

    /**
     * Format multiple selects field value.
     *
     * @param mixed $value
     *
     * @return null|array
     */
    private static function formatMultipleSelectsField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        return array_values(array_filter(array_map('trim', explode(',', $value))));
    }

    /**
     * Format linked records field value.
     *
     * @param mixed $value
     *
     * @return null|array
     */
    private static function formatLinkedRecordsField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Convert to array if not already
        if (!\is_array($value)) {
            if (\is_string($value) && self::isJsonString($value)) {
                $value = json_decode($value, true);
            } else {
                $value = [$value];
            }
        }

        // Linked records expect array of record IDs
        $recordIds = [];
        foreach ($value as $item) {
            if (\is_array($item) && isset($item['id'])) {
                $recordIds[] = ['id' => $item['id']];
            } elseif (\is_string($item) && !empty($item)) {
                $recordIds[] = ['id' => $item];
            }
        }

        return !empty($recordIds) ? $recordIds : null;
    }

    /**
     * Format attachments field value.
     *
     * @param mixed $value
     *
     * @return null|array
     */
    private static function formatAttachmentsField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        $attachments = [];

        if (\is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
            $attachments[] = ['url' => $value];
        } elseif (\is_string($value) && self::isJsonString($value)) {
            $decoded = json_decode($value, true);
            if (\is_array($decoded)) {
                return self::formatAttachmentsField($decoded);
            }
        } elseif (\is_array($value) && isset($value['url'])) {
            $attachment = ['url' => $value['url']];
            if (isset($value['filename'])) {
                $attachment['filename'] = $value['filename'];
            }
            $attachments[] = $attachment;
        } elseif (\is_array($value)) {
            foreach ($value as $item) {
                if (\is_string($item) && filter_var($item, FILTER_VALIDATE_URL)) {
                    $attachments[] = ['url' => $item];
                } elseif (\is_array($item) && isset($item['url'])) {
                    $attachment = ['url' => $item['url']];
                    if (isset($item['filename'])) {
                        $attachment['filename'] = $item['filename'];
                    } else {
                        $attachment['filename'] = basename($item['url']);
                    }
                    $attachments[] = $attachment;
                }
            }
        }

        return !empty($attachments) ? $attachments : null;
    }

    /**
     * Format collaborator field value.
     *
     * According to Airtable API documentation:
     * - singleCollaborator: expects a single object {"email": "user@example.com"} or {"id": "usrXXXXX"}
     * - multipleCollaborators: expects an array of objects [{"email": "user@example.com"}, ...]
     *
     * @param mixed  $value
     * @param bool   $isSingle  True for singleCollaborator, false for multipleCollaborators
     *
     * @return null|array Single object for singleCollaborator, array of objects for multipleCollaborators
     */
    private static function formatCollaboratorField($value, $isSingle = false)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Convert to array for uniform processing
        if (!\is_array($value)) {
            $value = [$value];
        }

        $collaborators = [];
        foreach ($value as $item) {
            if (\is_string($item)) {
                // Handle comma-separated emails within array items
                if (strpos($item, ',') !== false) {
                    $emails = array_map('trim', explode(',', $item));
                    foreach ($emails as $email) {
                        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
                            $collaborators[] = ['email' => $email];
                        } elseif (self::isAirtableUserId($email)) {
                            $collaborators[] = ['id' => $email];
                        }
                    }
                } elseif (filter_var($item, FILTER_VALIDATE_EMAIL)) {
                    $collaborators[] = ['email' => $item];
                } elseif (self::isAirtableUserId($item)) {
                    // Handle Airtable user IDs (format: usrXXXXXXXXXXXXXX)
                    $collaborators[] = ['id' => $item];
                }
            } elseif (\is_array($item)) {
                // Handle objects with email or id
                if (isset($item['email']) && filter_var($item['email'], FILTER_VALIDATE_EMAIL)) {
                    $collaborators[] = ['email' => $item['email']];
                } elseif (isset($item['id']) && self::isAirtableUserId($item['id'])) {
                    $collaborators[] = ['id' => $item['id']];
                }
            }
        }

        if (empty($collaborators)) {
            return;
        }

        // For singleCollaborator, return just the first collaborator object (not an array)
        if ($isSingle) {
            return $collaborators[0];
        }

        // For multipleCollaborators, return array of collaborator objects
        return $collaborators;
    }

    /**
     * Check if a string is a valid Airtable user ID.
     *
     * @param string $value
     *
     * @return bool
     */
    private static function isAirtableUserId($value)
    {
        return \is_string($value) && preg_match('/^usr[a-zA-Z0-9]{14,}$/', $value);
    }

    /**
     * Format barcode field value.
     *
     * @param mixed $value
     *
     * @return null|array
     */
    private static function formatBarcodeField($value)
    {
        if ($value === null || $value === '') {
            return;
        }

        // Barcode expects object with 'text' property
        if (\is_array($value) && isset($value['text'])) {
            return $value;
        }

        // Convert string to barcode format
        return ['text' => (string) $value];
    }

    /**
     * Auto-format value when field type is unknown.
     *
     * @param mixed $value
     *
     * @return mixed
     */
    private static function autoFormatValue($value)
    {
        // Try to detect attachment-like values
        if (self::looksLikeAttachment($value)) {
            return self::formatAttachmentsField($value);
        }

        // Try to detect linked records
        if (self::looksLikeLinkedRecords($value)) {
            return self::formatLinkedRecordsField($value);
        }

        // Return as is for other types
        return $value;
    }

    /**
     * Check if value looks like attachment data.
     *
     * @param mixed $value
     *
     * @return bool
     */
    private static function looksLikeAttachment($value)
    {
        // Check if it's a URL
        if (\is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
            return true;
        }

        // Check if it's an array with URL
        if (\is_array($value) && isset($value['url'])) {
            return true;
        }

        // Check if it's a JSON string containing attachment data
        if (\is_string($value) && self::isJsonString($value)) {
            $decoded = json_decode($value, true);
            if (\is_array($decoded) && isset($decoded['url'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if value looks like linked records.
     *
     * @param mixed $value
     *
     * @return bool
     */
    private static function looksLikeLinkedRecords($value)
    {
        if (\is_array($value)) {
            // Check if it contains objects with 'id' property
            foreach ($value as $item) {
                if (\is_array($item) && isset($item['id']) && \is_string($item['id'])) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check if a string is valid JSON.
     *
     * @param string $string
     *
     * @return bool
     */
    private static function isJsonString($string)
    {
        if (!\is_string($string)) {
            return false;
        }

        json_decode($string);

        return json_last_error() === JSON_ERROR_NONE;
    }
}
