<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Menu_model extends CI_Model
{
	private const TABLE = 'tbl_menu';
	private const CACHE_VERSION = 'v3';

	/**
	 * Build the menu tree for the current user with caching.
	 */
	public function get_active_tree(bool $forceRefresh = false, bool $isClient = false): array
	{
		$cacheKey = $this->build_cache_key();
		$cached = (!$forceRefresh && $this->cache_ready()) ? $this->cache->get($cacheKey) : null;

		if (is_array($cached)) {
			return $cached;
		}
		$menus = $this->fetch_user_menus($isClient);
		$tree = $this->build_tree($menus);

		if ($this->cache_ready()) {
			$this->cache->save($cacheKey, $tree, 300); // cache 5 minutes
		}

		return $tree;
	}

	public function get_admin_list(): array
	{
		return $this->db
			->order_by('parent_id ASC, sort_order ASC')
			->get(self::TABLE)
			->result_array();
	}

	public function save(array $data, ?int $id = null): int
	{
		if ($id) {
			$this->db->where('id', $id)->update(self::TABLE, $data);
			return $id;
		}
		$this->db->insert(self::TABLE, $data);
		return (int) $this->db->insert_id();
	}

	public function delete(int $id): void
	{
		$this->db->delete(self::TABLE, ['id' => $id]);
	}

	// ---------------------------------------------------------------------
	// Internals adapted from legacy Menu library

	private function cache_ready(): bool
	{
		if (!isset($this->cache)) {
			$this->load->driver('cache', ['adapter' => 'file']);
		}
		return isset($this->cache) && method_exists($this->cache, 'get');
	}

	private function build_cache_key(): string
	{
		$userId = $this->session->userdata('user_id') ?? 'guest';
		$userType = $this->session->userdata('user_type') ?? '0';
		$designation = ((int) $userType === 2) ? '0' : ($this->session->userdata('designations_id') ?? '0');
		$current = md5(trim($this->uri->uri_string(), '/'));
		return "menu_tree_" . self::CACHE_VERSION . "_{$userId}_{$userType}_{$designation}_{$current}";
	}

	private function fetch_user_menus(bool $isClient = false): array
	{
		$userType = (int) ($this->session->userdata('user_type') ?? 0);
		$designationId = $this->session->userdata('designations_id');
		$userId = (int) ($this->session->userdata('user_id') ?? 0);

		// Client menu is isolated and always comes from:
		// tbl_client_menu + tbl_client_role(user_id)
		if ($userType === 2 || $isClient) {
			return $this->fetch_client_user_menus($userId);
		}

		$table = self::TABLE;
		$idField = $this->db->field_exists('menu_id', $table) ? 'menu_id' : 'id';
		$parentField = $this->db->field_exists('parent', $table) ? 'parent' : 'parent_id';
		$sortField = $this->db->field_exists('sort', $table) ? 'sort' : ($this->db->field_exists('sort_order', $table) ? 'sort_order' : null);
		$statusField = $this->db->field_exists('status', $table) ? 'status' : ($this->db->field_exists('enabled', $table) ? 'enabled' : null);

		if ($userType !== 1) {
			// Employee: join role table to fetch allowed menus.
			$query = $this->db->select('m.*');
			$query = $query
				->from('tbl_user_role ur')
				->join("{$table} m", "ur.menu_id = m.{$idField}", 'left')
				->where('ur.designations_id', $designationId);

			if ($statusField) {
				$query->where("m.{$statusField}", 1);
			}
			if ($sortField) {
				$query->order_by("m.{$parentField} ASC, m.{$sortField} ASC");
			}
			$result = $query->get()->result_array();
		} else {
			// Admin: all active menus ordered.
			$this->db->from($table);
			if ($statusField) {
				$this->db->where($statusField, 1);
			}
			if ($sortField) {
				$this->db->order_by("{$parentField} ASC, {$sortField} ASC");
			}
			$result = $this->db->get()->result_array();

			// Append module stubs (preserved from legacy implementation).
			$now = date('Y-m-d H:i:s');
			$module = [
				[
					$idField => '111122222222',
					'label' => 'modules',
					'link' => '',
					'icon' => 'fa fa-plug',
					$parentField => '0',
					$sortField ?? 'sort' => '1000',
					'status' => 1,
					'time' => $now,
				],
				[
					$idField => '1111222222221',
					'label' => 'my_modules',
					'link' => 'admin/my_module',
					'icon' => 'fa fa-bitbucket',
					$parentField => '111122222222',
					$sortField ?? 'sort' => '1',
					'status' => 1,
					'time' => $now,
				],
				[
					$idField => '1111222222222',
					'label' => 'available_modules',
					'link' => 'admin/my_module/available_modules',
					'icon' => 'fa fa-plane',
					$parentField => '111122222222',
					$sortField ?? 'sort' => '2',
					'status' => '1',
					'time' => $now,
				],
			];
			$result = array_merge($result, $module);
		}

		// Normalize column names to current view expectations.
		return array_map(function (array $row) use ($idField, $parentField, $sortField) {
			return [
				'id' => $row[$idField] ?? null,
				'parent_id' => $row[$parentField] ?? null,
				'sort_order' => $sortField ? ($row[$sortField] ?? 0) : 0,
				'label' => $row['label'] ?? ($row['name'] ?? ''),
				'route' => $row['route'] ?? ($row['link'] ?? ''),
				'icon' => $row['icon'] ?? '',
			];
		}, $result);
	}

	private function fetch_client_user_menus(int $userId): array
	{
		$table = 'tbl_client_menu';
		if (!$this->db->table_exists($table)) {
			return array();
		}

		$idField = $this->db->field_exists('menu_id', $table) ? 'menu_id' : 'id';
		$parentField = $this->db->field_exists('parent', $table) ? 'parent' : 'parent_id';
		$sortField = $this->db->field_exists('sort', $table) ? 'sort' : ($this->db->field_exists('sort_order', $table) ? 'sort_order' : null);
		// $statusField = $this->db->field_exists('status', $table) ? 'status' : ($this->db->field_exists('enabled', $table) ? 'enabled' : null);

		$query = $this->db
			->select('m.*')
			->from('tbl_client_role ur')
			->join("{$table} m", "ur.menu_id = m.{$idField}", 'left')
			->where('ur.user_id', $userId);
		if ($sortField) {
			$query->order_by("m.{$parentField} ASC, m.{$sortField} ASC");
		}
		$selectedRows = $query->get()->result_array();
		if (empty($selectedRows)) {
			return array();
		}

		// Keep tree valid: include ancestors + descendants for selected client menus.
		$selectedIds = array();
		foreach ($selectedRows as $row) {
			$menuId = (int) ($row[$idField] ?? 0);
			if (!empty($menuId)) {
				$selectedIds[$menuId] = true;
			}
		}

		$this->db->from($table);
		
		if ($sortField) {
			$this->db->order_by("{$parentField} ASC, {$sortField} ASC");
		}
		$allMenus = $this->db->get()->result_array();

		$byId = array();
		$childrenMap = array();
		foreach ($allMenus as $row) {
			$menuId = (int) ($row[$idField] ?? 0);
			$parentId = (int) ($row[$parentField] ?? 0);
			if (empty($menuId)) {
				continue;
			}
			$byId[$menuId] = $row;
			$childrenMap[$parentId][] = $menuId;
		}

		$expanded = $selectedIds;
		foreach (array_keys($selectedIds) as $menuId) {
			$parentId = isset($byId[$menuId]) ? (int) ($byId[$menuId][$parentField] ?? 0) : 0;
			while (!empty($parentId) && isset($byId[$parentId])) {
				if (isset($expanded[$parentId])) {
					break;
				}
				$expanded[$parentId] = true;
				$parentId = (int) ($byId[$parentId][$parentField] ?? 0);
			}

			$stack = $childrenMap[$menuId] ?? array();
			while (!empty($stack)) {
				$childId = (int) array_pop($stack);
				if (empty($childId) || isset($expanded[$childId])) {
					continue;
				}
				$expanded[$childId] = true;
				if (!empty($childrenMap[$childId])) {
					foreach ($childrenMap[$childId] as $nextId) {
						if (!isset($expanded[$nextId])) {
							$stack[] = (int) $nextId;
						}
					}
				}
			}
		}

		$filtered = array_values(array_filter($allMenus, function ($row) use ($expanded, $idField) {
			$menuId = (int) ($row[$idField] ?? 0);
			return !empty($menuId) && isset($expanded[$menuId]);
		}));

		return array_map(function (array $row) use ($idField, $parentField, $sortField) {
			return array(
				'id' => $row[$idField] ?? null,
				'parent_id' => $row[$parentField] ?? null,
				'sort_order' => $sortField ? ($row[$sortField] ?? 0) : 0,
				'label' => $row['label'] ?? ($row['name'] ?? ''),
				'route' => $row['route'] ?? ($row['link'] ?? ''),
				'icon' => $row['icon'] ?? '',
			);
		}, $filtered);
	}

	private function build_tree(array $menus): array
	{
		$current = trim($this->uri->uri_string(), '/');

		$tree = [];
		$byId = [];
		foreach ($menus as $menu) {
			if (!isset($menu['id'])) {
				continue;
			}
			$menu['children'] = [];
			$byId[$menu['id']] = $menu;

			// update icon to remove fa fa- prefix if exists
			// if (isset($menu['icon'])) {
			// 	$item['icon'] = str_replace('fa-', '', $item['icon'] ?? '');
			// 	$item['icon'] = str_replace('fa ', '', $item['icon']);
			// 	$item['icon'] = str_replace('-o', '', $item['icon']);
			// 	// update into datatable 
			// 	$this->db->where('menu_id', $menu['id'])->update(self::TABLE, ['icon' => $menu['icon']]);
			// }
		}

		foreach ($byId as $id => &$menu) {
			if (!empty($menu['parent_id']) && isset($byId[$menu['parent_id']])) {
				$byId[$menu['parent_id']]['children'][] = &$menu;
			} else {
				$tree[] = &$menu;
			}
		}

		$sortFn = function (&$items) use (&$sortFn) {
			usort($items, fn($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));
			foreach ($items as &$item) {
				if (!empty($item['children'])) {
					$sortFn($item['children']);
				}
			}
		};
		$sortFn($tree);

		$this->mark_active($tree, $this->best_active_route($tree, $current));

		return $tree;
	}

	private function best_active_route(array $items, string $current): string
	{
		$bestRoute = '';
		$bestLength = 0;
		foreach ($items as $item) {
			$route = trim($item['route'] ?? '', '/');
			if ($this->route_matches($current, $route) && strlen($route) > $bestLength) {
				$bestRoute = $route;
				$bestLength = strlen($route);
			}
			if (!empty($item['children'])) {
				$childRoute = $this->best_active_route($item['children'], $current);
				if (strlen($childRoute) > $bestLength) {
					$bestRoute = $childRoute;
					$bestLength = strlen($childRoute);
				}
			}
		}
		return $bestRoute;
	}

	private function route_matches(string $current, string $route): bool
	{
		return $route !== '' && ($current === $route || strpos($current, $route) === 0);
	}

	private function mark_active(array &$items, string $activeRoute): bool
	{
		$found = false;
		foreach ($items as &$item) {
			$route = trim($item['route'] ?? '', '/');
			$directHit = $route !== '' && $route === $activeRoute;
			$childHit = false;
			if (!empty($item['children'])) {
				$childHit = $this->mark_active($item['children'], $activeRoute);
			}
			$item['is_active'] = $directHit || $childHit;
			$item['is_open'] = $childHit;
			$found = $found || $item['is_active'];
		}
		return $found;
	}
}
