<?php

declare(strict_types=1);

namespace Autogedal\FeedMonitor\Model\Fetch;

/**
 * XML feed with one element per product under the root (e.g. <item><sku/><Stoc/></item>). Rows hold the trimmed
 * text of the key and of the listed fields. Products without a code, without a required field or with a value that is
 * not accepted are rejected; the feed is invalid when none is left or too many are rejected (see RejectedRows).
 */
class XmlFormat implements FeedFormatInterface
{
    /**
     * @param string[] $fields child elements copied in each row, besides the key
     * @param string[] $required fields that must not be empty
     * @param array<string, string[]> $accepted field => values accepted (case insensitive)
     */
    public function __construct(
        private readonly string $item = 'item',
        private readonly string $key = 'sku',
        private readonly array $fields = [],
        private readonly array $required = [],
        private readonly array $accepted = [],
        private readonly float $maxRejectedShare = RejectedRows::DEFAULT_MAX_SHARE
    ) {
    }

    public function parse(string $content): ParsedFeed
    {
        $previous = libxml_use_internal_errors(true);
        try {
            $xml = simplexml_load_string($content);
        } finally {
            libxml_clear_errors();
            libxml_use_internal_errors($previous);
        }
        if ($xml === false) {
            throw InvalidFeedException::withSample('Raspunsul nu este XML valid.', $content);
        }

        $items = $xml->{$this->item};
        if (count($items) === 0 && $xml->count() > 0) {
            throw InvalidFeedException::withSample(
                sprintf('XML-ul nu contine elemente <%s> (radacina <%s>).', $this->item, $xml->getName()),
                $content
            );
        }

        $rows = [];
        $skipped = 0;
        foreach ($items as $node) {
            $code = trim((string)$node->{$this->key});
            if ($code === '') {
                $skipped++;
                continue;
            }
            $row = [$this->key => $code];
            foreach ($this->fields as $field) {
                $row[$field] = trim((string)$node->{$field});
            }
            if (!$this->isValid($row)) {
                $skipped++;
                continue;
            }
            $rows[$code] = $row;
        }

        RejectedRows::check(count($rows), $skipped, $this->maxRejectedShare, $content);

        return new ParsedFeed($rows, $skipped);
    }

    /**
     * @param array<string, string> $row
     */
    private function isValid(array $row): bool
    {
        foreach ($this->required as $field) {
            if (($row[$field] ?? '') === '') {
                return false;
            }
        }
        foreach ($this->accepted as $field => $values) {
            if (!in_array(strtolower($row[$field] ?? ''), array_map('strtolower', $values), true)) {
                return false;
            }
        }

        return true;
    }
}
