<?php

declare(strict_types=1);

namespace App\Framework\View\Table;

use App\Framework\View\Table\ValueObjects\ColumnDefinition;
use App\Framework\View\Table\ValueObjects\TableRowData;

final class TableBuilder
{
    /** @var ColumnDefinition[] */
    private array $columns = [];

    /** @var TableRowData[] */
    private array $rows = [];

    private ?string $cssClass = null;

    private ?TableOptions $options = null;

    private ?string $id = null;

    private function __construct()
    {
    }

    /**
     * Create a new TableBuilder with column definitions
     *
     * @param ColumnDefinition ...$columns
     */
    public static function fromColumns(ColumnDefinition ...$columns): self
    {
        $builder = new self();
        $builder->columns = $columns;

        return $builder;
    }

    /**
     * Add a single row to the table
     * Values must match the column order and types
     */
    public function addRow(mixed ...$values): self
    {
        $this->validateRowData($values);
        $this->rows[] = TableRowData::fromValues($this->columns, ...$values);

        return $this;
    }

    /**
     * Add multiple rows using a callback to extract values
     *
     * @param array $data
     * @param callable $extractor Function that takes a data item and returns an array of values
     */
    public function addRows(array $data, callable $extractor): self
    {
        foreach ($data as $item) {
            $values = $extractor($item);
            $this->addRow(...$values);
        }

        return $this;
    }

    /**
     * Add multiple rows from array data where each row is an array
     */
    public function addRowsFromArrays(array $rows): self
    {
        foreach ($rows as $rowData) {
            if (! is_array($rowData)) {
                throw new \InvalidArgumentException('Each row must be an array of values');
            }
            $this->addRow(...$rowData);
        }

        return $this;
    }

    /**
     * Set CSS class for the table
     */
    public function withCssClass(string $cssClass): self
    {
        $this->cssClass = $cssClass;

        return $this;
    }

    /**
     * Set table options (striped, bordered, etc.)
     */
    public function withOptions(TableOptions $options): self
    {
        $this->options = $options;

        return $this;
    }

    /**
     * Set table ID
     */
    public function withId(string $id): self
    {
        $this->id = $id;

        return $this;
    }

    /**
     * Build the final Table object
     */
    public function build(): Table
    {
        // Convert ColumnDefinitions to TableColumns
        $tableColumns = array_map(
            fn (ColumnDefinition $col) => $col->toTableColumn(),
            $this->columns
        );

        // Convert TableRowData to TableRows
        $tableRows = array_map(
            fn (TableRowData $rowData) => $rowData->toTableRow($tableColumns),
            $this->rows
        );

        return new Table(
            columns: $tableColumns,
            rows: $tableRows,
            cssClass: $this->cssClass,
            options: $this->options,
            id: $this->id
        );
    }

    /**
     * Validate that row data matches column expectations
     */
    private function validateRowData(array $values): void
    {
        $columnCount = count($this->columns);
        $valueCount = count($values);

        if ($valueCount !== $columnCount) {
            throw new \InvalidArgumentException(
                "Row value count ({$valueCount}) does not match column count ({$columnCount})"
            );
        }

        // Additional type validation can be added here based on column definitions
        foreach ($this->columns as $index => $column) {
            $value = $values[$index];
            $column->validateValue($value);
        }
    }
}
