<?php
/**
 * `wp blaminhor maintenance ...` — Maintenance mode commands.
 *
 * @package BlaminhorEssentials
 */

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

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
	return;
}

/**
 * Toggle and configure the Maintenance / Coming Soon mode.
 *
 * Every state change goes through process_settings(), which triggers the
 * plugin's cache_purge_request action — visitors see the new state on
 * their next request, no manual cache flush required.
 */
class Blaminhor_Essentials_CLI_Maintenance extends Blaminhor_Essentials_CLI_Module {

	/**
	 * @var string
	 */
	protected $module_id = 'maintenance';

	/**
	 * Show the current Maintenance settings.
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Render format (table, json, csv, yaml). Default: table.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance get
	 *     $ wp blaminhor maintenance get --format=json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function get( $args, $assoc_args ) {
		$module   = $this->get_module_instance();
		$settings = $module->get_effective_settings();

		$rows = array(
			array( 'key' => 'enabled',           'value' => $settings['enabled'] ? 'true' : 'false' ),
			array( 'key' => 'mode',              'value' => (string) $settings['mode'] ),
			array( 'key' => 'title',             'value' => (string) $settings['title'] ),
			array( 'key' => 'headline',          'value' => (string) $settings['headline'] ),
			array( 'key' => 'message',           'value' => (string) $settings['message'] ),
			array( 'key' => 'bg_color',          'value' => (string) $settings['bg_color'] ),
			array( 'key' => 'text_color',        'value' => (string) $settings['text_color'] ),
			array( 'key' => 'bypass_roles',      'value' => implode( ',', (array) $settings['bypass_roles'] ) ),
			array( 'key' => 'bypass_ips',        'value' => (string) $settings['bypass_ips'] ),
			array( 'key' => 'show_login_link',   'value' => $settings['show_login_link'] ? 'true' : 'false' ),
			array( 'key' => 'logo_id',           'value' => (string) (int) $settings['logo_id'] ),
			array( 'key' => 'countdown_target',  'value' => (string) $settings['countdown_target'] ),
			array( 'key' => 'end_time',          'value' => (string) $settings['end_time'] ),
			array( 'key' => 'contact_shortcode', 'value' => (string) $settings['contact_shortcode'] ),
			array( 'key' => 'signup_shortcode',  'value' => (string) $settings['signup_shortcode'] ),
			array( 'key' => 'live_site_url',     'value' => (string) $settings['live_site_url'] ),
		);

		$this->format_for_output( $rows, $assoc_args, array( 'key', 'value' ) );
	}

	/**
	 * Print whether maintenance mode is currently enabled (porcelain output).
	 *
	 * Exits 0 if enabled, 1 if disabled — useful for shell gates.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance status
	 *     enabled
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args (unused).
	 */
	public function status( $args, $assoc_args ) {
		$module = $this->get_module_instance();
		if ( $module->get_setting( 'enabled' ) ) {
			WP_CLI::line( 'enabled' );
			return;
		}
		WP_CLI::line( 'disabled' );
		WP_CLI::halt( 1 );
	}

	/**
	 * Enable maintenance / coming soon mode (and optionally update fields).
	 *
	 * Any field you pass via --flag is updated atomically with the toggle.
	 * Unspecified fields keep their previous value.
	 *
	 * ## OPTIONS
	 *
	 * [--mode=<mode>]
	 * : "maintenance" or "coming_soon".
	 *
	 * [--title=<title>]
	 * : Browser tab title.
	 *
	 * [--headline=<headline>]
	 * : Big headline shown on the page.
	 *
	 * [--message=<message>]
	 * : Body message (wp_kses_post sanitised — basic HTML allowed).
	 *
	 * [--bg-color=<hex>]
	 * : Background color (hex, e.g. #f1f1f1).
	 *
	 * [--text-color=<hex>]
	 * : Text color (hex).
	 *
	 * [--bypass-roles=<roles>]
	 * : Comma-separated WordPress role slugs allowed to bypass.
	 *
	 * [--bypass-ips=<ips>]
	 * : Comma-separated IPs allowed to bypass.
	 *
	 * [--show-login-link=<bool>]
	 * : Display a link to wp-login.php on the page.
	 *
	 * [--logo-id=<id>]
	 * : Attachment ID of the logo to display above the headline.
	 *
	 * [--countdown-target=<datetime>]
	 * : ISO datetime (YYYY-MM-DDTHH:MM) used by the Coming Soon countdown.
	 *
	 * [--end-time=<datetime>]
	 * : ISO datetime (YYYY-MM-DDTHH:MM) shown as "back online" estimate in Maintenance mode.
	 *
	 * [--contact-shortcode=<shortcode>]
	 * : Shortcode rendered in Maintenance mode (e.g. [contact-form-7 id="42"]).
	 *
	 * [--signup-shortcode=<shortcode>]
	 * : Shortcode rendered in Coming Soon mode (typically a newsletter signup).
	 *
	 * [--live-site-url=<url>]
	 * : External URL to a "View Live Site" link (Staging mode only).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance enable
	 *     $ wp blaminhor maintenance enable --headline="Back soon" --message="Be right back."
	 *     $ wp blaminhor maintenance enable --mode=coming_soon --bypass-ips=1.2.3.4
	 *     $ wp blaminhor maintenance enable --mode=coming_soon --countdown-target=2026-12-31T23:59
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function enable( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$assoc_args['enabled'] = '1';
		$this->apply( $assoc_args, __( 'Maintenance mode enabled.', 'blaminhor-essentials' ), 'maintenance_enable' );
	}

	/**
	 * Disable maintenance / coming soon mode.
	 *
	 * Other fields (mode, message, colors, bypass…) are preserved.
	 *
	 * ## OPTIONS
	 *
	 * [--yes]
	 * : Skip the interactive confirmation prompt.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance disable
	 *     $ wp blaminhor maintenance disable --yes
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function disable( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$module = $this->get_module_instance();

		if ( ! $module->get_setting( 'enabled' ) ) {
			WP_CLI::warning( __( 'Maintenance mode is already disabled.', 'blaminhor-essentials' ) );
			return;
		}

		WP_CLI::confirm(
			__( 'Disable maintenance mode? Visitors will see the live site again.', 'blaminhor-essentials' ),
			$assoc_args
		);

		// Omit the `enabled` key entirely — process_settings() uses isset() so
		// the absence is what flips it off.
		$assoc_args['__no_enabled__'] = true;
		$this->apply( $assoc_args, __( 'Maintenance mode disabled.', 'blaminhor-essentials' ), 'maintenance_disable' );
	}

	/**
	 * Update Maintenance settings without toggling on/off.
	 *
	 * Use this to edit message/colors/bypass while keeping the current state
	 * (whether the site is currently in maintenance or not).
	 *
	 * ## OPTIONS
	 *
	 * [--mode=<mode>]
	 * : "maintenance" or "coming_soon".
	 *
	 * [--title=<title>]
	 * : Browser tab title.
	 *
	 * [--headline=<headline>]
	 * : Big headline shown on the page.
	 *
	 * [--message=<message>]
	 * : Body message (wp_kses_post sanitised — basic HTML allowed).
	 *
	 * [--bg-color=<hex>]
	 * : Background color (hex, e.g. #f1f1f1).
	 *
	 * [--text-color=<hex>]
	 * : Text color (hex).
	 *
	 * [--bypass-roles=<roles>]
	 * : Comma-separated WordPress role slugs allowed to bypass.
	 *
	 * [--bypass-ips=<ips>]
	 * : Comma-separated IPs allowed to bypass.
	 *
	 * [--show-login-link=<bool>]
	 * : Display a link to wp-login.php on the page.
	 *
	 * [--logo-id=<id>]
	 * : Attachment ID of the logo to display above the headline.
	 *
	 * [--countdown-target=<datetime>]
	 * : ISO datetime (YYYY-MM-DDTHH:MM) used by the Coming Soon countdown.
	 *
	 * [--end-time=<datetime>]
	 * : ISO datetime (YYYY-MM-DDTHH:MM) shown as "back online" estimate in Maintenance mode.
	 *
	 * [--contact-shortcode=<shortcode>]
	 * : Shortcode rendered in Maintenance mode.
	 *
	 * [--signup-shortcode=<shortcode>]
	 * : Shortcode rendered in Coming Soon mode.
	 *
	 * [--live-site-url=<url>]
	 * : External URL to a "View Live Site" link (Staging mode only).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance set --message="Back in 1h" --headline="Maintenance"
	 *     $ wp blaminhor maintenance set --logo-id=42 --bg-color=#0f172a --text-color=#ffffff
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function set( $args, $assoc_args ) {
		$this->ensure_capabilities();
		$this->apply( $assoc_args, __( 'Maintenance settings updated.', 'blaminhor-essentials' ), 'maintenance_set' );
	}

	/**
	 * Build the $data array for process_settings() from CLI args while
	 * preserving every field not explicitly overridden.
	 *
	 * @param array  $assoc_args Raw CLI assoc args. The special key
	 *                           `__no_enabled__` instructs the builder to
	 *                           OMIT the `enabled` key (which is how the
	 *                           module turns the mode off — isset() check).
	 * @param string $success    Localised success message.
	 * @param string $event      Logger event slug.
	 */
	private function apply( $assoc_args, $success, $event ) {
		$module  = $this->get_module_instance();
		$current = $module->get_effective_settings();

		// Carry forward all current values so the form-style process_settings
		// has the full snapshot to re-apply.
		$data = array(
			'mode'              => isset( $assoc_args['mode'] ) ? (string) $assoc_args['mode'] : (string) $current['mode'],
			'title'             => isset( $assoc_args['title'] ) ? (string) $assoc_args['title'] : (string) $current['title'],
			'headline'          => isset( $assoc_args['headline'] ) ? (string) $assoc_args['headline'] : (string) $current['headline'],
			'message'           => isset( $assoc_args['message'] ) ? (string) $assoc_args['message'] : (string) $current['message'],
			'bg_color'          => isset( $assoc_args['bg-color'] ) ? (string) $assoc_args['bg-color'] : (string) $current['bg_color'],
			'text_color'        => isset( $assoc_args['text-color'] ) ? (string) $assoc_args['text-color'] : (string) $current['text_color'],
			'bypass_ips'        => isset( $assoc_args['bypass-ips'] ) ? str_replace( ',', "\n", (string) $assoc_args['bypass-ips'] ) : (string) $current['bypass_ips'],
			'logo_id'           => isset( $assoc_args['logo-id'] ) ? (string) (int) $assoc_args['logo-id'] : (string) (int) $current['logo_id'],
			'countdown_target'  => isset( $assoc_args['countdown-target'] ) ? (string) $assoc_args['countdown-target'] : (string) $current['countdown_target'],
			'end_time'          => isset( $assoc_args['end-time'] ) ? (string) $assoc_args['end-time'] : (string) $current['end_time'],
			'contact_shortcode' => isset( $assoc_args['contact-shortcode'] ) ? (string) $assoc_args['contact-shortcode'] : (string) $current['contact_shortcode'],
			'signup_shortcode'  => isset( $assoc_args['signup-shortcode'] ) ? (string) $assoc_args['signup-shortcode'] : (string) $current['signup_shortcode'],
			'live_site_url'     => isset( $assoc_args['live-site-url'] ) ? (string) $assoc_args['live-site-url'] : (string) $current['live_site_url'],
		);

		// Roles: CLI accepts a CSV; module expects an array of slugs.
		if ( isset( $assoc_args['bypass-roles'] ) ) {
			$data['bypass_roles'] = array_filter( array_map( 'trim', explode( ',', (string) $assoc_args['bypass-roles'] ) ) );
		} else {
			$data['bypass_roles'] = (array) $current['bypass_roles'];
		}

		// show_login_link: isset() pattern. Preserve current state unless flag passed.
		if ( isset( $assoc_args['show-login-link'] ) ) {
			if ( $this->parse_bool( $assoc_args['show-login-link'] ) ) {
				$data['show_login_link'] = '1';
			}
		} elseif ( ! empty( $current['show_login_link'] ) ) {
			$data['show_login_link'] = '1';
		}

		// enabled: same isset() pattern. The caller decides via --enabled
		// (forwarded by enable()) or via __no_enabled__ flag (forwarded by
		// disable()). For `set` (neither), preserve the current state.
		if ( empty( $assoc_args['__no_enabled__'] ) ) {
			if ( isset( $assoc_args['enabled'] ) ) {
				if ( $this->parse_bool( $assoc_args['enabled'] ) ) {
					$data['enabled'] = '1';
				}
			} elseif ( ! empty( $current['enabled'] ) ) {
				$data['enabled'] = '1';
			}
		}

		$result = $module->apply_settings( $data );
		if ( is_wp_error( $result ) ) {
			WP_CLI::error( $result->get_error_message() );
		}

		blaminhor_essentials_log(
			sprintf( 'Maintenance settings updated via WP-CLI (event=%s).', $event ),
			'info',
			'cli',
			array( 'event' => $event )
		);
		WP_CLI::success( $success );
	}

	/**
	 * Render the maintenance / coming-soon page HTML to STDOUT (headless preview).
	 *
	 * Captures the same markup a real visitor would see. No HTTP headers are
	 * emitted (so it works in a CLI context where the response was already
	 * sent). Useful for piping into a file or a screenshot tool, or for QA
	 * comparison between two configurations.
	 *
	 * Read-only — settings are not mutated. Renders the page for the mode
	 * currently configured. To preview a different mode, run `set --mode=...`
	 * first and re-run `preview`.
	 *
	 * ## OPTIONS
	 *
	 * [--out=<path>]
	 * : Write the HTML to this file instead of STDOUT.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance preview > preview.html
	 *     $ wp blaminhor maintenance preview --out=/tmp/preview.html
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function preview( $args, $assoc_args ) {
		$module = $this->get_module_instance();
		if ( ! method_exists( $module, 'render_maintenance_page' ) ) {
			WP_CLI::error( __( 'This build of Maintenance does not expose render_maintenance_page().', 'blaminhor-essentials' ) );
		}

		ob_start();
		try {
			$module->render_maintenance_page( true );
		} catch ( \Throwable $e ) {
			ob_end_clean();
			WP_CLI::error( sprintf(
				/* translators: %s: exception message */
				__( 'Preview render failed: %s', 'blaminhor-essentials' ),
				$e->getMessage()
			) );
		}
		$html = (string) ob_get_clean();

		if ( ! empty( $assoc_args['out'] ) ) {
			$path = (string) $assoc_args['out'];
			$ok   = file_put_contents( $path, $html );
			if ( false === $ok ) {
				WP_CLI::error( sprintf(
					/* translators: %s: file path */
					__( 'Failed to write preview to %s', 'blaminhor-essentials' ),
					$path
				) );
			}
			WP_CLI::success( sprintf(
				/* translators: 1: byte count, 2: file path */
				__( '%1$d bytes written to %2$s.', 'blaminhor-essentials' ),
				strlen( $html ),
				$path
			) );
			return;
		}

		WP_CLI::line( $html );
	}

	/**
	 * Dump Maintenance settings as a JSON envelope on STDOUT.
	 *
	 * Read-only. The output is a stable, version-tagged JSON document that
	 * captures every persisted field (including logo_id and shortcodes).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor maintenance export > maintenance.json
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args (unused).
	 */
	public function export( $args, $assoc_args ) {
		$module  = $this->get_module_instance();
		$payload = array(
			'module'         => 'maintenance',
			'plugin_version' => defined( 'BLAMINHOR_ESSENTIALS_VERSION' ) ? BLAMINHOR_ESSENTIALS_VERSION : '',
			'exported_at'    => gmdate( 'c' ),
			'settings'       => $module->get_effective_settings(),
		);
		WP_CLI::line( wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	}
}
