<?php
/**
 * Structure Viewer — Uninstall cleanup
 *
 * This class replaces the traditional uninstall.php because Freemius's
 * uninstall-feedback flow is incompatible with uninstall.php being present:
 *
 *   https://freemius.com/help/documentation/wordpress-sdk/deploying-your-plugin/
 *
 * Instead, the cleanup() method below is hooked to Freemius's
 * `after_uninstall` action (see structure-viewer.php). Freemius fires that
 * action AFTER it has reported the uninstall event and collected any user
 * feedback, so the plugin's state gets wiped at the right time.
 *
 * Removes:
 *   • Options:     structureviewer_settings
 *   • Transients:  structureviewer_file_count_*, structureviewer_top_plugins,
 *                  sv_welcome_modal_*, sv_comp_*, sv_fp_*, sv_fp_dl_*
 *   • Filesystem:  wp-content/uploads/sv-temp/
 *                  wp-content/uploads/sv-fp-temp-*
 *                  wp-content/uploads/sv-fp-output-*
 *                  wp-content/uploads/sv-fp-downloads/
 *                  wp-content/uploads/*-flatpack-*.zip   (pre-v2.2.1 location)
 *
 * Freemius licence/activation data is deliberately NOT removed here — that
 * data is managed by the Freemius SDK. If the user wants a complete reset
 * they can deactivate their licence via the Freemius menu before uninstalling.
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit( 'Direct access denied.' );
}

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- SV_ is the established plugin prefix used consistently across all plugin classes.
final class SV_Uninstall {

    /**
     * Entry point invoked by Freemius's `after_uninstall` action.
     * Safe to call multiple times — every operation is idempotent.
     */
    public static function cleanup() {
        self::delete_options();
        self::delete_transients();
        self::delete_filesystem_artifacts();
    }

    // ── Options ──────────────────────────────────────────────────────────────

    private static function delete_options() {
        delete_option( 'structureviewer_settings' );
    }

    // ── Transients ───────────────────────────────────────────────────────────

    /**
     * Wipe every transient this plugin writes.
     *
     * On single-site installs with the default DB object cache, transients are
     * stored in wp_options as `_transient_<key>` + `_transient_timeout_<key>`.
     * A direct SQL sweep covers both rows in one pass and picks up anything
     * left by older versions of the plugin.
     *
     * For object-cache installs (Redis, Memcached, etc.) we additionally call
     * delete_transient() for the keys we can name exactly — the SQL sweep is
     * a no-op there, but the API call flushes the cached value.
     */
    private static function delete_transients() {
        global $wpdb;

        $patterns = [
            'structureviewer_file_count_%',
            'structureviewer_top_plugins',
            'sv_welcome_modal_%',
            'sv_comp_%',
            'sv_fp_%',
            'sv_fp_dl_%',
        ];

        foreach ( $patterns as $pattern ) {
            // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
                    '_transient_' . $pattern,
                    '_transient_timeout_' . $pattern
                )
            );
            // phpcs:enable
        }

        // Also flush via API for the one transient we can name exactly.
        // Per-user and per-session transients use runtime-generated keys we
        // can't enumerate at uninstall time; the SQL sweep above is the path
        // that catches those on single-site installs.
        delete_transient( 'structureviewer_top_plugins' );
    }

    // ── Filesystem ───────────────────────────────────────────────────────────

    private static function delete_filesystem_artifacts() {
        $upload = wp_upload_dir();
        if ( empty( $upload['basedir'] ) || ! is_string( $upload['basedir'] ) ) {
            return;
        }
        $base = $upload['basedir'];

        // Comparator upload staging
        self::rmdir_recursive( $base . '/sv-temp' );

        // FlatPack temp and output dirs from any interrupted run
        foreach ( (array) glob( $base . '/sv-fp-temp-*', GLOB_ONLYDIR ) as $dir ) {
            self::rmdir_recursive( $dir );
        }
        foreach ( (array) glob( $base . '/sv-fp-output-*', GLOB_ONLYDIR ) as $dir ) {
            self::rmdir_recursive( $dir );
        }

        // FlatPack gated download root (v2.2.1+)
        self::rmdir_recursive( $base . '/sv-fp-downloads' );

        // Legacy pre-v2.2.1 output ZIPs dropped directly at the uploads root
        foreach ( (array) glob( $base . '/*-flatpack-*.zip' ) as $zip ) {
            @unlink( $zip ); // phpcs:ignore
        }
    }

    /**
     * Recursively delete a directory. Copy of the helper used in
     * SV_Pro_FlatPack, duplicated here because Pro code is not loaded during
     * the uninstall-from-wp-admin code path on non-licensed installs.
     */
    private static function rmdir_recursive( $dir ) {
        if ( ! is_dir( $dir ) ) {
            return;
        }
        $items = @scandir( $dir ); // phpcs:ignore
        if ( ! is_array( $items ) ) {
            return;
        }
        foreach ( $items as $item ) {
            if ( $item === '.' || $item === '..' ) {
                continue;
            }
            $path = $dir . DIRECTORY_SEPARATOR . $item;
            if ( is_dir( $path ) && ! is_link( $path ) ) {
                self::rmdir_recursive( $path );
            } else {
                @unlink( $path ); // phpcs:ignore
            }
        }
        @rmdir( $dir ); // phpcs:ignore
    }
}
