<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Verze release/ZIP logiky — při změně vynutí re-sync endpointů ve webrootu (WinSCP upload).
 */
if ( ! defined( 'ELEMENTSELL_RELEASE_LOGIC_VERSION' ) ) {
    define( 'ELEMENTSELL_RELEASE_LOGIC_VERSION', '2026-07-14.1' );
}

/**
 * @return string
 */
function elementsell_get_release_logic_version() {
    return defined( 'ELEMENTSELL_RELEASE_LOGIC_VERSION' )
        ? (string) ELEMENTSELL_RELEASE_LOGIC_VERSION
        : 'unknown';
}

/**
 * @param string $message
 * @return string
 */
function elementsell_append_release_logic_version_to_message( $message ) {
    return trim( (string) $message ) . ' [release-logic v' . elementsell_get_release_logic_version() . ']';
}

/**
 * Jednoduché vypuštění bez hesla — pouze na update serveru / WP_DEBUG / konstanta.
 *
 * @return bool
 */
function elementsell_is_simple_publish_allowed() {
    if ( defined( 'ELEMENTSELL_SIMPLE_PUBLISH' ) && ELEMENTSELL_SIMPLE_PUBLISH ) {
        return true;
    }

    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        return true;
    }

    if ( function_exists( 'elementsell_should_sync_publish_endpoint' ) ) {
        return elementsell_should_sync_publish_endpoint();
    }

    $home = strtolower( (string) home_url() );

    return strpos( $home, 'elementsell.cz' ) !== false
        || strpos( $home, 'platebniformular.cz' ) !== false;
}

/**
 * Token z elementsell-secrets.php v kořeni webu (jen na update serveru).
 *
 * @return string
 */
function elementsell_get_local_release_token() {
    if ( function_exists( 'elementsell_telemetry_read_local_server_secret' ) ) {
        return elementsell_telemetry_read_local_server_secret();
    }

    if ( ! function_exists( 'elementsell_should_sync_publish_endpoint' ) || ! elementsell_should_sync_publish_endpoint() ) {
        return '';
    }

    $secrets_file = trailingslashit( ABSPATH ) . 'elementsell-secrets.php';

    if ( ! is_readable( $secrets_file ) ) {
        return '';
    }

    $ELEMENTSELL_SECRET = '';

    include $secrets_file;

    if ( ! isset( $ELEMENTSELL_SECRET ) || ! is_string( $ELEMENTSELL_SECRET ) ) {
        return '';
    }

    $secret = trim( $ELEMENTSELL_SECRET );

    $invalid = [
        '',
        'your_secret_token',
        'change_me_stats_token',
        'change_me_stats_password',
        'change_me_strong_password',
        'change_me_release_only_token',
        'oselosralosla',
    ];

    if ( in_array( $secret, $invalid, true ) ) {
        return '';
    }

    return $secret;
}

/**
 * Dev: jeden klik — sync endpointů, patch bump, ZIP, manifest + releases/.
 *
 * @param string $changelog
 * @return array<string, mixed>|WP_Error
 */
function elementsell_simple_publish_release( $changelog = '' ) {
    if ( ! elementsell_is_simple_publish_allowed() ) {
        return new WP_Error( 'simple_publish_disabled', 'Jednoduché vypuštění není na tomto webu povoleno.' );
    }

    if ( ! function_exists( 'elementsell_should_sync_publish_endpoint' ) || ! elementsell_should_sync_publish_endpoint() ) {
        return new WP_Error( 'not_update_server', 'Jednoduché vypuštění funguje jen na elementsell.cz.' );
    }

    $token = elementsell_get_local_release_token();

    if ( $token === '' ) {
        return new WP_Error(
            'no_local_token',
            'V kořeni webu chybí platný soubor elementsell-secrets.php.'
        );
    }

    if ( function_exists( 'elementsell_force_sync_publish_endpoint_to_webroot' ) ) {
        elementsell_force_sync_publish_endpoint_to_webroot();
    }

    elementsell_sync_plugin_version_to_deployed();
    elementsell_reconcile_plugin_version_before_publish();

    $previous_version = elementsell_get_plugin_version_from_file();
    $version          = elementsell_bump_plugin_version_patch();

    if ( is_wp_error( $version ) ) {
        return $version;
    }

    $zip_path = elementsell_build_release_zip( $version, $token );

    if ( is_wp_error( $zip_path ) ) {
        elementsell_rollback_plugin_version_after_failed_publish( $previous_version );

        return $zip_path;
    }

    $result = elementsell_publish_release_on_local_server( $zip_path, $changelog, $version );

    if ( is_wp_error( $result ) ) {
        elementsell_rollback_plugin_version_after_failed_publish( $previous_version );
    }

    if ( file_exists( $zip_path ) ) {
        wp_delete_file( $zip_path );
    }

    if ( ! is_wp_error( $result ) ) {
        elementsell_invalidate_plugin_version_caches();
    }

    return $result;
}

/**
 * @return string
 */
function elementsell_get_release_unlock_transient_key() {
    return 'elementsell_release_unlock_' . get_current_user_id();
}

/**
 * @return bool
 */
function elementsell_is_release_publish_unlocked() {
    $stored = get_transient( elementsell_get_release_unlock_transient_key() );

    return is_string( $stored ) && $stored !== '';
}

/**
 * @return string
 */
function elementsell_get_stored_release_password() {
    $stored = get_transient( elementsell_get_release_unlock_transient_key() );

    return is_string( $stored ) ? $stored : '';
}

/**
 * @param string $release_password
 * @return void
 */
function elementsell_set_release_publish_unlocked( $release_password ) {
    set_transient(
        elementsell_get_release_unlock_transient_key(),
        (string) $release_password,
        30 * MINUTE_IN_SECONDS
    );
}

/**
 * @return void
 */
function elementsell_clear_release_publish_unlock() {
    delete_transient( elementsell_get_release_unlock_transient_key() );
}

/**
 * Ověří heslo pro vypuštění aktualizace — vždy proti remote endpointu na elementsell.cz.
 * Token existuje pouze server-side v elementsell-secrets.php (mimo plugin).
 *
 * @param string $release_password
 * @return true|WP_Error
 */
function elementsell_verify_release_password( $release_password ) {
    $release_password = trim( (string) $release_password );

    if ( $release_password === '' ) {
        return new WP_Error( 'no_password', 'Zadejte heslo.' );
    }

    if ( function_exists( 'elementsell_force_sync_publish_endpoint_to_webroot' ) ) {
        elementsell_force_sync_publish_endpoint_to_webroot();
    }

    $response = wp_remote_post(
        elementsell_get_release_publish_url(),
        [
            'timeout' => 20,
            'body'    => [
                'token'          => $release_password,
                'validate_only'  => '1',
            ],
        ]
    );

    if ( is_wp_error( $response ) ) {
        return new WP_Error(
            'server_unreachable',
            'Server aktualizací neodpovídá: ' . $response->get_error_message()
        );
    }

    $code = (int) wp_remote_retrieve_response_code( $response );
    $body = (string) wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );

    if ( is_array( $data ) && ! empty( $data['ok'] ) ) {
        return true;
    }

    if ( $code === 403 && ( ! is_array( $data ) || empty( $data['message'] ) ) ) {
        return new WP_Error(
            'server_forbidden',
            'Hosting blokuje POST na aktualizační skript. Po načtení webu elementsell.cz se soubor zkopíruje do kořene — zkuste za chvíli znovu.'
        );
    }

    if ( is_array( $data ) && ! empty( $data['message'] ) ) {
        $message = (string) $data['message'];

        if ( $message === 'Neplatný token.' ) {
            return new WP_Error( 'invalid_password', 'Neplatné heslo.' );
        }

        return new WP_Error( 'publish_failed', $message );
    }

    if ( $code === 503 ) {
        return new WP_Error(
            'server_not_ready',
            'Release token není nastaven na serveru. Vytvořte soubor elementsell-secrets.php v kořeni webu (mimo plugin).'
        );
    }

    if ( $code === 404 ) {
        return new WP_Error(
            'server_not_ready',
            'Na serveru chybí elementsell_publish_update.php.'
        );
    }

    return new WP_Error(
        'invalid_password',
        'Neplatné heslo nebo server vrátil chybu HTTP ' . $code . '.'
    );
}

/**
 * @param string $relative_path
 * @return bool
 */
function elementsell_release_should_exclude_path( $relative_path ) {
    $relative_path = str_replace( '\\', '/', strtolower( $relative_path ) );

    $blocked_prefixes = [
        '.git/',
        '.tmp-zip-compare/',
        '.claude/',
        'remote-server/',
        'scripts/',
        'assets/landing/',
        'demo-guitar-courses/',
        'mu-plugins-upload/',
        'website-docs/',
        // Emailing / AI automation — připravené v repu, ne do zákaznického ZIP.
        'includes/automation/',
    ];

    foreach ( $blocked_prefixes as $prefix ) {
        if ( strpos( $relative_path, $prefix ) === 0 ) {
            return true;
        }
    }

    $blocked_files = [
        'ai_context.md',
        'wordpress_org_submission.md',
        // Interní revize kódu — zákazníkovi by ukazovala slabiny pluginu.
        'code_review.md',
        '_inv_formonly_refs.txt',
        '_inv_s22.txt',
        '.gitignore',
        '.cursorignore',
        'elementsell-alive.php',
        'es-probe-3214.txt',
        '_temp_css.txt',
        'widget-ping.php',
        'includes/emails/email-smtp-config.php',
        'includes/admin/admin-billing-test.php',
        'includes/admin/admin-fio-test.php',
        'includes/admin/admin-access-test.php',
        'includes/admin/admin-reminder-test.php',
        'includes/admin/admin-publish-test.php',
        'includes/updates/test-plugin-updater-cli.php',
        'includes/pages/landing-page.php',
        'templates/elementsell-landing.php',
        'assets/elementsell-landing.css',
        'assets/elementsell-landing-flow.js',
    ];

    return in_array( $relative_path, $blocked_files, true );
}

/**
 * Šablona ELEMENTSELL_DISTRIBUTED_TOKEN v plugin.php (dev — prázdný token).
 *
 * @return string
 */
function elementsell_get_distributed_token_template_block() {
    return "if ( ! defined( 'ELEMENTSELL_DISTRIBUTED_TOKEN' ) ) {\n    define( 'ELEMENTSELL_DISTRIBUTED_TOKEN', '' );\n}";
}

/**
 * @param string $release_token
 * @return string
 */
function elementsell_build_distributed_token_define_line( $release_token ) {
    $escaped = str_replace( [ '\\', "'" ], [ '\\\\', "\\'" ], trim( (string) $release_token ) );

    return "define( 'ELEMENTSELL_DISTRIBUTED_TOKEN', '" . $escaped . "' );";
}

/**
 * Obsahuje plugin.php skutečný define/if blok pro token (ne jen komentář)?
 *
 * @param string $content
 * @return bool
 */
function elementsell_has_injectable_distributed_token_in_content( $content ) {
    return (bool) preg_match(
        "/(?:define\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"]|if\s*\(\s*!\s*defined\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"])/",
        (string) $content
    );
}

/**
 * Odstraní komentář, if-blok i define pro ELEMENTSELL_DISTRIBUTED_TOKEN.
 *
 * @param string $content
 * @return string
 */
function elementsell_remove_distributed_token_region_from_content( $content ) {
    $content = (string) $content;

    $content = preg_replace(
        '/^[ \t]*\/\/[^\r\n]*ELEMENTSELL_DISTRIBUTED_TOKEN[^\r\n]*(?:\r?\n)?/m',
        '',
        $content
    );

    $content = preg_replace(
        "/if\s*\(\s*!\s*defined\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"]\s*\)\s*\)\s*\{[\s\S]*?\}/",
        '',
        $content
    );

    $content = preg_replace(
        "/define\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"]\s*,[\s\S]*?\)\s*;/",
        '',
        $content
    );

    return $content;
}

/**
 * Vloží define tokenu za PF_VERSION (fallback po odstranění poškozeného bloku).
 *
 * @param string $content
 * @param string $define_line
 * @return string
 */
function elementsell_insert_distributed_token_define_after_pf_version( $content, $define_line ) {
    $content     = (string) $content;
    $define_line = (string) $define_line;
    $comment     = "// Při vypuštění aktualizace se nahradí skutečným tokenem (stejný jako \$ELEMENTSELL_SECRET na serveru).";

    if ( preg_match( elementsell_pf_version_define_pattern(), $content, $matches, PREG_OFFSET_CAPTURE ) ) {
        $insert_at = $matches[0][1] + strlen( $matches[0][0] );
        $before    = substr( $content, 0, $insert_at );
        $after     = substr( $content, $insert_at );

        return rtrim( $before ) . "\n\n" . $comment . "\n" . $define_line . $after;
    }

    return rtrim( $content ) . "\n\n" . $comment . "\n" . $define_line . "\n";
}

/**
 * Vloží do obsahu plugin.php šablonu tokenu (chybí-li).
 *
 * @param string $content
 * @return string
 */
function elementsell_ensure_distributed_token_template_in_content( $content ) {
    $content = (string) $content;

    if ( elementsell_has_injectable_distributed_token_in_content( $content ) ) {
        return $content;
    }

    $template = elementsell_get_distributed_token_template_block();
    $comment  = "// Při vypuštění aktualizace se nahradí skutečným tokenem (stejný jako \$ELEMENTSELL_SECRET na serveru).";

    if ( preg_match( elementsell_pf_version_define_pattern(), $content, $matches, PREG_OFFSET_CAPTURE ) ) {
        $insert_at = $matches[0][1] + strlen( $matches[0][0] );
        $before    = substr( $content, 0, $insert_at );
        $after     = substr( $content, $insert_at );

        return rtrim( $before ) . "\n\n" . $comment . "\n" . $template . $after;
    }

    return $content . "\n\n" . $comment . "\n" . $template . "\n";
}

/**
 * Zajistí šablonu tokenu v plugin.php na disku (před sestavením ZIP).
 *
 * @return void
 */
function elementsell_ensure_distributed_token_template_in_plugin_file() {
    if ( ! defined( 'PF_PLUGIN_FILE' ) || PF_PLUGIN_FILE === '' || ! is_readable( PF_PLUGIN_FILE ) || ! is_writable( PF_PLUGIN_FILE ) ) {
        return;
    }

    $content = (string) file_get_contents( PF_PLUGIN_FILE );
    $updated = elementsell_ensure_distributed_token_template_in_content( $content );

    if ( $updated === $content ) {
        return;
    }

    if ( false !== file_put_contents( PF_PLUGIN_FILE, $updated ) ) {
        elementsell_invalidate_plugin_version_caches();
    }
}

/**
 * Nahradí placeholder tokenu skutečnou hodnotou pro distribuovaný ZIP.
 *
 * @param string $content
 * @param string $release_token
 * @return string
 */
function elementsell_inject_distributed_token_into_plugin_content( $content, $release_token ) {
    $content       = elementsell_ensure_distributed_token_template_in_content( (string) $content );
    $release_token = trim( (string) $release_token );

    if ( $release_token === '' ) {
        return $content;
    }

    $define = elementsell_build_distributed_token_define_line( $release_token );

    $patterns = [
        "/if\s*\(\s*!\s*defined\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"]\s*\)\s*\)\s*\{[\s\S]*?define\s*\(\s*['\"]ELEMENTSELL_DISTRIBUTED_TOKEN['\"]\s*,[\s\S]*?\)\s*;[\s\r\n]*\}/",
        "/if\s*\(\s*!\s*defined\s*\(\s*'ELEMENTSELL_DISTRIBUTED_TOKEN'\s*\)\s*\)\s*\{[\s\S]*?define\s*\(\s*'ELEMENTSELL_DISTRIBUTED_TOKEN'\s*,\s*''\s*\)\s*;[\s\r\n]*\}/",
        "/if\s*\(\s*!\s*defined\s*\(\s*\"ELEMENTSELL_DISTRIBUTED_TOKEN\"\s*\)\s*\)\s*\{[\s\S]*?define\s*\(\s*\"ELEMENTSELL_DISTRIBUTED_TOKEN\"\s*,\s*\"\"\s*\)\s*;[\s\r\n]*\}/",
        "/define\s*\(\s*'ELEMENTSELL_DISTRIBUTED_TOKEN'\s*,[\s\S]*?\)\s*;/",
        "/define\s*\(\s*\"ELEMENTSELL_DISTRIBUTED_TOKEN\"\s*,[\s\S]*?\)\s*;/",
    ];

    foreach ( $patterns as $pattern ) {
        $updated = preg_replace( $pattern, $define, $content, 1 );

        if ( is_string( $updated ) && $updated !== $content && strpos( $updated, $release_token ) !== false ) {
            return $updated;
        }
    }

    $cleaned = elementsell_remove_distributed_token_region_from_content( $content );
    $content = elementsell_insert_distributed_token_define_after_pf_version( $cleaned, $define );

    if ( strpos( $content, $release_token ) === false ) {
        $content = elementsell_ensure_distributed_token_template_in_content( $content );

        foreach ( $patterns as $pattern ) {
            $updated = preg_replace( $pattern, $define, $content, 1 );

            if ( is_string( $updated ) && $updated !== $content && strpos( $updated, $release_token ) !== false ) {
                return $updated;
            }
        }

        $content = elementsell_insert_distributed_token_define_after_pf_version(
            elementsell_remove_distributed_token_region_from_content( $content ),
            $define
        );
    }

    if ( ! elementsell_has_injectable_distributed_token_in_content( $content ) ) {
        $content = elementsell_ensure_distributed_token_template_in_content( $content );
    }

    return $content;
}

/**
 * @param string $version
 * @return bool
 */
function elementsell_release_zip_exists_for_version( $version ) {
    if ( ! preg_match( '/^\d+\.\d+\.\d+$/', (string) $version ) ) {
        return false;
    }

    $releases_dir = trailingslashit( ABSPATH ) . 'releases';
    $filename     = 'elementsell-' . $version . '.zip';

    return is_readable( $releases_dir . '/' . $filename );
}

/**
 * plugin.php je o patch napřed oproti manifestu bez odpovídajícího ZIP (typicky neúspěšný publish).
 *
 * @param string $file_ver
 * @param string $deployed_ver
 * @return bool
 */
function elementsell_is_failed_publish_version_drift( $file_ver, $deployed_ver ) {
    $file_ver     = (string) $file_ver;
    $deployed_ver = (string) $deployed_ver;

    if ( ! preg_match( '/^\d+\.\d+\.\d+$/', $file_ver ) || ! preg_match( '/^\d+\.\d+\.\d+$/', $deployed_ver ) ) {
        return false;
    }

    if ( version_compare( $file_ver, $deployed_ver, '<=' ) ) {
        return false;
    }

    $expected_bump = elementsell_increment_patch_version( $deployed_ver );

    if ( is_wp_error( $expected_bump ) ) {
        return false;
    }

    if ( version_compare( $file_ver, $expected_bump, '!=' ) ) {
        return false;
    }

    return ! elementsell_release_zip_exists_for_version( $file_ver );
}

/**
 * Srovná plugin.php s manifestem po neúspěšném publishi (soubor novější než manifest).
 *
 * @return string Verze v plugin.php po srovnání.
 */
function elementsell_reconcile_plugin_version_before_publish() {
    $deployed = elementsell_get_deployed_version( true );
    $file     = elementsell_get_plugin_version_from_file();

    if ( $deployed === '' || ! preg_match( '/^\d+\.\d+\.\d+$/', $deployed ) ) {
        return $file;
    }

    if ( version_compare( $file, $deployed, '>' ) && elementsell_is_failed_publish_version_drift( $file, $deployed ) ) {
        $written = elementsell_write_plugin_version_to_file( $deployed );

        return is_wp_error( $written ) ? $file : $written;
    }

    return $file;
}

/**
 * Vrátí plugin.php na verzi před neúspěšným publishi.
 *
 * @param string $previous_version
 * @return void
 */
function elementsell_rollback_plugin_version_after_failed_publish( $previous_version ) {
    if ( ! preg_match( '/^\d+\.\d+\.\d+$/', (string) $previous_version ) ) {
        return;
    }

    $current = elementsell_get_plugin_version_from_file();

    if ( version_compare( $current, (string) $previous_version, '!=' ) ) {
        elementsell_write_plugin_version_to_file( (string) $previous_version );
    }
}

/**
 * Regex pro řádek Version: ve WordPress hlavičce pluginu.
 *
 * @return string
 */
function elementsell_plugin_version_header_pattern() {
    return '/^(\s*(?:\*\s*)?Version:\s*)(\d+\.\d+\.\d+)(?=\s*(?:\r?\n|$))/mi';
}

/**
 * Regex pro define( … 'PF_VERSION', '…' … ) v plugin.php.
 *
 * @return string
 */
function elementsell_pf_version_define_pattern() {
    return "/define\s*\(\s*['\"]PF_VERSION['\"]\s*,\s*['\"][^'\"]*['\"]\s*\)\s*;/";
}

/**
 * Opraví známé poškození verze (např. preg_replace s $1 místo ${1} → Version: $13.2.14).
 *
 * @param string $content
 * @return string
 */
function elementsell_repair_plugin_version_headers_in_content( $content ) {
    $content = (string) $content;

    // Version: $13.2.14 — literal $1 + semver (3.2.14).
    $content = preg_replace(
        '/^(\s*(?:\*\s*)?Version:\s*)\$1(\d+\.\d+\.\d+)/mi',
        '${1}${2}',
        $content
    );

    // Version: $Nsemver — $ + jedna číslice slepená se semver (obecnější $1 korupce).
    $content = preg_replace(
        '/^(\s*(?:\*\s*)?Version:\s*)\$(\d)(\d+\.\d+\.\d+)/mi',
        '${1}${3}',
        $content
    );

    // define( 'PF_VERSION', '$13.2.14' );
    $content = preg_replace(
        "/(define\s*\(\s*['\"]PF_VERSION['\"]\s*,\s*['\"])\$1(\d+\.\d+\.\d+)(['\"]\s*\)\s*;)/",
        '${1}${2}${3}',
        $content
    );

    $content = preg_replace(
        "/(define\s*\(\s*['\"]PF_VERSION['\"]\s*,\s*['\"])\$(\d)(\d+\.\d+\.\d+)(['\"]\s*\)\s*;)/",
        '${1}${3}${4}',
        $content
    );

    // Osamocený zbytek poškozené verze (např. ".2.12" po špatném replace).
    $content = preg_replace( '/^\s*\*?\s*\.\d+\.\d+\s*\r?\n/m', '', $content );

    // Version: 3.2.17.2.12 — slepená dvojitá verze.
    $content = preg_replace(
        '/^(\s*(?:\*\s*)?Version:\s*)(\d+\.\d+\.\d+)\.\d+\.\d+(?=\s*(?:\r?\n|$))/mi',
        '${1}${2}',
        $content
    );

    return $content;
}

/**
 * Kanonická Update URI pro distribuovaný build.
 *
 * @return string
 */
function elementsell_get_canonical_update_uri() {
    if ( ! function_exists( 'elementsell_get_update_server_public_base_url' ) ) {
        require_once __DIR__ . '/publish-endpoint-sync.php';
    }

    $base = rtrim( elementsell_get_update_server_public_base_url(), '/' );

    return $base . '/' . elementsell_get_update_manifest_filename();
}

/**
 * Odstraní duplicitní řádky Version: v hlavičce pluginu (ponechá první).
 *
 * @param string $content
 * @return string
 */
function elementsell_remove_duplicate_plugin_version_header_lines( $content ) {
    $found = false;

    return (string) preg_replace_callback(
        '/^(\s*\*\s*Version:\s*)(\d+\.\d+\.\d+)(?=\s*(?:\r?\n|$))/mi',
        static function ( $matches ) use ( &$found ) {
            if ( $found ) {
                return '';
            }

            $found = true;

            return (string) $matches[0];
        },
        (string) $content
    );
}

/**
 * Připraví plugin.php pro distribuovaný ZIP (hlavička, Update URI, token).
 *
 * @param string $content
 * @param string $version
 * @param string $release_token
 * @return string|WP_Error
 */
function elementsell_prepare_release_plugin_php_content( $content, $version, $release_token = '' ) {
    if ( ! preg_match( '/^\d+\.\d+\.\d+$/', (string) $version ) ) {
        return new WP_Error( 'bad_version', 'Verze musí být ve formátu X.Y.Z.' );
    }

    $content = elementsell_repair_plugin_version_headers_in_content( (string) $content );
    $content = elementsell_remove_duplicate_plugin_version_header_lines( $content );

    $header_result = elementsell_replace_plugin_version_header_in_content( $content, (string) $version );
    $content       = $header_result['content'];

    $define_result = elementsell_replace_pf_version_define_in_content( $content, (string) $version );
    $content       = $define_result['content'];

    $canonical_uri = elementsell_get_canonical_update_uri();
    $uri_replaced  = 0;

    $content = preg_replace(
        '/^(\s*\*\s*Update URI:\s*).*/mi',
        '${1}' . $canonical_uri,
        $content,
        1,
        $uri_replaced
    );

    if ( 0 === $uri_replaced ) {
        $content = preg_replace(
            '/^(\s*\*\s*License URI:[^\r\n]*\r?\n)/mi',
            '${1} * Update URI: ' . $canonical_uri . "\n",
            $content,
            1,
            $uri_replaced
        );
    }

    if ( 0 === $uri_replaced ) {
        return new WP_Error( 'update_uri', 'Do plugin.php se nepodařilo vložit Update URI.' );
    }

    $release_token = trim( (string) $release_token );

    if ( $release_token !== '' ) {
        $content = elementsell_inject_distributed_token_into_plugin_content( $content, $release_token );

        if ( strpos( $content, $release_token ) === false ) {
            $define  = elementsell_build_distributed_token_define_line( $release_token );
            $content = elementsell_insert_distributed_token_define_after_pf_version(
                elementsell_remove_distributed_token_region_from_content( $content ),
                $define
            );
        }
    }

    $content = elementsell_ensure_distributed_token_template_in_content( $content );

    if ( preg_match( '/^\s*\*?\s*\.\d+\.\d+\s*$/m', $content ) ) {
        return new WP_Error( 'header_corrupt', 'Hlavička plugin.php obsahuje poškozený zbytek verze.' );
    }

    if ( strpos( $content, 'elementsell-update.json' ) === false ) {
        return new WP_Error( 'update_uri', 'Distribuovaný plugin.php nemá správnou Update URI.' );
    }

    $header_version = elementsell_extract_plugin_version_from_header_content( $content );
    $define_version = elementsell_extract_pf_version_from_define_content( $content );

    if ( $header_version !== (string) $version || $define_version !== (string) $version ) {
        return new WP_Error(
            'version_mismatch',
            sprintf(
                'Verze v plugin.php (%s / %s) neodpovídá očekávané %s.',
                $header_version,
                $define_version,
                $version
            )
        );
    }

    return $content;
}

/**
 * Ověří plugin.php uvnitř release ZIP.
 *
 * @param string $plugin_content
 * @param string $version
 * @return true|WP_Error
 */
function elementsell_validate_release_zip_plugin_content( $plugin_content, $version ) {
    $plugin_content = (string) $plugin_content;

    if ( $plugin_content === '' ) {
        return new WP_Error( 'zip_invalid', 'ZIP neobsahuje obsah plugin.php.' );
    }

    if ( preg_match( '#^\s*<\?php#', $plugin_content ) !== 1 ) {
        return new WP_Error( 'zip_invalid', 'plugin.php v ZIP nezačíná platným PHP tagem.' );
    }

    if ( preg_match( '/^\s*\*?\s*\.\d+\.\d+\s*$/m', $plugin_content ) ) {
        return new WP_Error( 'zip_invalid', 'plugin.php v ZIP má poškozenou hlavičku verze.' );
    }

    if ( strpos( $plugin_content, 'elementsell-update.json' ) === false ) {
        return new WP_Error( 'zip_invalid', 'plugin.php v ZIP nemá Update URI na JSON manifest.' );
    }

    if ( strpos( $plugin_content, 'elementsell_publish_update.php' ) !== false
        && strpos( $plugin_content, 'elementsell-update.json' ) === false ) {
        return new WP_Error( 'zip_invalid', 'plugin.php v ZIP ukazuje na publish endpoint místo manifestu.' );
    }

    $header_version = elementsell_extract_plugin_version_from_header_content( $plugin_content );

    if ( $header_version !== (string) $version ) {
        return new WP_Error(
            'zip_invalid',
            sprintf( 'Verze v ZIP (%s) neodpovídá vypouštěné verzi %s.', $header_version, $version )
        );
    }

    // PHP gate helper musí být v ZIP a notice se nesmí registrovat naslepo.
    if ( strpos( $plugin_content, 'elementsell_php_version_is_supported' ) === false ) {
        return new WP_Error(
            'zip_invalid',
            'plugin.php v ZIP nenačítá bezpečnou kontrolu PHP (elementsell_php_version_is_supported).'
        );
    }

    if ( ! preg_match(
        "/elementsell_php_version_should_block_plugin\s*\(/",
        $plugin_content
    ) ) {
        return new WP_Error(
            'zip_invalid',
            'plugin.php v ZIP má poškozenou bootstrap kontrolu PHP.'
        );
    }

    return true;
}

/**
 * @param string $content
 * @return string
 */
function elementsell_extract_plugin_version_from_header_content( $content ) {
    $content = elementsell_repair_plugin_version_headers_in_content( (string) $content );

    if ( preg_match( elementsell_plugin_version_header_pattern(), $content, $matches ) ) {
        return (string) $matches[2];
    }

    if ( preg_match( '/Version:\s*(\d+\.\d+\.\d+)/i', $content, $matches ) ) {
        return (string) $matches[1];
    }

    return '';
}

/**
 * @param string $content
 * @return string
 */
function elementsell_extract_pf_version_from_define_content( $content ) {
    $content = elementsell_repair_plugin_version_headers_in_content( (string) $content );

    if ( preg_match( elementsell_pf_version_define_pattern(), $content, $matches ) ) {
        if ( preg_match( "/['\"](\d+\.\d+\.\d+)['\"]/", $matches[0], $version_match ) ) {
            return (string) $version_match[1];
        }
    }

    return '';
}

/**
 * @return string
 */
function elementsell_get_plugin_version_from_file() {
    $content = (string) file_get_contents( PF_PLUGIN_FILE );
    $header  = elementsell_extract_plugin_version_from_header_content( $content );

    if ( $header !== '' ) {
        return $header;
    }

    $defined = elementsell_extract_pf_version_from_define_content( $content );

    if ( $defined !== '' ) {
        return $defined;
    }

    return defined( 'PF_VERSION' ) ? PF_VERSION : '0.0.0';
}

/**
 * @param string $version
 * @return string|WP_Error
 */
function elementsell_increment_patch_version( $version ) {
    if ( ! preg_match( '/^(\d+)\.(\d+)\.(\d+)$/', (string) $version, $matches ) ) {
        return new WP_Error( 'bad_version', 'Verze musí být ve formátu X.Y.Z.' );
    }

    return $matches[1] . '.' . $matches[2] . '.' . ( (int) $matches[3] + 1 );
}

/**
 * @param string $manifest_path
 * @return string
 */
function elementsell_read_deployed_version_from_manifest_path( $manifest_path ) {
    if ( $manifest_path === '' || ! is_readable( $manifest_path ) ) {
        return '';
    }

    $data = json_decode( (string) file_get_contents( $manifest_path ), true );

    if ( is_array( $data ) && ! empty( $data['version'] ) && preg_match( '/^\d+\.\d+\.\d+$/', (string) $data['version'] ) ) {
        return (string) $data['version'];
    }

    return '';
}

/**
 * Nasazená verze u klientů — manifest elementsell-update.json (lokálně na update serveru, jinde přes HTTP).
 *
 * @param bool $force
 * @return string
 */
function elementsell_get_deployed_version( $force = false ) {
    $local_path = elementsell_get_local_update_manifest_path();

    if ( $local_path !== '' ) {
        $local_version = elementsell_read_deployed_version_from_manifest_path( $local_path );

        if ( $local_version !== '' ) {
            return $local_version;
        }
    }

    if ( function_exists( 'elementsell_get_remote_update_info' ) ) {
        $remote = elementsell_get_remote_update_info( $force );

        if ( is_array( $remote ) && ! empty( $remote['version'] ) ) {
            return (string) $remote['version'];
        }
    }

    return elementsell_get_plugin_version_from_file();
}

/**
 * Základ pro patch bump — vyšší z manifestu a plugin.php (po smazání releases/ platí kód).
 *
 * @return string
 */
function elementsell_get_publish_version_base() {
    $deployed = elementsell_get_deployed_version( true );
    $file     = elementsell_get_plugin_version_from_file();

    if ( $deployed === '' || ! preg_match( '/^\d+\.\d+\.\d+$/', $deployed ) ) {
        return $file;
    }

    if ( version_compare( $file, $deployed, '>' ) ) {
        if ( elementsell_is_failed_publish_version_drift( $file, $deployed ) ) {
            return $deployed;
        }

        return $file;
    }

    return $deployed;
}

/**
 * Verze po dalším publish (bez zápisu do plugin.php).
 *
 * @return string|WP_Error
 */
function elementsell_get_next_publish_version() {
    return elementsell_increment_patch_version( elementsell_get_publish_version_base() );
}

/**
 * Vymaže cache verze pluginu (OPcache, WP seznam pluginů, update transient).
 *
 * @return void
 */
function elementsell_invalidate_plugin_version_caches() {
    if ( defined( 'PF_PLUGIN_FILE' ) && is_string( PF_PLUGIN_FILE ) && PF_PLUGIN_FILE !== '' ) {
        clearstatcache( true, PF_PLUGIN_FILE );

        if ( function_exists( 'opcache_invalidate' ) ) {
            opcache_invalidate( PF_PLUGIN_FILE, true );
        }
    }

    if ( function_exists( 'wp_clean_plugins_cache' ) ) {
        wp_clean_plugins_cache( false );
    }

    if ( function_exists( 'elementsell_clear_update_info_cache' ) ) {
        elementsell_clear_update_info_cache();
    }
}

/**
 * @return bool True pokud konstanta PF_VERSION odpovídá verzi v plugin.php na disku.
 */
function elementsell_pf_version_matches_file() {
    if ( ! defined( 'PF_VERSION' ) || ! function_exists( 'elementsell_get_plugin_version_from_file' ) ) {
        return true;
    }

    $file = elementsell_get_plugin_version_from_file();

    if ( $file === '' || ! preg_match( '/^\d+\.\d+\.\d+$/', $file ) ) {
        return true;
    }

    return version_compare( (string) PF_VERSION, $file, '==' );
}

/**
 * Varování, když PHP konstanta PF_VERSION neodpovídá disku nebo nasazené verzi.
 *
 * @param string $deployed_ver
 * @return void
 */
function elementsell_render_plugin_version_memory_warning( $deployed_ver = '' ) {
    if ( ! defined( 'PF_VERSION' ) || ! function_exists( 'elementsell_get_plugin_version_from_file' ) ) {
        return;
    }

    $memory_ver = (string) PF_VERSION;
    $file_ver   = elementsell_get_plugin_version_from_file();
    $deployed   = $deployed_ver !== '' ? (string) $deployed_ver : '';

    if ( $deployed === '' && function_exists( 'elementsell_get_deployed_version' ) ) {
        $deployed = (string) elementsell_get_deployed_version( true );
    }

    $deployed_valid = $deployed !== '' && preg_match( '/^\d+\.\d+\.\d+$/', $deployed );
    $memory_stale   = $file_ver !== '' && version_compare( $memory_ver, $file_ver, '!=' );
    $file_behind    = $deployed_valid && $file_ver !== '' && version_compare( $file_ver, $deployed, '<' );
    $file_ahead     = $deployed_valid && $file_ver !== '' && version_compare( $file_ver, $deployed, '>' );
    $failed_drift   = $file_ahead
        && function_exists( 'elementsell_is_failed_publish_version_drift' )
        && elementsell_is_failed_publish_version_drift( $file_ver, $deployed );

    if ( ! $memory_stale && ! $file_behind && ! $failed_drift ) {
        return;
    }

    ?>
    <div class="notice notice-warning">
        <p>
            <?php if ( $memory_stale ) : ?>
                <strong>Verze v paměti (<?php echo esc_html( $memory_ver ); ?>) nesouhlasí s diskem (<?php echo esc_html( $file_ver ); ?>).</strong>
                Deaktivuj a aktivuj plugin, nebo obnov stránku (Ctrl+F5), případně vyčisti OPcache.
            <?php elseif ( $file_behind ) : ?>
                <strong>plugin.php (<?php echo esc_html( $file_ver ); ?>) je starší než nasazená verze (<?php echo esc_html( $deployed ); ?>).</strong>
                Při načtení administrace nebo publikování se verze na disku automaticky srovná s manifestem.
                <?php if ( version_compare( $memory_ver, $file_ver, '!=' ) ) : ?>
                    Obnov stránku (Ctrl+F5), aby WordPress načetl novou verzi z disku.
                <?php endif; ?>
            <?php elseif ( $failed_drift ) : ?>
                <strong>plugin.php (<?php echo esc_html( $file_ver ); ?>) je o patch napřed oproti manifestu (<?php echo esc_html( $deployed ); ?>), ale chybí ZIP v releases/.</strong>
                Při publikování se verze srovná automaticky.
            <?php endif; ?>
        </p>
    </div>
    <?php
}

/**
 * Vloží řádek Version: do docblock hlavičky plugin.php (chybí-li).
 *
 * @param string $content
 * @param string $version
 * @return array{content: string, replacements: int}
 */
function elementsell_insert_plugin_version_header_in_content( $content, $version ) {
    if ( preg_match( '/Version:/i', (string) $content ) ) {
        return [
            'content'      => (string) $content,
            'replacements' => 0,
        ];
    }

    $replacements = 0;
    $updated      = preg_replace(
        '/^(\s*\*\s*Plugin Name:[^\r\n]*(?:\r?\n))/mi',
        '${1} * Version: ' . $version . "\n",
        (string) $content,
        1,
        $replacements
    );

    return [
        'content'      => is_string( $updated ) ? $updated : (string) $content,
        'replacements' => is_string( $updated ) ? (int) $replacements : 0,
    ];
}

/**
 * @param string $content
 * @param string $version
 * @return array{content: string, replacements: int}
 */
function elementsell_replace_plugin_version_header_in_content( $content, $version ) {
    $content      = elementsell_repair_plugin_version_headers_in_content( (string) $content );
    $replacements = 0;

    $updated = preg_replace(
        elementsell_plugin_version_header_pattern(),
        '${1}' . $version,
        $content,
        1,
        $replacements
    );

    if ( is_string( $updated ) && $replacements > 0 ) {
        return [
            'content'      => $updated,
            'replacements' => (int) $replacements,
        ];
    }

    $replacements = 0;
    $updated      = preg_replace(
        '/Version:\s*[^\r\n]*/i',
        'Version: ' . $version,
        $content,
        1,
        $replacements
    );

    if ( is_string( $updated ) && $replacements > 0 ) {
        return [
            'content'      => $updated,
            'replacements' => (int) $replacements,
        ];
    }

    return elementsell_insert_plugin_version_header_in_content( $content, (string) $version );
}

/**
 * Vloží define( 'PF_VERSION', … ) za PF_PLUGIN_URL (chybí-li).
 *
 * @param string $content
 * @param string $version
 * @return array{content: string, replacements: int}
 */
function elementsell_insert_pf_version_define_in_content( $content, $version ) {
    if ( preg_match( elementsell_pf_version_define_pattern(), (string) $content ) ) {
        return [
            'content'      => (string) $content,
            'replacements' => 0,
        ];
    }

    $define_line  = "define( 'PF_VERSION', '" . $version . "' );";
    $replacements = 0;
    $updated      = preg_replace(
        "/(define\s*\(\s*'PF_PLUGIN_URL'\s*,[^\)]+\)\s*;\s*\r?\n)/",
        '${1}' . $define_line . "\n",
        (string) $content,
        1,
        $replacements
    );

    if ( is_string( $updated ) && $replacements > 0 ) {
        return [
            'content'      => $updated,
            'replacements' => (int) $replacements,
        ];
    }

    $updated = preg_replace(
        "/(define\s*\(\s*'PF_PLUGIN_DIR'\s*,[^\)]+\)\s*;\s*\r?\n)/",
        '${1}' . $define_line . "\n",
        (string) $content,
        1,
        $replacements
    );

    return [
        'content'      => is_string( $updated ) ? $updated : (string) $content,
        'replacements' => is_string( $updated ) ? (int) $replacements : 0,
    ];
}

/**
 * @param string $content
 * @param string $version
 * @return array{content: string, replacements: int}
 */
function elementsell_replace_pf_version_define_in_content( $content, $version ) {
    $content      = elementsell_repair_plugin_version_headers_in_content( (string) $content );
    $define_line  = "define( 'PF_VERSION', '" . $version . "' );";
    $replacements = 0;

    $updated = preg_replace(
        elementsell_pf_version_define_pattern(),
        $define_line,
        $content,
        1,
        $replacements
    );

    if ( is_string( $updated ) && $replacements > 0 ) {
        return [
            'content'      => $updated,
            'replacements' => (int) $replacements,
        ];
    }

    $replacements = 0;
    $updated      = preg_replace(
        "/define\s*\(\s*['\"]PF_VERSION['\"]\s*,\s*['\"][^'\"]*['\"]\s*\)\s*;/",
        $define_line,
        $content,
        1,
        $replacements
    );

    if ( is_string( $updated ) && $replacements > 0 ) {
        return [
            'content'      => $updated,
            'replacements' => (int) $replacements,
        ];
    }

    return elementsell_insert_pf_version_define_in_content( $content, (string) $version );
}

/**
 * @param string $version
 * @return string|WP_Error
 */
function elementsell_write_plugin_version_to_file( $version ) {
    if ( ! preg_match( '/^\d+\.\d+\.\d+$/', (string) $version ) ) {
        return new WP_Error( 'bad_version', 'Verze musí být ve formátu X.Y.Z.' );
    }

    if ( ! defined( 'PF_PLUGIN_FILE' ) || PF_PLUGIN_FILE === '' || ! is_readable( PF_PLUGIN_FILE ) ) {
        return new WP_Error( 'version_write', 'plugin.php nelze načíst (chybí soubor nebo oprávnění ke čtení).' );
    }

    if ( ! is_writable( PF_PLUGIN_FILE ) ) {
        $path = wp_normalize_path( PF_PLUGIN_FILE );

        return new WP_Error(
            'version_write',
            sprintf(
                'plugin.php není zapisovatelný (zkontrolujte chmod a vlastníka souboru). Cesta: %s',
                $path
            )
        );
    }

    $content = (string) file_get_contents( PF_PLUGIN_FILE );

    if ( $content === '' ) {
        return new WP_Error( 'version_write', 'plugin.php je prázdný nebo se nepodařilo načíst.' );
    }

    $content = elementsell_repair_plugin_version_headers_in_content( $content );

    $header_result = elementsell_replace_plugin_version_header_in_content( $content, (string) $version );
    $updated       = $header_result['content'];

    if ( 0 === (int) $header_result['replacements'] ) {
        $path = wp_normalize_path( PF_PLUGIN_FILE );

        return new WP_Error(
            'version_write',
            sprintf(
                'Nepodařilo se upravit hlavičku Version: v plugin.php (řádek s verzí nebyl nalezen). Cesta: %s',
                $path
            )
        );
    }

    $define_result = elementsell_replace_pf_version_define_in_content( $updated, (string) $version );
    $updated       = $define_result['content'];

    if ( 0 === (int) $define_result['replacements'] ) {
        $path = wp_normalize_path( PF_PLUGIN_FILE );

        return new WP_Error(
            'version_write',
            sprintf(
                'Nepodařilo se upravit PF_VERSION v plugin.php (define nebyl nalezen). Cesta: %s',
                $path
            )
        );
    }

    if ( $updated === $content ) {
        return new WP_Error( 'version_write', 'plugin.php se po úpravě verze nezměnil.' );
    }

    if ( false === file_put_contents( PF_PLUGIN_FILE, $updated ) ) {
        $path = wp_normalize_path( PF_PLUGIN_FILE );

        return new WP_Error(
            'version_write',
            sprintf(
                'Nepodařilo se uložit novou verzi do plugin.php (zápis selhal). Cesta: %s',
                $path
            )
        );
    }

    elementsell_invalidate_plugin_version_caches();

    $written = elementsell_get_plugin_version_from_file();

    if ( version_compare( $written, (string) $version, '!=' ) ) {
        return new WP_Error(
            'version_verify',
            sprintf(
                'plugin.php má po zápisu verzi %1$s, očekáváno %2$s.',
                $written,
                $version
            )
        );
    }

    return (string) $version;
}

/**
 * Srovná plugin.php s nasazenou verzí (např. po git deployi na update server).
 *
 * @return string Verze v plugin.php po synchronizaci.
 */
function elementsell_sync_plugin_version_to_deployed() {
    $deployed = elementsell_get_deployed_version( true );
    $file     = elementsell_get_plugin_version_from_file();

    if ( $deployed === '' || ! preg_match( '/^\d+\.\d+\.\d+$/', $deployed ) ) {
        return $file;
    }

    if ( version_compare( $file, $deployed, '>=' ) ) {
        return $file;
    }

    $written = elementsell_write_plugin_version_to_file( $deployed );

    return is_wp_error( $written ) ? $file : $written;
}

/**
 * Zvýší patch verzi v plugin.php a vrátí nové číslo.
 *
 * @return string|WP_Error
 */
function elementsell_bump_plugin_version_patch() {
    elementsell_sync_plugin_version_to_deployed();

    $new_version = elementsell_get_next_publish_version();

    if ( is_wp_error( $new_version ) ) {
        return $new_version;
    }

    return elementsell_write_plugin_version_to_file( $new_version );
}

/**
 * @param string $zip_entry
 * @return bool
 */
function elementsell_release_zip_entry_is_plugin_php( $zip_entry ) {
    $folder = preg_quote(
        function_exists( 'elementsell_get_release_zip_folder_name' )
            ? elementsell_get_release_zip_folder_name()
            : 'ElementSell',
        '#'
    );

    return (bool) preg_match( '#^' . $folder . '/plugin\.php$#i', (string) $zip_entry );
}

/**
 * @param string|null $version
 * @param string      $release_token Token pro distribuovaný build (stejný jako $ELEMENTSELL_SECRET).
 * @return string|WP_Error
 */
function elementsell_build_release_zip( $version = null, $release_token = '' ) {
    if ( ! class_exists( 'ZipArchive' ) ) {
        return new WP_Error( 'no_zip', 'Na serveru chybí PHP rozšíření ZipArchive.' );
    }

    elementsell_ensure_distributed_token_template_in_plugin_file();

    $version       = $version ? (string) $version : ( defined( 'PF_VERSION' ) ? PF_VERSION : elementsell_get_plugin_version_from_file() );
    $release_token = trim( (string) $release_token );
    $zip_path      = trailingslashit( get_temp_dir() ) . 'elementsell-' . $version . '-' . wp_generate_password( 8, false, false ) . '.zip';
    $zip      = new ZipArchive();

    if ( true !== $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) {
        return new WP_Error( 'zip_open', 'ZIP soubor se nepodařilo vytvořit.' );
    }

    $root = wp_normalize_path( PF_PLUGIN_DIR );
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator( $root, FilesystemIterator::SKIP_DOTS )
    );

    foreach ( $iterator as $file_info ) {
        if ( ! $file_info->isFile() ) {
            continue;
        }

        $absolute = wp_normalize_path( $file_info->getPathname() );
        $relative = ltrim( substr( $absolute, strlen( $root ) ), '/' );

        if ( elementsell_release_should_exclude_path( $relative ) ) {
            continue;
        }

        $zip_folder = function_exists( 'elementsell_get_release_zip_folder_name' )
            ? elementsell_get_release_zip_folder_name()
            : 'ElementSell';
        $zip_name   = $zip_folder . '/' . $relative;

        if ( $relative === 'plugin.php' ) {
            $content = elementsell_prepare_release_plugin_php_content(
                (string) file_get_contents( $absolute ),
                $version,
                $release_token
            );

            if ( is_wp_error( $content ) ) {
                $zip->close();

                return $content;
            }

            if ( false === $zip->addFromString( $zip_name, $content ) ) {
                $zip->close();

                return new WP_Error( 'zip_add', 'ZIP souboru plugin.php se nepodařilo přidat.' );
            }

            continue;
        }

        $zip->addFile( $absolute, $zip_name );
    }

    $zip->close();

    if ( ! file_exists( $zip_path ) ) {
        return new WP_Error( 'zip_missing', 'ZIP soubor nebyl vytvořen.' );
    }

    return $zip_path;
}

/**
 * @param mixed $version
 * @return bool
 */
function elementsell_is_release_version_format( $version ) {
    return is_string( $version ) && preg_match( '/^\d+\.\d+\.\d+$/', $version ) === 1;
}

/**
 * Normalizuje semver z query parametru (3.2.17 nebo dash-encoded 3-2-17).
 *
 * @param mixed $raw
 * @return string
 */
function elementsell_sanitize_release_version( $raw ) {
    $raw = trim( sanitize_text_field( wp_unslash( (string) $raw ) ) );

    if ( elementsell_is_release_version_format( $raw ) ) {
        return $raw;
    }

    if ( preg_match( '/^\d+-\d+-\d+$/', $raw ) ) {
        $candidate = str_replace( '-', '.', $raw );

        if ( elementsell_is_release_version_format( $candidate ) ) {
            return $candidate;
        }
    }

    return '';
}

/**
 * @param string $version
 * @return string
 */
function elementsell_encode_release_version_for_query( $version ) {
    $version = elementsell_sanitize_release_version( $version );

    return $version === '' ? '' : str_replace( '.', '-', $version );
}

/**
 * Krátkodobě uloží verzi ZIP pro stažení po redirectu (bez teček v URL).
 *
 * @param string $version
 * @return string Download token nebo prázdný řetězec.
 */
function elementsell_set_pending_release_zip_download( $version ) {
    $version = elementsell_sanitize_release_version( $version );
    $user_id = (int) get_current_user_id();

    if ( $version === '' || $user_id <= 0 ) {
        return '';
    }

    $token = wp_generate_password( 12, false, false );
    $key   = 'elementsell_zip_dl_' . $user_id . '_' . $token;

    set_transient( $key, $version, 10 * MINUTE_IN_SECONDS );

    return $token;
}

/**
 * @param string $token
 * @return string
 */
function elementsell_get_pending_release_zip_download( $token ) {
    $token   = preg_replace( '/[^a-zA-Z0-9]/', '', (string) $token );
    $user_id = (int) get_current_user_id();

    if ( $token === '' || $user_id <= 0 ) {
        return '';
    }

    $key     = 'elementsell_zip_dl_' . $user_id . '_' . $token;
    $version = get_transient( $key );

    delete_transient( $key );

    return elementsell_sanitize_release_version( $version );
}

/**
 * @param string $version
 * @return string
 */
function elementsell_get_release_zip_filename( $version ) {
    return 'elementsell-' . (string) $version . '.zip';
}

/**
 * @param string $version
 * @return string
 */
function elementsell_get_release_zip_file_path( $version ) {
    return trailingslashit( ABSPATH ) . 'releases/' . elementsell_get_release_zip_filename( $version );
}

/**
 * Veřejná URL ZIP v releases/ (manifest, klienti).
 *
 * @param string $filename
 * @return string
 */
function elementsell_get_release_zip_public_download_url( $filename ) {
    $filename = (string) $filename;

    if ( function_exists( 'elementsell_should_sync_publish_endpoint' ) && elementsell_should_sync_publish_endpoint() ) {
        return home_url( '/releases/' . rawurlencode( $filename ) );
    }

    $base = rtrim( elementsell_get_update_server_public_base_url(), '/' );

    return $base . '/releases/' . rawurlencode( $filename );
}

/**
 * Admin URL pro spolehlivé stažení ZIP po vytvoření (stejný host jako wp-admin).
 *
 * @param string $version
 * @return string
 */
function elementsell_get_release_zip_admin_download_url( $version ) {
    $version = elementsell_sanitize_release_version( $version );

    if ( $version === '' ) {
        return '';
    }

    $args      = [
        'action' => 'elementsell_download_built_release_zip',
    ];
    $nonce_key = $version;
    $token     = elementsell_set_pending_release_zip_download( $version );

    if ( $token !== '' ) {
        $args['dl']  = $token;
        $nonce_key   = $token;
    } else {
        $args['es_zip_ver'] = elementsell_encode_release_version_for_query( $version );
    }

    return wp_nonce_url(
        add_query_arg( $args, admin_url( 'admin-post.php' ) ),
        'elementsell_download_built_release_zip_' . $nonce_key
    );
}

/**
 * Odešle ZIP do prohlížeče jako přílohu.
 *
 * @param string $file_path
 * @param string $filename
 * @return void
 */
function elementsell_stream_release_zip_file( $file_path, $filename ) {
    $file_path = wp_normalize_path( (string) $file_path );
    $filename  = sanitize_file_name( (string) $filename );

    if ( $filename === '' || ! is_readable( $file_path ) ) {
        wp_die(
            esc_html__( 'ZIP soubor nebyl nalezen nebo není čitelný.', 'platebni-formular' ),
            '',
            [ 'response' => 404 ]
        );
    }

    if ( headers_sent() ) {
        wp_die(
            esc_html__( 'Stažení ZIP se nepodařilo spustit (hlavičky již byly odeslány).', 'platebni-formular' ),
            '',
            [ 'response' => 500 ]
        );
    }

    nocache_headers();
    header( 'Content-Type: application/zip' );
    header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    header( 'Content-Length: ' . (string) filesize( $file_path ) );

    readfile( $file_path );
    exit;
}

/**
 * Uloží ZIP do releases/ bez změny manifestu.
 *
 * @param string $zip_path
 * @param string $version
 * @return array<string, mixed>|WP_Error
 */
function elementsell_save_release_zip_to_releases_dir( $zip_path, $version ) {
    $version = elementsell_sanitize_release_version( $version );

    if ( $version === '' ) {
        return new WP_Error( 'bad_version', 'Verze musí být ve formátu X.Y.Z.' );
    }

    if ( ! file_exists( $zip_path ) ) {
        return new WP_Error( 'zip_missing', 'ZIP soubor neexistuje.' );
    }

    if ( ! class_exists( 'ZipArchive' ) ) {
        return new WP_Error( 'no_zip', 'Na serveru chybí PHP rozšíření ZipArchive.' );
    }

    $zip = new ZipArchive();

    if ( true !== $zip->open( $zip_path ) ) {
        return new WP_Error( 'zip_open', 'ZIP soubor se nepodařilo ověřit.' );
    }

    $has_plugin_file = false;
    $plugin_content  = '';

    for ( $i = 0; $i < $zip->numFiles; $i++ ) {
        $name = (string) $zip->getNameIndex( $i );

        if ( elementsell_release_zip_entry_is_plugin_php( $name ) ) {
            $has_plugin_file = true;
            $plugin_content  = (string) $zip->getFromIndex( $i );
            break;
        }
    }

    $zip->close();

    if ( ! $has_plugin_file ) {
        $folder = function_exists( 'elementsell_get_release_zip_folder_name' )
            ? elementsell_get_release_zip_folder_name()
            : 'ElementSell';

        return new WP_Error( 'zip_invalid', 'ZIP musí obsahovat složku ' . $folder . '/ s plugin.php.' );
    }

    $plugin_check = elementsell_validate_release_zip_plugin_content( $plugin_content, (string) $version );

    if ( is_wp_error( $plugin_check ) ) {
        return $plugin_check;
    }

    $releases_dir = trailingslashit( ABSPATH ) . 'releases';

    if ( ! is_dir( $releases_dir ) ) {
        wp_mkdir_p( $releases_dir );
    }

    $filename = elementsell_get_release_zip_filename( $version );
    $target   = $releases_dir . '/' . $filename;

    if ( ! copy( $zip_path, $target ) ) {
        return new WP_Error( 'copy_failed', 'ZIP se nepodařilo uložit do releases/.' );
    }

    $download_url = elementsell_get_release_zip_public_download_url( $filename );

    return [
        'ok'           => true,
        'version'      => $version,
        'filename'     => $filename,
        'file_path'    => $target,
        'download_url' => $download_url,
        'admin_download_url' => elementsell_get_release_zip_admin_download_url( $version ),
    ];
}

/**
 * ZIP aktuální verze z plugin.php do releases/ — bez bumpu a bez manifestu.
 *
 * @return array<string, mixed>|WP_Error
 */
function elementsell_build_release_zip_only() {
    if ( ! elementsell_is_simple_publish_allowed() ) {
        return new WP_Error( 'simple_publish_disabled', 'Jednoduché vypuštění není na tomto webu povoleno.' );
    }

    if ( ! function_exists( 'elementsell_should_sync_publish_endpoint' ) || ! elementsell_should_sync_publish_endpoint() ) {
        return new WP_Error( 'not_update_server', 'Vytvoření ZIP funguje jen na elementsell.cz.' );
    }

    if ( function_exists( 'elementsell_force_sync_publish_endpoint_to_webroot' ) ) {
        elementsell_force_sync_publish_endpoint_to_webroot();
    }

    $token   = elementsell_get_local_release_token();
    $version = elementsell_get_plugin_version_from_file();

    $zip_path = elementsell_build_release_zip( $version, $token );

    if ( is_wp_error( $zip_path ) ) {
        return $zip_path;
    }

    $result = elementsell_save_release_zip_to_releases_dir( $zip_path, $version );

    if ( file_exists( $zip_path ) ) {
        wp_delete_file( $zip_path );
    }

    if ( is_wp_error( $result ) ) {
        return $result;
    }

    $result['message'] = sprintf(
        'ZIP %s byl vytvořen (verze %s, manifest beze změny%s).',
        $result['filename'],
        $version,
        $token === '' ? ', bez distribuovaného tokenu' : ''
    );

    return $result;
}

/**
 * Vypuštění na stejném webu jako update server (elementsell.cz).
 * Po ověření hesla validate_only se ZIP uloží přímo — bez HTTP loopback uploadu.
 *
 * @param string $zip_path
 * @param string $changelog
 * @param string $version
 * @return array<string, mixed>|WP_Error
 */
function elementsell_publish_release_on_local_server( $zip_path, $changelog, $version ) {
    if ( ! function_exists( 'elementsell_should_sync_publish_endpoint' ) || ! elementsell_should_sync_publish_endpoint() ) {
        return new WP_Error( 'no_local_publish', 'Lokální vypuštění není na tomto webu dostupné.' );
    }

    elementsell_force_sync_publish_endpoint_to_webroot();

    $saved = elementsell_save_release_zip_to_releases_dir( $zip_path, $version );

    if ( is_wp_error( $saved ) ) {
        return $saved;
    }

    $download_url = (string) $saved['download_url'];

    $changelog_lines = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', (string) $changelog ) ) );

    if ( ! empty( $changelog_lines ) ) {
        $changelog_html = '<ul><li>' . implode( '</li><li>', array_map( 'esc_html', $changelog_lines ) ) . '</li></ul>';
    } else {
        $changelog_html = '<p>Aktualizace ElementSell.</p>';
    }

    $payload = [
        'name'           => 'ElementSell',
        'slug'           => 'elementsell',
        'version'        => $version,
        'author'         => 'ElementSell',
        'author_profile' => 'https://elementsell.cz',
        'homepage'       => 'https://elementsell.cz',
        'requires'       => '5.8',
        'tested'         => '6.7',
        'requires_php'   => '7.4',
        'download_url'   => $download_url,
        'sections'       => [
            'description' => 'Český platební formulář pro prodej online produktů v Elementoru.',
            'changelog'   => '<h4>Verze ' . esc_html( $version ) . '</h4>' . $changelog_html,
        ],
    ];

    $json_path = trailingslashit( ABSPATH ) . 'elementsell-update.json';

    if ( false === file_put_contents( $json_path, wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) ) ) {
        return new WP_Error( 'json_write', 'Manifest aktualizace se nepodařilo uložit.' );
    }

    return [
        'ok'           => true,
        'version'      => $version,
        'download_url' => $download_url,
        'message'      => 'Verze ' . $version . ' byla vypuštěna.',
    ];
}

/**
 * @param string $zip_path
 * @param string $changelog
 * @param string $release_password
 * @param string $version
 * @return array<string, mixed>|WP_Error
 */
function elementsell_publish_release_via_curl( $zip_path, $changelog, $release_password, $version ) {
    if ( ! function_exists( 'curl_init' ) || ! class_exists( 'CURLFile' ) ) {
        return new WP_Error( 'no_curl', 'Na serveru chybí cURL pro nahrání ZIP.' );
    }

    $zip_real = realpath( $zip_path );

    if ( false === $zip_real || ! is_readable( $zip_real ) ) {
        return new WP_Error( 'zip_missing', 'ZIP soubor neexistuje nebo není čitelný.' );
    }

    $curl = curl_init();

    if ( false === $curl ) {
        return new WP_Error( 'no_curl', 'cURL se nepodařilo spustit.' );
    }

    $filename = 'elementsell-' . $version . '.zip';
    $fields   = [
        'token'      => $release_password,
        'version'    => $version,
        'changelog'  => (string) $changelog,
        'plugin_zip' => new CURLFile( $zip_real, 'application/zip', $filename ),
    ];

    $curl_options = [
        CURLOPT_URL            => elementsell_get_release_publish_url(),
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $fields,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 300,
    ];

    if ( defined( 'CURLOPT_SAFE_UPLOAD' ) ) {
        $curl_options[ CURLOPT_SAFE_UPLOAD ] = true;
    }

    curl_setopt_array( $curl, $curl_options );

    $body = curl_exec( $curl );
    $code = (int) curl_getinfo( $curl, CURLINFO_HTTP_CODE );
    $error = curl_error( $curl );
    curl_close( $curl );

    if ( false === $body ) {
        return new WP_Error( 'curl_failed', 'Nahrání ZIP selhalo: ' . $error );
    }

    $data = json_decode( (string) $body, true );

    if ( $code < 200 || $code >= 300 ) {
        $message = is_array( $data ) && ! empty( $data['message'] )
            ? (string) $data['message']
            : 'Server vrátil chybu HTTP ' . $code;

        if ( $message === 'Neplatný token.' ) {
            $message = 'Neplatné heslo.';
        }

        return new WP_Error( 'publish_failed', $message );
    }

    if ( ! is_array( $data ) || empty( $data['ok'] ) ) {
        $message = is_array( $data ) && ! empty( $data['message'] )
            ? (string) $data['message']
            : 'Vypuštění aktualizace selhalo.';

        return new WP_Error( 'publish_failed', $message );
    }

    return $data;
}

/**
 * @param string $zip_path
 * @param string $changelog
 * @param string $release_password
 * @param string|null $version
 * @return array<string, mixed>|WP_Error
 */
function elementsell_publish_release_to_server( $zip_path, $changelog = '', $release_password = '', $version = null ) {
    $release_password = trim( (string) $release_password );
    $version          = $version ? (string) $version : ( defined( 'PF_VERSION' ) ? PF_VERSION : elementsell_get_plugin_version_from_file() );

    if ( $release_password === '' ) {
        return new WP_Error( 'no_password', 'Zadejte heslo pro vypuštění aktualizace.' );
    }

    if ( ! file_exists( $zip_path ) ) {
        return new WP_Error( 'zip_missing', 'ZIP soubor neexistuje.' );
    }

    if ( function_exists( 'elementsell_force_sync_publish_endpoint_to_webroot' ) ) {
        elementsell_force_sync_publish_endpoint_to_webroot();
    }

    if ( function_exists( 'elementsell_should_sync_publish_endpoint' ) && elementsell_should_sync_publish_endpoint() ) {
        $local = elementsell_publish_release_on_local_server( $zip_path, $changelog, $version );

        if ( ! is_wp_error( $local ) ) {
            elementsell_invalidate_plugin_version_caches();

            return $local;
        }
    }

    $remote = elementsell_publish_release_via_curl( $zip_path, $changelog, $release_password, $version );

    if ( ! is_wp_error( $remote ) ) {
        elementsell_invalidate_plugin_version_caches();
    }

    return $remote;
}

/**
 * Jedna kontrola připravenosti publish (pro admin i CLI test).
 *
 * @param string $id
 * @param string $label
 * @param bool   $ok
 * @param string $detail
 * @param string $fix
 * @return array<string, mixed>
 */
function elementsell_publish_readiness_check_item( $id, $label, $ok, $detail, $fix = '' ) {
    return [
        'id'     => (string) $id,
        'label'  => (string) $label,
        'ok'     => (bool) $ok,
        'detail' => (string) $detail,
        'fix'    => (string) $fix,
    ];
}

/**
 * Spustí kontroly připravenosti publish (dry-run, bez zápisu manifestu).
 *
 * @return array{ok: bool, checks: array<int, array<string, mixed>>, summary: string}
 */
function elementsell_run_publish_readiness_checks() {
    $checks = [];

    if ( ! defined( 'PF_PLUGIN_FILE' ) || PF_PLUGIN_FILE === '' ) {
        return [
            'ok'      => false,
            'checks'  => [
                elementsell_publish_readiness_check_item(
                    'plugin_file',
                    'plugin.php',
                    false,
                    'Konstanta PF_PLUGIN_FILE není definovaná.',
                    'Ověřte, že běžíte v kontextu pluginu ElementSell.'
                ),
            ],
            'summary' => 'FAIL — plugin.php nelze najít.',
        ];
    }

    $plugin_path = wp_normalize_path( PF_PLUGIN_FILE );
    $readable    = is_readable( $plugin_path );
    $writable    = is_writable( $plugin_path );

    $checks[] = elementsell_publish_readiness_check_item(
        'plugin_rw',
        'plugin.php čitelný a zapisovatelný',
        $readable && $writable,
        $readable
            ? ( $writable ? 'Soubor existuje a je zapisovatelný: ' . $plugin_path : 'Soubor existuje, ale není zapisovatelný: ' . $plugin_path )
            : 'Soubor nelze načíst: ' . $plugin_path,
        $writable ? '' : 'Nastavte chmod/vlastníka souboru plugin.php (typicky 644, vlastník = uživatel webserveru).'
    );

    $content = $readable ? (string) file_get_contents( $plugin_path ) : '';
    $header  = $content !== '' ? elementsell_extract_plugin_version_from_header_content( $content ) : '';
    $semver  = preg_match( '/^\d+\.\d+\.\d+$/', $header ) === 1;

    $checks[] = elementsell_publish_readiness_check_item(
        'version_header',
        'Hlavička Version: (semver)',
        $semver,
        $semver ? 'Version: ' . $header : 'Neplatná nebo chybějící verze v hlavičce plugin.php.',
        $semver ? '' : 'Opravte řádek * Version: X.Y.Z v hlavičce plugin.php.'
    );

    $pf_defined = $content !== '' && (bool) preg_match( elementsell_pf_version_define_pattern(), $content );
    $pf_version = $content !== '' ? elementsell_extract_pf_version_from_define_content( $content ) : '';

    $checks[] = elementsell_publish_readiness_check_item(
        'pf_version',
        'define PF_VERSION',
        $pf_defined && $semver && $pf_version === $header,
        $pf_defined
            ? 'PF_VERSION = ' . $pf_version . ( $pf_version === $header ? '' : ' (nesouhlasí s hlavičkou ' . $header . ')' )
            : 'V plugin.php chybí define( \'PF_VERSION\', ... ).',
        $pf_defined ? 'Srovnejte PF_VERSION s hlavičkou Version:.' : 'Přidejte define( \'PF_VERSION\', \'X.Y.Z\' ); za konstanty pluginu.'
    );

    $has_template = $content !== '' && elementsell_has_injectable_distributed_token_in_content( $content );
    $can_insert   = false;

    if ( $content !== '' ) {
        $with_template = elementsell_ensure_distributed_token_template_in_content( $content );
        $can_insert    = elementsell_has_injectable_distributed_token_in_content( $with_template );
    }

    $checks[] = elementsell_publish_readiness_check_item(
        'token_template',
        'Šablona ELEMENTSELL_DISTRIBUTED_TOKEN',
        $has_template || $can_insert,
        $has_template
            ? 'Šablona tokenu je v plugin.php přítomná.'
            : ( $can_insert ? 'Šablona chybí, ale lze ji automaticky vložit.' : 'Token blok nelze najít ani vložit.' ),
        $has_template || $can_insert ? '' : 'Přidejte za PF_VERSION blok if ( ! defined( \'ELEMENTSELL_DISTRIBUTED_TOKEN\' ) ) { define( ..., \'\' ); }'
    );

    $dry_token   = 'elementsell_publish_dry_run_token';
    $dry_version = $semver ? $header : '0.0.1';
    $dry_inject  = $content !== '' ? elementsell_inject_distributed_token_into_plugin_content( $content, $dry_token ) : '';
    $inject_ok   = is_string( $dry_inject ) && strpos( $dry_inject, $dry_token ) !== false;

    $checks[] = elementsell_publish_readiness_check_item(
        'token_inject',
        'Dry-run vložení tokenu',
        $inject_ok,
        $inject_ok ? 'Token lze vložit do plugin.php pro release ZIP.' : 'Vložení tokenu selhalo i po opravě šablony.',
        $inject_ok ? '' : 'Nahrajte aktualizovaný plugin-release.php (release-logic v' . elementsell_get_release_logic_version() . ') a ověřte blok ELEMENTSELL_DISTRIBUTED_TOKEN v plugin.php.'
    );

    $deployed = function_exists( 'elementsell_get_deployed_version' )
        ? elementsell_get_deployed_version( true )
        : '';
    $file_ver = $content !== '' ? $header : '';
    $drift    = $semver && $deployed !== '' && elementsell_is_failed_publish_version_drift( $file_ver, $deployed );
    $next_ver = function_exists( 'elementsell_get_next_publish_version' )
        ? elementsell_get_next_publish_version()
        : '';
    $next_str = is_wp_error( $next_ver ) ? '—' : (string) $next_ver;

    $checks[] = elementsell_publish_readiness_check_item(
        'version_sync',
        'Manifest vs plugin.php',
        ! $drift,
        $deployed !== ''
            ? sprintf(
                'Manifest: %s, plugin.php: %s, další publish: %s%s',
                $deployed,
                $file_ver,
                $next_str,
                $drift ? ' — detekován neúspěšný publish (plugin.php o 1 patch napřed bez ZIP)' : ''
            )
            : 'Manifest elementsell-update.json nelze načíst.',
        $drift ? 'Spusťte tento test znovu — reconcile při publishu srovná verzi. Případně ručně nastavte Version: na verzi manifestu.' : ( $deployed === '' ? 'Ověřte soubor elementsell-update.json v kořeni webu.' : '' )
    );

    $releases_dir  = trailingslashit( ABSPATH ) . 'releases';
    $releases_ok   = is_dir( $releases_dir ) && is_writable( $releases_dir );
    $checks[] = elementsell_publish_readiness_check_item(
        'releases_dir',
        'Složka releases/ zapisovatelná',
        $releases_ok,
        is_dir( $releases_dir )
            ? ( $releases_ok ? 'releases/ existuje a je zapisovatelná.' : 'releases/ existuje, ale není zapisovatelná.' )
            : 'Složka releases/ neexistuje (bude vytvořena při publishi).',
        $releases_ok ? '' : 'Vytvořte releases/ v kořeni webu a nastavte oprávnění zápisu.'
    );

    $manifest_path = function_exists( 'elementsell_get_local_update_manifest_path' )
        ? elementsell_get_local_update_manifest_path()
        : trailingslashit( ABSPATH ) . 'elementsell-update.json';
    $manifest_ok = is_readable( $manifest_path );

    $checks[] = elementsell_publish_readiness_check_item(
        'manifest',
        'elementsell-update.json čitelný',
        $manifest_ok,
        $manifest_ok ? 'Manifest: ' . wp_normalize_path( $manifest_path ) : 'Manifest nelze načíst: ' . wp_normalize_path( $manifest_path ),
        $manifest_ok ? '' : 'Ověřte existenci a oprávnění souboru elementsell-update.json.'
    );

    $zip_ok    = false;
    $zip_detail = 'Dry-run ZIP nebyl spuštěn.';
    $zip_fix    = '';

    if ( $semver && $inject_ok ) {
        $prepared = elementsell_prepare_release_plugin_php_content( $content, $dry_version, $dry_token );

        if ( is_wp_error( $prepared ) ) {
            $zip_detail = 'Příprava plugin.php selhala: ' . $prepared->get_error_message();
            $zip_fix    = 'Opravte chybu v plugin.php dle popisu výše.';
        } else {
            $validate = elementsell_validate_release_zip_plugin_content( $prepared, $dry_version );

            if ( is_wp_error( $validate ) ) {
                $zip_detail = 'Validace ZIP obsahu selhala: ' . $validate->get_error_message();
                $zip_fix    = 'Opravte hlavičku plugin.php a Update URI.';
            } else {
                $zip_ok = true;

                if ( ! class_exists( 'ZipArchive' ) || ! method_exists( 'ZipArchive', 'open' ) ) {
                    $zip_detail = 'Obsah plugin.php pro ZIP je v pořádku. Pozor: na serveru chybí PHP rozšíření ZipArchive.';
                    $zip_fix    = 'Povolte php-zip / ZipArchive na hostingu.';
                } else {
                    $zip_detail = 'Příprava plugin.php pro ZIP prošla (bez vytvoření souboru).';
                }
            }
        }
    } elseif ( ! $semver || ! $inject_ok ) {
        $zip_detail = 'Dry-run ZIP přeskočen — nejdřív opravte verzi nebo token.';
        $zip_fix    = 'Vyřešte chyby u verze a tokenu výše.';
    }

    $checks[] = elementsell_publish_readiness_check_item(
        'zip_dry_run',
        'Dry-run sestavení ZIP',
        $zip_ok,
        $zip_detail,
        $zip_fix
    );

    $all_ok = true;

    foreach ( $checks as $check ) {
        if ( empty( $check['ok'] ) ) {
            $all_ok = false;
            break;
        }
    }

    return [
        'ok'      => $all_ok,
        'checks'  => $checks,
        'summary' => $all_ok
            ? 'PASS — publish by měl proběhnout. Můžete kliknout na Publikovat.'
            : 'FAIL — opravte položky označené červeně před publikováním.',
    ];
}
