<?php

namespace Helpers\Validator;

use Helpers\Form\AbsFormService;
use Helpers\Log\LogSysteme;
use Helpers\Outil\ArrayHelper;
use Helpers\Outil\Date;
use Helpers\Outil\StringHelper;
use Helpers\Outil\ZoomHelper;
use Helpers\Parametres\Constante;
use Helpers\Trad\Trad;
use Psr\Http\Message\UploadedFileInterface;

class FormValidator extends Validator
{
    /** @var array<string> */
    private array $stackPrefix = [];

    private bool $ignorePerimtre = false;

    /**
     * @param array<mixed> $form
     * @param array<mixed> $post
     * @param array<mixed> $root
     * @return array<mixed>
     */
    public function validateForm(array $form, array $post, ?array $root = null) :array
    {
        $root = $root ?? $post;
        $listInput = $form;
        $formValueMap = [];
        while ($input = array_shift($listInput)) {
            // Case : using transformer
            $fnTransform = $input->validator['transform'] ?? null;
            if (is_callable($fnTransform)) {
                $input = $fnTransform($this, clone $input, $post, $root);
            }

            if ($input->disabled ?? false) {
                continue;
            }
            
            // Case : using callback
            $callback = $input->validator['callback'] ?? null;
            if (is_callable($callback)) {
                $callback($this, $input, $post, $this->getTrad(), $formValueMap, $root);
                continue;
            }

            // Case : group of inputs
            if ($input->type == 'GROUP') { // Input type inputprefixe[Field]
                // Add Field name prefixe for composite structure
                if (!empty($input->label)) {
                    $this->stackPrefix[] = $input->label;
                }
                $formValueMap[ $input->name ] = $this->validateForm(
                    $input->listChild,
                    $post[ $input->name ] ?? [],
                    $post
                );

                if (!empty($input->label)) {
                    array_pop($this->stackPrefix);
                }

                continue;
            }
            if ($input->type == 'TABLE') { // Input type inputprefixe[0][Field]
                $this->validateInput($input, $post, $root);

                // Add Field name prefixe for composite structure
                if (!empty($input->label)) {
                    $this->stackPrefix[] = $input->label;
                }

                $formValueMap[ $input->name ] = [];
                foreach (($post[ $input->name ] ?? []) as $idRow => $row) {
                    $this->stackPrefix[] = Trad::strBuild(
                        $input->item_label ?? $this->getTrad()->t('Ligne @1@'),
                        $idRow + 1
                    );
                    $formValueMap[ $input->name ][ $idRow ] = $this->validateForm(
                        $input->listChild,
                        $row,
                        $post
                    );
                    array_pop($this->stackPrefix);
                }
                
                if (!empty($input->label)) {
                    array_pop($this->stackPrefix);
                }

                continue;
            }

            if ($input->type == 'BRANCH') {
                $selected = ArrayHelper::pathAccess(
                    $post ?? [],
                    ArrayHelper::getAccessPath(
                        AbsFormService::getFullName((object)[
                            'name' => $input->branch_dependency,
                            'parent' => $input->parent ?? null,
                        ])
                    ),
                    ['??' => null]
                );
                
                // Add subform to the stack input to be validated
                $listInput = array_merge(
                    $listInput,
                    $input->mapSubform[ (string) $selected ]
                );
                continue;
            }
            
            if ($this->validateInput($input, $post, $root)) {
                $formValueMap[ $input->name ] = $post[ $input->name ] ?? null;
            }
        }
        return $formValueMap;
    }

    /**
     * @param \stdClass $input
     * @param array<mixed> $post
     * @param ?array<mixed> $root
     */
    public function validateInput(object $input, array &$post, ?array $root = null) :bool
    {
        $root = $root ?? $post;
        $name = $input->name;
        $label = $this->getFieldNamePrefix() . ($input->label ?? $input->name);
        if ($input->disabled ?? false) {
            unset($post[ $name ]);
            return false;
        }
        $isMandatory = $input->validator['mandatory'] ?? false;

        switch ($input->type) {
            case 'TABLE':
                $post[ $name ] = $post[ $name ] ?? [];
                if ($isMandatory && empty($post[ $name ] ?? [])) {
                    $this->setError(Trad::strBuild(
                        $this->getTrad()->t('Le tableau @1@ doit contenir au moins un élément.'),
                        $label
                    ));
                }
                return true;
            case 'ARRAY':
                foreach ($input->listChild as $child) {
                    // TODO : pass down $input->label as prefixe
                    $this->validateInput($child, $post[ $name ], $root);
                }
                return true;
            case 'TEXTAREA':
            case 'TEXT':
                $post[ $name ] =  $post[ $name ] ?? '';
                if (! StringHelper::isStringable($post[ $name ])) {
                    LogSysteme::prefixErrorLog('Unexpected non-string on "'.$name.'"');
                    $post[ $name ] = '';
                } else {
                    $post[ $name ] = (string) $post[ $name ];
                }
                $format = $input->validator['format'] ?? false;
                if ($format == 'code') {
                    $isValid =  $this->isCode(
                        $label,
                        $post[ $name ] ?? '',
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                        $input->validator['max'] ?? 200,
                    );
                } elseif ($format == 'integer') {
                    $isValid = $this->isInteger(
                        $label,
                        $post[ $name ] ?? '',
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                        $input->validator['max'] ?? 200,
                    );
                } elseif (isset($input->validator['float'])) {
                    return $this->isFloat(
                        $label,
                        $post[ $name ],
                        $input->validator['float'],
                        $isMandatory,
                        $input->validator['min'] ?? null,
                        $input->validator['max'] ?? null,
                    );
                } else {
                    $isValid = $this->isStringAvecEspace(
                        $label,
                        $post[ $name ] ?? '',
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                        $input->validator['max'] ?? 200,
                    );
                }
                

                $valiCodepost = $input->validator['code_postal'] ?? null;
                if (!empty($valiCodepost)) {
                    $isValid = $isValid && $this->isCodePostal(
                        $label,
                        $post[ $name ],
                        $post[ $valiCodepost ] ?? $post['CODE_PAYS'] ?? 'FR',
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                    );
                }
                if ($input->validator['email'] ?? false) {
                    $isValid = $isValid && $this->adresseMail(
                        $label,
                        $post[ $name ],
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                    );
                }
                if ($input->validator['url'] ?? false) {
                    $isValid = $isValid && $this->isUrlValide(
                        $label,
                        $post[ $name ],
                        $input->validator['min'] ?? ($isMandatory ? 1 : 0),
                    );
                }
                if ($input->validator['sql'] ?? false) {
                    $isValid = $isValid && $this->validateRawSql($label, $post[ $name ], $isMandatory);
                }

                return $isValid;
            case 'DATE':
                $value = $post[ $name ] = $post[ $name ] ?? '';

                // TODO : find better way
                // Case : support for Ymd value comming directly from DB
                $format = preg_match('/\d{8}/m', $value) ? 'aaaa-mm-jj' : null;

                if (($input->validator['max'] ?? '') == 'now') {
                    Date::dateToSql($post[ $name ], '', '', false, $format);
                    return $this->isDateLessThanToday($post[ $name ], $label);
                }
                if (($input->validator['min'] ?? '') == 'now') {
                    Date::dateToSql($post[ $name ], '', '', false, $format);
                    return $this->isDateGreaterThanToday($post[ $name ], $label);
                }

                return $this->isDate(
                    $post[ $name ],
                    $input->validator['min'] ?? '',
                    $input->validator['max'] ?? '',
                    ! $isMandatory,
                    $label,
                    $format
                );
            case 'SWITCH':
                $post[ $name ] = $this->isCheck($post[ $name ]);
                return true;
            case 'SELECT':
                // TODO : simplify, remove choices_value
                $listChoice =
                    is_array($input->choices) ?
                        array_keys($input->choices) :  $input->choices_value;
                if (is_callable($listChoice)) {
                    $listChoice = $listChoice($post, $root);
                }
                if (!empty($post[ $name ]) && ! in_array($post[ $name ], $listChoice)) {
                    return $this->setError('Choix non-valide');
                }

                if ($isMandatory && empty($post[ $name ])) {
                    return $this->setError(
                        Trad::strBuild(
                            $this->getTrad()->t('Le champ @1@ est un champ obligatoire.'),
                            $label
                        )
                    );
                }

                return true;
                
            case 'FLOAT':
                return $this->isFloat(
                    $label,
                    $post[ $name ],
                    $input->format,
                    $isMandatory,
                    $input->validator['min'] ?? null,
                    $input->validator['max'] ?? null,
                );
            case 'MONTANT':
            case 'DEVISE':
                $res = $this->isFloat(
                    $label,
                    $post[ $name ],
                    Constante::formatMontant,
                    $isMandatory
                );
                if ($input->validator['posOnly'] ?? false) {
                    $res = $res && $this->isTrue(
                        (float) $post['TOTAL_HT'] < 0,
                        Trad::strBuild(
                            $this->trad->t('Le champ @1@ ne peut pas être négatif.'),
                            $label
                        )
                    );
                }
                if ($input->validator['negOnly'] ?? false) {
                    $res = $res && $this->isTrue(
                        (float) $post['TOTAL_HT'] > 0,
                        Trad::strBuild(
                            $this->trad->t('Le champ @1@ ne peut pas être positif.'),
                            $label
                        )
                    );
                }

                return $res;
            case 'ZOOM':
                $post[ $name ] = $post[ $name ] ?? null;
                
                $dependency = [];
                foreach ($input->zoom_dependency ?? [] as $k => $v) {
                    $dependency[ is_string($k) ? $k : $v ] = $post[ $v ];
                }
                return $this->zoomListe(
                    $input->zoom_path ?? ZoomHelper::getZoomPath($input->entity),
                    $post[ $name ],
                    $label,
                    $this->pdo,
                    ! $isMandatory,
                    ($input->zoom_filter ?? [])
                    + $dependency
                    + ['ignorePerimtre' => $this->ignorePerimtre]
                );
            case 'FILE':
                $file = $post[ $name ] ?? null;
                if ($file instanceof UploadedFileInterface && $file->getSize() != 0) {
                    return $this->fileCheck(
                        $label,
                        $file->getClientFilename(),
                        [],
                        '',
                        $file->getSize()
                    );
                }
                // TODO : File is always mandatory ?
                if ($isMandatory) {
                    return $this->fileCheck($label, '', [], '', 0);
                }

                unset($post[ $name ]);
                return true;
            case 'MULTIPICK':
                $res = true;
                foreach ($post[ $name ] ?? [] as $entityId) {
                    $res = $res && $this->zoomListe( // TODO : factorize with ZOOM
                        $input->zoom_path ?? ZoomHelper::getZoomPath($input->entity),
                        $entityId,
                        $label,
                        $this->pdo,
                        ! $isMandatory,
                        ($input->zoom_filter ?? [])
                        + array_map(function ($key) use ($post) {
                            return $post[ $key ];
                        }, $input->zoom_dependency ?? [])
                    );
                }
                return $res;
            default:
        }
        return true;
    }

    public function getFieldNamePrefix() :string
    {
        $trad = $this->getTrad();
        return implode('', array_map(function ($item) use ($trad) {
            return $item . $trad->colon();
        }, $this->stackPrefix));
    }

    public function ignorePerimetre(bool $ignorePerimtre):void
    {
        $this->ignorePerimtre = $ignorePerimtre;
    }
}
