<?php

namespace Fotogenic_AI;

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

/**
 * PHP 7.4-compatible equivalent of array_is_list().
 *
 * @param array $value Candidate array.
 * @return bool
 */
function fotogenic_ai_array_is_list( array $value ): bool {
	if ( [] === $value ) {
		return true;
	}

	return array_keys( $value ) === range( 0, count( $value ) - 1 );
}

/**
 * PHP 7.4-compatible equivalent of get_debug_type().
 *
 * @param mixed $value Value to describe.
 * @return string
 */
function fotogenic_ai_get_debug_type( $value ): string {
	if ( is_object( $value ) ) {
		return get_class( $value );
	}
	if ( is_resource( $value ) ) {
		return 'resource (' . get_resource_type( $value ) . ')';
	}

	$types = [
		'boolean' => 'bool',
		'integer' => 'int',
		'double'  => 'float',
		'NULL'    => 'null',
	];
	$type = gettype( $value );

	return $types[ $type ] ?? $type;
}

/**
 * Normalize a WordPress display title into safe plain text for JSON and logs.
 *
 * WordPress title filters may return HTML entities intended for HTML output.
 * Studio transports those values as JSON and renders them with textContent, so
 * entities must be decoded once on the server without reintroducing markup.
 *
 * @param string $value Display title or label.
 * @return string
 */
function fotogenic_ai_plain_text_label( string $value ): string {
	$decoded = html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
	$plain   = wp_strip_all_tags( $decoded, true );

	return trim( (string) sanitize_text_field( $plain ) );
}

/**
 * PHP 7.4-compatible equivalent of str_contains().
 *
 * @param string $haystack String to search.
 * @param string $needle   Substring to find.
 * @return bool
 */
function fotogenic_ai_str_contains( string $haystack, string $needle ): bool {
	return '' === $needle || false !== strpos( $haystack, $needle );
}

/**
 * PHP 7.4-compatible equivalent of str_starts_with().
 *
 * @param string $haystack String to search.
 * @param string $needle   Prefix to find.
 * @return bool
 */
function fotogenic_ai_str_starts_with( string $haystack, string $needle ): bool {
	return '' === $needle || 0 === strpos( $haystack, $needle );
}

/**
 * PHP 7.4-compatible equivalent of str_ends_with().
 *
 * @param string $haystack String to search.
 * @param string $needle   Suffix to find.
 * @return bool
 */
function fotogenic_ai_str_ends_with( string $haystack, string $needle ): bool {
	if ( '' === $needle ) {
		return true;
	}

	return substr( $haystack, -strlen( $needle ) ) === $needle;
}

/**
 * Whether WooCommerce product APIs are available (no fatal when inactive).
 *
 * @return bool
 */
function fotogenic_ai_woocommerce_available(): bool {
    return Platform_Capabilities::instance()->woocommerce_status()['available'];
}

function is_block_product_editor_active(): bool {
    return function_exists( 'wc_get_container' ) && function_exists( 'wc_is_product_editor' ) && wc_is_product_editor();
}

function fotogenic_ai_minimum_cap(): string {
    return 'manage_options';
}

/**
 * Main plugin capability (filterable).
 * Security floor is enforced separately in fotogenic_ai_user_can_access().
 *
 * @return string
 */
function fotogenic_ai_required_cap(): string {
    $default = defined( 'FOTOGENIC_AI_REQUIRED_CAP' ) ? trim( (string) FOTOGENIC_AI_REQUIRED_CAP ) : fotogenic_ai_minimum_cap();
    if ( '' === $default ) {
        $default = fotogenic_ai_minimum_cap();
    }
    $cap = apply_filters( 'fotogenic_ai_required_cap', $default );
    if ( ! is_string( $cap ) ) {
        $cap = '';
    }
    $cap = trim( $cap );
    if ( '' === $cap ) {
        $cap = $default;
    }
    return $cap;
}

/**
 * Unified capability check for plugin UI + REST. Enforces a hard admin floor.
 *
 * @return bool
 */
function fotogenic_ai_user_can_access(): bool {
    $minimum_cap = fotogenic_ai_minimum_cap();
    if ( ! current_user_can( $minimum_cap ) ) {
        return false;
    }
    $required_cap = fotogenic_ai_required_cap();
    if ( $required_cap !== $minimum_cap && ! current_user_can( $required_cap ) ) {
        return false;
    }
    return true;
}

function fotogenic_ai_admin_nav_nonce_action(): string {
    return 'fotogenic_ai_admin_nav';
}

function fotogenic_ai_admin_nav_nonce_name(): string {
    return '_fotogenic_ai_nav';
}

function fotogenic_ai_json_encode_for_inline( $value, string $fallback = '{}' ): string {
    $json = wp_json_encode(
        $value,
        JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
    );
    return is_string( $json ) && '' !== $json ? $json : $fallback;
}

function fotogenic_ai_build_inline_json_assignment( array $path, $value ): string {
    if ( empty( $path ) ) {
        return '';
    }

    $script = 'window.FotogenicAI=window.FotogenicAI||{};';
    $target = 'window';
    $depth  = count( $path );
    foreach ( $path as $index => $segment ) {
        $segment = is_string( $segment ) ? trim( $segment ) : '';
        if ( '' === $segment ) {
            return '';
        }
        $target .= '[' . wp_json_encode( $segment ) . ']';
        if ( $index < ( $depth - 1 ) ) {
            $script .= $target . '=' . $target . '||{};';
            continue;
        }
        $script .= $target . '=' . fotogenic_ai_json_encode_for_inline( $value ) . ';';
    }

    return $script;
}

function fotogenic_ai_sign_admin_query_args( array $args ): array {
    $args[ fotogenic_ai_admin_nav_nonce_name() ] = wp_create_nonce( fotogenic_ai_admin_nav_nonce_action() );
    return $args;
}

function fotogenic_ai_get_signed_navigation_query_args(): array {
    static $args = null;
    if ( null !== $args ) {
        return $args;
    }

    $args = [];
    $nonce_name = fotogenic_ai_admin_nav_nonce_name();
	$nonce = isset( $_GET[ $nonce_name ] ) && is_scalar( $_GET[ $nonce_name ] )
		? sanitize_text_field( (string) wp_unslash( $_GET[ $nonce_name ] ) )
		: '';
    if ( '' === $nonce || ! wp_verify_nonce( $nonce, fotogenic_ai_admin_nav_nonce_action() ) ) {
        return $args;
    }

	$args = fotogenic_ai_parse_signed_navigation_query_args( $_GET );

	return $args;
}

/**
 * Parse nonce-verified admin navigation inputs into bounded route hints.
 *
 * Partial or invalid canonical hints are ignored. Optional source and view
 * hints are accepted only in their canonical Universal Studio forms.
 *
 * @param array $query Nonce-verified query input.
 * @return array<string, int|string>
 */
function fotogenic_ai_parse_signed_navigation_query_args( array $query ): array {
	$args            = [];
	$canonical_route = [];
	foreach ( [ 'workflow', 'sourceType', 'scope' ] as $field ) {
		if ( isset( $query[ $field ] ) && is_scalar( $query[ $field ] ) ) {
			$canonical_route[ $field ] = sanitize_key( (string) wp_unslash( $query[ $field ] ) );
		}
	}
	if (
		count( $canonical_route ) === 3
		&& class_exists( __NAMESPACE__ . '\\Studio_Toolbar_View_Model' )
	) {
		$canonical_route = Studio_Toolbar_View_Model::allowlist_route( $canonical_route );
		if ( ! empty( $canonical_route ) ) {
			$args      = $canonical_route;
			$source_id = isset( $query['sourceId'] ) && is_scalar( $query['sourceId'] )
				? absint( wp_unslash( $query['sourceId'] ) )
				: 0;
			if ( $source_id > 0 ) {
				$args['sourceId'] = $source_id;
			}
		}
	}

	$view = isset( $query['view'] ) && is_scalar( $query['view'] )
		? sanitize_key( (string) wp_unslash( $query['view'] ) )
		: '';
	if ( 'history' === $view ) {
		$args['view'] = 'history';
	}

	return $args;
}

function fotogenic_ai_get_settings_url( array $args = [] ): string {
	$url = admin_url( 'admin.php?page=fotogenic-ai' );
	$safe_args = [];
	$canonical_route = [];
	foreach ( [ 'workflow', 'sourceType', 'scope' ] as $field ) {
		if ( isset( $args[ $field ] ) ) {
			$canonical_route[ $field ] = (string) $args[ $field ];
		}
	}
	if (
		! empty( $canonical_route )
		&& class_exists( __NAMESPACE__ . '\\Studio_Toolbar_View_Model' )
	) {
		$canonical_route = Studio_Toolbar_View_Model::allowlist_route( $canonical_route );
	}
	if ( ! empty( $canonical_route ) ) {
		$safe_args = $canonical_route;
	}
	return add_query_arg( fotogenic_ai_sign_admin_query_args( $safe_args ), $url );
}

function fotogenic_ai_get_studio_url( array $args = [] ): string {
    $url = admin_url( 'admin.php?page=fotogenic-studio' );
    $safe_args = [];
	$canonical_route = [];
	foreach ( [ 'workflow', 'sourceType', 'scope' ] as $field ) {
		if ( isset( $args[ $field ] ) ) {
			$canonical_route[ $field ] = (string) $args[ $field ];
		}
	}
	if (
		! empty( $canonical_route )
		&& class_exists( __NAMESPACE__ . '\\Studio_Toolbar_View_Model' )
	) {
		$canonical_route = Studio_Toolbar_View_Model::allowlist_route( $canonical_route );
	}
	if ( ! empty( $canonical_route ) ) {
		$safe_args = $canonical_route;
		$source_id = isset( $args['sourceId'] ) && is_scalar( $args['sourceId'] ) ? absint( $args['sourceId'] ) : 0;
		if ( $source_id > 0 ) {
			$safe_args['sourceId'] = $source_id;
		}
	}
	$view = isset( $args['view'] ) && is_scalar( $args['view'] ) ? sanitize_key( (string) $args['view'] ) : '';
	if ( 'history' === $view ) {
		$safe_args['view'] = 'history';
	}
    return add_query_arg( fotogenic_ai_sign_admin_query_args( $safe_args ), $url );
}

function fotogenic_ai_get_encryption_key(): string {
    $material = AUTH_KEY . SECURE_AUTH_KEY . LOGGED_IN_KEY . NONCE_KEY;

    return hash( 'sha256', $material, true );
}

function fotogenic_ai_encrypt_string( string $plaintext, ?Platform_Capabilities $capabilities = null ): string {
    if ( '' === $plaintext ) {
        return '';
    }

    $key = fotogenic_ai_get_encryption_key();
    $capabilities = $capabilities ?? Platform_Capabilities::instance();
    $backend = $capabilities->preferred_crypto_backend();

    if ( 'sodium' === $backend ) {
        $nonce  = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
        $cipher = sodium_crypto_secretbox( $plaintext, $nonce, $key );
        $payload = [
            'v' => 1,
            'm' => 'sodium',
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption payloads must be encoded for safe JSON storage.
            'n' => base64_encode( $nonce ),
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption payloads must be encoded for safe JSON storage.
            'c' => base64_encode( $cipher ),
        ];

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- The encrypted JSON payload is encoded for option storage.
        return base64_encode( wp_json_encode( $payload ) );
    }

    if ( 'openssl' === $backend ) {
        $iv  = random_bytes( 12 );
        $tag = '';
        $cipher = openssl_encrypt( $plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag );
        if ( false === $cipher ) {
            return '';
        }

        $payload = [
            'v' => 1,
            'm' => 'openssl',
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption payloads must be encoded for safe JSON storage.
            'i' => base64_encode( $iv ),
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption payloads must be encoded for safe JSON storage.
            't' => base64_encode( $tag ),
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption payloads must be encoded for safe JSON storage.
            'c' => base64_encode( $cipher ),
        ];

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- The encrypted JSON payload is encoded for option storage.
        return base64_encode( wp_json_encode( $payload ) );
    }

    return '';
}

function fotogenic_ai_decrypt_string( string $payload, ?Platform_Capabilities $capabilities = null ): string {
    if ( '' === $payload ) {
        return '';
    }

    // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
    $decoded = base64_decode( $payload, true );
    if ( false === $decoded ) {
        return '';
    }

    $data = json_decode( $decoded, true );
    if ( ! is_array( $data ) || empty( $data['m'] ) ) {
        return '';
    }

    $key = fotogenic_ai_get_encryption_key();
    $capabilities = $capabilities ?? Platform_Capabilities::instance();
    $backend = $capabilities->stored_crypto_backend( $payload );

    if ( 'sodium' === $backend && $capabilities->crypto_backend_available( 'sodium' ) ) {
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
        $nonce  = base64_decode( (string) ( $data['n'] ?? '' ), true );
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
        $cipher = base64_decode( (string) ( $data['c'] ?? '' ), true );
        if ( false === $nonce || false === $cipher ) {
            return '';
        }
        $plain = sodium_crypto_secretbox_open( $cipher, $nonce, $key );

        return false === $plain ? '' : $plain;
    }

    if ( 'openssl' === $backend && $capabilities->crypto_backend_available( 'openssl' ) ) {
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
        $iv     = base64_decode( (string) ( $data['i'] ?? '' ), true );
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
        $tag    = base64_decode( (string) ( $data['t'] ?? '' ), true );
        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Stored encryption payloads are base64-encoded by the companion encrypt helper.
        $cipher = base64_decode( (string) ( $data['c'] ?? '' ), true );
        if ( false === $iv || false === $tag || false === $cipher ) {
            return '';
        }

        $plain = openssl_decrypt( $cipher, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag );

        return false === $plain ? '' : $plain;
    }

    return '';
}
