<?php
/**
 * Broken links list table.
 *
 * @package 404Zen
 */

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

if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class LGBF_List_Table extends WP_List_Table {

	/**
	 * Current status filter.
	 *
	 * @var string
	 */
	private $status_filter = 'problems';

	public function __construct() {
		parent::__construct(
			array(
				'singular' => 'lgbf_link',
				'plural'   => 'lgbf_links',
				'ajax'     => false,
			)
		);
	}

	/**
	 * @return array Columns.
	 */
	public function get_columns() {
		return array(
			'cb'           => '<input type="checkbox" />',
			'url'          => __( 'URL', '404zen-broken-link-fixer' ),
			'status'       => __( 'Status', '404zen-broken-link-fixer' ),
			'anchor_text'  => __( 'Anchor text', '404zen-broken-link-fixer' ),
			'source'       => __( 'Found in', '404zen-broken-link-fixer' ),
			'last_checked' => __( 'Last checked', '404zen-broken-link-fixer' ),
		);
	}

	/**
	 * @return array Sortable columns.
	 */
	protected function get_sortable_columns() {
		return array(
			'url'          => array( 'url', false ),
			'status'       => array( 'http_status', false ),
			'source'       => array( 'post_id', false ),
			'last_checked' => array( 'last_checked', true ),
		);
	}

	/**
	 * @return array Bulk actions.
	 */
	protected function get_bulk_actions() {
		return array(
			'recheck' => __( 'Recheck', '404zen-broken-link-fixer' ),
			'dismiss' => __( 'Dismiss', '404zen-broken-link-fixer' ),
			'unlink'  => __( 'Unlink (keep text)', '404zen-broken-link-fixer' ),
		);
	}

	/**
	 * Status filter views above the table.
	 *
	 * @return array
	 */
	protected function get_views() {
		$counts  = LGBF_Links_Store::get_counts();
		$current = $this->status_filter;
		$base    = admin_url( 'admin.php?page=lgbf-404zen' );

		$views_def = array(
			'problems'  => array( __( 'All problems', '404zen-broken-link-fixer' ), $counts['problems'] ),
			'broken'    => array( __( 'Broken', '404zen-broken-link-fixer' ), $counts['broken'] ),
			'timeout'   => array( __( 'Timeouts', '404zen-broken-link-fixer' ), $counts['timeout'] ),
			'redirect'  => array( __( 'Redirect chains', '404zen-broken-link-fixer' ), $counts['redirect'] ),
			'dismissed' => array( __( 'Dismissed', '404zen-broken-link-fixer' ), $counts['dismissed'] ),
			'ok'        => array( __( 'OK', '404zen-broken-link-fixer' ), $counts['ok'] ),
		);

		$views = array();
		foreach ( $views_def as $key => $def ) {
			$class         = ( $key === $current ) ? ' class="current"' : '';
			$url           = esc_url( add_query_arg( 'link_status', $key, $base ) );
			$views[ $key ] = sprintf( '<a href="%s"%s>%s <span class="count">(%d)</span></a>', $url, $class, esc_html( $def[0] ), (int) $def[1] );
		}

		return $views;
	}

	/**
	 * Loads items for the current page.
	 */
	public function prepare_items() {
		global $wpdb;

		$table = LGBF_Links_Store::table();

		$this->status_filter = isset( $_GET['link_status'] ) ? sanitize_key( wp_unslash( $_GET['link_status'] ) ) : 'problems'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! in_array( $this->status_filter, array( 'problems', 'broken', 'timeout', 'redirect', 'dismissed', 'ok' ), true ) ) {
			$this->status_filter = 'problems';
		}

		if ( 'problems' === $this->status_filter ) {
			$where = "status IN ('broken', 'timeout', 'redirect')";
		} else {
			$where = $wpdb->prepare( 'status = %s', $this->status_filter );
		}

		$type_filter = isset( $_GET['link_type'] ) ? sanitize_key( wp_unslash( $_GET['link_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( in_array( $type_filter, array( 'link', 'image' ), true ) ) {
			$where .= $wpdb->prepare( ' AND link_type = %s', $type_filter );
		}

		$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( '' !== $search ) {
			$like   = '%' . $wpdb->esc_like( $search ) . '%';
			$where .= $wpdb->prepare( ' AND (url LIKE %s OR anchor_text LIKE %s)', $like, $like );
		}

		$orderby_input = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'last_checked'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$allowed       = array( 'url', 'http_status', 'post_id', 'last_checked' );
		$orderby       = in_array( $orderby_input, $allowed, true ) ? $orderby_input : 'last_checked';

		$order_input = isset( $_GET['order'] ) ? strtolower( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'desc'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$order       = ( 'asc' === $order_input ) ? 'ASC' : 'DESC';

		$per_page     = $this->get_items_per_page( 'lgbf_links_per_page', 25 );
		$current_page = $this->get_pagenum();
		$offset       = ( $current_page - 1 ) * $per_page;

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name and whitelisted clauses.
		$total_items = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE {$where}" );

		$this->items = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$table} WHERE {$where} ORDER BY {$orderby} {$order}, id DESC LIMIT %d OFFSET %d",
				$per_page,
				$offset
			),
			ARRAY_A
		);
		// phpcs:enable

		$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );

		$this->set_pagination_args(
			array(
				'total_items' => $total_items,
				'per_page'    => $per_page,
				'total_pages' => (int) ceil( $total_items / $per_page ),
			)
		);
	}

	/**
	 * Checkbox column.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_cb( $item ) {
		return sprintf( '<input type="checkbox" name="link_ids[]" value="%d" />', (int) $item['id'] );
	}

	/**
	 * URL column with row actions.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_url( $item ) {
		$url  = esc_url( $item['url'] );
		$html = sprintf(
			'<strong><a href="%1$s" target="_blank" rel="noopener noreferrer" class="lgbf-url-text">%2$s</a></strong>',
			$url,
			esc_html( $item['url'] )
		);

		if ( 'image' === $item['link_type'] ) {
			$html .= ' <span class="lgbf-badge lgbf-badge-type">' . esc_html__( 'image', '404zen-broken-link-fixer' ) . '</span>';
		}

		$actions = array(
			'edit'    => sprintf( '<a href="#" class="lgbf-action" data-action="edit" data-id="%d">%s</a>', (int) $item['id'], esc_html__( 'Edit URL', '404zen-broken-link-fixer' ) ),
			'unlink'  => sprintf( '<a href="#" class="lgbf-action" data-action="unlink" data-id="%d">%s</a>', (int) $item['id'], esc_html__( 'Unlink', '404zen-broken-link-fixer' ) ),
			'dismiss' => sprintf( '<a href="#" class="lgbf-action" data-action="dismiss" data-id="%d">%s</a>', (int) $item['id'], esc_html__( 'Dismiss', '404zen-broken-link-fixer' ) ),
			'recheck' => sprintf( '<a href="#" class="lgbf-action" data-action="recheck" data-id="%d">%s</a>', (int) $item['id'], esc_html__( 'Recheck', '404zen-broken-link-fixer' ) ),
		);

		if ( 'image' === $item['link_type'] ) {
			unset( $actions['unlink'] );
		}

		// Third (and last) Pro touchpoint: offer auto-redirects on rows that need them.
		if ( in_array( $item['status'], array( 'broken', 'timeout' ), true ) ) {
			if ( lgbf_can_use_premium() && class_exists( 'LGBF_Redirects' ) ) {
				$redirect_url          = add_query_arg(
					array( 'page' => 'lgbf-404zen-redirects', 'lgbf_source' => rawurlencode( $item['url'] ) ),
					admin_url( 'admin.php' )
				);
				$actions['autoredirect'] = sprintf( '<a href="%s">%s</a>', esc_url( $redirect_url ), esc_html__( 'Auto-redirect', '404zen-broken-link-fixer' ) );
			} else {
				$actions['autoredirect'] = sprintf( '<a href="%s">%s</a>', esc_url( LGBF_Admin::upgrade_url() ), esc_html__( 'Auto-redirect (Pro)', '404zen-broken-link-fixer' ) );
			}
		}

		$html .= '<div class="lgbf-inline-edit" data-id="' . (int) $item['id'] . '" style="display:none;">';
		$html .= '<input type="url" class="lgbf-new-url regular-text" value="' . $url . '" /> ';
		$html .= '<button type="button" class="button button-primary button-small lgbf-save-url">' . esc_html__( 'Save', '404zen-broken-link-fixer' ) . '</button> ';
		$html .= '<button type="button" class="button button-small lgbf-cancel-edit">' . esc_html__( 'Cancel', '404zen-broken-link-fixer' ) . '</button>';
		$html .= '</div>';

		return $html . $this->row_actions( $actions );
	}

	/**
	 * Status column.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_status( $item ) {
		$status = $item['status'];
		$code   = (int) $item['http_status'];

		switch ( $status ) {
			case 'broken':
				$label = $code ? sprintf( 'HTTP %d', $code ) : __( 'Connection failed', '404zen-broken-link-fixer' );
				$class = 'lgbf-badge-broken';
				break;
			case 'timeout':
				$label = __( 'Timeout', '404zen-broken-link-fixer' );
				$class = 'lgbf-badge-broken';
				break;
			case 'redirect':
				/* translators: %d: number of redirect hops. */
				$label = sprintf( __( 'Redirect chain (%d hops)', '404zen-broken-link-fixer' ), (int) $item['redirect_count'] );
				$class = 'lgbf-badge-redirect';
				break;
			case 'dismissed':
				$label = __( 'Dismissed', '404zen-broken-link-fixer' );
				$class = 'lgbf-badge-muted';
				break;
			case 'pending':
				$label = __( 'Queued', '404zen-broken-link-fixer' );
				$class = 'lgbf-badge-muted';
				break;
			default:
				$label = $code ? sprintf( 'HTTP %d', $code ) : __( 'OK', '404zen-broken-link-fixer' );
				$class = 'lgbf-badge-ok';
		}

		$html = '<span class="lgbf-badge ' . esc_attr( $class ) . '">' . esc_html( $label ) . '</span>';

		if ( 'redirect' === $status && ! empty( $item['final_url'] ) ) {
			$html .= '<div class="lgbf-final-url">→ ' . esc_html( $item['final_url'] ) . '</div>';
		}

		return $html;
	}

	/**
	 * Anchor text column.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_anchor_text( $item ) {
		return esc_html( $item['anchor_text'] );
	}

	/**
	 * Source post column.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_source( $item ) {
		$post_id = (int) $item['post_id'];
		$title   = get_the_title( $post_id );
		if ( ! $title ) {
			return '&mdash;';
		}

		$edit_link = get_edit_post_link( $post_id );
		if ( $edit_link ) {
			return sprintf( '<a href="%s">%s</a>', esc_url( $edit_link ), esc_html( $title ) );
		}

		return esc_html( $title );
	}

	/**
	 * Last checked column.
	 *
	 * @param array $item Row.
	 * @return string
	 */
	public function column_last_checked( $item ) {
		if ( empty( $item['last_checked'] ) ) {
			return esc_html__( 'Never', '404zen-broken-link-fixer' );
		}

		$timestamp = strtotime( $item['last_checked'] . ' UTC' );
		if ( ! $timestamp ) {
			return esc_html( $item['last_checked'] );
		}

		/* translators: %s: human-readable time difference. */
		return esc_html( sprintf( __( '%s ago', '404zen-broken-link-fixer' ), human_time_diff( $timestamp, time() ) ) );
	}

	/**
	 * Fallback column renderer.
	 *
	 * @param array  $item        Row.
	 * @param string $column_name Column.
	 * @return string
	 */
	public function column_default( $item, $column_name ) {
		return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : '';
	}

	/**
	 * Message for the empty state.
	 */
	public function no_items() {
		if ( 'problems' === $this->status_filter ) {
			esc_html_e( 'No broken links found. Nice and tidy!', '404zen-broken-link-fixer' );
		} else {
			esc_html_e( 'No links match this filter.', '404zen-broken-link-fixer' );
		}
	}
}
