<?php

declare(strict_types=1);

namespace App\Modules\Administration\Services;

use App\Modules\Administration\Repositories\DynamicFormFieldRepository;
use App\Modules\Administration\Repositories\DynamicFormFieldOptionRepository;
use RuntimeException;

final class DynamicFormFieldService
{
    private const ALLOWED_TYPES = [
        'text',
        'textarea',
        'number',
        'date',
        'time',
        'select',
        'radio',
        'checkbox',
        'email',
        'phone',
        'file'
    ];

    private const CHOICE_TYPES = [
        'select',
        'radio',
        'checkbox'
    ];

    private const ALLOWED_WIDTHS = [
        3,
        4,
        6,
        8,
        9,
        12
    ];

    public function __construct(
        private DynamicFormFieldRepository $fields,
        private DynamicFormFieldOptionRepository $options
    ) {
    }

    public function byForm(
        int $formId
    ): array {
        return $this->fields
            ->byForm($formId);
    }

    public function bySection(
        int $sectionId
    ): array {
        return $this->fields
            ->bySection($sectionId);
    }

    public function find(
        int $id
    ): ?array {
        return $this->fields
            ->find($id);
    }
public function optionsByField(
    int $fieldId
): array {
    return $this->options
        ->byField($fieldId);
}
    public function create(
        int $formId,
        array $data
    ): void {
        if ($formId <= 0) {
            throw new RuntimeException(
                'Invalid form ID.'
            );
        }

        $sectionId = (int) (
            $data['section_id']
            ?? 0
        );

        if ($sectionId <= 0) {
            throw new RuntimeException(
                'Target section is required.'
            );
        }

        $fieldLabel = trim(
            (string) (
                $data['field_label']
                ?? ''
            )
        );

        if ($fieldLabel === '') {
            throw new RuntimeException(
                'Field label is required.'
            );
        }

        $fieldType = strtolower(
            trim(
                (string) (
                    $data['field_type']
                    ?? ''
                )
            )
        );

        if (
            !in_array(
                $fieldType,
                self::ALLOWED_TYPES,
                true
            )
        ) {
            throw new RuntimeException(
                'Invalid field type.'
            );
        }

        $fieldName = $this->makeFieldName(
            $fieldLabel
        );

        $fieldName = $this->uniqueFieldName(
            $formId,
            $fieldName
        );

        $columnWidth = (int) (
            $data['column_width']
            ?? 12
        );

        if (
            !in_array(
                $columnWidth,
                self::ALLOWED_WIDTHS,
                true
            )
        ) {
            $columnWidth = 12;
        }

        /*
         * Validate options before creating the field.
         */
        $optionLabels = [];

        if (
            in_array(
                $fieldType,
                self::CHOICE_TYPES,
                true
            )
        ) {
            $optionLabels = $this->cleanOptions(
                $data['options']
                ?? []
            );

            if (empty($optionLabels)) {
                throw new RuntimeException(
                    'At least one choice option is required.'
                );
            }
        }

        $now = date('Y-m-d H:i:s');

        /*
         * DynamicFormFieldRepository::create()
         * must return the inserted field ID.
         */
        $fieldId = $this->fields->create([
            'form_id' =>
                $formId,

            'section_id' =>
                $sectionId,

            'field_name' =>
                $fieldName,

            'field_label' =>
                $fieldLabel,

            'field_type' =>
                $fieldType,

            'placeholder' =>
                $this->nullableString(
                    $data['placeholder']
                    ?? null
                ),

            'help_text' =>
                $this->nullableString(
                    $data['help_text']
                    ?? null
                ),

            'default_value' =>
                $this->nullableString(
                    $data['default_value']
                    ?? null
                ),

            'is_required' =>
                !empty(
                    $data['is_required']
                )
                    ? 1
                    : 0,

            'validation_rules' =>
                $this->buildValidationRules(
                    $fieldType,
                    $data
                ),

            'sort_order' =>
                $this->fields
                    ->nextSortOrder(
                        $sectionId
                    ),

            'column_width' =>
                $columnWidth,

            'status' =>
                1,

            'created_at' =>
                $now,

            'updated_at' =>
                $now
        ]);

        if ($fieldId <= 0) {
            throw new RuntimeException(
                'Field creation failed.'
            );
        }

        if (
            in_array(
                $fieldType,
                self::CHOICE_TYPES,
                true
            )
        ) {
            $this->saveOptions(
                $fieldId,
                $optionLabels
            );
        }
    }

    public function update(
        int $id,
        array $data
    ): void {
        if ($id <= 0) {
            throw new RuntimeException(
                'Invalid field ID.'
            );
        }

        $field = $this->fields
            ->find($id);

        if ($field === null) {
            throw new RuntimeException(
                'Dynamic form field not found.'
            );
        }

        $fieldLabel = trim(
            (string) (
                $data['field_label']
                ?? ''
            )
        );

        if ($fieldLabel === '') {
            throw new RuntimeException(
                'Field label is required.'
            );
        }

        $columnWidth = (int) (
            $data['column_width']
            ?? 12
        );

        if (
            !in_array(
                $columnWidth,
                self::ALLOWED_WIDTHS,
                true
            )
        ) {
            $columnWidth = 12;
        }

        $updated = $this->fields->update(
            $id,
            [
                'field_label' =>
                    $fieldLabel,

                'placeholder' =>
                    $this->nullableString(
                        $data['placeholder']
                        ?? null
                    ),

                'help_text' =>
                    $this->nullableString(
                        $data['help_text']
                        ?? null
                    ),

                'default_value' =>
                    $this->nullableString(
                        $data['default_value']
                        ?? null
                    ),

                'is_required' =>
                    !empty(
                        $data['is_required']
                    )
                        ? 1
                        : 0,

                'validation_rules' =>
                    $this->buildValidationRules(
                        strtolower(
                            (string) (
                                $field['field_type']
                                ?? 'text'
                            )
                        ),
                        $data
                    ),

                'column_width' =>
                    $columnWidth,

                'updated_at' =>
                    date('Y-m-d H:i:s')
            ]
        );

        if (!$updated) {
            throw new RuntimeException(
                'Field update failed.'
            );
        }

        /*
         * Update options only when options[]
         * are submitted by the Edit Field modal.
         */
        $fieldType = (string) (
            $field['field_type']
            ?? ''
        );

        if (
            in_array(
                $fieldType,
                self::CHOICE_TYPES,
                true
            )
            &&
            array_key_exists(
                'options',
                $data
            )
        ) {
            $optionLabels = $this->cleanOptions(
                $data['options']
            );

            if (empty($optionLabels)) {
                throw new RuntimeException(
                    'At least one choice option is required.'
                );
            }

            $this->options
                ->deleteByField($id);

            $this->saveOptions(
                $id,
                $optionLabels
            );
        }
    }

    private function saveOptions(
        int $fieldId,
        array $optionLabels
    ): void {
        $sortOrder = 1;

        foreach (
            $optionLabels
            as $optionLabel
        ) {
            $optionValue =
                $this->makeOptionValue(
                    $optionLabel
                );

            $created = $this->options->create([
                'field_id' =>
                    $fieldId,

                'option_label' =>
                    $optionLabel,

                'option_value' =>
                    $optionValue,

                'sort_order' =>
                    $sortOrder,

                'status' =>
                    1,

                'created_at' =>
                    date('Y-m-d H:i:s'),

                'updated_at' =>
                    date('Y-m-d H:i:s')
            ]);

            if (!$created) {
                throw new RuntimeException(
                    'Field option creation failed.'
                );
            }

            $sortOrder++;
        }
    }

    private function cleanOptions(
        mixed $options
    ): array {
        if (!is_array($options)) {
            return [];
        }

        $cleaned = [];

        foreach ($options as $option) {
            $option = trim(
                (string) $option
            );

            if ($option === '') {
                continue;
            }

            $cleaned[] = $option;
        }

        return array_values(
            array_unique($cleaned)
        );
    }

    private function makeOptionValue(
        string $label
    ): string {
        $value = strtolower($label);

        $value = preg_replace(
            '/[^a-z0-9]+/',
            '_',
            $value
        );

        $value = trim(
            (string) $value,
            '_'
        );

        if ($value === '') {
            $value = 'option';
        }

        return $value;
    }

    private function makeFieldName(
        string $label
    ): string {
        $name = strtolower($label);

        $name = preg_replace(
            '/[^a-z0-9]+/',
            '_',
            $name
        );

        $name = trim(
            (string) $name,
            '_'
        );

        if ($name === '') {
            $name = 'field';
        }

        return $name;
    }

    private function uniqueFieldName(
        int $formId,
        string $baseName
    ): string {
        $fieldName = $baseName;

        $counter = 2;

        while (
            $this->fields->findByName(
                $formId,
                $fieldName
            ) !== null
        ) {
            $fieldName =
                $baseName
                . '_'
                . $counter;

            $counter++;
        }

        return $fieldName;
    }
public function deactivate(
    int $id
): void {
    if ($id <= 0) {
        throw new RuntimeException(
            'Invalid field ID.'
        );
    }

    $field = $this->fields
        ->find($id);

    if ($field === null) {
        throw new RuntimeException(
            'Dynamic form field not found.'
        );
    }

    $updated = $this->fields
        ->deactivate($id);

    if (!$updated) {
        throw new RuntimeException(
            'Field removal failed.'
        );
    }
}

private function buildValidationRules(
    string $fieldType,
    array $data
): ?string {

    $rules = [];

    switch ($fieldType) {

        case 'number':

            $rules = [

                'min' =>
                    $data['number_min']
                    ?? null,

                'max' =>
                    $data['number_max']
                    ?? null,

                'step' =>
                    $data['number_step']
                    ?? 1

            ];

        break;



        case 'date':

            $rules = [

                'min_date' =>
                    $data['date_min']
                    ?? null,

                'max_date' =>
                    $data['date_max']
                    ?? null

            ];

        break;



        case 'time':

            $rules = [

                'min_time' =>
                    $data['time_min']
                    ?? null,

                'max_time' =>
                    $data['time_max']
                    ?? null

            ];

        break;



        case 'file':

            $rules = [

                'extensions' =>
                    $data['allowed_extensions']
                    ?? [],

                'max_size' =>
                    $data['max_file_size']
                    ?? 5,

                'multiple' =>
                    !empty(
                        $data['multiple_upload']
                    )

            ];

        break;



        case 'checkbox':

            $rules = [

                'min_select' =>
                    $data['min_select']
                    ?? null,

                'max_select' =>
                    $data['max_select']
                    ?? null

            ];

        break;



        default:

            $rules = [

                'min_length' =>
                    $data['min_length']
                    ?? null,

                'max_length' =>
                    $data['max_length']
                    ?? null

            ];

    }


    return json_encode(
        $rules,
        JSON_UNESCAPED_UNICODE
        |
        JSON_UNESCAPED_SLASHES
    );

}
    private function nullableString(
        mixed $value
    ): ?string {
        $value = trim(
            (string) $value
        );

        return $value !== ''
            ? $value
            : null;
    }
public function reorder(
    array $fieldIds,
    array $sectionIds,
    array $sortOrders
): void {

    foreach ($fieldIds as $i => $fieldId) {

        $this->fields->updateOrder(
            (int) $fieldId,
            (int) ($sectionIds[$i] ?? 0),
            (int) ($sortOrders[$i] ?? 0)
        );

    }

}
}