<?php declare(strict_types = 0);
/*
** 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 Zabbix\Widgets\CWidgetField;
use Zabbix\Widgets\CWidgetForm;
use Zabbix\Widgets\Fields\CWidgetFieldCheckBox;
use Zabbix\Widgets\Fields\CWidgetFieldCheckBoxList;
use Zabbix\Widgets\Fields\CWidgetFieldColor;
use Zabbix\Widgets\Fields\CWidgetFieldIntegerBox;
use Zabbix\Widgets\Fields\CWidgetFieldRadioButtonList;
use Zabbix\Widgets\Fields\CWidgetFieldSelect;

/**
 * PRO-teaser helper (Zabbix 6.4-7.4 / manifest_version 2 tree).
 *
 * OWNED by the CI library (tools/pro-teaser/ProTeaser.v2.php) and injected into
 * every widget build by build-edition.py - widgets must NOT copy or edit it.
 * Driven entirely by the `teaser_fields` declaration in features.yaml, which
 * build-edition.py renders to services/_pro_teaser.json. The widget config form
 * only makes three generic calls:
 *
 *   ProTeaser::addFields($this);                 // in WidgetForm::addFields()
 *   ProTeaser::renderRows($form, $data['fields']); // in widget.edit.php
 *   $form->addJavaScript(ProTeaser::initJs());     // in widget.edit.php
 *
 * In PRO the fields render enabled; in FREE they render as the same native
 * Zabbix controls but disabled (greyed) with a padlock + upgrade link
 * (window.initmaxProTeaser, from pro-teaser.js). teaser_fields is the SINGLE
 * source of the PRO field definitions for both editions.
 */
final class ProTeaser {

	private static ?array $spec = null;

	public static function spec(): array {
		if (self::$spec === null) {
			$file = __DIR__ . '/_pro_teaser.json';
			self::$spec = is_file($file)
				? (json_decode((string) file_get_contents($file), true) ?: [])
				: [];
		}
		return self::$spec;
	}

	public static function edition(): string {
		return self::spec()['edition'] ?? 'pro';
	}

	public static function isFree(): bool {
		return self::edition() !== 'pro';
	}

	/** @return string[] names of the PRO fields, in declared order */
	public static function fieldNames(): array {
		return array_keys(self::spec()['fields'] ?? []);
	}

	/**
	 * Register the PRO fields on the widget form. Enabled in PRO, disabled
	 * (greyed) with neutral defaults in FREE.
	 */
	public static function addFields(CWidgetForm $form): void {
		foreach (self::fields() as $field) {
			$form->addField($field);
		}
	}

	/** @return CWidgetField[] the configured PRO field objects (name-keyed) */
	public static function fields(): array {
		$free = self::isFree();
		$disabled = ($free && defined(CWidgetField::class . '::FLAG_DISABLED'))
			? CWidgetField::FLAG_DISABLED : 0;

		$out = [];
		foreach (self::spec()['fields'] ?? [] as $name => $f) {
			$field = self::makeField((string) $name, $f);
			if ($field === null) {
				continue;
			}

			$default = ($free && array_key_exists('free_default', $f))
				? $f['free_default'] : ($f['default'] ?? null);
			if ($default !== null) {
				$field->setDefault($default);
			}
			if ($disabled !== 0) {
				$field->setFlags($disabled);
			}
			$out[$name] = $field;
		}
		return $out;
	}

	/** Render the PRO field rows (control + "?" help hint) into the form view. */
	public static function renderRows(\CWidgetFormView $form, array $fields): void {
		foreach (self::spec()['fields'] ?? [] as $name => $f) {
			if (!isset($fields[$name])) {
				continue;
			}
			$type = (string) ($f['type'] ?? '');
			$view = self::makeView($type, $fields[$name]);
			if ($view === null) {
				continue;
			}
			self::attachHint($view, $f);
			$form->addField($view);
		}
	}

	/**
	 * JS that wires window.initmaxProTeaser (logo + locks + footer). The
	 * library source (pro-teaser.js, shipped next to this file by the CI build)
	 * is inlined ahead of the call - its `window.x = window.x || new class`
	 * guard makes that idempotent and avoids any asset load-order race.
	 */
	public static function initJs(string $form_id = '', string $logo_free = 'initmax-free',
			string $logo_pro = 'initmax-pro'): string {
		$spec = self::spec();
		$free = self::isFree();

		$hints = [];
		foreach ($spec['fields'] ?? [] as $name => $f) {
			$hints[$name] = self::tr($f['hint_key'] ?? '');
		}

		$config = [
			'logo_class' => $free ? $logo_free : $logo_pro,
			'disable_fields' => $free ? array_keys($spec['fields'] ?? []) : [],
			'hints' => $hints,
			'upgrade_label' => self::tr($spec['upgrade_label_key'] ?? 'form.pro_teaser.upgrade'),
			'upgrade_url' => $spec['upgrade_url'] ?? '#',
			'feedback_label' => self::trOr('form.pro_teaser.feedback', 'Give us feedback'),
			'feedback_url' => $spec['feedback_url'] ?? '',
		];

		$lib = '';
		$lib_file = __DIR__ . '/pro-teaser.js';
		if (is_file($lib_file)) {
			$lib = (string) file_get_contents($lib_file) . "\n";
		}

		return $lib . 'window.initmaxProTeaser.apply(' . json_encode($form_id) . ', '
			. json_encode($config) . ');';
	}

	private static function makeField(string $name, array $f): ?CWidgetField {
		$label = self::tr($f['label_key'] ?? '');
		switch ($f['type'] ?? '') {
			case 'checkbox':
				// 7.0+: label = null (no left column) + caption = text (inline
				// next to the box); renderRows draws it + hint in one cell.
				// 6.4: no CFormField/caption-inline path - keep the label in
				// the left column (the classic 2-arg form) so the field still
				// has its text. Must match renderRows' version guard.
				// Label in the LEFT column, like every other field (and like the
				// widget's own core checkboxes) - a caption sitting inline to the
				// right of the box would be the only row in the form whose text
				// does not line up with the rest.
				return new CWidgetFieldCheckBox($name, $label);
			case 'integer':
				return new CWidgetFieldIntegerBox($name, $label, (int) ($f['min'] ?? 0), (int) ($f['max'] ?? 100));
			case 'radio':
				return new CWidgetFieldRadioButtonList($name, $label, self::options($f));
			case 'select':
				return new CWidgetFieldSelect($name, $label, self::options($f));
			case 'color':
				return new CWidgetFieldColor($name, $label);
			case 'checkboxlist':
				return new CWidgetFieldCheckBoxList($name, $label, self::options($f));
			default:
				return null;
		}
	}

	private static function makeView(string $type, CWidgetField $field) {
		switch ($type) {
			case 'checkbox':
				return new \CWidgetFieldCheckBoxView($field);
			case 'integer':
				return new \CWidgetFieldIntegerBoxView($field);
			case 'radio':
				return new \CWidgetFieldRadioButtonListView($field);
			case 'select':
				return new \CWidgetFieldSelectView($field);
			case 'color':
				return new \CWidgetFieldColorView($field);
			case 'checkboxlist':
				return new \CWidgetFieldCheckBoxListView($field);
			default:
				return null;
		}
	}

	/** The "?" help icon for a field, or null when it declares no hint. */
	private static function hintIcon(array $f) {
		$key = $f['hint_key'] ?? '';
		return $key === '' ? null : \makeHelpIcon([self::tr($key)]);
	}

	private static function attachHint($view, array $f): void {
		$icon = self::hintIcon($f);
		if ($icon === null) {
			return;
		}
		if (method_exists($view, 'setFieldHint')) {
			$view->setFieldHint($icon);
		}
		elseif (method_exists($view, 'setHint')) {
			$view->setHint($icon);
		}
	}

	/** @return array<int|string,string> option value => translated label */
	private static function options(array $f): array {
		$out = [];
		foreach ($f['options'] ?? [] as $value => $label_key) {
			$out[$value] = self::tr((string) $label_key);
		}
		return $out;
	}

	private static function tr(string $key): string {
		return $key === '' ? '' : WidgetTranslator::translate($key);
	}

	/** tr() with a literal fallback for keys older widgets don't translate yet. */
	private static function trOr(string $key, string $fallback): string {
		$out = self::tr($key);

		return ($out === '' || $out === $key) ? $fallback : $out;
	}
}
