<?php
/*
** Copyright (C) 2021-2026 initMAX s.r.o.
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/


namespace Modules\uxMAX\Services;

use CCheckBox;
use CCol;
use CColHeader;
use CDiv;
use CForm;
use CInput;
use CLabel;
use CRow;
use CScriptTag;
use CSelect;
use CSpan;
use CSubmit;
use CTable;
use CTabView;
use CTextBox;
use CUrl;
use Modules\uxMAX\Includes\Compat;

/**
 * The per-user "My uxMAX" page - written ONCE, rendered by both module trees.
 *
 * Same reasoning as ConfigFormPage: the two views that reference this class are
 * two-line files, so 6.0/6.2 and 6.4+ cannot end up with different pages.
 */
final class UserPrefsPage {

    /**
     * @param \CView $view
     * @param array  $data  override (int), colortags (array), admin_colortags (array)
     */
    public static function show($view, array $data): void {
        $match_options = [
            Preferences::MATCH_BEGIN => ModuleTranslator::translate('form.color-tags.match.starts-with'),
            Preferences::MATCH_CONTAIN => ModuleTranslator::translate('form.color-tags.match.contains'),
            Preferences::MATCH_END => ModuleTranslator::translate('form.color-tags.match.ends-with')
        ];

        $grid = (new CDiv([
            self::section(
                ModuleTranslator::translate('form.user.section.global-rules'),
                (new CDiv(self::globalTable($data['admin_colortags'], $match_options)))
                    ->addClass(ZBX_STYLE_TABLE_FORMS_SEPARATOR)
                    ->addClass('uxmax-colortag-rules')
            ),
            self::section(
                ModuleTranslator::translate('form.user.section.my-overrides'),
                self::overrideSection($data, $match_options)
            )
        ]))->addClass('uxmax-form-container');

        $form = (new CForm('post', (new CUrl('zabbix.php'))->getUrl()))
            ->addClass('uxmax-form');

        foreach (Compat::csrfVars('mod.uxmax.userprefs.update') as $name => $value) {
            $form->addVar($name, $value);
        }

        $form
            ->addVar('action', 'mod.uxmax.userprefs.update')
            ->addItem(getMessages())
            ->addItem(
                (new CTabView())
                    ->addTab('uxmax', ModuleTranslator::translate('tabs.general'), $grid)
                    ->setFooter(makeFormFooter(
                        new CSubmit('update', ModuleTranslator::translate('form.button.update'))
                    ))
            );

        Compat::page(ModuleTranslator::translate('menu.uxmax-user-preferences'))
            ->addItem($form)
            ->show();

        // A CScriptTag wraps its body in (function($){...})(jQuery), so nothing
        // declared here leaks into the page scope - which is what we want for a
        // handler bundle, and why it must not try to export anything.
        (new CScriptTag(self::clientScript(count($data['colortags']))))->show();
    }

    private static function section(string $title, $body): CDiv {
        return (new CDiv([
            (new CDiv($title))->addClass('uxmax-section-title'),
            (new CDiv($body))->addClass('uxmax-section-body')
        ]))->addClass('uxmax-section');
    }

    /**
     * Read-only mirror of the admin colour-tag table - same structure as the
     * editable override table below, just disabled and without Add / Remove.
     * Disabled inputs are not submitted, and the names are dummies the update
     * controller does not validate.
     */
    private static function globalTable(array $rules, array $match_options): CTable {
        $table = (new CTable())->setHeader([
            new CColHeader(ModuleTranslator::translate('form.color-tags.table.match')),
            new CColHeader(ModuleTranslator::translate('form.color-tags.table.string')),
            '',
            ''
        ]);

        if (!$rules) {
            $table->addRow(
                (new CCol(new CSpan(ModuleTranslator::translate('form.user.no-global-rules'))))
                    ->setColSpan(4)
                    ->addStyle('text-align:center;color:#888;font-style:italic;padding:8px;')
            );

            return $table;
        }

        foreach ($rules as $rule) {
            $table->addRow(new CRow([
                (new CSelect('_global_match'))
                    ->removeId()
                    ->addOptions(CSelect::createOptionsFromArray($match_options))
                    ->setValue((int) ($rule['match'] ?? Preferences::MATCH_BEGIN))
                    ->setReadonly()
                    ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH),
                (new CTextBox('_global_value', $rule['value'] ?? ''))
                    ->removeId()
                    ->setEnabled(false)
                    ->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH),
                new CLabel([
                    (new CInput('color', '_global_color', $rule['color'] ?? '#000000'))
                        ->removeId()
                        ->setEnabled(false)
                ]),
                new CCol('')
            ]));
        }

        return $table;
    }

    /**
     * @param array|null $rule  null renders the #{...} row template.
     */
    private static function overrideRow(string $row_num_token, $rule, array $match_options): CRow {
        $select = (new CSelect("colortags[{$row_num_token}][match]"))
            ->removeId()
            ->addOptions(CSelect::createOptionsFromArray($match_options))
            ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);

        if (is_array($rule) && array_key_exists('match', $rule)) {
            $select->setValue($rule['match']);
        }

        return (new CRow([
            $select,
            (new CTextBox("colortags[{$row_num_token}][value]",
                is_array($rule) ? ($rule['value'] ?? '') : '#{value}'))
                ->removeId()
                ->setAttribute('placeholder', ModuleTranslator::translate('form.color-tags.value'))
                ->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH),
            new CLabel([
                (new CInput('color', "colortags[{$row_num_token}][color]",
                    is_array($rule) ? ($rule['color'] ?? '#000000') : '#{color}'))->removeId()
            ]),
            Compat::buttonLink(ModuleTranslator::translate('form.color-tags.button.remove'))
                ->addClass('element-table-remove')
        ]))->addClass('form_row');
    }

    private static function overrideSection(array $data, array $match_options): CDiv {
        $table = (new CTable())
            ->setHeader([
                new CColHeader(ModuleTranslator::translate('form.color-tags.table.match')),
                new CColHeader(ModuleTranslator::translate('form.color-tags.table.string')),
                '',
                ''
            ])
            ->setFooter(
                (new CCol(
                    Compat::buttonLink(ModuleTranslator::translate('form.color-tags.button.add'))
                        ->addClass('element-table-add')
                ))->setColSpan(4)
            );

        // Pre-rendered server-side so the table is complete on first paint.
        foreach ($data['colortags'] as $i => $rule) {
            $table->addRow(self::overrideRow((string) $i, $rule, $match_options));
        }

        return new CDiv([
            new CDiv([
                (new CCheckBox('override', 1))->setChecked((int) $data['override']),
                (new CLabel(ModuleTranslator::translate('form.user.override-colortags'), 'override'))
                    ->addStyle('margin-left:6px;')
            ]),
            (new CDiv(new CSpan(ModuleTranslator::translate('form.user.collision-info'))))
                ->addStyle('margin:10px 0 8px 0;color:#555;font-size:12px;line-height:1.5;'),
            (new CDiv([
                $table,
                Compat::templateTag('colortag-row-tmpl',
                    self::overrideRow('#{rowNum}', null, $match_options))
            ]))
                ->setId('uxmax-colortag-table')
                ->addClass(ZBX_STYLE_TABLE_FORMS_SEPARATOR)
                ->addClass('uxmax-colortag-rules')
        ]);
    }

    /**
     * Add / Remove handling for the user's own override table. Deliberately not
     * jQuery's dynamicRows: this table has no dataCallback needs and the rows
     * are already rendered server-side.
     */
    private static function clientScript(int $next_row_num): string {
        return <<<JS
            let nextRowNum = {$next_row_num};

            function escapeHtmlAttr(s) {
                return String(s)
                    .replace(/&/g, '&amp;').replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
            }

            function renderRow(rule) {
                const tplEl = document.getElementById('colortag-row-tmpl');
                if (!tplEl) return null;

                let html = tplEl.innerHTML;

                html = html
                    .replace(/#\{rowNum\}/g, String(nextRowNum))
                    .replace(/#\{value\}/g, escapeHtmlAttr(rule.value || ''))
                    .replace(/#\{color\}/g, rule.color || '#000000');

                nextRowNum++;
                return html;
            }

            function appendRow(rule) {
                const html = renderRow(rule);
                if (!html) return null;

                const \$footerRow = \$('#uxmax-colortag-table .element-table-add').closest('tr');
                const \$row = \$(html);

                if (\$footerRow.length) {
                    \$row.insertBefore(\$footerRow);
                }
                else {
                    \$('#uxmax-colortag-table table tbody').append(\$row);
                }

                const matchEl = \$row.find('z-select[name\$="[match]"]')[0]
                    || \$row.find('select[name\$="[match]"]')[0];
                if (matchEl && rule.match !== undefined && rule.match !== '') {
                    matchEl.value = String(rule.match);
                }
                return \$row;
            }

            \$(document).on('click', '#uxmax-colortag-table .element-table-add', function (e) {
                e.preventDefault();
                appendRow({match: '', value: '', color: '#000000'});
            });

            \$(document).on('click', '#uxmax-colortag-table .element-table-remove', function (e) {
                e.preventDefault();
                \$(this).closest('tr.form_row').remove();
            });
            JS;
    }
}
