<?php
declare(strict_types=1);

namespace Autogedal\EanTools\Model\Spreadsheet;

use Generator;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem\Driver\File;

class CsvRowReader implements RowReaderInterface
{
    private const BOM = "\xEF\xBB\xBF";
    private const DELIMITERS = [';', ',', "\t", '|'];
    private const DEFAULT_DELIMITER = ',';
    private const SAMPLE_BYTES = 65536;
    private const SAMPLE_LINES = 20;
    private const SAMPLE_STREAM = 'php://temp';
    private const ENCLOSURE = '"';
    private const ESCAPE = "\0";

    public function __construct(private readonly File $file)
    {
    }

    public function read(string $path): Generator
    {
        try {
            $handle = $this->file->fileOpen($path, 'r');

            try {
                $sample = $this->file->fileRead($handle, self::SAMPLE_BYTES);
                $offset = str_starts_with($sample, self::BOM) ? strlen(self::BOM) : 0;
                $delimiter = $this->detectDelimiter(substr($sample, $offset));
                $this->file->fileSeek($handle, $offset);

                $line = 0;

                while (true) {
                    $data = $this->file->fileGetCsv(
                        $handle,
                        0,
                        $delimiter,
                        self::ENCLOSURE,
                        self::ESCAPE
                    );

                    if (!is_array($data)) {
                        break;
                    }

                    $line++;

                    if ($data === [null]) {
                        continue;
                    }

                    yield $line => $this->decodeRow($data);
                }
            } finally {
                $this->file->fileClose($handle);
            }
        } catch (FileSystemException $e) {
            throw new LocalizedException(__('The uploaded file could not be read.'), $e);
        }
    }

    private function detectDelimiter(string $sample): string
    {
        if (trim($sample) === '') {
            return self::DEFAULT_DELIMITER;
        }

        $handle = $this->file->fileOpen(self::SAMPLE_STREAM, 'w+');

        try {
            $this->file->fileWrite($handle, $sample);
            $best = self::DEFAULT_DELIMITER;
            $bestScore = -1;

            foreach (self::DELIMITERS as $delimiter) {
                $this->file->fileSeek($handle, 0);
                $minimum = PHP_INT_MAX;
                $maximum = 0;
                $lines = 0;

                while ($lines < self::SAMPLE_LINES) {
                    $row = $this->file->fileGetCsv(
                        $handle,
                        0,
                        $delimiter,
                        self::ENCLOSURE,
                        self::ESCAPE
                    );

                    if (!is_array($row)) {
                        break;
                    }

                    if ($row === [null]) {
                        continue;
                    }

                    $count = count($row) - 1;
                    if ($count === 0) {
                        continue 2;
                    }

                    $minimum = min($minimum, $count);
                    $maximum = max($maximum, $count);
                    $lines++;
                }

                if ($lines === 0) {
                    continue;
                }

                $score = $minimum * 10 - ($maximum - $minimum);
                if ($score > $bestScore) {
                    $bestScore = $score;
                    $best = $delimiter;
                }
            }

            return $best;
        } finally {
            $this->file->fileClose($handle);
        }
    }

    private function decodeRow(array $row): array
    {
        foreach ($row as $index => $cell) {
            $cell = (string)$cell;
            $row[$index] = $cell === '' || mb_check_encoding($cell, 'UTF-8')
                ? $cell
                : (string)mb_convert_encoding($cell, 'UTF-8', 'Windows-1252');
        }

        return $row;
    }
}
