<?php

declare(strict_types=1);

namespace PortalPeople\palletline\services;

use PortalPeople\shippingcosts\models\ColumnMapForm;
use PortalPeople\shippingcosts\services\MarkdownTableParser;
use RuntimeException;

/** Converts Palletline PDF Markdown into conservative importer rows. */
final class PalletlinePdfRateParser
{
    /** @return list<array<string, mixed>> */
    public function parse(string $markdown, string $provider = 'Palletline'): array
    {
        $tableRows = (new MarkdownTableParser())->parse($markdown);
        if ($tableRows !== []) {
            return $this->normalizeTableRows($tableRows, $provider);
        }

        return $this->parseFixedLayout($markdown, $provider);
    }

    /** @param list<array<string, string>> $rows @return list<array<string, mixed>> */
    private function normalizeTableRows(array $rows, string $provider): array
    {
        $normalized = [];
        foreach ($rows as $row) {
            $postcode = $this->value($row, ['PostCode', 'Postcode', 'Postcodes', 'Post Codes']);
            $service = $this->value($row, ['Service', 'Service Type', 'Type']);
            $normalized[] = [
                'Provider' => $this->value($row, ['Provider', 'Haulier', 'Carrier']) ?: $provider,
                'PostCode' => $postcode,
                'Service' => ColumnMapForm::normalizeType($service),
                'requires_review' => $postcode === '' || $service === '',
            ] + $this->priceColumns($row);
        }

        return $normalized;
    }

    /** @return list<array<string, mixed>> */
    private function parseFixedLayout(string $markdown, string $provider): array
    {
        preg_match_all('/```text\n(.*?)\n```/s', $markdown, $blocks);
        if (($blocks[1] ?? []) === []) {
            throw new RuntimeException('No fixed-layout text blocks were found in the PDF Markdown.');
        }

        $rows = [];
        foreach ($blocks[1] as $block) {
            $lines = preg_split('/\R/', $block) ?: [];
            foreach ($lines as $line) {
                $service = $this->service($line);
                if ($service === '') {
                    continue;
                }

                preg_match_all('/£?\s*(\d+\.\s*\d{2})/', $line, $matches);
                $prices = array_map(
                    static fn (string $value): float => (float) str_replace(' ', '', $value),
                    $matches[1] ?? []
                );
                $postcode = $this->postcode($line);
                $row = [
                    'Provider' => $provider,
                    'PostCode' => $postcode,
                    'Service' => ColumnMapForm::normalizeType($service),
                    'requires_review' => $postcode === '' || $prices === [],
                ];
                foreach ($prices as $index => $price) {
                    $row[$this->priceHeader($index)] = $price;
                }
                $rows[] = $row;
            }
        }

        if ($rows === []) {
            throw new RuntimeException('No Palletline service rows were found in the PDF.');
        }

        return $rows;
    }

    /** @param array<string, string> $row @param list<string> $names */
    private function value(array $row, array $names): string
    {
        foreach ($names as $name) {
            if (isset($row[$name]) && trim($row[$name]) !== '') {
                return trim($row[$name]);
            }
        }

        return '';
    }

    /** @param array<string, string> $row @return array<string, string> */
    private function priceColumns(array $row): array
    {
        $prices = [];
        foreach ($row as $header => $value) {
            if (preg_match('/price|cost|qtr|quarter|half|full|pallet|\b\d+\s*x\b/i', $header) === 1) {
                $prices[$header] = $this->number($value);
            }
        }

        return $prices;
    }

    private function number(string $value): string
    {
        return preg_replace('/[^0-9.]/', '', $value) ?? '';
    }

    private function service(string $line): string
    {
        if (preg_match('/\b(24hr|48hr|72hr|next\s*day|economy|standard|priority)\b/i', $line, $match) !== 1) {
            return '';
        }

        return $match[1];
    }

    private function postcode(string $line): string
    {
        if (preg_match('/\b[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}\b/i', $line, $match) === 1) {
            return strtoupper(preg_replace('/\s+/', ' ', trim($match[0])) ?? $match[0]);
        }

        return '';
    }

    private function priceHeader(int $index): string
    {
        return ['Qtr', 'Half', 'Full', '4th price', '5th price', '6th price'][$index] ?? 'Price ' . ($index + 1);
    }
}
