<?php

namespace GemBoards\Utils;

use GemBoards\Classes\Config;
use GemBoards\Classes\Exception\GemboardsInvalidArgumentException;
use GemBoards\Collections\RoleCollection;
use GemBoards\Entities\Role;
use GemBoards\utils\traits\Board;
use GemBoards\utils\traits\Bookmark;
use GemBoards\utils\traits\Card;
use GemBoards\Utils\Traits\DatetimeHelper;
use GemBoards\utils\traits\Email;
use GemBoards\utils\traits\Notification;
use GemBoards\utils\traits\Project;
use GemBoards\utils\traits\Template;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Helper {

	use DatetimeHelper, Card, Board, Project, Notification, Template, Bookmark, Email;

	const BOARDS_TABLE_NAME            = GEMBOARDS_PLUGIN_SLUG . '_boards';
	const BOARD_CARD_TABLE_NAME        = GEMBOARDS_PLUGIN_SLUG . '_cards';
	const CARD_ASSIGNEES_TABLE_NAME    = GEMBOARDS_PLUGIN_SLUG . '_card_assignees';
	const BOARD_COLUMN_TABLE_NAME      = GEMBOARDS_PLUGIN_SLUG . '_columns';
	const BOARD_ATTACHMENTS_TABLE_NAME = GEMBOARDS_PLUGIN_SLUG . '_task_attachments';
	const BOARD_CHECKLISTS_TABLE_NAME  = GEMBOARDS_PLUGIN_SLUG . '_task_checklists';
	const BOARDS_NOTIFICATION_TABLE    = GEMBOARDS_PLUGIN_SLUG . '_notifications';
	const BOARDS_NOTIFIED_USER_TABLE   = GEMBOARDS_PLUGIN_SLUG . '_notified_users';
	const USER_BOARD_IDS_META_KEY      = 'gemboards_user_board_ids';
	const BOARD_TASK_POST_TYPE         = 'gemboards_task';
	const PROJECTS_TABLE_NAME          = GEMBOARDS_PLUGIN_SLUG . '_projects';
	const FOLDERS_TABLE_NAME           = GEMBOARDS_PLUGIN_SLUG . '_folders';
	const BOOKMARKS_TABLE_NAME         = GEMBOARDS_PLUGIN_SLUG . '_bookmarks';

	const ROLES_TABLE_NAME = GEMBOARDS_PLUGIN_SLUG . '_roles';

	public static function is_valid_site_url( string $url ): bool {
		return str_starts_with( $url, get_option( 'siteurl' ) );
	}

	public static function config( string $key, $default = null ) {
		return Config::get( $key, $default );
	}

	public static function get_settings_url( string $tab = 'my-settings' ): string {
		return add_query_arg( [
			'tab' => $tab,
		], home_url( '/workspace/settings' ) );
	}

	public static function get_settings( $key, $default = null ) {
		global $gemboards_settings;

		$value = $gemboards_settings->{$key} ?? $default;
		$value = apply_filters( "gemboards/get_{$key}_settings", $value, $key );

		return apply_filters( 'gemboards/get_settings', $value, $key );
	}

	public static function get_notification_setting( string $key, string $type = 'gemboards' ): bool {
		$notifications = self::get_settings('notifications');
		if ( ! $notifications || 'object' !== gettype($notifications) ) {
			return false;
		}

		if ( ! isset($notifications->{$key}) ) {
			return false;
		}

		if ( ! isset($notifications->{$key}->{$type}) ) {
			return false;
		}

		return $notifications->{$key}->{$type};
	}

	public static function has_pro(): bool {
		return class_exists( 'GemBoardsPro' );
	}

	public static function move_array_item_by_key( $items, $key, $value, $new_position ) {
		$index = array_search( $value, array_column( $items, $key ), true );

		if ( false === $index ) {
			return $items; // not found, return original
		}

		// Remove the item
		$item = $items[ $index ];
		unset( $items[ $index ] );

		// Reindex the array to fix numeric keys
		$items = array_values( $items );

		// Insert at new position (0-based)
		array_splice( $items, $new_position, 0, [ $item ] );

		return $items;
	}

	/**
	 * @param int $user_id
	 * @param string $object_type
	 *
	 * @return Role[]
	 */
	public static function get_user_roles( int $user_id, string $object_type = 'project' ): array {
		try {
			return ( new RoleCollection( [
				'per_page' => - 1,
				'where'    => [
					[
						'key'   => 'user_id',
						'value' => $user_id,
					],
					[
						'key'   => 'object_type',
						'value' => $object_type,
					],
				],
			] ) )->get_results();
		} catch ( GemboardsInvalidArgumentException $e ) {
			self::error_log( sprintf(
				'Exception caught in %s on line %d: %s',
				$e->getFile(),
				$e->getLine(),
				$e->getMessage()
			) );
		}

		return [];
	}

	/**
	 * Converts a string (e.g. 'yes' or 'no') to a bool.
	 *
	 * @param string|bool $string String to convert. If a bool is passed it will be returned as-is.
	 *
	 * @return bool
	 */
	public static function string_to_bool( $string ): bool {
		$string = $string ?? '';

		return is_bool( $string ) ? $string : ( 'yes' === strtolower( $string ) || 1 === $string || 'true' === strtolower( $string ) || '1' === $string );
	}

	/**
	 * Converts a bool to a 'yes' or 'no'.
	 *
	 * @param bool|string $bool Bool to convert. If a string is passed it will first be converted to a bool.
	 *
	 * @return string
	 */
	public static function bool_to_string( $bool ): string {
		if ( ! is_bool( $bool ) ) {
			$bool = self::string_to_bool( $bool );
		}

		return true === $bool ? 'yes' : 'no';
	}

	/**
	 * Implodes an array into a human-readable string with a localized conjunction before the last item.
	 *
	 * Examples:
	 * - ['a'] → "a"
	 * - ['a', 'b'] → "a or b"
	 * - ['a', 'b', 'c'] → "a, b, or c"
	 *
	 * @param array $items The list of strings to join.
	 * @param string $conjunction Optional. The conjunction to use before the last item (e.g. 'or', 'and').
	 *                            If empty, defaults to localized 'or'.
	 *
	 * @return string The imploded string.
	 */
	public static function implode_with( array $items, string $conjunction = '' ): string {
		$count = count( $items );

		if ( '' === $conjunction ) {
			$conjunction = __( 'or', 'gemboards' );
		}

		if ( 0 === $count ) {
			return '';
		}

		if ( 1 === $count ) {
			return $items[0];
		}

		if ( 2 === $count ) {
			return $items[0] . " $conjunction " . $items[1];
		}

		$last = array_pop( $items );

		return implode( ', ', $items ) . ", $conjunction " . $last;
	}

	public static function error_log( string $message ) {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// phpcs:disable WordPress.PHP.DevelopmentFunctions
			error_log( $message );
			// phpcs:enable
		}
	}

	public static function generate_hash( $chars ) {
		$length = max( 16, (int) $chars );
		try {
			// Generate enough bytes to cover the requested length when hex-encoded
			$bytes = random_bytes( (int) ceil( $length / 2 ) );
			$token = bin2hex( $bytes ); // hex uses [0-9a-f], safe for URLs
		} catch ( \Exception $e ) {
			$token = wp_generate_password( $length, false, false );
			$token = strtolower( preg_replace( '/[^a-zA-Z0-9]/', '', $token ) );
		}

		return substr( $token, 0, $length );
	}

	/**
	 * Simple check for validating a URL, it must start with http:// or https://.
	 * and pass FILTER_VALIDATE_URL validation.
	 *
	 * @param string $url to check.
	 *
	 * @return bool
	 */
	public static function is_valid_url( string $url ): bool {

		// Must start with http:// or https://.
		/** @noinspection HttpUrlsUsage */
		if ( 0 !== strpos( $url, 'http://' ) && 0 !== strpos( $url, 'https://' ) ) {
			return false;
		}

		// Must pass validation.
		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Alias for is_valid_url()
	 *
	 * @param string $url
	 *
	 * @return bool
	 * @see is_valid_url()
	 */
	public static function is_url( string $url ): bool {
		return self::is_valid_url( $url );
	}
}
