<?php
/**
 * Leads admin screen: menu registration, page router, and state-change handlers.
 *
 * Lists new leads, processes the dismiss, mark-spam, and promote row actions,
 * and renders the lead detail as a gated three-step launchpad. Every write
 * verifies a nonce and re-checks the relevant capability before mutating data,
 * then uses Post/Redirect/Get. Render is capability-gated at the top. Leads are
 * never hard-deleted; dismiss, spam, and promote flip a lifecycle status only.
 *
 * Promotion deduplicates on phone or email: with a match staff are shown a
 * server-rendered warning interstitial and choose to link the lead to the
 * existing customer or create a new one; with no match the lead is promoted
 * straight through. Promotion is contact-only (D-07): it creates the customer
 * record only, never the vehicle.
 *
 * The lead detail is a gated, sequential launchpad (D-08): step 1 promotes the
 * lead to a customer, step 2 creates the vehicle from the lead's snapshot, and
 * step 3 creates the work order. Each step unlocks only after the previous one
 * is satisfied. Create work order is blocked until BOTH a customer and a vehicle
 * exist for the lead, enforced server-side in the handler (not just hidden in
 * the UI) so no orphan work order can be created.
 *
 * @package WrenchPress
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class WrenchPress_Leads_Admin_Page
 */
class WrenchPress_Leads_Admin_Page {

	/**
	 * Admin page slug.
	 */
	const MENU_SLUG = 'wrenchpress-leads';

	/**
	 * Per-row nonce base for the Promote action link. The lead id is appended so
	 * each row carries a distinct nonce; the list table mints the matching link.
	 */
	const PROMOTE_NONCE = 'wrenchpress_promote_lead_';

	/**
	 * Per-row nonce base for the Archive action link. The lead id is appended so
	 * each row carries a distinct nonce; the list table mints the matching link.
	 */
	const ARCHIVE_NONCE = 'wrenchpress_archive_lead_';

	/**
	 * Per-row nonce base for the Restore action link. The lead id is appended so
	 * each row carries a distinct nonce; the list table mints the matching link.
	 */
	const RESTORE_NONCE = 'wrenchpress_restore_lead_';

	/**
	 * Nonce action for the "Link to existing customer" interstitial choice.
	 */
	const PROMOTE_LINK_NONCE = 'wrenchpress_promote_link';

	/**
	 * Nonce action for the "Create new customer" interstitial choice.
	 */
	const PROMOTE_CREATE_NONCE = 'wrenchpress_promote_create';

	/**
	 * Nonce action base for the launchpad "Create vehicle" step. The lead id is
	 * appended so each lead carries a distinct nonce.
	 */
	const CREATE_VEHICLE_NONCE = 'wrenchpress_lead_create_vehicle_';

	/**
	 * Nonce action base for the launchpad "Create work order" step. The lead id is
	 * appended so each lead carries a distinct nonce.
	 */
	const CREATE_WORK_ORDER_NONCE = 'wrenchpress_lead_create_work_order_';

	/**
	 * Dispatch the dismiss and spam row actions on page load (before output).
	 * Both are nonce-protected GET links.
	 *
	 * @return void
	 */
	public static function handle_load() {
		// phpcs:disable WordPress.Security.NonceVerification -- the specific handler each branch calls verifies its own nonce before any write.
		$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';

		if ( '' === $action && isset( $_POST['wrenchpress_action'] ) ) {
			$action = sanitize_key( wp_unslash( $_POST['wrenchpress_action'] ) );
		}
		// phpcs:enable WordPress.Security.NonceVerification

		if ( 'dismiss' === $action ) {
			self::handle_status_change( 'dismissed' );
		} elseif ( 'spam' === $action ) {
			self::handle_status_change( 'spam' );
		} elseif ( 'archive' === $action ) {
			self::handle_archive();
		} elseif ( 'restore' === $action ) {
			self::handle_restore();
		} elseif ( 'promote' === $action ) {
			self::handle_promote();
		} elseif ( 'promote_link' === $action ) {
			self::handle_promote_link();
		} elseif ( 'promote_create' === $action ) {
			self::handle_promote_create();
		} elseif ( 'create_vehicle' === $action ) {
			self::handle_create_vehicle_from_lead();
		} elseif ( 'create_work_order' === $action ) {
			self::handle_create_work_order_from_lead();
		}
	}

	/**
	 * Lead pending the dedup interstitial, set by handle_promote() when a match
	 * is found so render() can show the warning view instead of the list.
	 *
	 * @var object|null
	 */
	private static $promote_lead = null;

	/**
	 * Matching customers for the pending interstitial.
	 *
	 * @var array
	 */
	private static $promote_matches = array();

	/**
	 * Dispatch the Promote row action (nonce-protected GET link).
	 *
	 * Verifies the per-row promote nonce and re-checks the capability, loads the
	 * lead, then runs the dedup query. With no match the promotion proceeds
	 * straight through (no confirmation needed); with a match it stashes the lead
	 * and matches for render() to show the interstitial, writing nothing yet
	 * (D-05: warned, never silently blocked).
	 *
	 * @return void
	 */
	private static function handle_promote() {
		$id = isset( $_GET['lead'] ) ? absint( wp_unslash( $_GET['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce checked immediately below.

		check_admin_referer( self::PROMOTE_NONCE . $id );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		$lead = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		if ( ! $lead ) {
			self::redirect_to_list( 'promote_error' );
		}

		$matches = WrenchPress_Promotion::find_matches( $lead );

		if ( empty( $matches ) ) {
			WrenchPress_Promotion::promote_new( $lead );
			self::redirect_to_view( $id, 'promoted' );
		}

		// A likely duplicate exists: warn and let staff choose (D-05). Stash for
		// render() to display the interstitial; no write happens here.
		self::$promote_lead    = $lead;
		self::$promote_matches = $matches;
	}

	/**
	 * Handle the "Link to existing customer" interstitial choice (POST).
	 *
	 * Verifies the link nonce and capability, then links the lead's vehicle to
	 * the chosen existing customer without creating a duplicate (D-06).
	 *
	 * @return void
	 */
	private static function handle_promote_link() {
		check_admin_referer( self::PROMOTE_LINK_NONCE );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		$id          = isset( $_POST['lead'] ) ? absint( wp_unslash( $_POST['lead'] ) ) : 0;
		$customer_id = isset( $_POST['customer'] ) ? absint( wp_unslash( $_POST['customer'] ) ) : 0;
		$lead        = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		if ( $lead && $customer_id > 0 ) {
			WrenchPress_Promotion::promote_link( $lead, $customer_id );
		}

		self::redirect_to_view( $id, 'linked' );
	}

	/**
	 * Handle the "Create new customer" interstitial choice (POST).
	 *
	 * Verifies the create nonce and capability, then promotes the lead into a new
	 * customer and vehicle even though a possible duplicate was flagged (D-05:
	 * staff may deliberately create anyway).
	 *
	 * @return void
	 */
	private static function handle_promote_create() {
		check_admin_referer( self::PROMOTE_CREATE_NONCE );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		$id   = isset( $_POST['lead'] ) ? absint( wp_unslash( $_POST['lead'] ) ) : 0;
		$lead = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		if ( $lead ) {
			WrenchPress_Promotion::promote_new( $lead );
		}

		self::redirect_to_view( $id, 'promoted' );
	}

	/**
	 * Handle the launchpad "Create vehicle" step (POST, step 2 of D-08).
	 *
	 * Verifies the per-lead nonce, re-checks the create-vehicles capability, loads
	 * the lead, and requires that the lead already has a promoted customer (step 1
	 * done) before creating the vehicle from the lead's snapshot. Without a
	 * promoted customer there is nothing to own the vehicle, so the step is
	 * refused. PRG back to the lead detail with a success or error flag.
	 *
	 * @return void
	 */
	private static function handle_create_vehicle_from_lead() {
		$id = isset( $_POST['lead'] ) ? absint( wp_unslash( $_POST['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked immediately below.

		check_admin_referer( self::CREATE_VEHICLE_NONCE . $id );

		if ( ! current_user_can( WrenchPress_Capabilities::CREATE_VEHICLES ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		$lead = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		if ( ! $lead ) {
			self::redirect_to_list( 'promote_error' );
		}

		$customer_id = isset( $lead->promoted_customer_id ) ? absint( $lead->promoted_customer_id ) : 0;

		// Step 2 is gated on step 1: the lead must already have a promoted customer
		// to own the vehicle. Guard server-side, not just in the UI.
		if ( 0 === $customer_id ) {
			self::redirect_to_view( $id, 'step_locked' );
		}

		$vehicle_id = WrenchPress_Promotion::create_vehicle_from_lead( $customer_id, $lead );

		if ( false === $vehicle_id ) {
			self::redirect_to_view( $id, 'vehicle_error' );
		}

		self::redirect_to_view( $id, 'vehicle_created' );
	}

	/**
	 * Handle the launchpad "Create work order" step (POST, step 3 of D-08).
	 *
	 * Verifies the per-lead nonce, re-checks the manage-work-orders capability,
	 * loads the lead, then resolves the lead's customer id and a vehicle id for
	 * that customer. Both must be non-zero: this server-side guard mirrors the
	 * work-order repository's own orphan guard (Pitfall 3), so a crafted POST
	 * cannot create a work order without a customer and a vehicle even if the UI
	 * control were bypassed. Pre-fills the tax rate from the shop default, creates
	 * the work order, and PRG to the new work order's detail screen.
	 *
	 * @return void
	 */
	private static function handle_create_work_order_from_lead() {
		$id = isset( $_POST['lead'] ) ? absint( wp_unslash( $_POST['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked immediately below.

		check_admin_referer( self::CREATE_WORK_ORDER_NONCE . $id );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_WORKORDERS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		$lead = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		if ( ! $lead ) {
			self::redirect_to_list( 'promote_error' );
		}

		$customer_id = isset( $lead->promoted_customer_id ) ? absint( $lead->promoted_customer_id ) : 0;
		$vehicle_id  = $customer_id > 0 ? self::vehicle_id_for_customer( $customer_id ) : 0;

		// Orphan guard (Pitfall 3): both a customer AND a vehicle must exist. This
		// is the server-side enforcement of the launchpad gate; the disabled UI
		// control is convenience, this guard is the real boundary.
		if ( 0 === $customer_id || 0 === $vehicle_id ) {
			self::redirect_to_view( $id, 'step_locked' );
		}

		$work_order_id = WrenchPress_Work_Orders_Repository::create(
			array(
				'customer_id' => $customer_id,
				'vehicle_id'  => $vehicle_id,
				'tax_rate'    => WrenchPress_Settings_Page::default_tax_rate(),
			)
		);

		if ( false === $work_order_id || 0 === (int) $work_order_id ) {
			self::redirect_to_view( $id, 'work_order_error' );
		}

		self::redirect_to_work_order( (int) $work_order_id );
	}

	/**
	 * Resolve a vehicle id owned by the given customer, or 0 when none exists.
	 *
	 * Used by both the launchpad gate (to enable step 3) and the create-work-order
	 * handler (to attach the vehicle). Returns the first active vehicle's id.
	 *
	 * @param int $customer_id Owning customer id.
	 * @return int Vehicle id, or 0 when the customer has no active vehicle.
	 */
	private static function vehicle_id_for_customer( $customer_id ) {
		$customer_id = absint( $customer_id );

		if ( 0 === $customer_id ) {
			return 0;
		}

		$vehicles = WrenchPress_Vehicles_Repository::list_active(
			array(
				'customer_id' => $customer_id,
				'per_page'    => 1,
			)
		);

		if ( empty( $vehicles ) ) {
			return 0;
		}

		$first = $vehicles[0];

		return isset( $first->id ) ? absint( $first->id ) : 0;
	}

	/**
	 * Verify the per-row nonce and capability, then flip the lead status (PRG).
	 *
	 * @param string $status Target status: 'dismissed' or 'spam'.
	 * @return void
	 */
	private static function handle_status_change( $status ) {
		$id = isset( $_GET['lead'] ) ? absint( wp_unslash( $_GET['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce checked immediately below.

		$nonce_action = ( 'spam' === $status )
			? 'wrenchpress_spam_lead_' . $id
			: 'wrenchpress_dismiss_lead_' . $id;

		check_admin_referer( $nonce_action );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		if ( $id > 0 ) {
			WrenchPress_Leads_Repository::set_status( $id, $status );
		}

		self::redirect_to_list( 'spam' === $status ? 'spammed' : 'dismissed' );
	}

	/**
	 * Verify the per-row nonce and capability, then archive the lead (PRG).
	 *
	 * Mirrors the customers/vehicles archive handler: the row is retained and only
	 * its status flips to 'archived'. Never hard-deletes.
	 *
	 * @return void
	 */
	private static function handle_archive() {
		$id = isset( $_GET['lead'] ) ? absint( wp_unslash( $_GET['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce checked immediately below.

		check_admin_referer( self::ARCHIVE_NONCE . $id );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		if ( $id > 0 ) {
			WrenchPress_Leads_Repository::archive( $id );
		}

		// Land on the Archived view so the just-archived lead stays visible.
		self::redirect_to_list( 'archived', 'archived' );
	}

	/**
	 * Verify the per-row nonce and capability, then restore the lead to the active
	 * inbox (status = 'new') and PRG back to the list.
	 *
	 * @return void
	 */
	private static function handle_restore() {
		$id = isset( $_GET['lead'] ) ? absint( wp_unslash( $_GET['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce checked immediately below.

		check_admin_referer( self::RESTORE_NONCE . $id );

		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		if ( $id > 0 ) {
			WrenchPress_Leads_Repository::restore( $id );
		}

		self::redirect_to_list( 'restored' );
	}

	/**
	 * Redirect to the list with a success flag and exit (PRG).
	 *
	 * @param string $flag   Query flag: dismissed, spammed, archived, or restored.
	 * @param string $status Optional status view to land on so the affected lead
	 *                       stays visible after the action (e.g. 'archived'). When
	 *                       empty the list falls back to the default Active view.
	 * @return void
	 */
	private static function redirect_to_list( $flag, $status = '' ) {
		$query = array(
			'page' => self::MENU_SLUG,
			$flag  => 1,
		);

		if ( '' !== $status ) {
			$query['status'] = $status;
		}

		wp_safe_redirect(
			add_query_arg( $query, admin_url( 'admin.php' ) )
		);
		exit;
	}

	/**
	 * Redirect back to the lead detail launchpad with a status flag and exit (PRG).
	 *
	 * @param int    $lead_id Lead id to return to.
	 * @param string $flag    Query flag describing the step outcome.
	 * @return void
	 */
	private static function redirect_to_view( $lead_id, $flag ) {
		wp_safe_redirect(
			add_query_arg(
				array(
					'page'   => self::MENU_SLUG,
					'action' => 'view',
					'lead'   => (int) $lead_id,
					$flag    => 1,
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	/**
	 * Redirect to a newly created work order's detail screen and exit (PRG).
	 *
	 * @param int $work_order_id New work order id.
	 * @return void
	 */
	private static function redirect_to_work_order( $work_order_id ) {
		wp_safe_redirect(
			add_query_arg(
				array(
					'page'       => WrenchPress_Work_Orders_Admin_Page::MENU_SLUG,
					'action'     => 'view',
					'work_order' => (int) $work_order_id,
					'created'    => 1,
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	/**
	 * Route the page to the read-only lead view or the leads list.
	 *
	 * @return void
	 */
	public static function render() {
		if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'pitdesk-repair-crm' ) );
		}

		// A pending dedup match was stashed by handle_promote(); show the warning
		// interstitial instead of the list (no write has happened yet).
		if ( null !== self::$promote_lead ) {
			self::render_promote_interstitial( self::$promote_lead, self::$promote_matches );
			return;
		}

		$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only view routing.

		if ( 'view' === $action ) {
			self::render_view();
			return;
		}

		self::render_list();
	}

	/**
	 * Render the dedup warning interstitial.
	 *
	 * Server-rendered (not a JS modal): shows the warning copy, each matching
	 * customer read-only with every value escaped (the matched data is adjacent
	 * to untrusted lead input, so it is treated as stored-XSS surface), and two
	 * nonced choice forms - link to an existing customer or create a new one. No
	 * write happens here; the POST handlers do the work after re-checking nonce
	 * and capability.
	 *
	 * @param object $lead    Lead awaiting promotion.
	 * @param array  $matches Matching active customer rows.
	 * @return void
	 */
	private static function render_promote_interstitial( $lead, array $matches ) {
		echo '<div class="wrap">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Promote lead to customer', 'pitdesk-repair-crm' ) . '</h1>';
		echo '<hr class="wp-header-end">';

		echo '<div class="notice notice-warning"><p>' . esc_html__( 'A customer already matches this phone or email. Review the match before continuing.', 'pitdesk-repair-crm' ) . '</p></div>';

		foreach ( $matches as $match ) {
			$name = trim( (string) $match->first_name . ' ' . (string) $match->last_name );

			echo '<h2>' . esc_html( $name ) . '</h2>';
			echo '<table class="form-table" role="presentation"><tbody>';

			$rows = array(
				__( 'Phone', 'pitdesk-repair-crm' )   => $match->phone,
				__( 'Email', 'pitdesk-repair-crm' )   => $match->email,
				__( 'Address', 'pitdesk-repair-crm' ) => $match->address,
			);

			foreach ( $rows as $label => $value ) {
				echo '<tr><th scope="row">' . esc_html( $label ) . '</th><td>' . esc_html( (string) $value ) . '</td></tr>';
			}

			echo '</tbody></table>';

			echo '<form method="post" action="' . esc_url( self::list_url() ) . '" style="display:inline">';
			wp_nonce_field( self::PROMOTE_LINK_NONCE );
			echo '<input type="hidden" name="wrenchpress_action" value="promote_link" />';
			echo '<input type="hidden" name="lead" value="' . esc_attr( (string) (int) $lead->id ) . '" />';
			echo '<input type="hidden" name="customer" value="' . esc_attr( (string) (int) $match->id ) . '" />';
			echo '<button type="submit" class="button button-primary">' . esc_html__( 'Link to existing customer', 'pitdesk-repair-crm' ) . '</button>';
			echo '</form> ';
		}

		echo '<p>';
		echo '<form method="post" action="' . esc_url( self::list_url() ) . '" style="display:inline">';
		wp_nonce_field( self::PROMOTE_CREATE_NONCE );
		echo '<input type="hidden" name="wrenchpress_action" value="promote_create" />';
		echo '<input type="hidden" name="lead" value="' . esc_attr( (string) (int) $lead->id ) . '" />';
		echo '<button type="submit" class="button">' . esc_html__( 'Create new customer', 'pitdesk-repair-crm' ) . '</button>';
		echo '</form> ';
		echo '<a href="' . esc_url( self::list_url() ) . '" class="button button-link">' . esc_html__( 'Back to Leads', 'pitdesk-repair-crm' ) . '</a>';
		echo '</p>';

		echo '</div>';
	}

	/**
	 * Render the leads list screen.
	 *
	 * @return void
	 */
	private static function render_list() {
		$list_table = new WrenchPress_Leads_List_Table();
		$list_table->prepare_items();

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter carried across the search form.
		$status = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : '';

		echo '<div class="wrap">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Leads', 'pitdesk-repair-crm' ) . '</h1>';
		echo '<hr class="wp-header-end">';

		self::maybe_render_success_notice();

		// Status tabs with counts (Active | Archived | All). Not auto-printed by display().
		$list_table->views();

		echo '<form method="get">';
		echo '<input type="hidden" name="page" value="' . esc_attr( self::MENU_SLUG ) . '" />';

		if ( '' !== $status ) {
			echo '<input type="hidden" name="status" value="' . esc_attr( $status ) . '" />';
		}

		$list_table->display();
		echo '</form>';

		echo '</div>';
	}

	/**
	 * Render the success admin notice for the PRG flags.
	 *
	 * @return void
	 */
	private static function maybe_render_success_notice() {
		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- success flags come from our own PRG redirects.
		if ( isset( $_GET['dismissed'] ) ) {
			$message = __( 'Lead dismissed.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['spammed'] ) ) {
			$message = __( 'Lead marked as spam.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['archived'] ) ) {
			$message = __( 'Lead archived.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['restored'] ) ) {
			$message = __( 'Lead restored.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['promoted'] ) ) {
			$message = __( 'Lead promoted. The customer record was created. Open the lead to create the vehicle and work order.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['linked'] ) ) {
			$message = __( 'Lead linked to the existing customer. Open the lead to create the vehicle and work order.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['promote_error'] ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'That lead could not be promoted.', 'pitdesk-repair-crm' ) . '</p></div>';
			return;
		} else {
			return;
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
	}

	/**
	 * Render the read-only detail of a single lead followed by the gated
	 * three-step launchpad (D-08). Every untrusted lead value is escaped on
	 * output.
	 *
	 * @return void
	 */
	private static function render_view() {
		$id   = isset( $_GET['lead'] ) ? absint( wp_unslash( $_GET['lead'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only view.
		$lead = $id > 0 ? WrenchPress_Leads_Repository::get( $id ) : null;

		echo '<div class="wrap">';
		echo '<h1 class="wp-heading-inline">' . esc_html__( 'View Lead', 'pitdesk-repair-crm' ) . '</h1>';
		echo '<hr class="wp-header-end">';

		if ( ! $lead ) {
			echo '<div class="notice notice-error"><p>' . esc_html__( 'That lead could not be found.', 'pitdesk-repair-crm' ) . '</p></div>';
			echo '<p><a href="' . esc_url( self::list_url() ) . '" class="button">' . esc_html__( 'Back to Leads', 'pitdesk-repair-crm' ) . '</a></p></div>';
			return;
		}

		self::maybe_render_launchpad_notice();

		$rows = array(
			__( 'First name', 'pitdesk-repair-crm' )       => $lead->first_name,
			__( 'Last name', 'pitdesk-repair-crm' )        => $lead->last_name,
			__( 'Phone', 'pitdesk-repair-crm' )            => $lead->phone,
			__( 'Email', 'pitdesk-repair-crm' )            => $lead->email,
			__( 'Address', 'pitdesk-repair-crm' )          => $lead->address,
			__( 'Make', 'pitdesk-repair-crm' )             => $lead->make,
			__( 'Model', 'pitdesk-repair-crm' )            => $lead->model,
			__( 'Year', 'pitdesk-repair-crm' )             => $lead->year ? (string) $lead->year : '',
			__( 'VIN', 'pitdesk-repair-crm' )              => $lead->vin,
			__( 'License plate', 'pitdesk-repair-crm' )    => $lead->plate,
			__( 'Mileage', 'pitdesk-repair-crm' )          => $lead->mileage ? (string) $lead->mileage : '',
			__( 'Fuel type', 'pitdesk-repair-crm' )        => $lead->fuel_type,
			__( 'Transmission', 'pitdesk-repair-crm' )     => $lead->transmission,
			__( 'Color', 'pitdesk-repair-crm' )            => $lead->color,
			__( 'Reason for visit', 'pitdesk-repair-crm' ) => $lead->subject,
			__( 'Notes', 'pitdesk-repair-crm' )            => $lead->description,
			__( 'Consent given', 'pitdesk-repair-crm' )    => $lead->consent ? __( 'Yes', 'pitdesk-repair-crm' ) : __( 'No', 'pitdesk-repair-crm' ),
			__( 'Consent at', 'pitdesk-repair-crm' )       => self::format_datetime( $lead->consent_at ),
			__( 'Source', 'pitdesk-repair-crm' )           => $lead->source,
			__( 'Status', 'pitdesk-repair-crm' )           => ucfirst( (string) $lead->status ),
			__( 'Submitted', 'pitdesk-repair-crm' )        => self::format_datetime( $lead->created_at ),
		);

		echo '<table class="form-table" role="presentation"><tbody>';
		foreach ( $rows as $label => $value ) {
			echo '<tr><th scope="row">' . esc_html( $label ) . '</th><td>' . nl2br( esc_html( (string) $value ) ) . '</td></tr>';
		}
		echo '</tbody></table>';

		self::render_launchpad( $lead );

		echo '<p><a href="' . esc_url( self::list_url() ) . '" class="button">' . esc_html__( 'Back to Leads', 'pitdesk-repair-crm' ) . '</a></p>';
		echo '</div>';
	}

	/**
	 * Render the gated, sequential launchpad for a lead (D-08).
	 *
	 * Determines the lead's pipeline state, then renders ordered steps with later
	 * steps visually locked until their prerequisites are met:
	 *   1. Promote to customer   (done when the lead has a promoted_customer_id)
	 *   2. Create vehicle         (done when that customer has an active vehicle)
	 *   3. Create appointment     (optional deep link; unlocks once a customer and
	 *                              a vehicle exist; never gates step 4)
	 *   4. Create work order      (enabled only when steps 1 AND 2 are both done)
	 *
	 * The gating for the promote, vehicle, and work-order steps is mirrored by a
	 * server-side guard in each handler, so a locked step cannot be triggered by a
	 * crafted POST. No orphan work order is reachable from this pipeline. The
	 * appointment step is a pure deep link into the already-built calendar and
	 * carries no handler of its own.
	 *
	 * @param object $lead Lead snapshot.
	 * @return void
	 */
	private static function render_launchpad( $lead ) {
		$lead_id     = isset( $lead->id ) ? absint( $lead->id ) : 0;
		$customer_id = isset( $lead->promoted_customer_id ) ? absint( $lead->promoted_customer_id ) : 0;

		$step1_done = ( $customer_id > 0 );
		$vehicle_id = $step1_done ? self::vehicle_id_for_customer( $customer_id ) : 0;
		$step2_done = ( $vehicle_id > 0 );

		echo '<h2>' . esc_html__( 'Turn this lead into a job', 'pitdesk-repair-crm' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'Complete each step in order. Create work order unlocks once a customer and a vehicle exist for this lead.', 'pitdesk-repair-crm' ) . '</p>';

		echo '<ol class="wrenchpress-launchpad">';

		// Step 1: Promote to customer.
		self::render_launchpad_step(
			__( 'Promote to customer', 'pitdesk-repair-crm' ),
			$step1_done,
			false,
			static function () use ( $lead_id, $step1_done ) {
				if ( $step1_done ) {
					echo '<span class="wrenchpress-launchpad-status">' . esc_html__( 'Customer created.', 'pitdesk-repair-crm' ) . '</span>';
					return;
				}

				$promote_url = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::MENU_SLUG,
							'action' => 'promote',
							'lead'   => $lead_id,
						),
						admin_url( 'admin.php' )
					),
					self::PROMOTE_NONCE . $lead_id
				);

				echo '<a href="' . esc_url( $promote_url ) . '" class="button button-primary">' . esc_html__( 'Promote to customer', 'pitdesk-repair-crm' ) . '</a>';
			}
		);

		// Step 2: Create vehicle. Locked until step 1 is done.
		$step2_locked = ! $step1_done;
		self::render_launchpad_step(
			__( 'Create vehicle', 'pitdesk-repair-crm' ),
			$step2_done,
			$step2_locked,
			static function () use ( $lead_id, $step2_done, $step2_locked ) {
				if ( $step2_done ) {
					echo '<span class="wrenchpress-launchpad-status">' . esc_html__( 'Vehicle created from the lead snapshot.', 'pitdesk-repair-crm' ) . '</span>';
					return;
				}

				$attrs = $step2_locked ? ' disabled' : '';

				echo '<form method="post" action="' . esc_url( self::list_url() ) . '">';
				wp_nonce_field( self::CREATE_VEHICLE_NONCE . $lead_id );
				echo '<input type="hidden" name="wrenchpress_action" value="create_vehicle" />';
				echo '<input type="hidden" name="lead" value="' . esc_attr( (string) $lead_id ) . '" />';
				echo '<button type="submit" class="button button-primary"' . esc_attr( $attrs ) . '>' . esc_html__( 'Create vehicle from lead', 'pitdesk-repair-crm' ) . '</button>';
				echo '</form>';

				if ( $step2_locked ) {
					echo '<p class="description">' . esc_html__( 'Promote the lead to a customer first.', 'pitdesk-repair-crm' ) . '</p>';
				}
			}
		);

		// Step 3: Create appointment. Optional convenience step: a plain deep link
		// into the appointments calendar with this lead's customer and vehicle
		// prefilled. Locked until a customer and a vehicle both exist, since the
		// calendar prefill needs both. It never gates the Create work order step
		// below it; the two steps are independent.
		$step3_locked = ! $step2_done;
		self::render_launchpad_step(
			__( 'Create appointment', 'pitdesk-repair-crm' ),
			false,
			$step3_locked,
			static function () use ( $customer_id, $vehicle_id, $lead_id, $step3_locked ) {
				if ( ! current_user_can( WrenchPress_Capabilities::MANAGE_APPOINTMENTS ) ) {
					echo '<p class="description">' . esc_html__( 'You do not have permission to schedule appointments.', 'pitdesk-repair-crm' ) . '</p>';
					return;
				}

				if ( $step3_locked ) {
					echo '<button type="button" class="button button-primary" disabled>' . esc_html__( 'Schedule appointment', 'pitdesk-repair-crm' ) . '</button>';
					echo '<p class="description">' . esc_html__( 'A customer and a vehicle must exist for this lead first.', 'pitdesk-repair-crm' ) . '</p>';
					return;
				}

				// return_lead sends the user back to this lead after the appointment
				// is created (the island reads prefill.returnUrl and redirects).
				$schedule_url = add_query_arg(
					array(
						'page'        => 'wrenchpress-appointments',
						'customer'    => (int) $customer_id,
						'vehicle'     => (int) $vehicle_id,
						'return_lead' => (int) $lead_id,
					),
					admin_url( 'admin.php' )
				);

				echo '<a href="' . esc_url( $schedule_url ) . '" class="button button-primary">' . esc_html__( 'Schedule appointment', 'pitdesk-repair-crm' ) . '</a>';
			}
		);

		// Step 4: Create work order. Enabled only when steps 1 AND 2 are done.
		// Independent of the optional appointment step above.
		$step4_locked = ! ( $step1_done && $step2_done );
		self::render_launchpad_step(
			__( 'Create work order', 'pitdesk-repair-crm' ),
			false,
			$step4_locked,
			static function () use ( $lead_id, $step4_locked ) {
				$attrs = $step4_locked ? ' disabled' : '';

				echo '<form method="post" action="' . esc_url( self::list_url() ) . '">';
				wp_nonce_field( self::CREATE_WORK_ORDER_NONCE . $lead_id );
				echo '<input type="hidden" name="wrenchpress_action" value="create_work_order" />';
				echo '<input type="hidden" name="lead" value="' . esc_attr( (string) $lead_id ) . '" />';
				echo '<button type="submit" class="button button-primary"' . esc_attr( $attrs ) . '>' . esc_html__( 'Create work order', 'pitdesk-repair-crm' ) . '</button>';
				echo '</form>';

				if ( $step4_locked ) {
					echo '<p class="description">' . esc_html__( 'A customer and a vehicle must exist for this lead first.', 'pitdesk-repair-crm' ) . '</p>';
				}
			}
		);

		echo '</ol>';
	}

	/**
	 * Render a single launchpad step (list item) with a done or locked marker and
	 * its action body.
	 *
	 * @param string   $title  Step title.
	 * @param bool     $done   Whether the step is already satisfied.
	 * @param bool     $locked Whether the step is locked pending a prerequisite.
	 * @param callable $body   Callback that echoes the step's action controls.
	 * @return void
	 */
	private static function render_launchpad_step( $title, $done, $locked, $body ) {
		$classes = 'wrenchpress-launchpad-step';

		if ( $done ) {
			$classes .= ' is-done';
		} elseif ( $locked ) {
			$classes .= ' is-locked';
		}

		echo '<li class="' . esc_attr( $classes ) . '">';
		echo '<h3>' . esc_html( (string) $title );

		/*
		 * The step state is icon-only on screen, so the glyph is hidden from the
		 * accessibility tree and paired with a visually-hidden text equivalent.
		 * Both cues are needed: aria-hidden alone leaves the state unannounced,
		 * and the hidden text alone leaves the icon read as a named decoration.
		 */
		if ( $done ) {
			echo ' <span class="dashicons dashicons-yes" aria-hidden="true"></span>';
			echo '<span class="screen-reader-text"> ' . esc_html__( 'Done', 'pitdesk-repair-crm' ) . '</span>';
		} elseif ( $locked ) {
			echo ' <span class="dashicons dashicons-lock" aria-hidden="true"></span>';
			echo '<span class="screen-reader-text"> ' . esc_html__( 'Locked', 'pitdesk-repair-crm' ) . '</span>';
		}

		echo '</h3>';

		call_user_func( $body );

		echo '</li>';
	}

	/**
	 * Render the admin notice for the launchpad PRG step flags.
	 *
	 * @return void
	 */
	private static function maybe_render_launchpad_notice() {
		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- step flags come from our own PRG redirects.
		if ( isset( $_GET['vehicle_created'] ) ) {
			$message = __( 'Vehicle created from the lead. You can now create the work order.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['promoted'] ) ) {
			$message = __( 'Customer record created. Now create the vehicle, then the work order.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['linked'] ) ) {
			$message = __( 'Lead linked to the existing customer. Now create the vehicle, then the work order.', 'pitdesk-repair-crm' );
		} elseif ( isset( $_GET['vehicle_error'] ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'The vehicle could not be created from this lead.', 'pitdesk-repair-crm' ) . '</p></div>';
			return;
		} elseif ( isset( $_GET['work_order_error'] ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'The work order could not be created from this lead.', 'pitdesk-repair-crm' ) . '</p></div>';
			return;
		} elseif ( isset( $_GET['step_locked'] ) ) {
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'Complete the earlier steps before that one.', 'pitdesk-repair-crm' ) . '</p></div>';
			return;
		} else {
			return;
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
	}

	/**
	 * URL of the Leads list screen.
	 *
	 * @return string
	 */
	private static function list_url() {
		return add_query_arg( array( 'page' => self::MENU_SLUG ), admin_url( 'admin.php' ) );
	}

	/**
	 * Format a stored datetime for display, or return an empty string.
	 *
	 * @param string $value Stored datetime.
	 * @return string
	 */
	private static function format_datetime( $value ) {
		$value = (string) $value;

		if ( '' === $value || '0000-00-00 00:00:00' === $value ) {
			return '';
		}

		$timestamp = strtotime( $value );

		if ( ! $timestamp ) {
			return '';
		}

		return date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
	}
}
