<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\SEO;

use WP_Post;

if (! defined('ABSPATH')) {
    exit;
}

final class RealEstateSchema
{
    /**
     * @return array<string, string>
     */
    public function propertyTypeLabels(): array
    {
        return [
            'SingleFamilyResidence' => __('Single-family residence', 'help4-builder-suite'),
            'Apartment' => __('Apartment', 'help4-builder-suite'),
            'House' => __('House', 'help4-builder-suite'),
        ];
    }

    /**
     * @return array<string, string>
     */
    public function availabilityLabels(): array
    {
        return [
            '' => __('Not specified', 'help4-builder-suite'),
            'InStock' => __('Available', 'help4-builder-suite'),
            'PreOrder' => __('Coming soon', 'help4-builder-suite'),
            'LimitedAvailability' => __('Pending / limited availability', 'help4-builder-suite'),
            'SoldOut' => __('Sold / unavailable', 'help4-builder-suite'),
        ];
    }

    /**
     * @return array<string, string>
     */
    public function listingActionLabels(): array
    {
        return [
            'Sell' => __('For sale', 'help4-builder-suite'),
            'LeaseOut' => __('For lease / rent', 'help4-builder-suite'),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function forPost(int $postId): array
    {
        $saved = get_post_meta($postId, '_h4_seo_real_estate', true);
        return $this->sanitize(is_array($saved) ? $saved : []);
    }

    /**
     * @param array<string, mixed> $raw
     * @return array<string, mixed>
     */
    public function sanitize(array $raw): array
    {
        $propertyType = sanitize_text_field((string) ($raw['property_type'] ?? 'SingleFamilyResidence'));
        if (! isset($this->propertyTypeLabels()[$propertyType])) {
            $propertyType = 'SingleFamilyResidence';
        }

        $availability = sanitize_text_field((string) ($raw['availability'] ?? ''));
        if (! isset($this->availabilityLabels()[$availability])) {
            $availability = '';
        }

        $listingAction = sanitize_text_field((string) ($raw['listing_action'] ?? 'Sell'));
        if (! isset($this->listingActionLabels()[$listingAction])) {
            $listingAction = 'Sell';
        }

        $textFields = [
            'listing_id',
            'address_street',
            'address_locality',
            'address_region',
            'postal_code',
            'address_country',
            'price_currency',
            'floor_size_unit',
            'lot_size_unit',
        ];
        $clean = [];
        foreach ($textFields as $key) {
            $clean[$key] = sanitize_text_field((string) ($raw[$key] ?? ''));
        }

        $clean['property_type'] = $propertyType;
        $clean['availability'] = $availability;
        $clean['listing_action'] = $listingAction;
        $clean['price'] = $this->decimal($raw['price'] ?? '');
        $clean['bedrooms'] = $this->decimal($raw['bedrooms'] ?? '');
        $clean['bathrooms'] = $this->decimal($raw['bathrooms'] ?? '');
        $clean['rooms'] = $this->decimal($raw['rooms'] ?? '');
        $clean['floor_size'] = $this->decimal($raw['floor_size'] ?? '');
        $clean['lot_size'] = $this->decimal($raw['lot_size'] ?? '');
        $clean['latitude'] = $this->coordinate($raw['latitude'] ?? '', -90.0, 90.0);
        $clean['longitude'] = $this->coordinate($raw['longitude'] ?? '', -180.0, 180.0);
        $clean['year_built'] = $this->year($raw['year_built'] ?? '');
        $clean['valid_through'] = $this->date($raw['valid_through'] ?? '');

        $images = $raw['image_urls'] ?? [];
        if (is_string($images)) {
            $images = preg_split('/\r\n|\r|\n/', $images) ?: [];
        }
        $clean['image_urls'] = [];
        if (is_array($images)) {
            foreach ($images as $image) {
                $url = $this->httpUrl((string) $image);
                if ($url !== '') {
                    $clean['image_urls'][] = $url;
                }
            }
        }
        $clean['image_urls'] = array_slice(array_values(array_unique($clean['image_urls'])), 0, 100);

        return $clean;
    }

    /**
     * @param array<string, mixed> $data
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function buildGraph(
        int $postId,
        array $data,
        string $title,
        string $description,
        string $canonical,
        string $primaryImage,
        array $settings
    ): array {
        $post = get_post($postId);
        if (! $post instanceof WP_Post) {
            return [];
        }

        $data = $this->sanitize($data);
        $url = $canonical !== '' ? $canonical : (string) get_permalink($postId);
        $listingId = $url . '#real-estate-listing';
        $propertyId = $url . '#property';
        $offerId = $url . '#offer';

        $imageUrls = $data['image_urls'];
        if ($primaryImage !== '') {
            array_unshift($imageUrls, $primaryImage);
        }
        $imageUrls = array_values(array_unique(array_filter(array_map([$this, 'httpUrl'], $imageUrls))));
        $imageNodes = [];
        foreach ($imageUrls as $index => $imageUrl) {
            $imageNodes[] = [
                '@type' => 'ImageObject',
                '@id' => $url . '#property-image-' . ($index + 1),
                'contentUrl' => $imageUrl,
                'url' => $imageUrl,
                'caption' => $title,
            ];
        }

        $listing = [
            '@type' => 'RealEstateListing',
            '@id' => $listingId,
            'url' => $url,
            'name' => $title,
            'description' => $description,
            'datePosted' => get_the_date(DATE_W3C, $postId),
            'dateModified' => get_the_modified_date(DATE_W3C, $postId),
            'mainEntity' => ['@id' => $propertyId],
            'isPartOf' => ['@id' => home_url('/') . '#website'],
        ];
        if ((string) $data['valid_through'] !== '') {
            $listing['expires'] = (string) $data['valid_through'];
        }

        $property = [
            '@type' => (string) $data['property_type'],
            '@id' => $propertyId,
            'url' => $url,
            'name' => $title,
            'description' => $description,
        ];
        if ((string) $data['listing_id'] !== '') {
            $property['identifier'] = [
                '@type' => 'PropertyValue',
                'propertyID' => 'MLS',
                'value' => (string) $data['listing_id'],
            ];
        }
        if ($imageNodes !== []) {
            $property['image'] = array_map(
                static fn (array $image): array => ['@id' => (string) $image['@id']],
                $imageNodes
            );
        }

        $address = $this->addressNode($data);
        if ($address !== []) {
            $property['address'] = $address;
        }
        if ((string) $data['latitude'] !== '' && (string) $data['longitude'] !== '') {
            $property['geo'] = [
                '@type' => 'GeoCoordinates',
                'latitude' => (float) $data['latitude'],
                'longitude' => (float) $data['longitude'],
            ];
        }

        $numberFields = [
            'bedrooms' => 'numberOfBedrooms',
            'bathrooms' => 'numberOfBathroomsTotal',
            'rooms' => 'numberOfRooms',
        ];
        foreach ($numberFields as $dataKey => $schemaKey) {
            if ((string) $data[$dataKey] !== '') {
                $property[$schemaKey] = (float) $data[$dataKey];
            }
        }
        if ((string) $data['year_built'] !== '') {
            $property['yearBuilt'] = (int) $data['year_built'];
        }
        if ((string) $data['floor_size'] !== '') {
            $property['floorSize'] = [
                '@type' => 'QuantitativeValue',
                'value' => (float) $data['floor_size'],
            ];
            if ((string) $data['floor_size_unit'] !== '') {
                $property['floorSize']['unitText'] = (string) $data['floor_size_unit'];
            }
        }
        if ((string) $data['lot_size'] !== '') {
            $lotSize = [
                '@type' => 'PropertyValue',
                'propertyID' => 'lotSize',
                'name' => __('Lot size', 'help4-builder-suite'),
                'value' => (float) $data['lot_size'],
            ];
            if ((string) $data['lot_size_unit'] !== '') {
                $lotSize['unitText'] = (string) $data['lot_size_unit'];
            }
            $property['additionalProperty'] = [$lotSize];
        }

        $offer = [
            '@type' => 'Offer',
            '@id' => $offerId,
            'url' => $url,
            'itemOffered' => ['@id' => $propertyId],
            'seller' => ['@id' => home_url('/') . '#organization'],
            'businessFunction' => 'https://purl.org/goodrelations/v1#' . (string) $data['listing_action'],
        ];
        if ((string) $data['price'] !== '') {
            $offer['price'] = (string) $data['price'];
        }
        if ((string) $data['price_currency'] !== '') {
            $offer['priceCurrency'] = strtoupper((string) $data['price_currency']);
        }
        if ((string) $data['availability'] !== '') {
            $offer['availability'] = 'https://schema.org/' . (string) $data['availability'];
        }
        if ((string) $data['valid_through'] !== '') {
            $offer['validThrough'] = (string) $data['valid_through'];
        }

        $listing = apply_filters('h4bs_real_estate_listing_schema', $listing, $postId, $data);
        $property = apply_filters('h4bs_real_estate_property_schema', $property, $postId, $data);
        $offer = apply_filters('h4bs_real_estate_offer_schema', $offer, $postId, $data);
        $imageNodes = apply_filters('h4bs_real_estate_image_schema', $imageNodes, $postId, $data);

        $graph = [
            '@context' => 'https://schema.org',
            '@graph' => array_values(array_filter(array_merge([$listing, $property, $offer], is_array($imageNodes) ? $imageNodes : []), 'is_array')),
        ];

        $filteredGraph = apply_filters('h4bs_real_estate_graph', $graph, $postId, $data, $settings);
        return is_array($filteredGraph) ? $filteredGraph : $graph;
    }

    /**
     * @param array<string, mixed> $data
     * @return array<string, string>
     */
    private function addressNode(array $data): array
    {
        $map = [
            'address_street' => 'streetAddress',
            'address_locality' => 'addressLocality',
            'address_region' => 'addressRegion',
            'postal_code' => 'postalCode',
            'address_country' => 'addressCountry',
        ];
        $address = ['@type' => 'PostalAddress'];
        foreach ($map as $dataKey => $schemaKey) {
            if ((string) $data[$dataKey] !== '') {
                $address[$schemaKey] = (string) $data[$dataKey];
            }
        }

        return count($address) > 1 ? $address : [];
    }

    private function decimal(mixed $value): string
    {
        $value = trim((string) $value);
        if ($value === '' || ! is_numeric($value)) {
            return '';
        }
        $number = (float) $value;
        return $number >= 0 ? rtrim(rtrim(number_format($number, 4, '.', ''), '0'), '.') : '';
    }

    private function coordinate(mixed $value, float $minimum, float $maximum): string
    {
        $value = trim((string) $value);
        if ($value === '' || ! is_numeric($value)) {
            return '';
        }
        $number = (float) $value;
        if ($number < $minimum || $number > $maximum) {
            return '';
        }
        return rtrim(rtrim(number_format($number, 7, '.', ''), '0'), '.');
    }

    private function year(mixed $value): string
    {
        $year = absint($value);
        $maximum = (int) gmdate('Y') + 10;
        return $year >= 1000 && $year <= $maximum ? (string) $year : '';
    }

    private function date(mixed $value): string
    {
        $value = sanitize_text_field((string) $value);
        if ($value === '' || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            return '';
        }
        [$year, $month, $day] = array_map('intval', explode('-', $value));
        return checkdate($month, $day, $year) ? $value : '';
    }

    private function httpUrl(string $url): string
    {
        $url = esc_url_raw(trim($url), ['http', 'https']);
        $scheme = strtolower((string) wp_parse_url($url, PHP_URL_SCHEME));
        return in_array($scheme, ['http', 'https'], true) ? $url : '';
    }
}
