<?php

namespace App\Services\QrCode;

use App\Enums\QrCodeType;
use App\Models\QrCode;

/**
 * Turns a {@see QrCode} model into the raw string its QR image encodes.
 *
 * Field names read from `payload` per {@see QrCodeType}:
 * - Text: `text`
 * - Email: `address`, `subject` (optional), `body` (optional)
 * - Phone: `number` (should already include a leading `+` and country code)
 * - Sms: `number`, `message` (optional)
 * - Wifi: `ssid`, `password` (optional), `encryption` (optional, default `WPA`), `hidden` (optional bool)
 * - VCard: `first_name`, `last_name`, `phone` (optional), `email` (optional), `organization` (optional), `title` (optional), `url` (optional)
 */
class QrPayloadBuilder
{
    /**
     * Build the payload string for the given QR code.
     */
    public function build(QrCode $qrCode): string
    {
        if ($qrCode->is_dynamic) {
            return $qrCode->publicUrl();
        }

        $payload = $qrCode->payload ?? [];

        return match ($qrCode->type) {
            QrCodeType::Url => (string) $qrCode->target_url,
            QrCodeType::Text => (string) ($payload['text'] ?? ''),
            QrCodeType::Email => $this->buildEmail($payload),
            QrCodeType::Phone => $this->buildPhone($payload),
            QrCodeType::Sms => $this->buildSms($payload),
            QrCodeType::Wifi => $this->buildWifi($payload),
            QrCodeType::VCard => $this->buildVCard($payload),
        };
    }

    /**
     * Build a `mailto:` payload, appending subject/body as a query string
     * only when present.
     *
     * @param  array<string, mixed>  $payload
     */
    private function buildEmail(array $payload): string
    {
        $address = (string) ($payload['address'] ?? '');

        $params = array_filter([
            'subject' => $payload['subject'] ?? null,
            'body' => $payload['body'] ?? null,
        ], fn (mixed $value): bool => filled($value));

        $query = $params === [] ? '' : '?'.http_build_query($params, '', '&', PHP_QUERY_RFC3986);

        return "mailto:{$address}{$query}";
    }

    /**
     * Build a `tel:` payload.
     *
     * @param  array<string, mixed>  $payload
     */
    private function buildPhone(array $payload): string
    {
        return 'tel:'.($payload['number'] ?? '');
    }

    /**
     * Build an `SMSTO:number:message` payload.
     *
     * @param  array<string, mixed>  $payload
     */
    private function buildSms(array $payload): string
    {
        $number = (string) ($payload['number'] ?? '');
        $message = (string) ($payload['message'] ?? '');

        return "SMSTO:{$number}:{$message}";
    }

    /**
     * Build a `WIFI:` payload, escaping the classic set of special
     * characters (`\ ; , : "`) in the SSID and password so the QR reader's
     * field parser doesn't misinterpret them as delimiters.
     *
     * @param  array<string, mixed>  $payload
     */
    private function buildWifi(array $payload): string
    {
        $ssid = $this->escapeWifiValue((string) ($payload['ssid'] ?? ''));
        $password = $this->escapeWifiValue((string) ($payload['password'] ?? ''));
        $encryption = (string) ($payload['encryption'] ?? 'WPA');
        $hidden = ! empty($payload['hidden']) ? 'true' : 'false';

        return "WIFI:T:{$encryption};S:{$ssid};P:{$password};H:{$hidden};;";
    }

    /**
     * Escape backslash, semicolon, comma, colon and double-quote characters
     * for use inside a WiFi QR field, per the de-facto WIFI: QR spec.
     */
    private function escapeWifiValue(string $value): string
    {
        return addcslashes($value, '\\;,:"');
    }

    /**
     * Build a VCARD 3.0 payload. Line endings are `\r\n` (CRLF) as required
     * by the vCard spec — a bare `\n` breaks some scanners.
     *
     * @param  array<string, mixed>  $payload
     */
    private function buildVCard(array $payload): string
    {
        $firstName = (string) ($payload['first_name'] ?? '');
        $lastName = (string) ($payload['last_name'] ?? '');

        $lines = [
            'BEGIN:VCARD',
            'VERSION:3.0',
            "N:{$lastName};{$firstName};;;",
            'FN:'.trim("{$firstName} {$lastName}"),
        ];

        foreach ([
            'organization' => 'ORG',
            'title' => 'TITLE',
            'phone' => 'TEL',
            'email' => 'EMAIL',
            'url' => 'URL',
        ] as $field => $property) {
            if (filled($payload[$field] ?? null)) {
                $lines[] = "{$property}:{$payload[$field]}";
            }
        }

        $lines[] = 'END:VCARD';

        return implode("\r\n", $lines);
    }
}
