<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Tables;

use Builder\Core\Foundation\Enums\PublicationStatus;
use Closure;
use Illuminate\Support\Carbon;
use NumberFormatter;

final class Column
{
    private string $label;

    private bool $sortable = false;

    private bool $searchable = false;

    private ?string $component = null;

    private ?Closure $format = null;

    private bool $hidden = false;

    /** @var array<string, array{label: string, color: string}>|null */
    private ?array $badges = null;

    /** @var list<string> */
    private array $eagerLoad = [];

    /**
     * @var ?array{table: string, foreignKey: string, ownerKey: string, column: string, type: string}
     */
    private ?array $joinConfig = null;

    private function __construct(private readonly string $key)
    {
        $this->label = $key;
    }

    public static function make(string $key): self
    {
        return new self($key);
    }

    public function label(string $label): self
    {
        $this->label = $label;

        return $this;
    }

    public function sortable(bool $value = true): self
    {
        $this->sortable = $value;

        return $this;
    }

    public function searchable(bool $value = true): self
    {
        $this->searchable = $value;

        return $this;
    }

    public function component(string $component): self
    {
        $this->component = $component;

        return $this;
    }

    public function format(Closure $format): self
    {
        $this->format = $format;

        return $this;
    }

    /**
     * Format the column value as a date using Carbon.
     *
     * @example Column::make('created_at')->formatDate('d-m-Y')
     */
    public function formatDate(string $format = 'd-m-Y'): self
    {
        $this->format = static function (mixed $value) use ($format): ?string {
            if ($value === null || $value === '') {
                return null;
            }

            return Carbon::parse($value)->format($format);
        };

        return $this;
    }

    /**
     * Format the column value as a currency amount using PHP's NumberFormatter.
     * Falls back to number_format() when the intl extension is unavailable.
     *
     * @example Column::make('price')->formatCurrency('EUR')
     * @example Column::make('price')->formatCurrency('USD', 'en_US')
     */
    public function formatCurrency(string $currency = 'EUR', ?string $locale = null): self
    {
        $this->format = static function (mixed $value) use ($currency, $locale): ?string {
            if ($value === null) {
                return null;
            }

            $amount = (float) $value;

            $resolvedLocale = $locale ?? app()->getLocale();
            $fmt = new NumberFormatter($resolvedLocale, NumberFormatter::CURRENCY);
            $result = $fmt->formatCurrency($amount, $currency);

            return $result !== false ? $result : $currency.' '.number_format($amount, 2, '.', ',');
        };

        return $this;
    }

    /**
     * Format the column value as a localised decimal number using PHP's NumberFormatter.
     * Falls back to number_format() when the intl extension is unavailable.
     *
     * @example Column::make('views')->formatNumber()
     * @example Column::make('rating')->formatNumber(2)
     * @example Column::make('sales')->formatNumber(0, 'nl_NL')
     */
    public function formatNumber(int $decimals = 0, ?string $locale = null): self
    {
        $this->format = static function (mixed $value) use ($decimals, $locale): ?string {
            if ($value === null) {
                return null;
            }

            $number = (float) $value;

            $resolvedLocale = $locale ?? app()->getLocale();
            $fmt = new NumberFormatter($resolvedLocale, NumberFormatter::DECIMAL);
            $fmt->setAttribute(NumberFormatter::FRACTION_DIGITS, $decimals);
            $result = $fmt->format($number);

            return $result !== false ? $result : number_format($number, $decimals);
        };

        return $this;
    }

    public function hidden(bool $value = true): self
    {
        $this->hidden = $value;

        return $this;
    }

    /**
     * Pre-configured badge column for draft/published/scheduled publication statuses.
     */
    public static function publishingStatus(string $name = 'status'): self
    {
        return self::make($name)
            ->label(__('builder::builder.publishing.status'))
            ->sortable()
            ->badge([
                PublicationStatus::Draft->value => [
                    'label' => __('builder::builder.publishing.status_draft'),
                    'color' => PublicationStatus::Draft->color(),
                ],
                PublicationStatus::Published->value => [
                    'label' => __('builder::builder.publishing.status_published'),
                    'color' => PublicationStatus::Published->color(),
                ],
                PublicationStatus::Scheduled->value => [
                    'label' => __('builder::builder.publishing.status_scheduled'),
                    'color' => PublicationStatus::Scheduled->color(),
                ],
            ]);
    }

    /** @param array<string, array{label: string, color: string}> $badges */
    public function badge(array $badges): self
    {
        $this->badges = $badges;
        $this->component = 'BadgeCell';

        return $this;
    }

    /**
     * Eager-load the given Eloquent relations when this column is shown in the index.
     * The loaded relations are available in format() closures via the $record argument.
     */
    public function eagerLoad(string ...$relations): self
    {
        $this->eagerLoad = array_values($relations);

        return $this;
    }

    /** @return list<string> */
    public function getEagerLoad(): array
    {
        return $this->eagerLoad;
    }

    /**
     * Add a LEFT JOIN (or INNER JOIN) to the index query so a related column can be
     * displayed and sorted. The joined column is aliased to this column's key.
     *
     * @example Column::make('country_name')->join('countries', 'country_id', 'id', 'name')->sortable()
     */
    public function join(
        string $table,
        string $foreignKey,
        string $ownerKey = 'id',
        ?string $column = null,
        string $type = 'left',
    ): self {
        $this->joinConfig = [
            'table' => $table,
            'foreignKey' => $foreignKey,
            'ownerKey' => $ownerKey,
            'column' => $column ?? $this->key,
            'type' => $type,
        ];

        return $this;
    }

    /**
     * @return ?array{table: string, foreignKey: string, ownerKey: string, column: string, type: string}
     */
    public function getJoinConfig(): ?array
    {
        return $this->joinConfig;
    }

    public function getKey(): string
    {
        return $this->key;
    }

    public function getLabel(): string
    {
        return $this->label;
    }

    public function isSortable(): bool
    {
        return $this->sortable;
    }

    public function isSearchable(): bool
    {
        return $this->searchable;
    }

    public function getComponent(): ?string
    {
        return $this->component;
    }

    public function getFormat(): ?Closure
    {
        return $this->format;
    }

    public function isHidden(): bool
    {
        return $this->hidden;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'key' => $this->key,
            'label' => $this->label,
            'sortable' => $this->sortable,
            'searchable' => $this->searchable,
            'component' => $this->component,
            'hidden' => $this->hidden,
            'badges' => $this->badges,
        ];
    }
}
