<?php

namespace Simply_Static;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * The core plugin class
 */
class Plugin {

	/**
	 * The slug of the plugin; used in actions, filters, i18n, table names, etc.
	 *
	 * @var string
	 */
	const SLUG = 'simply-static';

	/**
	 * Singleton instance.
	 *
	 * @var Simply_Static
	 */
	protected static $instance = null;

	/**
	 * An instance of the options structure containing all options for this plugin
	 *
	 * @var Simply_Static\Options
	 */
	protected $options = null;

	/**
	 * View object.
	 *
	 * @var \Simply_Static\View
	 */
	protected $view = null;

	/**
	 * Archive creation process
	 *
	 * @var \Simply_Static\Archive_Creation_Job
	 */
	protected $archive_creation_job = null;

	/**
	 * Current page name.
	 *
	 * @var string
	 */
	protected $current_page = '';

	/**
	 * @var null|\Simply_Static\Page_Handlers
	 */
	protected $page_handlers = null;

	/**
	 * @var null|\Simply_Static\Integrations
	 */
	protected $integrations = null;


	/**
	 * Return an instance of the Simply Static plugin
	 * @return Simply_Static
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
			self::$instance->includes();


   add_action( 'activated_plugin', array( '\\Simply_Static\\Util', 'maybe_auto_include_activated_plugin' ), 10, 2 );
   add_action( 'deactivated_plugin', array( '\\Simply_Static\\Util', 'maybe_auto_remove_deactivated_plugin' ), 10, 2 );
   add_action( 'after_switch_theme', array( '\\Simply_Static\\Util', 'maybe_auto_include_active_theme' ) );

			// Apply hooks after init to avoid loading issues.
			add_action( 'init', function () {
				// Run export via WP-Cron.
				add_action( 'simply_static_site_export_cron', array( self::$instance, 'run_static_export' ) );

				// Filters.
				add_filter( 'simplystatic.archive_creation_job.task_list', array(
					self::$instance,
					'filter_task_list'
				), 10, 2 );

				// Maybe clear local directory.
				add_action( 'ss_after_setup_task', array( self::$instance, 'maybe_clear_directory' ) );

				// Add quick link to the plugin page.
				add_filter( 'plugin_action_links_simply-static/simply-static.php', array(
					self::$instance,
					'add_quick_links'
				) );

				// Handle Basic Auth.
				add_filter( 'http_request_args', array( self::$instance, 'add_http_filters' ), 10, 2 );

				// Set up integrations.
				self::$instance->integrations = new Integrations();
				self::$instance->integrations->load();

				// Set up defaults.
				self::$instance->options              = Options::instance();
				self::$instance->view                 = new View();
				$archive_job_class = apply_filters( 'ss_archive_creation_job_class', '\\Simply_Static\\Archive_Creation_Job' );
				self::$instance->archive_creation_job = new $archive_job_class();
				self::$instance->page_handlers        = new Page_Handlers();

				// Set up pagination.
				$page                         = isset( $_GET['page'] ) ? $_GET['page'] : '';
				self::$instance->current_page = $page;

				// Maybe run upgrade.
				Upgrade_Handler::run();

				// Multisite.
				if ( is_multisite() ) {
					Multisite::get_instance();
				}

				// Plugin compatibility.
				Plugin_Compatibility::get_instance();

				// Boot up admin.
				Admin_Settings::get_instance();
				// Register admin REST routes in a dedicated controller.
				Admin_Rest::get_instance();

				// Dashboard widget.
				Admin_Dashboard_Widget::get_instance();
			} );
		}

		return self::$instance;
	}

	public function get_integrations() {
		return $this->integrations->get_integrations();
	}

	public function get_integration( $integration ) {
		$integrations = $this->integrations->get_integrations();
		if ( empty( $integrations[ $integration ] ) ) {
			return null;
		}

		$class = $integrations[ $integration ];

		return new $class();
	}

	/**
	 * Include required files
	 *
	 * @return void
	 */
	private function includes() {
		$path = plugin_dir_path( dirname( __FILE__ ) );
		require_once $path . 'src/class-ss-phpuri.php';
		require_once $path . 'src/class-ss-options.php';
		require_once $path . 'src/class-ss-view.php';
		require_once $path . 'src/class-ss-html-encoding-helper.php';
		require_once $path . 'src/class-ss-url-extractor.php';
		require_once $path . 'src/class-ss-url-fetcher.php';
		require_once $path . 'src/background/class-ss-async-request.php';
		require_once $path . 'src/background/class-ss-background-process.php';
		require_once $path . 'src/tasks/exceptions/class-ss-pause-exception.php';
		require_once $path . 'src/class-ss-archive-creation-job.php';
		require_once $path . 'src/tasks/traits/class-ss-skip-further-processing-exception.php';
		require_once $path . 'src/tasks/traits/trait-ss-can-process-pages.php';
		require_once $path . 'src/tasks/traits/trait-ss-can-transfer.php';
		require_once $path . 'src/tasks/class-ss-task.php';
		require_once $path . 'src/tasks/class-ss-setup-task.php';
		require_once $path . 'src/tasks/class-ss-discover-urls-task.php';
		require_once $path . 'src/tasks/class-ss-fetch-urls-task.php';
		require_once $path . 'src/tasks/class-ss-transfer-files-locally-task.php';
		require_once $path . 'src/tasks/class-ss-create-zip-archive.php';
		require_once $path . 'src/tasks/class-ss-wrapup-task.php';
		require_once $path . 'src/tasks/class-ss-cancel-task.php';
		require_once $path . 'src/tasks/class-ss-generate-404-task.php';
		require_once $path . 'src/handlers/class-ss-page-handler.php';
		require_once $path . 'src/class-ss-query.php';
		require_once $path . 'src/models/class-ss-model.php';
		require_once $path . 'src/models/class-ss-page.php';
		require_once $path . 'src/class-ss-diagnostic.php';
		require_once $path . 'src/class-ss-sql-permissions.php';
		require_once $path . 'src/class-ss-upgrade-handler.php';
		require_once $path . 'src/class-ss-util.php';
		require_once $path . 'src/class-ss-page-handlers.php';
		require_once $path . 'src/class-ss-integrations.php';
		require_once $path . 'src/admin/inc/class-ss-admin-settings.php';
		require_once $path . 'src/admin/inc/class-ss-admin-rest.php';
		require_once $path . 'src/admin/inc/class-ss-admin-meta.php';
		require_once $path . 'src/admin/inc/class-ss-admin-dashboard-widget.php';
		require_once $path . 'src/admin/inc/class-ss-migrate-settings.php';
		require_once $path . 'src/class-ss-multisite.php';
		require_once $path . 'src/class-ss-plugin-compatibility.php';
	}

	/**
	 * Old method to include admin menu.
	 *
	 * @return void
	 * @deprecated
	 */
	public function add_plugin_admin_menu() {
		// Deprecated, only for upgrade support.
	}

	/**
	 * Get Options instance.
	 *
	 * @return Options|null
	 */
	public function get_options() {
		return $this->options;
	}

	/**
	 * Set Options instance.
	 *
	 * @param Options $options Options instance.
	 *
	 * @return void
	 */
	public function set_options( Options $options ) {
		$this->options = $options;
	}

	/**
	 * Handle static export.
	 *
	 * @param int $blog_id given blog id.
	 *
	 * @return bool Whether the export was started successfully
	 */
	public function run_static_export( $blog_id = 0, $type = 'export' ) {
		// Check if an export is already running
		if ( $this->archive_creation_job->is_running() ) {
			Util::debug_log( "Export already running. Blocking new export request." );
			Util::debug_log( "Current task: " . $this->archive_creation_job->get_current_task() );
			Util::debug_log( "Is job done: " . ($this->archive_creation_job->is_job_done() ? 'true' : 'false') );

			// For cron jobs or programmatic calls, we just return without starting a new export
			// The REST API endpoints will handle their own error responses
			return false;
		}

		if ( ! $blog_id ) {
			$blog_id = get_current_blog_id();
		}
		do_action( 'ss_before_static_export', $blog_id, $type );

		// Clear transients.
		Util::clear_transients();

		// Start export.
		$this->archive_creation_job->start( $blog_id, $type );

		// Determine server type for basic auth check.
		$server_type   = esc_html( $_SERVER['SERVER_SOFTWARE'] );
		$basic_auth_on = false;

		switch ( $server_type ) {
			case ( strpos( $server_type, 'Apache' ) !== false ) :
				if ( isset( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_USER'] ) ) {
					$basic_auth_on = true;
				}
				break;
			case ( strpos( $server_type, 'nginx' ) !== false ) :
				if ( isset( $_SERVER['REMOTE_USER'] ) && ! empty( $_SERVER['REMOTE_USER'] ) ) {
					$basic_auth_on = true;
				}
				break;
			case ( strpos( $server_type, 'IIS' ) !== false ) :
				if ( isset( $_SERVER['AUTH_USER'] ) && ! empty( $_SERVER['AUTH_USER'] ) ) {
					$basic_auth_on = true;
				}
				break;
		}

		// Exit if Basic Auth but no credentials were provided.
		if ( $basic_auth_on ) {
			$options         = get_option( 'simply-static' );
			$basic_auth_user = $options['http_basic_auth_username'];
			$basic_auth_pass = $options['http_basic_auth_password'];

			if ( empty( $basic_auth_user ) && empty( $basic_auth_pass ) ) {
				// Cancel export.
				$message = __( 'Missing Basic Auth credentials - you need to configure the Basic Auth credentials in Simply Static -> Settings -> Misc -> Basic Auth to continue the export.', 'simply-static' );
				$this->archive_creation_job->cancel();
				$this->archive_creation_job->save_status_message( $message, 'error' );

				// Reset logs.
				$options['archive_name']       = null;
				$options['archive_start_time'] = null;
				$options['archive_end_time']   = null;

				update_option( 'simply-static', $options );
				return false;
			}
		}

		return true;
	}

	/**
	 * Handle pause archive job.
	 *
	 * @return void
	 */
	public function pause_static_export() {
		// Clear WP object cache.
		wp_cache_flush();

		// Cancel export.
		$this->archive_creation_job->pause();

		$this->get_archive_creation_job()->save_status_message( "Export paused.", 'pause', true );
	}

	/**
	 * Handle resume archive job.
	 *
	 * @return void
	 */
	public function resume_static_export() {
		// Clear WP object cache.
		wp_cache_flush();

		$this->get_archive_creation_job()->save_status_message( "Export resumed.", 'resume', true );

		// Cancel export.
		$this->archive_creation_job->resume();
	}

	/**
	 * Handle cancel archive job.
	 *
	 * @return void
	 */
	public function cancel_static_export() {
		// Clear WP object cache.
		wp_cache_flush();

		$this->get_archive_creation_job()->save_status_message( "Export cancelled.", 'cancel', true );

		// Cancel export.
		$this->archive_creation_job->cancel();
	}

	/**
	 * Get activity log data.
	 *
	 * @param int $blog_id given blog id.
	 *
	 * @return mixed
	 */
	public function get_activity_log( $blog_id = 0 ) {
		$blog_id = $blog_id ?: get_current_blog_id();

		do_action( 'ss_before_render_activity_log', $blog_id );

		$log = $this->options->get( 'archive_status_messages' );

		do_action( 'ss_after_render_activity_log', $blog_id, $this->get_archive_creation_job() );

		return $log;
	}

	/**
	 * Get export log data.
	 *
	 * @param int    $per_page given per page.
	 * @param int    $current_page given current page.
	 * @param int    $blog_id given blog id.
	 * @param string $search optional search term to filter by URL, status code, or notes.
	 *
	 * @return array
	 */
	public function get_export_log( $per_page, $current_page = 1, $blog_id = 0, $search = '' ) {
		global $wpdb;

		$blog_id = $blog_id ?: get_current_blog_id();

		do_action( 'ss_before_render_export_log', $blog_id, $this->get_archive_creation_job() );

		$per_page = $per_page ?: 25;
		$offset   = ( intval( $current_page ) - 1 ) * intval( $per_page );

		$query = Page::query()
		    ->limit( $per_page )
		    ->offset( $offset )
		    ->order( 'http_status_code DESC' );

		// Restrict to single-export pages when a single export is active.
		$use_single = get_option( 'simply-static-use-single' );
		if ( ! empty( $use_single ) ) {
			$ids = array_map( 'intval', explode( ',', $use_single ) );
			if ( count( $ids ) === 1 ) {
				$query->where( 'post_id = ?', $ids[0] );
			} else {
				$in_clause = implode( ',', $ids );
				$query->where( "post_id IN ({$in_clause})" );
			}
		}

		if ( ! empty( $search ) ) {
			$like = '%' . $wpdb->esc_like( $search ) . '%';
			$query->where(
				$wpdb->prepare(
					'(url LIKE %s OR http_status_code LIKE %s OR status_message LIKE %s)',
					$like,
					$like,
					$like
				)
			);
		}

		$static_pages = apply_filters( 'ss_total_pages_log', $query->find() );

		if ( ! empty( $search ) ) {
			$like               = '%' . $wpdb->esc_like( $search ) . '%';
			$count_query        = Page::query();

			// Restrict count to single-export pages as well.
			if ( ! empty( $use_single ) ) {
				if ( count( $ids ) === 1 ) {
					$count_query->where( 'post_id = ?', $ids[0] );
				} else {
					$count_query->where( "post_id IN ({$in_clause})" );
				}
			}

			$count_query->where(
				$wpdb->prepare(
					'(url LIKE %s OR http_status_code LIKE %s OR status_message LIKE %s)',
					$like,
					$like,
					$like
				)
			);
			$total_static_pages = apply_filters( 'ss_total_pages', (int) $count_query->count() );
			$http_status_codes  = Page::get_http_status_codes_summary();
		} else {
			if ( ! empty( $use_single ) ) {
				// For single exports, count only the pages belonging to the exported post(s).
				$count_query = Page::query();
				if ( count( $ids ) === 1 ) {
					$count_query->where( 'post_id = ?', $ids[0] );
				} else {
					$count_query->where( "post_id IN ({$in_clause})" );
				}
				$total_static_pages = apply_filters( 'ss_total_pages', (int) $count_query->count() );
				$http_status_codes  = Page::get_http_status_codes_summary();
			} else {
				$http_status_codes  = Page::get_http_status_codes_summary();
				$total_static_pages = apply_filters( 'ss_total_pages', array_sum( array_values( $http_status_codes ) ) );
			}
		}

		$total_pages = ceil( $total_static_pages / $per_page );

		do_action( 'ss_after_render_export_log', $blog_id, $this->get_archive_creation_job() );

		$static_pages_formatted = [];

		foreach ( $static_pages as $static_page ) {
			$msg                = '';
			$parent_static_page = $static_page->parent_static_page();
			if ( $parent_static_page ) {
				$display_url = Util::get_path_from_local_url( $parent_static_page->url );
				$msg         .= "<a href='" . $parent_static_page->url . "'>" . sprintf( __( 'Found on %s', 'simply-static' ), $display_url ) . "</a>";
			}

			// Combine status messages.
			if ( $msg !== '' && $static_page->status_message ) {
				$msg .= ' ';
			}

			// Avoid duplicate status messages.
			if ( ! empty ( $static_page->status_message ) ) {
				if ( strpos( $static_page->status_message, ';' ) !== false ) {
					$cleaned = implode( '', array_unique( explode( '; ', $static_page->status_message ) ) );
					$msg     .= $cleaned;
				} else {
					$msg .= $static_page->status_message;
				}
			} else {
				$msg .= $static_page->status_message;
			}

			$information = [
				'id'          => $static_page->id,
				'url'         => $static_page->url,
				'processable' => in_array( $static_page->http_status_code, Page::$processable_status_codes ),
				'code'        => $static_page->http_status_code,
				'notes'       => $msg,
				'error'       => $static_page->error_message,
			];

			$static_pages_formatted[] = $information;
		}

		return [
			'static_pages'       => $static_pages_formatted,
			'total_static_pages' => $total_static_pages,
			'total_pages'        => $total_pages,
			'status_codes'       => $http_status_codes,
		];
	}

	/**
	 * Starts the archive creation job.
	 *
	 * @return Archive_Creation_Job|null
	 */
	public function get_archive_creation_job() {
		return $this->archive_creation_job;
	}

	/**
	 * Return the task list for the Archive Creation Job to process
	 *
	 * @param array $task_list The list of tasks to process.
	 * @param string $delivery_method The method of delivering static files.
	 *
	 * @return array The list of tasks to process.
	 */
	public function filter_task_list( $task_list, $delivery_method ): array {
		// 404-only export short-circuit
		$only_404 = get_option( 'simply-static-404-only' );
		if ( ! empty( $only_404 ) ) {
			$task_list = [ 'setup' ];
			// Always include the 404 generator for this mode.
			$task_list[] = 'generate_404';
			if ( 'zip' === $delivery_method ) {
				$task_list[] = 'create_zip_archive';
			} elseif ( 'local' === $delivery_method ) {
				$task_list[] = 'transfer_files_locally';
			} else {
				// For other delivery methods in Pro, keep placeholder; Free doesn't handle them.
			}
			$task_list[] = 'wrapup';
			return $task_list;
		}
		$generate_404 = $this->options->get( 'generate_404' );

		$task_list[] = 'setup';

		if ( $this->options->get( 'smart_crawl' ) ) {
			// Only include discover_urls on full exports (exclude update, single, and build exports).
			$use_single  = get_option( 'simply-static-use-single' );
			$use_build   = get_option( 'simply-static-use-build' );
			$export_type = $this->options->get( 'generate_type' );
			if ( empty( $use_single ) && empty( $use_build ) && 'update' !== $export_type ) {
				$task_list[] = 'discover_urls';
			}
		}

		$task_list[] = 'fetch_urls';

		// Add 404 task
		if ( $generate_404 ) {
			$task_list[] = 'generate_404';
		}

		if ( 'zip' === $delivery_method ) {
			$task_list[] = 'create_zip_archive';
		} elseif ( 'local' === $delivery_method ) {
			$task_list[] = 'transfer_files_locally';
		}

		$task_list[] = 'wrapup';

		return $task_list;
	}


	/**
	 * Maybe clear local directory before export.
	 *
	 * @return void
	 */
	public function maybe_clear_directory() {
		// Check the export type.
		$use_single            = get_option( 'simply-static-use-single' );
		$use_build             = get_option( 'simply-static-use-build' );
		$only_404              = get_option( 'simply-static-404-only' );
		$clear_local_directory = apply_filters(
			'ss_clear_local_directory',
			// Only clear on full local exports (not build, not single, not update, not 404-only)
			empty( $use_build )
			&& empty( $use_single )
			&& empty( $only_404 )
			&& $this->options->get( 'clear_directory_before_export' )
			&& 'local' === $this->options->get( 'delivery_method' )
		);

		// Clear out the local directory before copying files.
		if ( $clear_local_directory ) {
			$local_dir = apply_filters( 'ss_local_dir', $this->options->get( 'local_dir' ) );

			// Make sure the directory exists and is not empty.
			$iterator = new \FilesystemIterator( $local_dir );

			if ( is_dir( $local_dir ) && $iterator->valid() ) {
				Transfer_Files_Locally_Task::delete_local_directory_static_files( $local_dir, $this->options );
			}
		} else {
			// Provide a small hint in debug log when skipping clearing due to special export modes
			if ( ! empty( $only_404 ) ) {
				Util::debug_log( 'Skipping clearing local directory: 404-only export.' );
			}
		}
	}

	/**
	 * Register quick links in plugins settings page.
	 *
	 * @param array $links given list of links.
	 *
	 * @return array
	 */
	public function add_quick_links( $links ) {
		$settings_url = esc_url( get_admin_url() . 'admin.php?page=simply-static-settings' );
		$docs_url     = esc_url( 'https://docs.simplystatic.com' );

		$links[] = '<a href="' . $settings_url . '">' . esc_html__( 'Settings', 'simply-static' ) . '</a>';
		$links[] = '<a target="_blank" href="' . $docs_url . '">' . esc_html__( 'Docs', 'simply-static' ) . '</a>';

		return $links;
	}

	/**
	 * Set HTTP Basic Auth for wp-background-processing
	 *
	 * @param array $parsed_args given args.
	 * @param string $url given URL.
	 *
	 * @return array
	 */
	public function add_http_filters( $parsed_args, $url ) {

		if ( ! Util::is_local_url( $url ) ) {
			return $parsed_args;
		}

		// Basic Auth?
		$basic_auth_user = self::$instance->options->get( 'http_basic_auth_username' );
		$basic_auth_pass = self::$instance->options->get( 'http_basic_auth_password' );

		if ( ! empty( $basic_auth_user ) && ! empty( $basic_auth_pass ) ) {
			$parsed_args['headers']['Authorization'] = 'Basic ' . base64_encode( $basic_auth_user . ':' . $basic_auth_pass );
		}

		return $parsed_args;
	}
}
