<?php

declare(strict_types=1);

final class MyronAssetUploadRequestHandler
{
    /**
     * @var list<string>
     */
    private const DEFAULT_FILE_FIELDS = ['upload_file', 'asset_upload_file'];

    public function __construct(
        private readonly MyronFileRepository $files,
    ) {
    }

    /**
     * @param array<string, mixed> $uploadedFiles
     * @param array<string, mixed> $input
     * @param array<string, mixed> $options
     * @return array{ok:bool,file?:array<string,mixed>,asset?:array<string,string>,error?:string,status:int}
     */
    public function handle(array $uploadedFiles, array $input, string $uploadedByUserId, array $options = []): array
    {
        try {
            $file = $this->upload($uploadedFiles, $input, $uploadedByUserId, $options);

            return [
                'ok' => true,
                'file' => $file,
                'asset' => myronAssetPickerPayload($file),
                'status' => 200,
            ];
        } catch (Throwable $exception) {
            return [
                'ok' => false,
                'error' => $this->operatorMessage($exception),
                'status' => $this->statusForException($exception),
            ];
        }
    }

    /**
     * @param array<string, mixed> $uploadedFiles
     * @param array<string, mixed> $input
     * @param array<string, mixed> $options
     * @return array<string, mixed>
     */
    public function upload(array $uploadedFiles, array $input, string $uploadedByUserId, array $options = []): array
    {
        $file = $this->uploadedFileFrom($uploadedFiles, $options);
        $metadata = $this->metadataFrom($input, $options);
        $metadataRequired = (bool) ($options['metadataRequired'] ?? true);

        return $this->files->uploadFile($file, $uploadedByUserId, $metadata, $metadataRequired);
    }

    /**
     * @param array<string, mixed> $uploadedFiles
     * @param array<string, mixed> $options
     * @return array<string, mixed>
     */
    private function uploadedFileFrom(array $uploadedFiles, array $options): array
    {
        $fields = $this->fileFieldsFrom($options);
        foreach ($fields as $field) {
            $candidate = $uploadedFiles[$field] ?? null;
            if (is_array($candidate)) {
                return $candidate;
            }
        }

        throw new RuntimeException('Choose a file to upload.');
    }

    /**
     * @param array<string, mixed> $options
     * @return list<string>
     */
    private function fileFieldsFrom(array $options): array
    {
        $configured = $options['fileFields'] ?? null;
        if (!is_array($configured)) {
            return self::DEFAULT_FILE_FIELDS;
        }

        $fields = [];
        foreach ($configured as $field) {
            $fieldName = trim((string) $field);
            if ($fieldName !== '') {
                $fields[] = $fieldName;
            }
        }

        return $fields === [] ? self::DEFAULT_FILE_FIELDS : array_values(array_unique($fields));
    }

    /**
     * @param array<string, mixed> $input
     * @param array<string, mixed> $options
     * @return array<string, mixed>
     */
    private function metadataFrom(array $input, array $options): array
    {
        $metadata = $input;
        $assetClass = trim((string) ($options['assetClass'] ?? $metadata['asset_class'] ?? ''));
        if ($assetClass !== '') {
            $metadata['asset_class'] = $assetClass;
        }

        return $metadata;
    }

    private function operatorMessage(Throwable $exception): string
    {
        if (!$exception instanceof RuntimeException) {
            return 'Upload failed because the server could not process the file.';
        }

        $message = trim($exception->getMessage());

        return $message === '' ? 'Upload failed.' : $message;
    }

    private function statusForException(Throwable $exception): int
    {
        return $exception instanceof RuntimeException ? 422 : 500;
    }
}
