<?php

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

namespace Icinga\Module\Icingadb\View;

use Icinga\Module\Icingadb\Common\HostStates;
use Icinga\Module\Icingadb\Common\Icons;
use Icinga\Module\Icingadb\Common\ServiceStates;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\Service;
use Icinga\Module\Icingadb\Util\PerfDataSet;
use Icinga\Module\Icingadb\Util\PluginOutput;
use Icinga\Module\Icingadb\Widget\CheckAttempt;
use Icinga\Module\Icingadb\Widget\IconImage;
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
use Icinga\Module\Icingadb\Widget\StateChange;
use ipl\Html\Attributes;
use ipl\Html\Html;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlElement;
use ipl\Html\HtmlString;
use ipl\Html\Text;
use ipl\Html\ValidHtml;
use ipl\I18n\Translation;
use ipl\Web\Common\ItemRenderer;
use ipl\Web\Layout\ItemLayout;
use ipl\Web\Widget\EmptyState;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\StateBall;
use ipl\Web\Widget\TimeSince;

/**
 * @template Item of Host|Service
 *
 * @implements ItemRenderer<Item>
 */
abstract class BaseHostAndServiceRenderer implements ItemRenderer
{
    use Translation;

    /**
     * Create subject for the given item
     *
     * @param Item $item The item to create subject for
     *
     * @param string $layout The name of the layout
     *
     * @return ValidHtml
     */
    abstract protected function createSubject($item, string $layout): ValidHtml;

    public function assembleAttributes($item, Attributes $attributes, string $layout): void
    {
        if ($item->state->is_overdue) {
            $attributes->get('class')->addValue('overdue');
        }
    }

    public function assembleVisual($item, HtmlDocument $visual, string $layout): void
    {
        if ($layout === 'header') {
            if ($item->state->state_type === 'soft') {
                $stateType = 'soft_state';
                $previousStateType = 'previous_soft_state';

                if ($item->state->previous_soft_state === 0) {
                    $previousStateType = 'hard_state';
                }
            } else {
                $stateType = 'hard_state';
                $previousStateType = 'previous_hard_state';

                if ($item->state->hard_state === $item->state->previous_hard_state) {
                    $previousStateType = 'previous_soft_state';
                }
            }

            if ($item instanceof Host) {
                $state = HostStates::text($item->state->$stateType);
                $previousState = HostStates::text($item->state->$previousStateType);
            } else {
                $state = ServiceStates::text($item->state->$stateType);
                $previousState = ServiceStates::text($item->state->$previousStateType);
            }

            $stateChange = new StateChange($state, $previousState);
            if ($stateType === 'soft_state') {
                $stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
            }

            if ($previousStateType === 'previous_soft_state') {
                $stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
                if ($stateType === 'soft_state') {
                    $visual->getAttributes()->add('class', 'small-state-change');
                }
            }

            $stateChange->setIcon($this->getStateBallIcon($item));
            $stateChange->setHandled(
                $item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable)
            );

            $visual->addHtml($stateChange);

            return;
        }

        $ballSize = $layout === 'minimal' ? StateBall::SIZE_BIG : StateBall::SIZE_LARGE;

        $stateBall = new StateBall($item->state->getStateText(), $ballSize);
        $stateBall->add($this->getStateBallIcon($item));
        if ($item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable)) {
            $stateBall->getAttributes()->add('class', 'handled');
        }

        $visual->addHtml($stateBall);
        if ($layout !== 'minimal' && $item->state->state_type === 'soft') {
            $visual->addHtml(
                new CheckAttempt((int) $item->state->check_attempt, (int) $item->max_check_attempts)
            );
        }
    }

    public function assembleCaption($item, HtmlDocument $caption, string $layout): void
    {
        if ($item->state->soft_state === null && $item->state->output === null) {
            $caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.')));
        } else {
            if (empty($item->state->output)) {
                $pluginOutput = new EmptyState($this->translate('Output unavailable.'));
            } else {
                $pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($item));
            }

            $caption->addHtml($pluginOutput);
        }
    }

    public function assembleTitle($item, HtmlDocument $title, string $layout): void
    {
        $title->addHtml(Html::sprintf(
            $this->translate('%s is %s', '<hostname> is <state-text>'),
            $this->createSubject($item, $layout),
            Html::tag('span', ['class' => 'state-text'], $item->state->getStateTextTranslated())
        ));

        if (isset($item->state->affects_children) && $item->state->affects_children) {
            $total = (int) $item->total_children;

            $title->addHtml(new HtmlElement(
                'span',
                Attributes::create([
                    'class' => 'affected-objects',
                    'title' => sprintf(
                        $this->translatePlural(
                            '%d affected object',
                            'Up to %d affected objects',
                            $total
                        ),
                        $total
                    )
                ]),
                new Icon(Icons::UNREACHABLE),
                Text::create($total)
            ));
        }
    }

    public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void
    {
        if ($item->state->is_overdue) {
            $since = new TimeSince($item->state->next_update->getTimestamp());
            $since->prepend($this->translate('Overdue') . ' ');
            $since->prependHtml(new Icon(Icons::WARNING));

            $info->addHtml($since);
        } elseif ($item->state->last_state_change !== null && $item->state->last_state_change->getTimestamp() > 0) {
            $info->addHtml(new TimeSince($item->state->last_state_change->getTimestamp()));
        }
    }

    public function assembleFooter($item, HtmlDocument $footer, string $layout): void
    {
        $pieChartLimit = 5;
        $statusIcons = new HtmlElement('div', Attributes::create(['class' => 'status-icons']));
        $isService = $item instanceof Service;

        if (
            ($isService && $item->state->last_comment->service_id === $item->id)
            || $item->state->last_comment->host_id === $item->id
        ) {
            $comment = $item->state->last_comment;

            if ($isService) {
                $comment->service = $item;
            } else {
                $comment->host = $item;
            }

            $commentItem = new ItemLayout(
                $comment,
                (new CommentRenderer())
                    ->setTicketLinkDisabled()
                    ->setNoObjectLink()
                    ->setNoSubjectLink()
            );

            $statusIcons->addHtml(
                new HtmlElement(
                    'div',
                    Attributes::create(['class' => 'comment-wrapper']),
                    new HtmlElement(
                        'div',
                        $commentItem->getAttributes()->add('class', 'comment-popup'),
                        $commentItem
                    ),
                    (new Icon('comments', ['class' => 'comment-icon']))
                )
            );
        }

        if ($item->state->is_acknowledged) {
            $title = $isService
                ? sprintf(
                    $this->translate('Service "%s" on "%s" is acknowledged'),
                    $item->display_name,
                    $item->host->display_name
                )
                : sprintf($this->translate('Host "%s" is acknowledged'), $item->display_name);

            $statusIcons->addHtml(new Icon(Icons::IS_ACKNOWLEDGED, ['title' => $title]));
        }

        if ($item->state->in_downtime) {
            if ($isService) {
                $message = $item->state->is_handled
                        ? $this->translate('Service "%s" on "%s" is handled by Downtime')
                        : $this->translate('Service "%s" on "%s" is in Downtime');

                $title = sprintf($message, $item->display_name, $item->host->display_name);
            } else {
                $message = $item->state->is_handled
                        ? $this->translate('Host "%s" is handled by Downtime')
                        : $this->translate('Host "%s" is in Downtime');

                $title = sprintf($message, $item->display_name);
            }

            $statusIcons->addHtml(new Icon(Icons::IN_DOWNTIME, ['title' => $title]));
        }

        if ($item->state->is_flapping) {
            $title = $isService
                ? sprintf(
                    $this->translate('Service "%s" on "%s" is in flapping state'),
                    $item->display_name,
                    $item->host->display_name
                )
                : sprintf($this->translate('Host "%s" is in flapping state'), $item->display_name);

            $statusIcons->addHtml(new Icon(Icons::IS_FLAPPING, ['title' => $title]));
        }

        if (! $item->state->is_reachable) {
            $title = $isService
                ? sprintf(
                    $this->translate('Service "%s" on "%s" is unreachable'),
                    $item->display_name,
                    $item->host->display_name
                )
                : sprintf($this->translate('Host "%s" is unreachable'), $item->display_name);

            $statusIcons->addHtml(new Icon(Icons::HOST_DOWN, ['title' => $title]));
        }

        if (! $item->notifications_enabled) {
            $title = $isService
                ? sprintf(
                    $this->translate('Service "%s" on "%s" has notifications disabled'),
                    $item->display_name,
                    $item->host->display_name
                )
                : sprintf($this->translate('Host "%s" has notifications disabled'), $item->display_name);

            $statusIcons->addHtml(new Icon(Icons::NOTIFICATIONS_DISABLED, ['title' => $title]));
        }

        if (! $item->active_checks_enabled) {
            $title = $isService
                ? sprintf(
                    $this->translate('Service "%s" on "%s" has active checks disabled'),
                    $item->display_name,
                    $item->host->display_name
                )
                : sprintf($this->translate('Host "%s" has active checks disabled'), $item->display_name);

            $statusIcons->addHtml(new Icon(Icons::ACTIVE_CHECKS_DISABLED, ['title' => $title]));
        }

        $performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data']));
        if ($item->state->performance_data) {
            $pieChartData = PerfDataSet::fromString($item->state->normalized_performance_data)->asArray();

            $pies = [];
            foreach ($pieChartData as $i => $perfdata) {
                if ($perfdata->isVisualizable()) {
                    $pies[] = $perfdata->asInlinePie()->render();
                }

                // Check if number of visualizable pie charts is larger than $PIE_CHART_LIMIT
                if (count($pies) > $pieChartLimit) {
                    break;
                }
            }

            $maxVisiblePies = $pieChartLimit - 2;
            $numOfPies = count($pies);
            foreach ($pies as $i => $pie) {
                if (
                    // Show max. 5 elements: if there are more than 5, show 4 + `…`
                    $i > $maxVisiblePies && $numOfPies > $pieChartLimit
                ) {
                    $performanceData->addHtml(new HtmlElement('span', null, Text::create('…')));
                    break;
                }

                $performanceData->addHtml(HtmlString::create($pie));
            }
        }

        if (! $statusIcons->isEmpty()) {
            $footer->addHtml($statusIcons);
        }

        if (! $performanceData->isEmpty()) {
            $footer->addHtml($performanceData);
        }
    }

    public function assemble($item, string $name, HtmlDocument $element, string $layout): bool
    {
        if ($name === 'icon-image') {
            if (isset($item->icon_image->icon_image)) {
                $element->addHtml(new IconImage($item->icon_image->icon_image, $item->icon_image_alt));
            }

            return true;
        }

        return false;
    }

    protected function getStateBallIcon(Host|Service $item): ?Icon
    {
        $icon = $item->state->getIcon();

        if ($icon === null) {
            if (! $item->notifications_enabled) {
                $icon = new Icon(Icons::NOTIFICATIONS_DISABLED, [
                    'title' => sprintf(
                        '%s (%s)',
                        strtoupper($item->state->getStateTextTranslated()),
                        $this->translate('has notifications disabled')
                    )
                ]);
            } elseif (! $item->active_checks_enabled) {
                $icon = new Icon(Icons::ACTIVE_CHECKS_DISABLED, [
                    'title' => sprintf(
                        '%s (%s)',
                        strtoupper($item->state->getStateTextTranslated()),
                        $this->translate('has active checks disabled')
                    )
                ]);
            }
        }

        return $icon;
    }
}
