<?php

namespace App\LeadExports;

use App\Models\LeadExport;

/**
 * Builds an OpenAPI 3.0 document describing the JSON payload a lead export POSTs to
 * its configured CRM endpoint. In MAPPED mode the request body is derived from the
 * export's field_map (destination paths + types, nesting dot-notation into objects);
 * in PASSTHROUGH mode the body is a free-form object keyed by the form's field names.
 *
 * Outbound counterpart of NotificationOpenApiGenerator.
 */
class LeadExportOpenApiGenerator
{
    public function generate(LeadExport $leadExport): array
    {
        $target = $this->target($leadExport);

        return [
            'openapi' => '3.0.0',
            'info' => [
                'title' => $leadExport->name . ' lead export',
                'description' => 'Outbound CRM lead notification payload for "' . $leadExport->name . '".',
                'version' => '1.0.0',
            ],
            'servers' => [
                ['url' => $target['server']],
            ],
            'paths' => [
                $target['path'] => [
                    'post' => [
                        'summary' => 'Receive a ' . $leadExport->name . ' lead',
                        'requestBody' => [
                            'required' => true,
                            'content' => [
                                'application/json' => [
                                    'schema' => $this->requestSchema($leadExport),
                                ],
                            ],
                        ],
                        'responses' => [
                            '200' => ['description' => 'Lead accepted.'],
                            '4XX' => ['description' => 'Lead rejected.'],
                        ],
                    ],
                ],
            ],
        ];
    }

    /**
     * Split the configured URL into an OpenAPI server + path. Falls back to a
     * placeholder when no URL is set yet, so the spec is still readable.
     *
     * @return array{server: string, path: string}
     */
    private function target(LeadExport $leadExport): array
    {
        $url = $leadExport->url;

        if (empty($url)) {
            return ['server' => 'https://your-crm.example', 'path' => '/leads'];
        }

        $parts = parse_url($url);
        $server = ($parts['scheme'] ?? 'https') . '://' . ($parts['host'] ?? 'your-crm.example');
        if (!empty($parts['port'])) {
            $server .= ':' . $parts['port'];
        }

        return [
            'server' => $server,
            'path' => $parts['path'] ?? '/',
        ];
    }

    /**
     * Request body schema. Passthrough payloads carry arbitrary form-field keys, so
     * they are described as a free-form string object; mapped payloads enumerate the
     * configured destinations.
     */
    private function requestSchema(LeadExport $leadExport): array
    {
        if (!$leadExport->isMapped()) {
            return [
                'type' => 'object',
                'description' => 'Keys are the form field names; values are the submitted answers.',
                'additionalProperties' => ['type' => 'string'],
            ];
        }

        $schema = ['type' => 'object', 'properties' => []];

        foreach (($leadExport->field_map ?? []) as $field) {
            $destination = $field['destination'] ?? null;
            if (empty($destination)) {
                continue;
            }

            $this->setNested($schema, explode('.', $destination), $this->leafSchema($field));
        }

        return $schema;
    }

    /**
     * OpenAPI schema for a single mapped field, by its configured type.
     *
     * @param array<string, mixed> $field
     * @return array<string, mixed>
     */
    private function leafSchema(array $field): array
    {
        $type = $field['type'] ?? LeadExportFieldType::STRING;
        $description = $field['label'] ?? null;

        $leaf = match ($type) {
            LeadExportFieldType::INTEGER => ['type' => 'integer'],
            LeadExportFieldType::FLOAT => ['type' => 'number'],
            LeadExportFieldType::BOOLEAN => ['type' => 'boolean'],
            LeadExportFieldType::DATE => ['type' => 'string', 'format' => 'date-time'],
            LeadExportFieldType::ARRAY => ['type' => 'array', 'items' => ['type' => 'string']],
            default => ['type' => 'string'],
        };

        if (!empty($description)) {
            $leaf['description'] = $description;
        }

        return $leaf;
    }

    /**
     * Place a leaf schema at a dot-notation path within an object schema, creating
     * intermediate object nodes as needed.
     *
     * @param array<string, mixed> $schema
     * @param array<int, string> $segments
     * @param array<string, mixed> $leaf
     */
    private function setNested(array &$schema, array $segments, array $leaf): void
    {
        $key = array_shift($segments);

        if (empty($segments)) {
            $schema['properties'][$key] = $leaf;
            return;
        }

        if (!isset($schema['properties'][$key]) || ($schema['properties'][$key]['type'] ?? null) !== 'object') {
            $schema['properties'][$key] = ['type' => 'object', 'properties' => []];
        }

        $this->setNested($schema['properties'][$key], $segments, $leaf);
    }
}
