<?php

namespace Formatrica\Submission\Pipeline\Stages;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Formatrica\Submission\File\File_Uploader;
use Formatrica\Submission\Pipeline\Submission_Context;
use Formatrica\Submission\Pipeline\Submission_Stage;
use Formatrica\Submission\Validation\Field_Validator;
use Formatrica\Submission\Validation\Validation_Result;

/**
 * Validate all fields, process file uploads (only when validation passed), then
 * run the formatrica_sanitized_submission filter. Populates the context with the
 * sanitised values, errors, and field metadata.
 */
final class Validation_Stage implements Submission_Stage
{
    public function __construct(
        private Field_Validator $field_validator,
        private File_Uploader $file_uploader
    ) {
    }

    public function handle(Submission_Context $ctx): void
    {
        $fields = $ctx->config['fields'];

        $validation_result = $this->field_validator->validate_all($fields, $ctx->request_data, $ctx->files);
        $ctx->validation_result = $validation_result;

        if ($validation_result->is_valid()) {
            [$sanitised, $errors, $field_meta] = $this->process_file_uploads($fields, $ctx->files, $validation_result);
            [$sanitised, $field_meta] = $this->process_calculations($fields, $sanitised, $field_meta);
        } else {
            [$sanitised, $errors, $field_meta] = $validation_result->to_tuple();
        }

        $filtered_sanitised = apply_filters('formatrica_sanitized_submission', $sanitised, $ctx->form_id, $ctx->form, $ctx->request_data, $ctx->context);

        if (is_array($filtered_sanitised)) {
            $sanitised = $filtered_sanitised;
        }

        $ctx->sanitised = $sanitised;
        $ctx->errors = $errors;
        $ctx->field_meta = $field_meta;
    }

    /**
     * @param array<int, array<string, mixed>> $fields
     * @param array<string, mixed> $files
     * @return array{0: array<string, mixed>, 1: array<string, string>, 2: array<int, array<string, mixed>>}
     */
    private function process_file_uploads(array $fields, array $files, Validation_Result $validation_result): array
    {
        [$sanitised, $errors, $field_meta] = $validation_result->to_tuple();

        foreach ($fields as $field) {
            $type = $field['type'] ?? 'text';
            $name = $field['name'] ?? null;

            if (!$name) {
                continue;
            }

            // Renderer/validator parity: never process uploads for a type this
            // edition cannot render (a crafted multipart request for a
            // preserved Pro field must be inert).
            if (!\Formatrica\Fields\Field_Renderer_Registry::default()->is_available($type)) {
                continue;
            }

            $upload_handler = \Formatrica\Submission\File\File_Uploader::upload_handler($type);

            if ($type === 'file') {
                [$value, $meta, $error] = $this->file_uploader->handle($field, $files);
            } elseif ($upload_handler !== null) {
                // Edition upload handler (Pro registers multi_file).
                [$value, $meta, $error] = $upload_handler($field, $files, $this->file_uploader);
            } else {
                continue;
            }

            if ($error) {
                $errors[$name] = $error;
            }

            $sanitised[$name] = $value;

            foreach ($field_meta as &$fm) {
                if ($fm['name'] === $name) {
                    $fm['value'] = $value;
                    $fm['meta'] = $meta;
                    break;
                }
            }
            unset($fm);
        }

        return [$sanitised, $errors, $field_meta];
    }

    /**
     * Compute calculation fields (Pro fields.calculation) authoritatively from
     * the sanitised submission and write their display value into both the
     * value map and the field meta. Runs after files so a formula can't
     * reference a not-yet-processed value it shouldn't (files aren't numeric
     * anyway). The stored/emailed number is the server's — the client preview is
     * advisory. Capability-blind: a grandfathered calc field keeps computing.
     *
     * @param array<int, array<string, mixed>> $fields
     * @param array<string, mixed> $sanitised
     * @param array<int, array<string, mixed>> $field_meta
     * @return array{0: array<string, mixed>, 1: array<int, array<string, mixed>>}
     */
    private function process_calculations(array $fields, array $sanitised, array $field_meta): array
    {
        // Calculation (Pro fields.calculation) is computed server-side by the Pro
        // engine. In a Free package the engine is absent, so calculation fields
        // are left as-is (they render nothing and carry no user input).
        if (!class_exists(\Formatrica\Pro\Calculation\Calculation_Service::class)) {
            return [$sanitised, $field_meta];
        }

        $service = new \Formatrica\Pro\Calculation\Calculation_Service();

        foreach ($fields as $field) {
            if (($field['type'] ?? '') !== 'calculation') {
                continue;
            }
            $name = $field['name'] ?? null;
            if (!$name) {
                continue;
            }

            $result = $service->compute($field, $sanitised);
            $sanitised[$name] = $result['display'];

            foreach ($field_meta as &$fm) {
                if (($fm['name'] ?? null) === $name) {
                    $fm['value'] = $result['display'];
                    $fm['meta'] = ['raw' => $result['value'], 'scale' => $result['scale']];
                    break;
                }
            }
            unset($fm);
        }

        return [$sanitised, $field_meta];
    }
}
