<?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 ClientContractsExport 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' => 'Referenca'],
        'customer' => ['label' => 'Stranka'],
        'address' => ['label' => 'Naslov'],
        'start' => ['label' => 'Začetek'],
        'segment' => ['label' => 'Segment'],
        'balance' => ['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 ($column === 'start') {
                $formats[$letter] = self::DATE_EXCEL_FORMAT;
            }
        }

        return $formats;
    }

    private function resolveValue(Contract $contract, string $column): mixed
    {
        return match ($column) {
            'reference' => $contract->reference,
            'customer' => optional($contract->clientCase?->person)->full_name,
            'address' => optional($contract->clientCase?->person?->address)->address,
            'start' => $this->formatDate($contract->start_date),
            'segment' => $contract->segments?->first()?->name,
            'balance' => optional($contract->account)->balance_amount,
            default => null,
        };
    }

    private function formatDate(?string $date): mixed
    {
        if (empty($date)) {
            return null;
        }

        try {
            $carbon = Carbon::parse($date);

            return ExcelDate::dateTimeToExcel($carbon);
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * @return array<string, string>
     */
    private function getColumnLetterMap(): array
    {
        if ($this->columnLetterMap !== []) {
            return $this->columnLetterMap;
        }

        $letter = 'A';
        foreach ($this->columns as $column) {
            $this->columnLetterMap[$letter] = $column;
            $letter++;
        }

        return $this->columnLetterMap;
    }

    public function bindValue(Cell $cell, $value): bool
    {
        if (is_numeric($value)) {
            $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);

            return true;
        }

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