<?php
declare(strict_types=1);

namespace KbMarkdownImporter\Frontend;

use KbMarkdownImporter\Plugin;

final class ProductUpdatesFeed
{
    public static function items(array $settings = []): array
    {
        $settings = $settings ?: Plugin::settings();
        $source = (string) ($settings['product_updates_source'] ?? 'rss');
        $url = esc_url_raw((string) ('rest' === $source ? ($settings['product_updates_rest_url'] ?? '') : ($settings['product_updates_feed_url'] ?? '')));

        if ('' === $url) {
            return [];
        }

        $cacheKey = 'kb_product_updates_' . md5($source . $url . wp_json_encode([
            $settings['product_updates_feed_item_path'] ?? '',
            $settings['product_updates_feed_product_field'] ?? '',
            $settings['product_updates_feed_version_field'] ?? '',
            $settings['product_updates_feed_date_field'] ?? '',
            $settings['product_updates_feed_changelog_field'] ?? '',
            $settings['product_updates_rest_list_path'] ?? '',
            $settings['product_updates_rest_product_field'] ?? '',
            $settings['product_updates_rest_version_field'] ?? '',
            $settings['product_updates_rest_date_field'] ?? '',
            $settings['product_updates_rest_changelog_field'] ?? '',
        ]));
        $cached = get_transient($cacheKey);

        if (is_array($cached)) {
            return $cached;
        }

        $response = wp_remote_get($url, [
            'timeout' => 8,
            'redirection' => 3,
            'user-agent' => 'KB Markdown Importer/' . KB_MARKDOWN_IMPORTER_VERSION,
        ]);

        if (is_wp_error($response) || 200 !== (int) wp_remote_retrieve_response_code($response)) {
            return [];
        }

        $body = (string) wp_remote_retrieve_body($response);
        $items = 'rest' === $source ? self::parseJson($body, $settings) : self::parseXml($body, $settings);
        set_transient($cacheKey, $items, 15 * MINUTE_IN_SECONDS);

        return $items;
    }

    private static function parseXml(string $xml, array $settings): array
    {
        if ('' === trim($xml) || ! class_exists(\DOMDocument::class)) {
            return [];
        }

        $previous = libxml_use_internal_errors(true);
        $document = new \DOMDocument();
        $loaded = $document->loadXML($xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
        libxml_clear_errors();
        libxml_use_internal_errors($previous);

        if (! $loaded) {
            return [];
        }

        $xpath = new \DOMXPath($document);
        $itemNodes = $xpath->query(self::itemPath((string) ($settings['product_updates_feed_item_path'] ?? 'channel/item')));

        if (! $itemNodes) {
            return [];
        }

        $limit = max(1, min(20, (int) ($settings['product_updates_feed_limit'] ?? 5)));
        $items = [];

        foreach ($itemNodes as $itemNode) {
            if (count($items) >= $limit) {
                break;
            }

            $date = self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_date_field'] ?? 'pubDate'));
            $items[] = [
                'product' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_product_field'] ?? 'title')),
                'version' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_version_field'] ?? 'category')),
                'date' => self::formatDate($date),
                'changelog' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_changelog_field'] ?? 'description')),
            ];
        }

        return $items;
    }

    private static function parseJson(string $json, array $settings): array
    {
        $data = json_decode($json, true);

        if (! is_array($data)) {
            return [];
        }

        $nodes = self::jsonList($data, (string) ($settings['product_updates_rest_list_path'] ?? 'content,data,items'));

        if (! $nodes) {
            return [];
        }

        $limit = max(1, min(20, (int) ($settings['product_updates_feed_limit'] ?? 5)));
        $items = [];

        foreach ($nodes as $node) {
            if (count($items) >= $limit || ! is_array($node)) {
                break;
            }

            $date = self::jsonField($node, (string) ($settings['product_updates_rest_date_field'] ?? 'releaseDate,date,updatedAt,createdAt'));
            $items[] = [
                'product' => self::jsonField($node, (string) ($settings['product_updates_rest_product_field'] ?? 'product.name,productName,name')),
                'version' => self::jsonField($node, (string) ($settings['product_updates_rest_version_field'] ?? 'version,versionName,name')),
                'date' => self::formatDate($date),
                'changelog' => self::jsonField($node, (string) ($settings['product_updates_rest_changelog_field'] ?? 'changelog,changeLog,description,changes')),
            ];
        }

        return $items;
    }

    private static function jsonList(array $data, string $paths): array
    {
        foreach (self::pathAlternatives($paths) as $path) {
            $value = '' === $path ? $data : self::jsonValue($data, $path);

            if (is_array($value) && array_is_list($value)) {
                return $value;
            }
        }

        return array_is_list($data) ? $data : [];
    }

    private static function jsonField(array $data, string $paths): string
    {
        foreach (self::pathAlternatives($paths) as $path) {
            $value = self::jsonValue($data, $path);

            if (is_scalar($value)) {
                return trim(wp_strip_all_tags((string) $value));
            }

            if (is_array($value)) {
                $text = implode(', ', array_filter(array_map(static fn ($item): string => is_scalar($item) ? (string) $item : '', $value)));
                if ('' !== $text) {
                    return trim(wp_strip_all_tags($text));
                }
            }
        }

        return '';
    }

    private static function jsonValue(array $data, string $path): mixed
    {
        $value = $data;

        foreach (self::pathSegments($path) as $segment) {
            if (! is_array($value) || ! array_key_exists($segment, $value)) {
                return null;
            }

            $value = $value[$segment];
        }

        return $value;
    }

    private static function pathAlternatives(string $paths): array
    {
        return array_values(array_map('trim', explode(',', $paths)));
    }

    private static function itemPath(string $path): string
    {
        $segments = self::pathSegments($path ?: 'channel/item');
        $first = (string) array_shift($segments);
        $query = '//*[local-name()="' . self::localName($first ?: 'item') . '"]';

        foreach ($segments as $segment) {
            $query .= '/*[local-name()="' . self::localName((string) $segment) . '"]';
        }

        return $query;
    }

    private static function fieldValue(\DOMXPath $xpath, \DOMNode $node, string $path): string
    {
        $segments = self::pathSegments($path);

        if (! $segments) {
            return '';
        }

        $attribute = null;
        $last = (string) end($segments);

        if (str_starts_with($last, '@')) {
            $attribute = substr($last, 1);
            array_pop($segments);
        }

        $query = '.';
        foreach ($segments as $segment) {
            $query .= '/*[local-name()="' . self::localName((string) $segment) . '"]';
        }

        $result = $xpath->query($query, $node);
        $target = $result && $result->length > 0 ? $result->item(0) : null;

        if (! $target) {
            return '';
        }

        if ($attribute && $target instanceof \DOMElement) {
            return trim(wp_strip_all_tags(html_entity_decode($target->getAttribute($attribute), ENT_QUOTES | ENT_XML1, get_bloginfo('charset'))));
        }

        return trim(wp_strip_all_tags(html_entity_decode($target->textContent, ENT_QUOTES | ENT_XML1, get_bloginfo('charset'))));
    }

    private static function pathSegments(string $path): array
    {
        $path = trim(str_replace('.', '/', $path), '/ ');

        if ('' === $path) {
            return [];
        }

        return array_values(array_filter(array_map('trim', explode('/', $path)), static fn (string $segment): bool => '' !== $segment));
    }

    private static function localName(string $segment): string
    {
        $segment = trim($segment);
        $parts = explode(':', $segment);

        return preg_replace('/[^A-Za-z0-9_-]/', '', (string) end($parts)) ?: '';
    }

    private static function formatDate(string $date): string
    {
        if ('' === $date) {
            return '';
        }

        $timestamp = strtotime($date);

        if (! $timestamp) {
            return $date;
        }

        return wp_date((string) get_option('date_format'), $timestamp);
    }
}
