<?php

namespace SHServ\Models;

use \SHServ\Middleware\ControlScripts;
use \SHServ\Entities\Script;

class Scripts extends \SHServ\Middleware\Model {

	public function get_scopes_list(): Array {
		$instances = app() -> control_scripts_instances;
		$scopes = [];

		foreach($instances as $name => $scope) {
			$ref = new \ReflectionClass($scope);
			$path_info = pathinfo($ref -> getFileName());

			$scopes[] = [
				"name" => $name,
				"filename" => $path_info["basename"],
				"path" => $path_info["dirname"],
				"state" => $this -> script_state("scope", $name) ? "enabled" : "disabled"
			];
		}

		return $scopes;
	}

	public function remove_scope(String $name): Bool {
		$scopes = app() -> control_scripts_instances;

		if(!isset($scopes[$name])) {
			return false;
		}

		$ref = new \ReflectionClass($scopes[$name]);
		$filepath = $ref -> getFileName();

		if(!file_exists($filepath)) {
			return false;
		}

		$script = new Script($name);
		if(!$script -> remove()) {
			return false;
		}

		if(!@unlink($filepath)) {
			return false;
		}

		return true;
	}

	public function script_state(String $type, String $uniq_name): Bool {
		$result = $this -> thin_builder() -> select(
			Script::$table_name,
			[ "state" ],
			[ ["type", "=", $type], "AND", [ "uniq_name", '=', $uniq_name ] ]
		);

		return !$result ? false : ($result[0]["state"] == "enabled");
	}

	public function select_scripts_by_aliases_types(String $type, Array $uniq_names): Array {
		if(empty($uniq_names)) {
			return [];
		}

		$result = $this -> thin_builder() -> select(
			Script::$table_name,
			Script::get_fields(),
			[ ["type", "=", $type], "AND", [ "uniq_name", 'IN', $uniq_names ] ]
		);

		return !$result ? [] : array_map(function($item){
			return new Script($item["id"], $item);
		}, $result);
	}

	public function set_script_state(String $type, String $uniq_name, Bool $state): String | Bool {
		// Тут нужно проверить существует ли скрипт с таким именем
		
		if($type == "scope") {
			$scopes_names = array_map(function($scope) {
				return $scope["name"];
			}, $this -> get_scopes_list());

			if(!in_array($uniq_name, $scopes_names)) {
				return "scope_not_found"; // Err. Scope not exists
			}
		} elseif($type == "regular") {
			$regular_scripts_names = array_map(function($regular_script) {
				return $regular_script["alias"];
			}, $this -> regular_scripts_list());

			if(!in_array($uniq_name, $regular_scripts_names)) {
				return "regular_script_not_found"; // Err. Regular script not exists
			}
		} elseif($type == "action") {
			$action_scripts_names = array_map(function($action_script) {
				return $action_script["alias"];
			}, $this -> actions_scripts_list());

			if(!in_array($uniq_name, $action_scripts_names)) {
				return "action_script_not_found"; // Err. Action script not exists
			}
		} else {
			return "invalid_type"; // Err of type
		}

		$result = $this -> thin_builder() -> select(
			Script::$table_name,
			Script::get_fields(),
			[ ["type", "=", $type], "AND", [ "uniq_name", '=', $uniq_name ] ]
		);

		if(!$result) {
			return $this -> thin_builder() -> insert(
				Script::$table_name,
				[
					"uniq_name" => $uniq_name,
					"type" => $type,
					"state" => $state ? "enabled" : "disabled",
					"create_at" => date("Y-m-d H:i:s")
				]
			) ? true : "undefined_error";
		}

		// update
		$script = new Script($result[0]["id"], $result[0]);
		$script -> state = $state ? "enabled" : "disabled";
		return $script -> update() ? true : false;
	}

	public function enable_script(String $type, String $uniq_name): String | Bool {
		return $this -> set_script_state($type, $uniq_name, true);
	}

	public function disable_script(String $type, String $uniq_name): String | Bool {
		return $this -> set_script_state($type, $uniq_name, false);
	}

	public function regular_scripts_list(): Array {
		return $this -> get_scripts_list( "regular", ControlScripts::get_regular_scripts() );
	}

	public function actions_scripts_list(): Array {
		return $this -> get_scripts_list( "action", ControlScripts::get_actions_scripts() );
	}

	public function prepare_script_to_view(String $type, Array $script_from_control, $script_entity = null, Bool $skip_state_callback = false): Array {
		$prepared = [
			"alias" => $script_from_control["attributes"]["alias"],
			"type" => $type,
			"icon" => $script_from_control["attributes"]["icon"] ?? "",
			"code" => $script_from_control["code"] ?? "",
			"name" => $script_from_control["attributes"]["name"],
			"display_name" => $script_from_control["attributes"]["name"],
			"description" => $script_from_control["attributes"]["description"],
			"filename" => $script_from_control["attributes"]["filename"],
			"path" => $script_from_control["attributes"]["path"],
			"created_by" => $script_from_control["attributes"]["author"],
			"scope" => isset($script_from_control["attributes"]["classname"]) ? substr(strrchr($script_from_control["attributes"]["classname"], "\\"), 1) : null,
			"danger_level" => $script_from_control["attributes"]["danger_level"] ?? "safe",
		];

		// indicators: явный state_callback из Scope
		if(!$skip_state_callback && isset($script_from_control["state_callback"]) && is_callable($script_from_control["state_callback"])) {
			try {
				$indicators = ($script_from_control["state_callback"])();
				if(is_array($indicators)) {
					$prepared["indicators"] = $indicators;
				}
			} catch(\Throwable $e) {
				// Игнорируем ошибки state_callback, не ломаем ответ API
			}
		}

		// params_schema: runtime (registry) имеет приоритет над DB
		if(isset($script_from_control["params_schema"])) {
			$prepared["params_schema"] = $script_from_control["params_schema"];
		} elseif($script_entity && $script_entity -> params_schema) {
			$prepared["params_schema"] = json_decode($script_entity -> params_schema, true);
		} else {
			$prepared["params_schema"] = null;
		}

		if($script_entity) {
			$prepared = array_merge($prepared, $script_entity -> to_array());
		} else {
			$prepared["state"] = (!$script_entity or $script_entity -> state != "enabled") ? "disabled" : "enabled";
		}

		return $prepared;
	}

	public function action_script_by_alias(String $alias): ?Array {
		$scripts = ControlScripts::get_actions_scripts();
		if(!isset($scripts[$alias])) {
			return null;
		}
		$script_data = $scripts[$alias];
		$entities = $this -> select_scripts_by_aliases_types("action", [$alias]);
		$entity = $entities[0] ?? null;
		return $this -> prepare_script_to_view("action", $script_data, $entity);
	}

	public function get_scripts_list(String $type, Array $scripts, Bool $skip_state_callback = true): Array {
		$scripts_entities = $this -> select_scripts_by_aliases_types($type, array_keys($scripts));

		$data = [];

		foreach($scripts as $alias => $script_data) {
			$script_entity = array_values(array_filter($scripts_entities, function($item) use($alias) {
				return $item -> uniq_name == $alias;
			}));

			$data[] = $this -> prepare_script_to_view($type, $script_data, $script_entity[0] ?? null, $skip_state_callback);

			// Lazy sync: обновить params_schema в БД если runtime schema изменилась
			if(isset($script_data["params_schema"])) {
				$this -> sync_params_schema_to_db($type, $alias, $script_data["params_schema"]);
			}
		}

		return $data;
	}

	protected function sync_params_schema_to_db(String $type, String $uniq_name, ?Array $schema): void {
		$result = $this -> thin_builder() -> select(
			Script::$table_name,
			["id", "params_schema"],
			[["type", "=", $type], "AND", ["uniq_name", '=', $uniq_name]]
		);

		if(!$result) {
			return;
		}

		$encoded = $schema ? json_encode($schema, JSON_UNESCAPED_UNICODE) : null;
		if($result[0]["params_schema"] !== $encoded) {
			$this -> thin_builder() -> update(
				Script::$table_name,
				["params_schema" => $encoded],
				[["id", "=", $result[0]["id"]]]
			);
		}
	}

	public function scope_is_exists(String $name): Bool {
		$instances = app() -> control_scripts_instances;

		return isset($instances[$name]);
	}
}