<?php

namespace App\Exports;

use App\Models\Contract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;

class SegmentContractsExport extends DefaultValueBinder implements FromQuery, ShouldAutoSize, WithColumnFormatting, WithCustomValueBinder, WithHeadings, WithMapping
{
    public const DATE_EXCEL_FORMAT = 'dd"."mm"."yyyy';

    public const TEXT_EXCEL_FORMAT = NumberFormat::FORMAT_TEXT;

    /**
     * @var array<string, string>
     */
    private array $columnLetterMap = [];

    /**
     * @var array<string, array{label: string}>
     */
    public const COLUMN_METADATA = [
        'reference' => ['label' => 'Pogodba'],
        'client_case' => ['label' => 'Primer'],
        'address' => ['label' => 'Naslov'],
        'client' => ['label' => 'Stranka'],
        'type' => ['label' => 'Vrsta'],
        'start_date' => ['label' => 'Začetek'],
        'end_date' => ['label' => 'Konec'],
        'account' => ['label' => 'Stanje'],
    ];

    /**
     * @param  array<int, string>  $columns
     */
    public function __construct(private Builder $query, private array $columns) {}

    /**
     * @return array<int, string>
     */
    public static function allowedColumns(): array
    {
        return array_keys(self::COLUMN_METADATA);
    }

    public static function columnLabel(string $column): string
    {
        return self::COLUMN_METADATA[$column]['label'] ?? $column;
    }

    public function query(): Builder
    {
        return $this->query;
    }

    /**
     * @return array<int, mixed>
     */
    public function map($row): array
    {
        return array_map(fn (string $column) => $this->resolveValue($row, $column), $this->columns);
    }

    /**
     * @return array<int, string>
     */
    public function headings(): array
    {
        return array_map(fn (string $column) => self::columnLabel($column), $this->columns);
    }

    /**
     * @return array<string, string>
     */
    public function columnFormats(): array
    {
        $formats = [];

        foreach ($this->getColumnLetterMap() as $letter => $column) {
            if ($column === 'reference') {
                $formats[$letter] = self::TEXT_EXCEL_FORMAT;

                continue;
            }

            if (in_array($column, ['start_date', 'end_date'], true)) {
                $formats[$letter] = self::DATE_EXCEL_FORMAT;
            }
        }

        return $formats;
    }

    private function resolveValue(Contract $contract, string $column): mixed
    {
        return match ($column) {
            'reference' => $contract->reference,
            'client_case' => optional($contract->clientCase?->person)->full_name,
            'address' => optional($contract->clientCase?->person?->address)->address,
            'client' => optional($contract->clientCase?->client?->person)->full_name,
            'type' => optional($contract->type)->name,
            'start_date' => $this->formatDate($contract->start_date),
            'end_date' => $this->formatDate($contract->end_date),
            'account' => optional($contract->account)->balance_amount,
            default => null,
        };
    }

    private function formatDate(mixed $value): ?float
    {
        $carbon = Carbon::make($value);

        if (! $carbon) {
            return null;
        }

        return ExcelDate::dateTimeToExcel($carbon->copy()->startOfDay());
    }

    private function columnLetter(int $index): string
    {
        $index++;
        $letter = '';

        while ($index > 0) {
            $remainder = ($index - 1) % 26;
            $letter = chr(65 + $remainder).$letter;
            $index = intdiv($index - 1, 26);
        }

        return $letter;
    }

    public function bindValue(Cell $cell, $value): bool
    {
        $columnKey = $this->getColumnLetterMap()[$cell->getColumn()] ?? null;

        if ($columnKey === 'reference') {
            $cell->setValueExplicit((string) $value, DataType::TYPE_STRING);

            return true;
        }

        return parent::bindValue($cell, $value);
    }

    /**
     * @return array<string, string>
     */
    private function getColumnLetterMap(): array
    {
        if ($this->columnLetterMap === []) {
            foreach ($this->columns as $index => $column) {
                $this->columnLetterMap[$this->columnLetter($index)] = $column;
            }
        }

        return $this->columnLetterMap;
    }
}
