<?php

declare(strict_types=1);

/**
 * APO-194 Card 4 — Upload a file via the JSON-content base64 envelope.
 *
 * URI:   POST /api/v1/files
 * Scope: content.write
 *
 * Accepts the canonical envelope:
 *   { filename, contentType, contentBase64, sizeBytes, sha256, metadata? }
 *
 * Where `metadata` is the optional alt_text / description / caption / etc.
 * payload the FileRepository expects. Multipart admin form path goes
 * through MyronFilesActionService::uploadFromMultipart — both produce
 * identical file rows + identical bytes-on-disk; verifier asserts the
 * byte-equal property.
 */
final class MyronUploadFileAction implements MyronAction
{
    public function __construct(
        private readonly MyronFilesActionService $service,
    ) {
    }

    public function metadata(): MyronActionMetadata
    {
        return new MyronActionMetadata(
            uri:           '/api/v1/files',
            method:        'POST',
            domain:        'content',
            resource:      'file',
            verb:          'upload',
            inputSchema:   [
                'type'       => 'object',
                'properties' => [
                    'filename'      => ['type' => 'string'],
                    'contentType'   => ['type' => 'string'],
                    'contentBase64' => ['type' => 'string'],
                    'sizeBytes'     => ['type' => 'integer'],
                    'sha256'        => ['type' => 'string'],
                    'metadata'      => ['type' => 'object'],
                ],
                'required'   => ['filename', 'contentType', 'contentBase64', 'sizeBytes', 'sha256'],
            ],
            outputSchema:  [
                'type'       => 'object',
                'properties' => [
                    'id'             => ['type' => 'string'],
                    'file_url'       => ['type' => 'string'],
                    'original_filename' => ['type' => 'string'],
                    'mime_type'      => ['type' => 'string'],
                    'file_size_bytes' => ['type' => 'integer'],
                ],
            ],
            requiredScope:   MyronApiScopes::CONTENT_WRITE,
            summary:         'Upload a file using the canonical JSON-content base64 envelope. AI clients use this for image/document uploads via MCP; admin form path uses multipart with identical semantics.',
            sideEffects:     ['inserts files row', 'writes bytes to public/uploads/'],
            preconditions:   ['filename matches allowed pattern', 'contentType is permitted', 'sha256 matches decoded bytes'],
            postconditions:  ['file row exists; bytes persisted; metadata recorded'],
            audienceClass:   4,
            adminAliases:    ['upload-file', 'upload-asset-picker', 'upload-document'],
            snapshotInclude: false,
            exposeToMcp:     true,
        );
    }

    public function execute(array $input, MyronActionCallerContext $caller): MyronActionResult
    {
        $envelope = [
            'filename'      => $input['filename']      ?? '',
            'contentType'   => $input['contentType']   ?? '',
            'contentBase64' => $input['contentBase64'] ?? '',
            'sizeBytes'     => $input['sizeBytes']     ?? 0,
            'sha256'        => $input['sha256']        ?? '',
        ];
        $metadata = is_array($input['metadata'] ?? null) ? (array) $input['metadata'] : [];

        try {
            return MyronActionResult::ok($this->service->uploadFromEnvelope($envelope, $metadata, $caller));
        } catch (MyronJsonContentEnvelopeError $e) {
            return MyronActionResult::error('input_invalid', $e->getMessage());
        } catch (InvalidArgumentException $e) {
            return MyronActionResult::error('input_invalid', $e->getMessage());
        } catch (Throwable $e) {
            return MyronActionResult::error('business_rule_failed', $e->getMessage());
        }
    }
}
