<?php

// SPDX-FileCopyrightText: 2022 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\Icingadb\Widget\ItemTable;

use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Form;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
use ipl\Html\HtmlString;
use ipl\Html\Text;
use ipl\Html\ValidHtml;
use ipl\I18n\Translation;
use ipl\Orm\Common\SortUtil;
use ipl\Orm\Query;
use ipl\Web\Control\SortControl;
use ipl\Web\Widget\EmptyStateBar;
use ipl\Web\Widget\Icon;

/** @todo Figure out what this might (should) have in common with the new ItemTable implementation */
abstract class StateItemTable extends BaseHtmlElement
{
    use Translation;

    protected $baseAttributes = [
        'class' => 'state-item-table'
    ];

    /** @var array<string, string> The columns to render */
    protected $columns;

    /** @var iterable The datasource */
    protected $data;

    /** @var string The sort rules */
    protected $sort;

    protected $tag = 'table';

    /** @var ?ValidHtml Message to show if the list is empty */
    protected $emptyStateMessage;

    /**
     * Create a new item table
     *
     * @param iterable $data Datasource of the table
     * @param array<string, string> $columns The columns to render, keys are labels
     */
    public function __construct(iterable $data, array $columns)
    {
        $this->data = $data;
        $this->columns = array_flip($columns);

        $this->addAttributes($this->baseAttributes);

        $this->init();
    }

    /**
     * Initialize the item table
     *
     * If you want to adjust the item table after construction, override this method.
     */
    protected function init()
    {
    }

    /**
     * Get message to show if the list is empty
     *
     * @return ValidHtml
     */
    public function getEmptyStateMessage(): ValidHtml
    {
        if ($this->emptyStateMessage === null) {
            return new Text($this->translate('No items found.'));
        }

        return $this->emptyStateMessage;
    }

    /**
     * Set message to show if the list is empty
     *
     * @param mixed $message If empty, the default message is used
     *
     * @return $this
     */
    public function setEmptyStateMessage($message): self
    {
        if (empty($message)) {
            $this->emptyStateMessage = null;
        } else {
            $this->emptyStateMessage = Html::wantHtml($message);
        }

        return $this;
    }

    /**
     * Get the columns being rendered
     *
     * @return array<string, string>
     */
    public function getColumns(): array
    {
        return $this->columns;
    }

    /**
     * Set sort rules (as returned by {@see SortControl::getSort()})
     *
     * @param ?string $sort
     *
     * @return $this
     */
    public function setSort(?string $sort): self
    {
        $this->sort = $sort;

        return $this;
    }

    abstract protected function getItemClass(): string;

    abstract protected function getVisualColumn(): string;

    protected function getVisualLabel()
    {
        return new Icon('heartbeat', ['title' => $this->translate('Severity')]);
    }

    protected function assembleColumnHeader(BaseHtmlElement $header, string $name, $label): void
    {
        $sortRules = [];
        if ($this->sort !== null) {
            $sortRules = SortUtil::createOrderBy($this->sort);
        }

        $active = false;
        $sortDirection = null;
        foreach ($sortRules as $rule) {
            if ($rule[0] === $name) {
                $sortDirection = $rule[1];
                $active = true;
                break;
            }
        }

        if ($sortDirection === 'desc') {
            $value = "$name asc";
        } else {
            $value = "$name desc";
        }

        $icon = 'sort';
        if ($active) {
            $icon = $sortDirection === 'desc' ? 'sort-up' : 'sort-down';
        }

        $form = new Form();
        $form->setAttribute('method', 'GET');

        $button = $form->createElement('button', 'sort', [
            'value' => $value,
            'type'  => 'submit',
            'title' => is_string($label) ? $label : null,
            'class' => $active ? 'active' : null
        ]);
        $button->addHtml(
            Html::tag(
                'span',
                null,
                // With &nbsp; to have the height sized the same as the others
                $label ?? HtmlString::create('&nbsp;')
            ),
            new Icon($icon)
        );
        $form->addElement($button);

        $header->add($form);

        switch (true) {
            case substr($name, -7) === '.output':
            case substr($name, -12) === '.long_output':
                $header->getAttributes()->add('class', 'has-plugin-output');
                break;
            case substr($name, -22) === '.icon_image.icon_image':
                $header->getAttributes()->add('class', 'has-icon-images');
                break;
            case substr($name, -17) === '.performance_data':
            case substr($name, -28) === '.normalized_performance_data':
                $header->getAttributes()->add('class', 'has-performance-data');
                break;
        }
    }

    protected function assemble()
    {
        $itemClass = $this->getItemClass();

        $headerRow = new HtmlElement('tr');

        $visualCell = new HtmlElement('th', Attributes::create(['class' => 'has-visual']));
        $this->assembleColumnHeader($visualCell, $this->getVisualColumn(), $this->getVisualLabel());
        $headerRow->addHtml($visualCell);

        foreach ($this->columns as $name => $label) {
            $headerCell = new HtmlElement('th');
            $this->assembleColumnHeader($headerCell, $name, is_int($label) ? $name : $label);
            $headerRow->addHtml($headerCell);
        }

        $this->addHtml(new HtmlElement('thead', null, $headerRow));

        $body = new HtmlElement('tbody', Attributes::create(['data-base-target' => '_next']));
        foreach ($this->data as $item) {
            $body->addHtml(new $itemClass($item, $this));
        }

        if ($body->isEmpty()) {
            $body->addHtml(new HtmlElement(
                'tr',
                null,
                new HtmlElement(
                    'td',
                    Attributes::create(['colspan' => count($this->columns) + 1]),
                    new EmptyStateBar($this->getEmptyStateMessage())
                )
            ));
        }

        $this->addHtml($body);
    }

    /**
     * Enrich the given list of column names with appropriate labels
     *
     * @param Query $query
     * @param array $columns
     *
     * @return array
     */
    public static function applyColumnMetaData(Query $query, array $columns): array
    {
        $newColumns = [];
        foreach ($columns as $columnPath) {
            $label = $query->getResolver()->getColumnDefinition($columnPath)->getLabel();
            $newColumns[$label ?? $columnPath] = $columnPath;
        }

        return $newColumns;
    }
}
