<?php

namespace YahnisElsts\AdminMenuEditor\RevisedUiDev;

use YahnisElsts\AdminMenuEditor\Customizable\Builders\ElementBuilderFactory;
use YahnisElsts\AdminMenuEditor\Customizable\Controls\ControlFlow\ForEachBlock;
use YahnisElsts\AdminMenuEditor\Customizable\Controls\ControlFlow\WithBlock;
use YahnisElsts\AdminMenuEditor\Customizable\Controls\InterfaceStructure;

use YahnisElsts\AdminMenuEditor\Customizable\Rendering\FormTableRenderer;
use YahnisElsts\AdminMenuEditor\Customizable\Schemas\{Enum, SchemaFactory, Struct};
use YahnisElsts\AdminMenuEditor\Customizable\RevisedUiPack;
use YahnisElsts\AdminMenuEditor\Customizable\Settings\WithSchema\SettingWithSchema;
use YahnisElsts\AdminMenuEditor\Customizable\Storage\AbstractSettingsDictionary;
use YahnisElsts\AdminMenuEditor\Customizable\Storage\LazyArrayStorage;
use YahnisElsts\AdminMenuEditor\Tweaks\Core\ameAdminCssTweak;
use YahnisElsts\AdminMenuEditor\Tweaks\Core\ameTweakManager;
use YahnisElsts\AdminMenuEditor\WireDSL\Wire;
use YahnisElsts\AdminMenuEditor\WireDSL\WireContext;
use YahnisElsts\WpDependencyWrapper\v1\ScriptDependency;

class AmeRevisedUiDevModule extends \ameModule {
	protected $tabSlug = 'revised-ui-dev';
	protected $tabTitle = 'Revised UI';

	private AbstractSettingsDictionary $settings;

	public function __construct($menuEditor, $moduleDir = null) {
		parent::__construct($menuEditor, $moduleDir);

		$this->settings = new RevisedUiTestSettings();
	}

	protected function outputMainTemplate(): bool {
		?>
		<h2>Knockout Container</h2>
		<div id="ws-ame-revised-ui-container" data-bind="event: {
	        'adminMenuEditor:deleteUrlBlock':       onDeleteUrlBlock,
	        'adminMenuEditor:toggleUrlBlockEditor': onToggleUrlBlockEditor,
	        'adminMenuEditor:addUrlBlock':          onAddUrlBlock,
	        'adminMenuEditor:addCssSnippet':        onAddCssSnippet,
	        'adminMenuEditor:editObject':           onEditTweak,
	        'adminMenuEditor:deleteObject':         onDeleteTweak
	     }">
			<div class="ame-sticky-top-bar">
				<div class="ame-sticky-top-bar-flex-content">
					<?php require AME_ROOT_DIR . '/modules/actor-selector/actor-selector-template.php'; ?>
				</div>
			</div>

			<div data-bind="component: mainUi.rootComponent"></div>

			<?php
			$this->outputTemplate('dialogs');
			?>
		</div>
		<?php

		echo '<h2>FormTableRenderer Version</h2>';
		$structure = $this->getInterfaceStructure();
		$renderer = new FormTableRenderer();
		$renderer->renderStructure($structure, new WireContext());
		echo '<hr>';

		return true;
	}

	private function getInterfaceStructure(): InterfaceStructure {
		$s = $this->settings;
		$b = $s->elementBuilder();

		$structure = $b->structure(
			$b->section(
				'Block access by URL',
				new ForEachBlock(
					$s->findSetting('sampleBlocksByUrl'),
					[
						$b->group(
							'',
							$b->group(
								'',
								$b->actorFeatureCheckbox(Wire::bind('$item.enabledForActor'))
									->label(Wire::bind('$item.title'))
									->classes('ame-gcg-auto-margin-after'),
								$b->eventButton('Edit')
									->eventName('adminMenuEditor:toggleUrlBlockEditor')
									->iconClass('dashicons dashicons-arrow-right')
									->classes('ame-rev-compact-event-button', 'ame-toggle-url-block-editor'),
								$b->eventButton('Delete')
									->eventName('adminMenuEditor:deleteUrlBlock')
									->iconClass('dashicons dashicons-trash')
									->classes('ame-rev-compact-event-button', 'ame-delete-url-block')
									->eventData(Wire::resolutionOf(Wire::bind('$item')))
							)->fullWidth()->classes('ame-gcg-layout-row'),
							$b->group(
								'',
								...$this->getBlockEditPanelContents($b)
							)
								->fullWidth()
								->classes('ame-rui-block-by-url-edit-panel', 'hidden'),
						)
							->fullWidth()
							->classes('ame-rui-block-by-url-container'),
					]
				),
				$b->eventButton('Add Blocked URL')
					->eventName('adminMenuEditor:addUrlBlock')
					->classes('ame-add-url-block')
			),
			$b->section(
				'Sample Records',
				new ForEachBlock(
					$s->findSetting('sampleRecords'),
					[
						$b->group(
							'Group Title',
							$b->actorFeatureCheckbox($s->ref('$item.enabledForActor'))
								->slot(
									'actions',
									$b->eventButton()
										->eventName('adminMenuEditor:editObject')
										->iconClass('dashicons dashicons-edit')
										->kind('link'),
									$b->eventButton()
										->eventName('adminMenuEditor:deleteObject')
										->iconClass('dashicons dashicons-trash')
										->classes('ame-delete-action-button')
										->kind('link'),
								)
								->label(Wire::bind('$item.label')),
							$b->textBox(Wire::bind('$item.label')),
							$b->codeEditor(Wire::bind('$item.code'))->cssMode()
						),
					]
				),
			)
		);

		//Add the tweak module's interface structure to the test various UI components.
		$tweakManager = $this->getTweakModule();
		$struct = $tweakManager->getInterfaceStructure();
		foreach ($struct->getAsSections() as $child) {
			$structure->add($child);
		}

		return $structure->build();
	}

	private function getTweakModule(): ameTweakManager {
		$modules = $this->menuEditor->get_loaded_modules();
		if ( !isset($modules['tweaks']) ) {
			throw new \LogicException(
				'Tweak module is not loaded. The KO Customizable Dev module requires the Tweak module to be active to display tweak UI prototypes.'
			);
		}

		if ( !($modules['tweaks'] instanceof ameTweakManager) ) {
			throw new \LogicException(
				'Tweak module is not an instance of ameTweakManager. This should never happen.'
			);
		}

		return $modules['tweaks'];
	}

	private function getBlockEditPanelContents(ElementBuilderFactory $b): array {
		$s = $this->settings;
		$blocksSetting = $s->findSetting('sampleBlocksByUrl');
		if ( !($blocksSetting instanceof SettingWithSchema) ) {
			throw new \LogicException('Sample blocks setting not found.');
		}
		/** @noinspection PhpPossiblePolymorphicInvocationInspection */
		$blockItemSchema = $blocksSetting->getSchema()->getItemSchema();
		if ( !($blockItemSchema instanceof Struct) ) {
			throw new \LogicException('Sample blocks item schema is not a struct. This should never happen.');
		}
		$matchTypeSchema = $blockItemSchema->getChildSchema('matchType');
		if ( !($matchTypeSchema instanceof Enum) ) {
			throw new \LogicException('Match type schema is not an enum. This should never happen.');
		}

		//Generate tooltip content for the match type options.
		$matchTypeTooltips = [];
		foreach ($matchTypeSchema->getEnumValues() as $value) {
			$details = $matchTypeSchema->getValueDetails($value);
			if ( !empty($details['description']) ) {
				$matchTypeTooltips[$value] = sprintf(
					'<strong>%s</strong> - %s',
					esc_html($details['label']),
					esc_html($details['description'])
				);
			}
		}
		$matchTypeTooltipString = implode('<br><br>', $matchTypeTooltips);

		return [
			$b->textBox(Wire::bind('$item.title'))
				->inputClasses('large-text')
				->asGroup('Title'),
			$b->textArea(Wire::bind('$item.urlList'))
				->params(['rows' => 3, 'useTextInputBinding' => true])
				->asGroup('URLs'),
			$b->select(Wire::bind('$item.matchType'))->tooltip($matchTypeTooltipString)->asGroup('Match type'),
		];
	}

	private function getAddBlockUiPack(): RevisedUiPack {
		$s = new UrlBlockDialogSettings($this->settings);

		$b = $s->elementBuilder();
		$structure = $b->structure(
			new WithBlock(
				$s->findSetting('tempUrlBlock'),
				[
					$b->group(
						'',
						...$this->getBlockEditPanelContents($b))->classes('ame-gcg-dialog-panel-style')->build(),
				]
			)
		);

		return new RevisedUiPack($structure->build());
	}

	private function getEditAdminCssUiPack(): RevisedUiPack {
		$s = new AdminCssDialogSettings();
		$b = $s->elementBuilder();

		$structure = $b->structure(
			new WithBlock(
				$s->findSetting('tempAdminCssSnippet'),
				[
					$b->group(
						'',
						$b->textBox(Wire::bind('$item.label'))->inputClasses('large-text')->asGroup('Name'),
						$b->codeEditor(Wire::bind('$item.css'))->cssMode()->asGroup('CSS code')
					)->classes('ame-gcg-dialog-panel-style')->build(),
				]
			)
		);

		return new RevisedUiPack($structure->build());
	}

	public function enqueueTabScripts() {
		parent::enqueueTabScripts();

		$baseDeps = $this->menuEditor->get_base_dependencies();

		$mainUiPack = new RevisedUiPack($this->getInterfaceStructure());

		//Include the settings used to store the user-defined tweak suffix.
		$tweakModule = $this->getTweakModule();
		$tweakSettings = $tweakModule->loadSettings();
		$lastSuffixSetting = $tweakSettings->getSetting('lastUserTweakSuffix');
		$mainUiPack->addExtraSetting($lastSuffixSetting);

		//For user-defined tweaks, we also need their "typeId" setting and other metadata.
		//These are not directly referenced in the UI structure.
		$userDefinedTweaks = $tweakSettings->get('userDefinedTweaks', []);
		foreach (array_keys($userDefinedTweaks) as $tweakId) {
			foreach (['typeId', 'isUserDefined', 'label'] as $field) {
				$settingId = "tweaks.$tweakId.$field";
				$setting = $tweakSettings->getSetting($settingId);
				$mainUiPack->addExtraSetting($setting);
			}
		}

		$blockUiPack = $this->getAddBlockUiPack();
		$editAdminCssUiPack = $this->getEditAdminCssUiPack();

		$mainUiPack->enqueueDependencies();
		$blockUiPack->enqueueDependencies();
		$editAdminCssUiPack->enqueueDependencies();

		ScriptDependency::create(plugins_url('revised-ui-dev.js', __FILE__))
			->addDependencies('jquery', $baseDeps()->koPackage()->qtip())
			->setType('module')
			->setInFooter()
			->addJsVariable('wsRevisedUiData', [
				'mainUi'         => $mainUiPack->serializeForJs(),
				'addBlockUi'     => $blockUiPack->serializeForJs(),
				'editAdminCssUi' => $editAdminCssUiPack->serializeForJs(),
			])
			->enqueue();
	}

	public function enqueueTabStyles() {
		parent::enqueueTabStyles();

		$componentStyleRelativePath = 'customizables/revised-ui/styles/components.css';
		$componentStylePath = AME_ROOT_DIR . '/' . $componentStyleRelativePath;

		wp_enqueue_style(
			'ame-revised-ui-components',
			plugins_url($componentStyleRelativePath, $this->menuEditor->plugin_file),
			[],
			filemtime($componentStylePath)
		);

		$this->enqueueLocalStyle('ame-revised-ui-dev', 'revised-ui-dev.css');
	}
}

class RevisedUiTestSettings extends AbstractSettingsDictionary {
	public function __construct() {
		parent::__construct(
			new LazyArrayStorage(),
			'ame_revised_ui_test_settings--'
		);

		//Add some sample snippets to the settings for testing rendering.
		$this->set('sampleRecords', [
			'foo' => [
				'label'           => 'Sample snippet 1',
				'enabledForActor' => [
					'role:administrator' => true,
					'role:subscriber'    => false,
				],
				'code'            => 'body { color: red; }',
			],
			'bar' => [
				'label'           => 'Increase font size',
				'enabledForActor' => [
					'role:administrator' => true,
					'role:subscriber'    => true,
				],
				'code'            => 'body { font-size: 18px; }',
			],
		]);

		//Add some sample blocks by URL for testing.
		$this->set('sampleBlocksByUrl', [
			[
				'title'           => 'Block admin pages for subscribers',
				'urlList'         => '[ame-wp-admin]',
				'matchType'       => 'startsWith',
				'enabledForActor' => [
					'role:administrator' => false,
					'role:subscriber'    => true,
				],
			],
			[
				'title'           => 'Block some settings pages for regular administrators',
				'urlList'         => implode("\n", [
					'/wp-admin/options-general.php',
					'/wp-admin/options-writing.php',
					'/wp-admin/options-reading.php',
				]),
				'matchType'       => 'full',
				'enabledForActor' => [
					'role:administrator' => true,
					'user:admin'         => false, //Allow a specific admin user to access these pages.
				],
			],
			[
				'title'           => 'Block "Add Page" for editors (test)',
				'urlList'         => 'http://localhost/ame-tests/wp-admin/post-new.php?post_type=page',
				'matchType'       => 'full',
				'enabledForActor' => [
					'role:editor' => true,
				],
			],
		]);
	}

	protected function createDefaults(): array {
		return [];
	}

	protected function createSettings(): array {
		$f = $this->settingFactory();
		$s = new SchemaFactory();

		return $f->buildSettings([
			'sampleRecords'     => $s->record(
				$s->string()->max(100),
				$s->struct([
					'label'           => $s->string('Label')->min(3)->max(200),
					'enabledForActor' => $s->actorAccess(),
					'code'            => $s->string('CSS Snippet')->min(10)
						->settingParams(['description' => 'This description was set via the schema of the "code" field.']),
				]),
				'CSS Snippets'
			),
			'sampleBlocksByUrl' => $s->arr($this->getUrlBlockSchema($s), 'Blocks by URL'),
		]);
	}

	public function getUrlBlockSchema(SchemaFactory $s): Struct {
		return $s->struct([
			'title'           => $s->string('Title')->min(1)->max(200),
			//Newline-separated list of URLs.
			'urlList'         => $s->string('URLs')->min(3)->max(1024)->settingParams([
				'description' => 'Enter one URL per line. You can use the <code>[ame-wp-admin]</code> shortcode to represent the base URL of the WordPress admin dashboard.',
			]),
			'matchType'       => $s->enum(
				['auto', 'startsWith', 'contains', 'full'],
				'Match type'
			)
				->defaultValue('auto')
				->describeValue(
					'auto',
					'Auto',
					'Full match for inputs that look like fully qualified URLs, or "contains" for everything else.'
				)
				->describeValue(
					'startsWith',
					'Starts with',
					'The URL must start with one of the specified strings.'
				)
				->describeValue(
					'contains',
					'Contains',
					'The URL must contain one of the specified strings.'
				)
				->describeValue(
					'full',
					'Full',
					'Everything including protocol, domain, path, and query parameters must match. Additional parameters in the URL are allowed.'
				),
			'enabledForActor' => $s->actorAccess(),
		]);
	}
}

class UrlBlockDialogSettings extends AbstractSettingsDictionary {
	private RevisedUiTestSettings $realSettings;

	public function __construct(RevisedUiTestSettings $realSettings) {
		parent::__construct(new LazyArrayStorage());
		$this->realSettings = $realSettings;
	}

	protected function createDefaults(): array {
		return [];
	}

	protected function createSettings(): array {
		$f = $this->settingFactory();
		$s = new SchemaFactory();
		return $f->buildSettings([
			'tempUrlBlock' => $this->realSettings->getUrlBlockSchema($s),
		]);
	}
}

class AdminCssDialogSettings extends AbstractSettingsDictionary {
	public function __construct() {
		parent::__construct(new LazyArrayStorage());
	}

	protected function createDefaults(): array {
		return [];
	}

	protected function createSettings(): array {
		$f = $this->settingFactory();
		$s = new SchemaFactory();
		return $f->buildSettings([
			'tempAdminCssSnippet' => $s->struct(array_merge(
				ameTweakManager::createBaseTweakSettingsFields($s),
				ameAdminCssTweak::createSettingsSchemaFields($s)
			)),
		]);
	}
}