<?php

namespace SHServ\Middleware;

use \SHServ\Models\Devices;
use \SHServ\Models\Scripts;
use \SHServ\Helpers\DeviceScriptsHelper;
use \SHServ\Entities\Device;

abstract class ControlScripts {
	protected $devices_model;
	protected $device_scripts_helper;
	protected $modes_context;

	abstract public function register_sync_map(): void;
	abstract protected function register_events_handlers(): void;
	abstract protected function register_regular_scripts(): void;
	abstract protected function register_actions_scripts(): void;

	public function __construct() {
		list($scope_folder, $scope_name) = explode("\\", str_replace("\\Scopes", "", str_replace("SHServ", "", static::class)));

		if($scope_folder != "ControlScripts" or (new Scripts()) -> script_state("scope", $scope_name)) {
			$this -> register_sync_map();
			$this -> register_events_handlers();
			$this -> register_regular_scripts();
			$this -> register_actions_scripts();
		}
	}

	protected function registry(): ScriptsRegistry {
		return app() -> scripts_registry;
	}

	protected function add_event_handler(String $event_name, callable $handler): void {
		events() -> handler("app:{$event_name}", function(Array $params) use ($handler, $event_name) {
			try {
				$handler($params["device"], $params["data"]);
			} catch(\Exception $e) {
				logging() -> error('php:ControlScripts', 'Event handler failed', [
					'event'   => $event_name,
					'scope'   => static::class,
					'message' => $e -> getMessage(),
					'trace'   => $e -> getTraceAsString(),
				]);
			}
		});
	}

	protected function devices(): Devices {
		if(!$this -> devices_model) {
			$this -> devices_model = new Devices();
		}

		return $this -> devices_model;
	}

	protected function helper(): DeviceScriptsHelper {
		if(!$this -> device_scripts_helper) {
			$this -> device_scripts_helper = new DeviceScriptsHelper($this -> devices());
		}

		return $this -> device_scripts_helper;
	}

	protected function mode(): \SHServ\Middleware\ModesContext {
		if(!$this -> modes_context) {
			$this -> modes_context = new \SHServ\Middleware\ModesContext();
		}

		return $this -> modes_context;
	}

	protected function get_scope_name(): String {
		$ref = new \ReflectionClass(static::class);
		return $ref -> getShortName();
	}

	protected function delay_action(String $timer_alias, String $action_alias, Array $params, int $delay_seconds): bool {
		$timers = new \SHServ\Models\Timers();
		return $timers -> create_timer($timer_alias, $this -> get_scope_name(), "action", $action_alias, $params, time() + $delay_seconds);
	}

	protected function delay_event(String $timer_alias, String $event_name, Array $params, int $delay_seconds): bool {
		$timers = new \SHServ\Models\Timers();
		return $timers -> create_timer($timer_alias, $this -> get_scope_name(), "event", $event_name, $params, time() + $delay_seconds);
	}

	protected function delay_regular(String $timer_alias, String $regular_alias, int $delay_seconds): bool {
		$timers = new \SHServ\Models\Timers();
		return $timers -> create_timer($timer_alias, $this -> get_scope_name(), "regular", $regular_alias, [], time() + $delay_seconds);
	}

	protected function cancel_timer(String $timer_alias): bool {
		$timers = new \SHServ\Models\Timers();
		return $timers -> cancel_timer($timer_alias, $this -> get_scope_name());
	}

	protected function add_regular_script(Array $attributes, callable $script): bool {
		$reg = $this -> registry();
		if(!isset($attributes["alias"])) {
			return false;
		}

		if(isset($reg ->regular[$attributes["alias"]])) {
			return false;
		}

		$ref = new \ReflectionClass(static::class);
		$path_info = pathinfo($ref -> getFileName());

		$attributes["name"]        = $attributes["name"] ?? "unknown";
		$attributes["description"] = $attributes["description"] ?? "";
		$attributes["classname"]   = static::class;
		$attributes["path"]        = $path_info["dirname"];
		$attributes["filename"]    = $path_info["basename"];
		$attributes["author"]      = $attributes["author"] ?? "Unknown author";

		$reg ->regular[$attributes["alias"]] = [
			"attributes" => $attributes,
			"code"       => $this -> get_source_code($script),
			"script"     => $script
		];

		return true;
	}

	protected static function ensureRegistry(): \SHServ\Middleware\ScriptsRegistry {
		$app = app();
		if(!isset($app -> scripts_registry) || $app -> scripts_registry === null) {
			$app -> scripts_registry = new \SHServ\Middleware\ScriptsRegistry();
		}
		return $app -> scripts_registry;
	}

	public static function flush_statics(): void {
		self::ensureRegistry() -> flush();
	}

	public static function get_regular_scripts(): Array {
		return self::ensureRegistry() -> getRegularScripts();
	}

	public static function get_actions_scripts(): Array {
		return self::ensureRegistry() -> getActionsScripts();
	}

	public function add_action_script(Array $attributes, callable $script): bool {
		$reg = $this -> registry();
		if(!isset($attributes["alias"])) {
			return false;
		}

		if(isset($reg ->actions[$attributes["alias"]])) {
			return false;
		}

		$ref = new \ReflectionClass(static::class);
		$path_info = pathinfo($ref -> getFileName());

		$attributes["name"]        = $attributes["name"] ?? "unknown";
		$attributes["description"] = $attributes["description"] ?? "";
		$attributes["classname"]   = static::class;
		$attributes["path"]        = $path_info["dirname"];
		$attributes["filename"]    = $path_info["basename"];
		$attributes["author"]      = $attributes["author"] ?? "Unknown author";

		$dangerLevel = $attributes["danger_level"] ?? "safe";
		if(!in_array($dangerLevel, ["safe", "cautious", "dangerous"], true)) {
			$dangerLevel = "safe";
		}
		$attributes["danger_level"] = $dangerLevel;

		$reg ->actions[$attributes["alias"]] = [
			"attributes"     => $attributes,
			"code"           => $this -> get_source_code($script),
			"script"         => $script,
			"params_schema"  => $attributes["params_schema"] ?? null,
			"danger_level"   => $dangerLevel,
			"state_callback" => $attributes["state_callback"] ?? null,
		];

		return true;
	}

	public static function run_action_script(String $alias, Array $params): Array | null {
		$reg = app() -> scripts_registry;
		if(!isset($reg ->actions[$alias])) {
			return null;
		}

		$scripts_model = new Scripts();
		if(!$scripts_model -> script_state("action", $alias)) {
			return null;
		}

		logging() -> info('php:ControlScripts', 'Run action script', ['alias' => $alias]);

		$start_time = microtime(true);

		$result = $reg ->actions[$alias]["script"]($params);

		$exec_time = microtime(true) - $start_time;
		$exec_time = round($exec_time, 3);

		logging() -> debug('php:ControlScripts', 'Action script finished', ['alias' => $alias, 'exec_time' => $exec_time]);

		return [
			"result"    => $result,
			"exec_time" => "{$exec_time} seconds"
		];
	}

	public static function run_regular_script(String $alias): bool {
		$reg = app() -> scripts_registry;
		if(!isset($reg ->regular[$alias])) {
			return false;
		}

		$scripts_model = new Scripts();
		if(!$scripts_model -> script_state("regular", $alias)) {
			return false;
		}

		logging() -> info('php:ControlScripts', 'Run regular script', ['alias' => $alias]);

		try {
			$reg ->regular[$alias]["script"]();
		} catch(\Exception $e) {
			logging() -> error('php:ControlScripts', 'Regular script failed', ['alias' => $alias, 'message' => $e -> getMessage()]);
			return false;
		}

		return true;
	}

	protected function get_source_code($func): String {
		$ref_func = new \ReflectionFunction($func);
		$file_name = $ref_func -> getFileName();
		$start_line = $ref_func -> getStartLine();
		$end_line = $ref_func -> getEndLine();
		$lines = file($file_name);

		$code = implode(
			"",
			array_slice(
				$lines,
				$start_line - 1,
				$end_line - $start_line + 1
			)
		);

		return $code;
	}

	protected function add_sync_connection(Array $sync_connection) {
		$registry = app() -> scripts_registry;
		foreach($registry -> syncMap["connections"] as $existing) {
			if($existing === $sync_connection) {
				return;
			}
		}
		$registry -> syncMap["connections"][] = $sync_connection;
	}

	public function sync_map() {
		return app() -> scripts_registry -> getSyncMap();
	}

	// --- Button helpers ---

	protected function btn_on_online(String $alias, Array $muted = []): void {
		$this -> add_event_handler("button@{$alias}.online", function(Device $btns_block, Array $data) use ($muted) {
			$btns_block_api = $btns_block -> device_api();
			if($btns_block_api instanceof \SHServ\Tools\DeviceAPI\Button) {
				foreach($muted as $ch) {
					$btns_block_api -> set_channel_state("mute", $ch);
				}
			}

			$this -> helper() -> sync_btn_channels($this -> sync_map(), $btns_block -> alias);
		});
	}

	protected function set_btns_click_handlers($alias): void {
		$self = $this;
		$buttons = $this -> helper() -> prepare_sync_map_by_alias($this -> sync_map(), $alias);

		foreach($buttons as $btn_channel => $entry) {
			if($entry[0]["type"] != "relay") {
				continue;
			}

			$relay_alias = $entry[0]["alias"];
			$relay_channel = $entry[0]["channel"];

			$this -> add_event_handler("button@{$alias}({$btn_channel}).press", function(Device $btns_block, Array $data) use ($self, $btn_channel, $relay_alias, $relay_channel) {
				$btns_block_api = $btns_block -> device_api();
				$relay_api = $this -> devices() -> by_alias($relay_alias) -> device_api();

				if($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay and $btns_block_api instanceof \SHServ\Tools\DeviceAPI\Button) {
					$relay_api -> toggle_channel($relay_channel);
					$this -> helper() -> sync_relay_to_btns($this -> sync_map(), $relay_alias);
				}
			});
		}
	}

	/**
	 * Автоматическая регистрация обработчиков для блоков кнопок.
	 * Массив: alias => ["mute" => [каналы]]
	 * Для каждого alias регистрирует btn_on.online (mute + sync) и click handlers (toggle relay + sync).
	 */
	protected function auto_button_bindings(Array $config): void {
		foreach($config as $alias => $options) {
			$muted = $options["mute"] ?? [];
			$this -> btn_on_online($alias, $muted);
			$this -> set_btns_click_handlers($alias);
		}
	}

	// --- Group helpers ---

	protected function group_set_state(Array $targets, bool $state): Array {
		return $this -> helper() -> group_set_state($targets, $state, $this -> sync_map());
	}

	protected function group_toggle(Array $targets): Array {
		return $this -> helper() -> group_toggle($targets, $this -> sync_map());
	}

	/**
	 * Зарегистрировать action script для группового toggle нескольких реле.
	 * Pairs: строка (alias) или [alias, channel].
	 */
	protected function add_group_toggle_action(String $alias, String $name, Array $pairs, Array $extra_attrs = []): bool {
		$attrs = array_merge([
			"alias" => $alias,
			"name"  => $name,
		], $extra_attrs);

		return $this -> add_action_script($attrs, function($params) use ($pairs) {
			return $this -> group_toggle($pairs);
		});
	}

	/**
	 * Зарегистрировать action script для toggle канала реле.
	 * После toggle автоматически синхронизирует индикаторы кнопок через sync_map.
	 */
	protected function add_toggle_action(String $alias, String $name, String $relay_alias, int $channel = 0, Array $extra_attrs = []): bool {
		$attrs = array_merge([
			"alias" => $alias,
			"name"  => $name,
		], $extra_attrs);

		return $this -> add_action_script($attrs, function($params) use ($relay_alias, $channel) {
			$relay_api = $this -> devices() -> by_alias($relay_alias) -> device_api();
			$result = false;
			if($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay) {
				$result = $relay_api -> toggle_channel($channel);
				$this -> helper() -> sync_relay_to_btns($this -> sync_map(), $relay_alias);
			}
			return ["result" => $result];
		});
	}

	/**
	 * Зарегистрировать action script для включения (on) канала реле.
	 * Для многоканальных реле укажите $channel, для одноканальных оставьте null.
	 */
	protected function add_on_action(String $alias, String $name, String $relay_alias, ?int $channel = null, Array $extra_attrs = []): bool {
		$attrs = array_merge([
			"alias" => $alias,
			"name"  => $name,
		], $extra_attrs);

		return $this -> add_action_script($attrs, function($params) use ($relay_alias, $channel) {
			$relay_api = $this -> devices() -> by_alias($relay_alias) -> device_api();
			$result = false;
			if($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay) {
				$result = $channel === null
					? $relay_api -> set_state(true)
					: $relay_api -> set_channel_state(true, $channel);
				$this -> helper() -> sync_relay_to_btns($this -> sync_map(), $relay_alias);
			}
			return ["result" => $result];
		});
	}

	/**
	 * Зарегистрировать action script для выключения (off) канала реле.
	 * Для многоканальных реле укажите $channel, для одноканальных оставьте null.
	 */
	protected function add_off_action(String $alias, String $name, String $relay_alias, ?int $channel = null, Array $extra_attrs = []): bool {
		$attrs = array_merge([
			"alias" => $alias,
			"name"  => $name,
		], $extra_attrs);

		return $this -> add_action_script($attrs, function($params) use ($relay_alias, $channel) {
			$relay_api = $this -> devices() -> by_alias($relay_alias) -> device_api();
			$result = false;
			if($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay) {
				$result = $channel === null
					? $relay_api -> set_state(false)
					: $relay_api -> set_channel_state(false, $channel);
				$this -> helper() -> sync_relay_to_btns($this -> sync_map(), $relay_alias);
			}
			return ["result" => $result];
		});
	}

	/**
	 * Зарегистрировать action script для управления люком (open/close).
	 */
	protected function add_hatch_action(String $alias, String $name, String $hatch_alias, String $operation, int $percent = 100, Array $extra_attrs = []): bool {
		$attrs = array_merge([
			"alias" => $alias,
			"name"  => $name,
		], $extra_attrs);

		return $this -> add_action_script($attrs, function($params) use ($hatch_alias, $operation, $percent) {
			$hatch_api = $this -> devices() -> by_alias($hatch_alias) -> device_api();
			$result = false;
			if($hatch_api instanceof \SHServ\Tools\DeviceAPI\Hatch) {
				$result = $operation === "open"
					? $hatch_api -> open($percent)
					: $hatch_api -> close($percent);
			}
			return ["result" => $result];
		});
	}
}
