<?php
namespace boru\ai\Provider\OpenAI;

use boru\ai\Asset\Attachment;
use boru\ai\Schema\ContentPart;
use boru\ai\Schema\Message;
use boru\ai\Schema\Request;
use boru\ai\Schema\Response;
use boru\ai\Schema\ToolCall;
use boru\ai\Schema\ToolResult;

class OpenAIMapper
{
    public function mapRequest(Request $request)
    {
        $payload = array(
            'model' => $request->model(),
            'input' => $this->mapMessages($request),
        );

        $attachmentInputs = $this->mapAttachments($request);
        if (!empty($attachmentInputs)) {
            $payload['input'] = array_merge($payload['input'], $attachmentInputs);
        }

        if ($request->instructions()) {
            $payload['instructions'] = $request->instructions();
        }

        if (!empty($request->tools())) {
            $payload['tools'] = $this->mapTools($request->tools());
        }

        if (!empty($request->toolResults())) {
            $payload['input'] = array_merge($payload['input'], $this->mapToolResults($request->toolResults()));
        }

        foreach ($request->options() as $key => $value) {
            if ($value !== null) {
                $payload[$key] = $value;
            }
        }

        return $payload;
    }

    protected function mapMessages(Request $request)
    {
        $input = array();
        foreach ($request->messages() as $message) {
            if (!$message instanceof Message) {
                continue;
            }

            $content = array();
            foreach ($message->parts() as $part) {
                if (!$part instanceof ContentPart) {
                    continue;
                }

                if ($part->type() === 'text') {
                    $content[] = array(
                        'type' => 'input_text',
                        'text' => (string)$part->value(),
                    );
                } elseif ($part->type() === 'image') {
                    $content[] = array(
                        'type' => 'input_image',
                        'image_url' => $part->value(),
                    );
                } elseif ($part->type() === 'file') {
                    $content[] = array(
                        'type' => 'input_file',
                        'file_id' => $part->value(),
                    );
                }
            }

            $input[] = array(
                'type' => 'message',
                'role' => $message->role(),
                'content' => $content,
            );
        }
        return $input;
    }

    protected function mapAttachments(Request $request)
    {
        $input = array();

        foreach ($request->attachments() as $attachment) {
            if (!$attachment instanceof Attachment) {
                continue;
            }

            $binding = $request->findAssetBinding($attachment->assetId(), 'openai');
            if (!$binding) {
                continue;
            }

            if ($attachment->kind() === 'image') {
                $entry = array(
                    'type' => 'message',
                    'role' => 'user',
                    'content' => array(
                        array(
                            'type' => 'input_image',
                            'file_id' => $binding->providerAssetId(),
                        ),
                    ),
                );
                $detail = $attachment->detail();
                if ($detail) {
                    $entry['content'][0]['detail'] = $detail;
                }
                $input[] = $entry;
                continue;
            }

            $input[] = array(
                'type' => 'message',
                'role' => 'user',
                'content' => array(
                    array(
                        'type' => 'input_file',
                        'file_id' => $binding->providerAssetId(),
                    ),
                ),
            );
        }

        return $input;
    }

    protected function mapTools(array $tools)
    {
        $mapped = array();
        foreach ($tools as $tool) {
            if (is_array($tool) && isset($tool['name'])) {
                $mapped[] = array(
                    'type' => 'function',
                    'name' => $tool['name'],
                    'description' => isset($tool['description']) ? $tool['description'] : '',
                    'parameters' => isset($tool['schema']) ? $tool['schema'] : array(
                        'type' => 'object',
                        'properties' => array(),
                    ),
                );
            }
        }
        return $mapped;
    }

    protected function mapToolResults(array $toolResults)
    {
        $mapped = array();
        foreach ($toolResults as $toolResult) {
            if (!$toolResult instanceof ToolResult) {
                continue;
            }

            $mapped[] = array(
                'type' => 'function_call_output',
                'call_id' => $toolResult->toolCallId(),
                'output' => is_scalar($toolResult->output()) ? (string)$toolResult->output() : json_encode($toolResult->output()),
            );
        }
        return $mapped;
    }

    public function mapResponse(array $payload)
    {
        $response = new Response();
        $response->provider('openai');

        if (isset($payload['id'])) {
            $response->id($payload['id']);
        }
        if (isset($payload['model'])) {
            $response->model($payload['model']);
        }
        if (isset($payload['status'])) {
            $response->status($payload['status']);
        }
        if (isset($payload['usage']) && is_array($payload['usage'])) {
            $response->usage($this->normalizeUsage($payload['usage']));
        }
        if (isset($payload['error'])) {
            $response->error($payload['error']);
        }

        $response->raw($payload);
        $response->text($this->extractText($payload));
        $this->extractToolCalls($response, $payload);

        if (!$response->status()) {
            $response->status($response->hasToolCalls() ? 'tool_calling' : 'completed');
        }

        return $response;
    }

    protected function extractText(array $payload)
    {
        $parts = array();
        if (!isset($payload['output']) || !is_array($payload['output'])) {
            return '';
        }

        foreach ($payload['output'] as $item) {
            if (!is_array($item)) {
                continue;
            }

            if (isset($item['type']) && $item['type'] === 'message' && isset($item['content']) && is_array($item['content'])) {
                foreach ($item['content'] as $content) {
                    if (is_array($content) && isset($content['type']) && $content['type'] === 'output_text' && isset($content['text'])) {
                        $parts[] = $content['text'];
                    }
                }
            }
        }

        return implode("\n", $parts);
    }

    protected function extractToolCalls(Response $response, array $payload)
    {
        if (!isset($payload['output']) || !is_array($payload['output'])) {
            return;
        }

        foreach ($payload['output'] as $item) {
            if (!is_array($item)) {
                continue;
            }
            if (!isset($item['type']) || $item['type'] !== 'function_call') {
                continue;
            }

            $toolCall = new ToolCall();
            if (isset($item['call_id'])) {
                $toolCall->id($item['call_id']);
            } elseif (isset($item['id'])) {
                $toolCall->id($item['id']);
            }
            if (isset($item['name'])) {
                $toolCall->name($item['name']);
            }
            if (isset($item['arguments'])) {
                $toolCall->argumentsJson($item['arguments']);
                $decoded = json_decode($item['arguments'], true);
                if (is_array($decoded)) {
                    $toolCall->arguments($decoded);
                }
            }
            $toolCall->status('requested');
            $response->addToolCall($toolCall);
        }
    }

    protected function normalizeUsage(array $usage)
    {
        $normalized = array(
            'raw' => $usage,
        );

        if (isset($usage['input_tokens'])) {
            $normalized['input_tokens'] = (int)$usage['input_tokens'];
        }
        if (isset($usage['output_tokens'])) {
            $normalized['output_tokens'] = (int)$usage['output_tokens'];
        }
        if (isset($usage['total_tokens'])) {
            $normalized['total_tokens'] = (int)$usage['total_tokens'];
        }
        if (isset($usage['input_tokens_details']) && is_array($usage['input_tokens_details']) && isset($usage['input_tokens_details']['cached_tokens'])) {
            $normalized['cached_input_tokens'] = (int)$usage['input_tokens_details']['cached_tokens'];
        }
        if (isset($usage['output_tokens_details']) && is_array($usage['output_tokens_details']) && isset($usage['output_tokens_details']['reasoning_tokens'])) {
            $normalized['reasoning_tokens'] = (int)$usage['output_tokens_details']['reasoning_tokens'];
        }

        return $normalized;
    }
}
