<?php
/**
 * Pro task communication: author ↔ chief inline messaging plus email notifications
 * for assignment, new message, and completion. All gated behind newsai_is_pro().
 *
 * Schema lives in {wp_prefix}newsai_task_messages (created in newsroom-ai-assistant.php
 * during DB version 1.2). Settings live under newsai_task_* options.
 *
 * @package Newsroom_AI_Assistant
 */

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

// ============================================================
// Gates & helpers
// ============================================================

if ( ! function_exists( 'newsai_task_messaging_active' ) ) {
	function newsai_task_messaging_active() {
		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			return false;
		}
		return get_option( 'newsai_task_messaging_enabled', 'yes' ) === 'yes';
	}
}

if ( ! function_exists( 'newsai_task_emails_active' ) ) {
	function newsai_task_emails_active() {
		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			return false;
		}
		return get_option( 'newsai_task_email_enabled', 'yes' ) === 'yes';
	}
}

if ( ! function_exists( 'newsai_task_email_for' ) ) {
	function newsai_task_email_for( $event ) {
		if ( ! newsai_task_emails_active() ) {
			return false;
		}
		$opt = 'newsai_task_email_' . sanitize_key( $event );
		return get_option( $opt, 'yes' ) === 'yes';
	}
}

if ( ! function_exists( 'newsai_task_is_chief_user' ) ) {
	/**
	 * Editorial "chief" = can edit others' posts (Editor / Administrator in default WP).
	 * Must match admin menu + task list gating — do not treat manage_options-only roles as
	 * chiefs or thread lists / unread counts disagree with the sidebar bubble.
	 *
	 * @param int $user_id User ID.
	 * @return bool
	 */
	function newsai_task_is_chief_user( $user_id ) {
		$user_id = (int) $user_id;
		if ( $user_id < 1 ) {
			return false;
		}
		return user_can( $user_id, 'edit_others_posts' );
	}
}

if ( ! function_exists( 'newsai_task_message_visible_to' ) ) {
	/**
	 * Non-chief viewers only see their own messages plus chief/editor messages — never
	 * another author's messages in shared (unassigned) tasks.
	 *
	 * @param int $message_author_id User ID who wrote the row.
	 * @param int $viewer_id         User reading the thread.
	 */
	function newsai_task_message_visible_to( $message_author_id, $viewer_id ) {
		$message_author_id = (int) $message_author_id;
		$viewer_id         = (int) $viewer_id;
		if ( $viewer_id < 1 || $message_author_id < 1 ) {
			return false;
		}
		if ( newsai_task_is_chief_user( $viewer_id ) ) {
			return true;
		}
		if ( $message_author_id === $viewer_id ) {
			return true;
		}
		return newsai_task_is_chief_user( $message_author_id );
	}
}

if ( ! function_exists( 'newsai_task_can_access' ) ) {
	/**
	 * Access check for thread AJAX.
	 *
	 * Chiefs: any task. Authors: tasks assigned to them, or unassigned (author_id = 0)
	 * "pool" tasks visible to every author with edit_posts.
	 *
	 * @param int $alert_id Alert ID.
	 * @param int $user_id  User ID. Defaults to current user.
	 * @return bool
	 */
	function newsai_task_can_access( $alert_id, $user_id = 0 ) {
		$user_id  = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		$alert_id = (int) $alert_id;
		if ( $user_id < 1 || $alert_id < 1 ) {
			return false;
		}
		if ( newsai_task_is_chief_user( $user_id ) ) {
			return true;
		}
		if ( ! user_can( $user_id, 'edit_posts' ) ) {
			return false;
		}
		global $wpdb;
		$table  = $wpdb->prefix . 'newsai_alerts';
		$author = (int) $wpdb->get_var(
			$wpdb->prepare( "SELECT author_id FROM {$table} WHERE id = %d", $alert_id )
		);
		if ( $author === (int) $user_id ) {
			return true;
		}
		// Shared / open pool tasks (no assignee yet).
		return $author === 0;
	}
}

if ( ! function_exists( 'newsai_task_get_alert' ) ) {
	function newsai_task_get_alert( $alert_id ) {
		global $wpdb;
		$table = $wpdb->prefix . 'newsai_alerts';
		return $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", (int) $alert_id )
		);
	}
}

// ============================================================
// Unread tracking (per-user, per-task)
//
// Stored as a single user-meta entry `newsai_task_thread_seen` holding
// { alert_id => last_seen_message_id }. One meta read per page renders the
// badge for every task in the list; map is capped at 500 entries to avoid
// unbounded growth on accounts with very long histories.
// ============================================================

if ( ! function_exists( 'newsai_task_get_seen_map' ) ) {
	function newsai_task_get_seen_map( $user_id = 0 ) {
		$user_id = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		if ( $user_id < 1 ) {
			return [];
		}
		$raw = get_user_meta( $user_id, 'newsai_task_thread_seen', true );
		if ( is_array( $raw ) ) {
			return $raw;
		}
		if ( is_string( $raw ) && $raw !== '' ) {
			$decoded = json_decode( $raw, true );
			return is_array( $decoded ) ? $decoded : [];
		}
		return [];
	}
}

if ( ! function_exists( 'newsai_task_set_seen' ) ) {
	function newsai_task_set_seen( $alert_id, $msg_id, $user_id = 0 ) {
		$user_id = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		if ( $user_id < 1 || (int) $alert_id < 1 || (int) $msg_id < 1 ) {
			return;
		}
		$map     = newsai_task_get_seen_map( $user_id );
		$current = isset( $map[ (int) $alert_id ] ) ? (int) $map[ (int) $alert_id ] : 0;
		if ( (int) $msg_id <= $current ) {
			return;
		}
		$map[ (int) $alert_id ] = (int) $msg_id;
		if ( count( $map ) > 500 ) {
			$map = array_slice( $map, -500, null, true );
		}
		update_user_meta( $user_id, 'newsai_task_thread_seen', $map );
		// Watermark advanced — drop the cached total so the menu bubble refreshes on next render.
		if ( function_exists( 'newsai_task_invalidate_unread_cache' ) ) {
			newsai_task_invalidate_unread_cache( $user_id );
		}
	}
}

if ( ! function_exists( 'newsai_task_get_unread_counts' ) ) {
	/**
	 * @param int[] $alert_ids Alert IDs to inspect.
	 * @param int   $user_id   User to count for. Defaults to current.
	 * @return array<int,int> Map of alert_id => unread count (messages not authored by the user
	 *                        with id greater than the user's last-seen watermark for that task).
	 */
	function newsai_task_get_unread_counts( $alert_ids, $user_id = 0 ) {
		$user_id   = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		$alert_ids = array_values( array_unique( array_filter( array_map( 'intval', (array) $alert_ids ) ) ) );
		$out       = [];
		foreach ( $alert_ids as $aid ) {
			$out[ $aid ] = 0;
		}
		if ( $user_id < 1 || empty( $alert_ids ) ) {
			return $out;
		}
		global $wpdb;
		$msg_table    = $wpdb->prefix . 'newsai_task_messages';
		$placeholders = implode( ',', array_fill( 0, count( $alert_ids ), '%d' ) );
		// Pull (alert_id, id, user_id) so we can apply the variable last-seen watermark per task
		// in PHP. A pure-SQL aggregate would need a correlated subquery against user meta.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT alert_id, id, user_id FROM {$msg_table} WHERE alert_id IN ({$placeholders})",
				$alert_ids
			)
		);
		if ( empty( $rows ) ) {
			return $out;
		}
		$seen = newsai_task_get_seen_map( $user_id );
		$viewer_is_chief = newsai_task_is_chief_user( $user_id );
		foreach ( $rows as $row ) {
			$aid       = (int) $row->alert_id;
			$mid       = (int) $row->id;
			$uid       = (int) $row->user_id;
			$watermark = isset( $seen[ $aid ] ) ? (int) $seen[ $aid ] : 0;
			if ( $uid === $user_id || $mid <= $watermark || ! isset( $out[ $aid ] ) ) {
				continue;
			}
			if ( ! $viewer_is_chief && ! newsai_task_message_visible_to( $uid, $user_id ) ) {
				continue;
			}
			$out[ $aid ]++;
		}
		return $out;
	}
}

if ( ! function_exists( 'newsai_task_get_user_unread_total' ) ) {
	/**
	 * Returns the total unread message count for the current (or supplied) user across every
	 * task the user can access (assigned author or chief). Used by the admin menu bubble and
	 * the "My discussions" dashboard card.
	 *
	 * Result is cached for 5 minutes per user via transient so the menu render stays fast.
	 *
	 * @param int $user_id User to compute for. Defaults to current.
	 * @return int
	 */
	function newsai_task_get_user_unread_total( $user_id = 0 ) {
		if ( ! function_exists( 'newsai_task_messaging_active' ) || ! newsai_task_messaging_active() ) {
			return 0;
		}
		$user_id = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		if ( $user_id < 1 ) {
			return 0;
		}
		$cache_key = 'newsai_task_unread_total_' . $user_id;
		$cached    = get_transient( $cache_key );
		if ( $cached !== false ) {
			return (int) $cached;
		}
		$rows = newsai_task_get_user_threads_with_unread( $user_id, 500 );
		$total = 0;
		foreach ( $rows as $r ) {
			$total += (int) $r['unread'];
		}
		set_transient( $cache_key, $total, 5 * MINUTE_IN_SECONDS );
		return $total;
	}
}

if ( ! function_exists( 'newsai_task_invalidate_unread_cache' ) ) {
	function newsai_task_invalidate_unread_cache( $user_id = 0 ) {
		if ( $user_id > 0 ) {
			delete_transient( 'newsai_task_unread_total_' . (int) $user_id );
			return;
		}
		// Fan out — for the rare bulk path (license toggle, etc.) we just wipe via the
		// transient suffix since WP doesn't offer pattern deletes via the API.
		global $wpdb;
		if ( function_exists( 'newsai_delete_transients_by_prefix' ) ) {
			newsai_delete_transients_by_prefix( 'newsai_task_unread_total_' );
		}
	}
}

if ( ! function_exists( 'newsai_task_get_user_threads_with_unread' ) ) {
	/**
	 * Returns the tasks the given user can see, with their unread count.
	 * Sorted by unread DESC, then by most-recent-message DESC.
	 *
	 * @param int $user_id User ID.
	 * @param int $limit   Max tasks to consider.
	 * @return array<int,array{alert_id:int,title:string,unread:int,last_ts:string}>
	 */
	function newsai_task_get_user_threads_with_unread( $user_id, $limit = 50 ) {
		$user_id = (int) $user_id;
		if ( $user_id < 1 ) {
			return [];
		}
		global $wpdb;
		$table = $wpdb->prefix . 'newsai_alerts';

		// Chiefs see everything. Authors see personal assignments plus unassigned pool tasks.
		if ( newsai_task_is_chief_user( $user_id ) ) {
			$alerts = $wpdb->get_results(
				$wpdb->prepare( "SELECT id, title, author_id FROM {$table} ORDER BY id DESC LIMIT %d", $limit )
			);
		} else {
			$alerts = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT id, title, author_id FROM {$table} WHERE author_id = %d OR author_id = 0 ORDER BY id DESC LIMIT %d",
					$user_id,
					$limit
				)
			);
		}
		if ( empty( $alerts ) ) {
			return [];
		}
		$alert_ids = array_map( static function ( $a ) { return (int) $a->id; }, $alerts );
		$unread    = newsai_task_get_unread_counts( $alert_ids, $user_id );

		// Fetch most-recent message timestamp per alert so we can sort by activity.
		$msg_table    = $wpdb->prefix . 'newsai_task_messages';
		$placeholders = implode( ',', array_fill( 0, count( $alert_ids ), '%d' ) );
		$last_map = [];
		if ( newsai_task_is_chief_user( $user_id ) ) {
			$ts_rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT alert_id, MAX(created_utc) AS last_ts FROM {$msg_table} WHERE alert_id IN ({$placeholders}) GROUP BY alert_id",
					$alert_ids
				)
			);
			foreach ( (array) $ts_rows as $r ) {
				$last_map[ (int) $r->alert_id ] = (string) $r->last_ts;
			}
		} else {
			$msg_rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT alert_id, user_id, created_utc FROM {$msg_table} WHERE alert_id IN ({$placeholders}) ORDER BY id ASC",
					$alert_ids
				)
			);
			foreach ( (array) $msg_rows as $mr ) {
				if ( ! newsai_task_message_visible_to( (int) $mr->user_id, $user_id ) ) {
					continue;
				}
				$aid = (int) $mr->alert_id;
				$ts  = (string) $mr->created_utc;
				if ( ! isset( $last_map[ $aid ] ) || strcmp( $ts, $last_map[ $aid ] ) > 0 ) {
					$last_map[ $aid ] = $ts;
				}
			}
		}
		$out = [];
		foreach ( $alerts as $a ) {
			$aid = (int) $a->id;
			$out[] = [
				'alert_id' => $aid,
				'title'    => (string) $a->title,
				'unread'   => isset( $unread[ $aid ] ) ? (int) $unread[ $aid ] : 0,
				'last_ts'  => isset( $last_map[ $aid ] ) ? $last_map[ $aid ] : '',
			];
		}
		usort( $out, static function ( $a, $b ) {
			if ( $a['unread'] !== $b['unread'] ) {
				return $b['unread'] <=> $a['unread'];
			}
			return strcmp( $b['last_ts'], $a['last_ts'] );
		} );
		return $out;
	}
}

if ( ! function_exists( 'newsai_task_thread_participants' ) ) {
	/**
	 * All user IDs that should know about a task event: the assigned author plus
	 * anyone who has posted in the thread (typically the chief who assigned it).
	 *
	 * @param int $alert_id Alert ID.
	 * @return int[]
	 */
	function newsai_task_thread_participants( $alert_id ) {
		$alert = newsai_task_get_alert( $alert_id );
		$out   = [];
		if ( $alert && (int) $alert->author_id > 0 ) {
			$out[] = (int) $alert->author_id;
		}
		global $wpdb;
		$msg_table = $wpdb->prefix . 'newsai_task_messages';
		$rows      = $wpdb->get_col(
			$wpdb->prepare( "SELECT DISTINCT user_id FROM {$msg_table} WHERE alert_id = %d", (int) $alert_id )
		);
		if ( is_array( $rows ) ) {
			foreach ( $rows as $uid ) {
				$out[] = (int) $uid;
			}
		}
		return array_values( array_unique( array_filter( $out ) ) );
	}
}

// ============================================================
// Email senders
// ============================================================

if ( ! function_exists( 'newsai_task_admin_url_for_alert' ) ) {
	/**
	 * Build a deep link straight to the right surface for the recipient. Authors land on
	 * "My Inbox" (the slim page they can actually open), chiefs go to the full dashboard.
	 *
	 * @param int   $alert_id Alert ID.
	 * @param array $extra    Extra query args.
	 * @param int   $user_id  Recipient user — when supplied, decides the landing surface.
	 * @return string
	 */
	function newsai_task_admin_url_for_alert( $alert_id, $extra = [], $user_id = 0 ) {
		$user_id = $user_id > 0 ? (int) $user_id : (int) get_current_user_id();
		$is_chief = $user_id > 0
			? newsai_task_is_chief_user( $user_id )
			: current_user_can( 'edit_others_posts' );
		$args = $is_chief
			? array_merge( [ 'page' => 'newsai-settings', 'tab' => 'alerts', 'open_task' => (int) $alert_id ], $extra )
			: array_merge( [ 'page' => 'newsai-inbox', 'open_task' => (int) $alert_id ], $extra );
		return esc_url_raw( add_query_arg( $args, admin_url( 'admin.php' ) ) );
	}
}

if ( ! function_exists( 'newsai_task_email_intro' ) ) {
	function newsai_task_email_intro() {
		$site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		return [
			'site' => $site,
			'from' => sprintf( '%s <%s>', $site, get_bloginfo( 'admin_email' ) ),
		];
	}
}

if ( ! function_exists( 'newsai_task_send_assignment_email' ) ) {
	/**
	 * @param object $alert  Alert row.
	 * @param int    $by_id  User who assigned (chief), 0 if system.
	 * @return bool
	 */
	function newsai_task_send_assignment_email( $alert, $by_id = 0 ) {
		if ( ! newsai_task_email_for( 'assigned' ) ) {
			return false;
		}
		if ( ! $alert || (int) $alert->author_id < 1 ) {
			return false;
		}
		$author = get_userdata( (int) $alert->author_id );
		if ( ! $author || empty( $author->user_email ) ) {
			return false;
		}
		$by   = $by_id > 0 ? get_userdata( $by_id ) : null;
		$by_n = $by ? $by->display_name : __( 'an editor', 'newsroom-ai-assistant' );

		$date = '';
		if ( (int) $alert->year > 0 ) {
			$date = sprintf( '%04d-%02d-%02d', (int) $alert->year, (int) $alert->month, (int) $alert->day );
		} elseif ( (int) $alert->month > 0 ) {
			$date = sprintf( '%s/%s', (int) $alert->day, (int) $alert->month );
		} else {
			$date = __( 'recurring', 'newsroom-ai-assistant' );
		}

		$subject = sprintf(
			/* translators: 1: site name, 2: assignment title */
			__( '[%1$s] New assignment: %2$s', 'newsroom-ai-assistant' ),
			get_bloginfo( 'name' ),
			$alert->title
		);

		$url = newsai_task_admin_url_for_alert( $alert->id, [], (int) $author->ID );

		$body  = sprintf(
			/* translators: 1: author display name, 2: editor display name */
			__( "Hi %1\$s,\n\n%2\$s assigned you the following story.", 'newsroom-ai-assistant' ),
			$author->display_name,
			$by_n
		);
		$body .= "\n\n";
		$body .= __( 'Title:', 'newsroom-ai-assistant' ) . ' ' . $alert->title . "\n";
		$body .= __( 'Date:', 'newsroom-ai-assistant' ) . ' ' . $date . "\n";
		if ( ! empty( $alert->target_keyword ) ) {
			$body .= __( 'Keyword:', 'newsroom-ai-assistant' ) . ' ' . $alert->target_keyword . "\n";
		}
		if ( ! empty( $alert->ref_link ) ) {
			$body .= __( 'Reference:', 'newsroom-ai-assistant' ) . ' ' . $alert->ref_link . "\n";
		}
		if ( ! empty( $alert->notes ) ) {
			$body .= "\n" . __( 'Brief:', 'newsroom-ai-assistant' ) . "\n" . $alert->notes . "\n";
		}
		$body .= "\n" . __( 'Open in WordPress:', 'newsroom-ai-assistant' ) . ' ' . $url . "\n";

		return (bool) wp_mail( $author->user_email, $subject, $body );
	}
}

if ( ! function_exists( 'newsai_task_get_message_notify_recipient_ids' ) ) {
	/**
	 * Users who should receive new-message email / unread cache busts: every chief plus the
	 * assignee on assigned tasks. Peer authors on pool tasks are excluded so hidden traffic
	 * does not notify or spam them.
	 *
	 * @param object $alert     Alert row.
	 * @param int    $sender_id Sender to exclude.
	 * @return int[]
	 */
	function newsai_task_get_message_notify_recipient_ids( $alert, $sender_id ) {
		if ( ! $alert ) {
			return [];
		}
		$sender_id = (int) $sender_id;
		$assignee  = (int) $alert->author_id;
		$ids       = [];

		$participants = newsai_task_thread_participants( (int) $alert->id );
		foreach ( $participants as $pid ) {
			$pid = (int) $pid;
			if ( $pid < 1 || $pid === $sender_id ) {
				continue;
			}
			if ( newsai_task_is_chief_user( $pid ) ) {
				$ids[] = $pid;
			} elseif ( $assignee > 0 && $pid === $assignee ) {
				$ids[] = $pid;
			}
		}
		$has_chief = false;
		foreach ( $ids as $i ) {
			if ( newsai_task_is_chief_user( $i ) ) {
				$has_chief = true;
				break;
			}
		}
		// Pool thread: first author message may not include any chief in participants yet —
		// still notify every editor so the menu bubble updates.
		if ( ! $has_chief ) {
			$editors = get_users(
				[
					'capability' => 'edit_others_posts',
					'fields'     => 'ID',
					'number'     => 100,
				]
			);
			foreach ( $editors as $eid ) {
				$eid = (int) $eid;
				if ( $eid > 0 && $eid !== $sender_id ) {
					$ids[] = $eid;
				}
			}
		}
		return array_values( array_unique( array_filter( $ids ) ) );
	}
}

if ( ! function_exists( 'newsai_task_send_message_email' ) ) {
	/**
	 * Notify chiefs (editors) plus the assignee on assigned tasks — never peer authors on pool
	 * tasks, so message bodies are not emailed across the newsroom.
	 *
	 * @param int    $alert_id Alert ID.
	 * @param int    $sender_id Sender user ID.
	 * @param string $message_body Plain text body.
	 * @return int Number of emails sent.
	 */
	function newsai_task_send_message_email( $alert_id, $sender_id, $message_body ) {
		if ( ! newsai_task_email_for( 'message' ) ) {
			return 0;
		}
		$alert = newsai_task_get_alert( $alert_id );
		if ( ! $alert ) {
			return 0;
		}
		$participants = newsai_task_get_message_notify_recipient_ids( $alert, (int) $sender_id );
		if ( empty( $participants ) ) {
			return 0;
		}
		$sender = get_userdata( (int) $sender_id );

		$subject = sprintf(
			/* translators: 1: site name, 2: task title */
			__( '[%1$s] New message on: %2$s', 'newsroom-ai-assistant' ),
			get_bloginfo( 'name' ),
			$alert->title
		);

		$sname = $sender ? $sender->display_name : __( 'Someone', 'newsroom-ai-assistant' );
		$sent  = 0;
		foreach ( $participants as $uid ) {
			$user = get_userdata( $uid );
			if ( ! $user || empty( $user->user_email ) ) {
				continue;
			}
			$url   = newsai_task_admin_url_for_alert( $alert_id, [], (int) $user->ID );
			$body  = sprintf(
				/* translators: 1: recipient name, 2: sender name, 3: task title */
				__( "Hi %1\$s,\n\n%2\$s posted on \"%3\$s\":", 'newsroom-ai-assistant' ),
				$user->display_name,
				$sname,
				$alert->title
			) . "\n\n";
			$body .= $message_body . "\n\n";
			$body .= __( 'Open thread:', 'newsroom-ai-assistant' ) . ' ' . $url . "\n";
			if ( wp_mail( $user->user_email, $subject, $body ) ) {
				$sent++;
			}
		}
		return $sent;
	}
}

if ( ! function_exists( 'newsai_task_send_done_email' ) ) {
	function newsai_task_send_done_email( $alert_id, $marked_by_id ) {
		if ( ! newsai_task_email_for( 'done' ) ) {
			return 0;
		}
		$alert = newsai_task_get_alert( $alert_id );
		if ( ! $alert ) {
			return 0;
		}
		$participants = newsai_task_get_message_notify_recipient_ids( $alert, (int) $marked_by_id );
		if ( empty( $participants ) ) {
			return 0;
		}
		$marker = get_userdata( (int) $marked_by_id );

		$subject = sprintf(
			/* translators: 1: site name, 2: task title */
			__( '[%1$s] Completed: %2$s', 'newsroom-ai-assistant' ),
			get_bloginfo( 'name' ),
			$alert->title
		);
		$mname = $marker ? $marker->display_name : __( 'Editor', 'newsroom-ai-assistant' );

		$sent = 0;
		foreach ( $participants as $uid ) {
			$user = get_userdata( $uid );
			if ( ! $user || empty( $user->user_email ) ) {
				continue;
			}
			$url  = newsai_task_admin_url_for_alert( $alert_id, [], (int) $user->ID );
			$body = sprintf(
				/* translators: 1: recipient name, 2: marker name, 3: task title */
				__( "Hi %1\$s,\n\n%2\$s marked the assignment \"%3\$s\" as done.", 'newsroom-ai-assistant' ),
				$user->display_name,
				$mname,
				$alert->title
			) . "\n\n";
			$body .= __( 'Open in WordPress:', 'newsroom-ai-assistant' ) . ' ' . $url . "\n";
			if ( wp_mail( $user->user_email, $subject, $body ) ) {
				$sent++;
			}
		}
		return $sent;
	}
}

// ============================================================
// Public actions consumed by the dashboard save flow
// ============================================================

if ( ! function_exists( 'newsai_task_after_save' ) ) {
	/**
	 * Hook target after a task is inserted or updated.
	 *
	 * @param int  $alert_id    Alert ID.
	 * @param bool $is_new      True for inserts.
	 * @param int  $prev_author Previous author_id for updates (0 for new).
	 * @return void
	 */
	function newsai_task_after_save( $alert_id, $is_new, $prev_author = 0 ) {
		$alert = newsai_task_get_alert( $alert_id );
		if ( ! $alert ) {
			return;
		}
		if ( (int) $alert->author_id < 1 ) {
			return;
		}
		// Send only on a new task OR when the assignee actually changed.
		if ( ! $is_new && (int) $alert->author_id === (int) $prev_author ) {
			return;
		}
		newsai_task_send_assignment_email( $alert, (int) get_current_user_id() );
	}
}
add_action( 'newsai_task_assigned', 'newsai_task_after_save', 10, 3 );

if ( ! function_exists( 'newsai_task_after_done' ) ) {
	function newsai_task_after_done( $alert_id, $marked_by_id ) {
		newsai_task_send_done_email( (int) $alert_id, (int) $marked_by_id );
	}
}
add_action( 'newsai_task_completed', 'newsai_task_after_done', 10, 2 );

// ============================================================
// AJAX: list + post messages
// ============================================================

if ( ! function_exists( 'newsai_task_ajax_list_messages' ) ) {
	function newsai_task_ajax_list_messages() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		if ( ! newsai_task_messaging_active() ) {
			wp_send_json_error( [ 'message' => 'pro_required' ], 402 );
		}
		$alert_id = isset( $_POST['alert_id'] ) ? (int) $_POST['alert_id'] : 0;
		if ( $alert_id < 1 || ! newsai_task_can_access( $alert_id ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		global $wpdb;
		$msg_table = $wpdb->prefix . 'newsai_task_messages';
		$rows      = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, user_id, body, created_utc FROM {$msg_table} WHERE alert_id = %d ORDER BY id ASC LIMIT 200",
				$alert_id
			)
		);
		$viewer      = (int) get_current_user_id();
		$viewer_chief = newsai_task_is_chief_user( $viewer );
		$out         = [];
		$max_visible = 0;
		foreach ( (array) $rows as $r ) {
			if ( ! $viewer_chief && ! newsai_task_message_visible_to( (int) $r->user_id, $viewer ) ) {
				continue;
			}
			$u = get_userdata( (int) $r->user_id );
			$out[] = [
				'id'       => (int) $r->id,
				'user_id'  => (int) $r->user_id,
				'name'     => $u ? $u->display_name : ( '#' . (int) $r->user_id ),
				'avatar'   => esc_url_raw( get_avatar_url( (int) $r->user_id, [ 'size' => 32 ] ) ),
				'body'     => (string) $r->body,
				'created'  => (string) $r->created_utc,
				'is_self'  => (int) $r->user_id === $viewer,
			];
			if ( (int) $r->id > $max_visible ) {
				$max_visible = (int) $r->id;
			}
		}
		// Watermark only advances for messages the viewer can actually see — hidden peer
		// traffic in pool tasks must not clear chief-only unread badges incorrectly.
		if ( $max_visible > 0 ) {
			newsai_task_set_seen( $alert_id, $max_visible );
		}
		wp_send_json_success( [
			'messages' => $out,
			'count'    => count( $out ),
			'max_id'   => $max_visible,
			'can_post' => true,
		] );
	}
}
add_action( 'wp_ajax_newsai_task_messages_list', 'newsai_task_ajax_list_messages' );

if ( ! function_exists( 'newsai_task_ajax_post_message' ) ) {
	function newsai_task_ajax_post_message() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		if ( ! newsai_task_messaging_active() ) {
			wp_send_json_error( [ 'message' => 'pro_required' ], 402 );
		}
		$alert_id = isset( $_POST['alert_id'] ) ? (int) $_POST['alert_id'] : 0;
		$body_raw = isset( $_POST['body'] ) ? (string) wp_unslash( $_POST['body'] ) : '';
		$body     = sanitize_textarea_field( $body_raw );
		if ( $alert_id < 1 || $body === '' ) {
			wp_send_json_error( [ 'message' => 'missing_fields' ], 400 );
		}
		if ( mb_strlen( $body, 'UTF-8' ) > 4000 ) {
			$body = mb_substr( $body, 0, 4000, 'UTF-8' );
		}
		if ( ! newsai_task_can_access( $alert_id ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		global $wpdb;
		$msg_table = $wpdb->prefix . 'newsai_task_messages';
		$ok = $wpdb->insert(
			$msg_table,
			[
				'alert_id'    => $alert_id,
				'user_id'     => (int) get_current_user_id(),
				'body'        => $body,
				'created_utc' => gmdate( 'Y-m-d H:i:s' ),
			],
			[ '%d', '%d', '%s', '%s' ]
		);
		if ( ! $ok ) {
			wp_send_json_error( [ 'message' => 'db_error' ], 500 );
		}
		$msg_id = (int) $wpdb->insert_id;
		// Sender has implicitly "seen" their own message — keeps the badge accurate after refresh.
		newsai_task_set_seen( $alert_id, $msg_id );
		$alert = newsai_task_get_alert( $alert_id );
		if ( $alert ) {
			$notify_ids = newsai_task_get_message_notify_recipient_ids( $alert, (int) get_current_user_id() );
			foreach ( $notify_ids as $pid ) {
				newsai_task_invalidate_unread_cache( (int) $pid );
			}
		}
		newsai_task_send_message_email( $alert_id, (int) get_current_user_id(), $body );
		wp_send_json_success( [ 'id' => $msg_id ] );
	}
}
add_action( 'wp_ajax_newsai_task_messages_post', 'newsai_task_ajax_post_message' );

// ============================================================
// Settings localize for the dashboard JS
// ============================================================

if ( ! function_exists( 'newsai_task_is_plugin_screen' ) ) {
	function newsai_task_is_plugin_screen( $hook = '' ) {
		$hook = (string) $hook;
		if ( $hook === '' ) {
			$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
			$hook   = $screen ? (string) $screen->id : '';
		}
		return ( strpos( $hook, 'newsai-settings' ) !== false || strpos( $hook, 'newsai-inbox' ) !== false );
	}
}

if ( ! function_exists( 'newsai_task_localize_dashboard_vars' ) ) {
	function newsai_task_localize_dashboard_vars( $hook ) {
		if ( ! newsai_task_is_plugin_screen( (string) $hook ) ) {
			return;
		}
		// The plugin already enqueues newsai-script on settings pages; piggyback on it.
		wp_localize_script(
			'newsai-script',
			'newsai_task_vars',
			[
				'ajax_url'     => admin_url( 'admin-ajax.php' ),
				'nonce'        => wp_create_nonce( 'newsai_ajax_nonce' ),
				'messaging_on' => newsai_task_messaging_active(),
				'lang'         => function_exists( 'newsai_get_ui_lang' ) ? newsai_get_ui_lang() : 'en',
				'i18n'         => [
					'open'    => function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Επικοινωνία' : 'Discussion',
					'send'    => function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Αποστολή' : 'Send',
					'placeholder' => function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Γράψε μήνυμα στον/στην…' : 'Type a message…',
					'empty'   => function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Καμία συνομιλία ακόμα.' : 'No messages yet.',
					'pro_lock'=> function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Pro feature' : 'Pro feature',
					'loading' => function_exists( 'newsai_get_ui_lang' ) && newsai_get_ui_lang() === 'el' ? 'Φόρτωση…' : 'Loading…',
				],
			]
		);
	}
}
add_action( 'admin_enqueue_scripts', 'newsai_task_localize_dashboard_vars', 20 );

// ============================================================
// Retention: auto-purge thread messages once a task has been done & quiet for N days.
//
// Stored in option `newsai_task_messages_retention_days` (0 = disabled). Cron runs daily
// and deletes every message belonging to alerts where status='done' AND the most recent
// message is older than the retention window. The task row itself is kept.
// ============================================================

if ( ! function_exists( 'newsai_task_get_retention_days' ) ) {
	function newsai_task_get_retention_days() {
		$d = (int) get_option( 'newsai_task_messages_retention_days', 30 );
		if ( $d < 0 ) { $d = 0; }
		if ( $d > 365 ) { $d = 365; }
		return $d;
	}
}

if ( ! function_exists( 'newsai_task_purge_expired_threads' ) ) {
	function newsai_task_purge_expired_threads() {
		if ( ! function_exists( 'newsai_task_messaging_active' ) || ! newsai_task_messaging_active() ) {
			return 0;
		}
		$days = newsai_task_get_retention_days();
		if ( $days < 1 ) {
			return 0; // 0 means "never auto-purge".
		}
		global $wpdb;
		$alerts_table = $wpdb->prefix . 'newsai_alerts';
		$msg_table    = $wpdb->prefix . 'newsai_task_messages';
		$cutoff       = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );

		// Two-step: first find expired alert IDs, then delete their messages. Keeps queries
		// simple and lets us count deletions for the activity log.
		$expired_ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT m.alert_id
				 FROM {$msg_table} m
				 INNER JOIN {$alerts_table} a ON a.id = m.alert_id
				 WHERE a.status = 'done'
				 GROUP BY m.alert_id
				 HAVING MAX(m.created_utc) < %s",
				$cutoff
			)
		);
		if ( empty( $expired_ids ) ) {
			return 0;
		}
		$ids   = array_map( 'intval', $expired_ids );
		$ids_s = implode( ',', $ids );
		$wpdb->query( "DELETE FROM {$msg_table} WHERE alert_id IN ({$ids_s})" );
		newsai_task_invalidate_unread_cache();
		update_option( 'newsai_task_last_purge', time(), false );
		update_option( 'newsai_task_last_purge_count', (int) $wpdb->rows_affected, false );
		return (int) $wpdb->rows_affected;
	}
}
add_action( 'newsai_task_messages_purge', 'newsai_task_purge_expired_threads' );

if ( ! function_exists( 'newsai_task_schedule_purge_cron' ) ) {
	function newsai_task_schedule_purge_cron() {
		if ( ! wp_next_scheduled( 'newsai_task_messages_purge' ) ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'newsai_task_messages_purge' );
		}
	}
}
add_action( 'admin_init', 'newsai_task_schedule_purge_cron' );

// ============================================================
// AJAX: manual "clear discussion" (chief only)
// ============================================================

if ( ! function_exists( 'newsai_task_ajax_clear_thread' ) ) {
	function newsai_task_ajax_clear_thread() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		if ( ! newsai_task_messaging_active() ) {
			wp_send_json_error( [ 'message' => 'pro_required' ], 402 );
		}
		// Only chiefs can wipe a discussion — authors get the close button instead.
		if ( ! current_user_can( 'edit_others_posts' ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		$alert_id = isset( $_POST['alert_id'] ) ? (int) $_POST['alert_id'] : 0;
		if ( $alert_id < 1 || ! newsai_task_can_access( $alert_id ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		global $wpdb;
		$msg_table = $wpdb->prefix . 'newsai_task_messages';
		$deleted   = (int) $wpdb->delete( $msg_table, [ 'alert_id' => $alert_id ], [ '%d' ] );
		// Reset watermarks for every participant of this thread so the badge clears cleanly.
		$participants = newsai_task_thread_participants( $alert_id );
		foreach ( $participants as $pid ) {
			$map = newsai_task_get_seen_map( (int) $pid );
			if ( isset( $map[ (int) $alert_id ] ) ) {
				unset( $map[ (int) $alert_id ] );
				update_user_meta( (int) $pid, 'newsai_task_thread_seen', $map );
			}
			newsai_task_invalidate_unread_cache( (int) $pid );
		}
		wp_send_json_success( [ 'deleted' => $deleted ] );
	}
}
add_action( 'wp_ajax_newsai_task_messages_clear', 'newsai_task_ajax_clear_thread' );

// ============================================================
// Drawer markup + inline JS, printed on the plugin settings pages.
// Kept self-contained so it survives admin-ui.php refactors and so any future
// task list (e.g. dashboard widget) can reuse the same drawer via `.newsai-task-thread-open`.
// ============================================================

if ( ! function_exists( 'newsai_task_thread_drawer_markup' ) ) {
	function newsai_task_thread_drawer_markup() {
		if ( ! newsai_task_is_plugin_screen() ) {
			return;
		}
		if ( ! newsai_task_messaging_active() ) {
			return;
		}
		$is_chief = current_user_can( 'edit_others_posts' );
		?>
		<style>
			/* Drawer visibility is controlled via the .is-open class so theme/plugin CSS that
			   ignores the [hidden] attribute cannot leave the dialog visible. */
			#newsai-task-thread-backdrop,
			#newsai-task-thread-drawer { display: none; }
			#newsai-task-thread-backdrop.is-open,
			#newsai-task-thread-drawer.is-open { display: flex; }
			#newsai-task-thread-close { font-size: 22px; line-height: 1; padding: 4px 8px; cursor: pointer; background: transparent; border: 1px solid transparent; color: #1d2327; border-radius: 4px; }
			#newsai-task-thread-close:hover { background: #f0f0f1; }
			#newsai-task-thread-clear { font-size: 11px; padding: 4px 8px; cursor: pointer; background: transparent; border: 1px solid #d63638; color: #d63638; border-radius: 4px; line-height: 1; }
			#newsai-task-thread-clear:hover { background: #fee2e2; }
		</style>
		<div id="newsai-task-thread-backdrop" style="position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:99998;"></div>
		<aside id="newsai-task-thread-drawer" role="dialog" aria-modal="true" aria-labelledby="newsai-task-thread-title"
		       style="position:fixed;top:0;right:0;width:min(420px,95vw);height:100vh;background:#fff;box-shadow:-12px 0 28px rgba(0,0,0,.18);z-index:99999;flex-direction:column;">
			<header style="padding:14px 16px;border-bottom:1px solid #e2e4e7;display:flex;justify-content:space-between;align-items:flex-start;gap:8px;flex-shrink:0;">
				<div style="min-width:0;flex:1;">
					<div style="font-size:11px;color:#646970;text-transform:uppercase;letter-spacing:.04em;"><?php echo esc_html__( 'Task discussion', 'newsroom-ai-assistant' ); ?></div>
					<strong id="newsai-task-thread-title" style="font-size:14px;display:block;line-height:1.3;overflow-wrap:anywhere;"></strong>
				</div>
				<div style="display:flex;align-items:center;gap:6px;flex-shrink:0;">
					<?php if ( $is_chief ) : ?>
						<button type="button" id="newsai-task-thread-clear" title="<?php echo esc_attr__( 'Delete all messages', 'newsroom-ai-assistant' ); ?>">🗑️ <?php echo esc_html__( 'Clear', 'newsroom-ai-assistant' ); ?></button>
					<?php endif; ?>
					<button type="button" id="newsai-task-thread-close" aria-label="<?php echo esc_attr__( 'Close', 'newsroom-ai-assistant' ); ?>" title="<?php echo esc_attr__( 'Close (Esc)', 'newsroom-ai-assistant' ); ?>">✕</button>
				</div>
			</header>
			<div id="newsai-task-thread-body" style="flex:1;overflow:auto;padding:14px 16px;background:#f6f7f7;">
				<p style="color:#646970;font-size:12px;"><?php echo esc_html__( 'Loading…', 'newsroom-ai-assistant' ); ?></p>
			</div>
			<form id="newsai-task-thread-form" style="border-top:1px solid #e2e4e7;padding:10px 12px;display:flex;flex-direction:column;gap:8px;background:#fff;flex-shrink:0;">
				<textarea id="newsai-task-thread-input" rows="3" required maxlength="4000"
				          placeholder="<?php echo esc_attr__( 'Type a message…', 'newsroom-ai-assistant' ); ?>"
				          style="width:100%;resize:vertical;font-family:inherit;font-size:13px;padding:8px;border:1px solid #c3c4c7;border-radius:4px;"></textarea>
				<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;">
					<small style="color:#646970;font-size:11px;">
						<?php
						if ( $is_chief ) {
							echo esc_html__( 'Visible to everyone with access to this task.', 'newsroom-ai-assistant' );
						} else {
							echo esc_html__( 'You only see your own messages and the editor’s — not other writers’.', 'newsroom-ai-assistant' );
						}
						?>
					</small>
					<button type="submit" class="button button-primary"><?php echo esc_html__( 'Send', 'newsroom-ai-assistant' ); ?></button>
				</div>
			</form>
		</aside>
		<?php
	}
}
add_action( 'admin_footer', 'newsai_task_thread_drawer_markup' );

if ( ! function_exists( 'newsai_task_thread_drawer_script' ) ) {
	function newsai_task_thread_drawer_script() {
		if ( ! newsai_task_is_plugin_screen() ) {
			return;
		}
		if ( ! newsai_task_messaging_active() ) {
			return;
		}
		?>
		<script>
		(function () {
			if (typeof jQuery === 'undefined') { return; }
			var $ = jQuery;
			// Wait for DOM ready so the drawer markup is guaranteed to be in the document
			// when we wire selectors. wp_localize_script runs early; guard for it too.
			$(function () {
				if (typeof newsai_task_vars === 'undefined') { return; }
				var $drawer   = $('#newsai-task-thread-drawer');
				var $backdrop = $('#newsai-task-thread-backdrop');
				if (!$drawer.length || !$backdrop.length) { return; }

				var $title = $('#newsai-task-thread-title');
				var $body  = $('#newsai-task-thread-body');
				var $form  = $('#newsai-task-thread-form');
				var $input = $('#newsai-task-thread-input');
				var currentAlert = 0;
				var isOpen = false;

				function openDrawer(alertId, alertTitle) {
					var id = parseInt(alertId, 10) || 0;
					if (!id) { return; }
					currentAlert = id;
					isOpen = true;
					$title.text(alertTitle || ('#' + id));
					$body.html('<p style="color:#646970;font-size:12px;">' + escapeHtml(newsai_task_vars.i18n.loading) + '</p>');
					$drawer.addClass('is-open');
					$backdrop.addClass('is-open');
					$('body').addClass('newsai-task-thread-open-body');
					loadThread();
					setTimeout(function () { try { $input.trigger('focus'); } catch (e) {} }, 60);
				}
				function closeDrawer() {
					isOpen = false;
					currentAlert = 0;
					$drawer.removeClass('is-open');
					$backdrop.removeClass('is-open');
					$('body').removeClass('newsai-task-thread-open-body');
				}
				function escapeHtml(s) {
					return String(s == null ? '' : s)
						.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
						.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
				}
				function renderMessages(messages) {
					if (!messages || !messages.length) {
						$body.html('<p style="color:#646970;font-size:12px;margin:0;">' + escapeHtml(newsai_task_vars.i18n.empty) + '</p>');
						return;
					}
					var html = messages.map(function (m) {
						var align = m.is_self ? 'flex-end' : 'flex-start';
						var bg = m.is_self ? '#2271b1' : '#ffffff';
						var fg = m.is_self ? '#ffffff' : '#1d2327';
						var border = m.is_self ? 'transparent' : '#e2e4e7';
						var bodyHtml = escapeHtml(m.body).replace(/\n/g, '<br>');
						return '<div style="display:flex;flex-direction:column;align-items:' + align + ';margin-bottom:10px;">' +
							'<div style="font-size:11px;color:#646970;margin-bottom:3px;">' + escapeHtml(m.name) + ' · ' + escapeHtml(m.created) + ' UTC</div>' +
							'<div style="max-width:85%;background:' + bg + ';color:' + fg + ';border:1px solid ' + border + ';border-radius:10px;padding:8px 12px;font-size:13px;line-height:1.4;white-space:normal;overflow-wrap:anywhere;">' + bodyHtml + '</div>' +
						'</div>';
					}).join('');
					$body.html(html);
					$body.scrollTop($body[0].scrollHeight);
				}
				function clearBadgeForAlert(alertId) {
					// Server has already bumped the seen-watermark when serving the thread;
					// just hide the badge on this page for an instant UI response.
					$('.newsai-task-thread-badge[data-alert-id="' + alertId + '"]').hide().text('0');
				}
				function loadThread() {
					if (!currentAlert) { return; }
					$.post(newsai_task_vars.ajax_url, {
						action: 'newsai_task_messages_list',
						nonce: newsai_task_vars.nonce,
						alert_id: currentAlert
					}).done(function (resp) {
						if (resp && resp.success) {
							renderMessages(resp.data.messages || []);
							clearBadgeForAlert(currentAlert);
						} else {
							var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Error loading thread.';
							$body.html('<p style="color:#b91c1c;font-size:12px;">' + escapeHtml(msg) + '</p>');
						}
					}).fail(function (xhr) {
						$body.html('<p style="color:#b91c1c;font-size:12px;">Network error (' + (xhr && xhr.status ? xhr.status : '0') + ').</p>');
					});
				}

				// Open: delegated so it works for any task row injected later.
				$(document).off('click.newsaiTaskThread').on('click.newsaiTaskThread', '.newsai-task-thread-open', function (e) {
					e.preventDefault();
					openDrawer($(this).data('alert-id'), $(this).data('alert-title'));
				});
				// Clear thread (chief only — button is absent for authors).
				$(document).off('click.newsaiTaskThreadClear').on('click.newsaiTaskThreadClear', '#newsai-task-thread-clear', function (e) {
					e.preventDefault();
					if (!currentAlert) { return; }
					var confirmMsg = <?php echo wp_json_encode( __( 'Delete every message in this discussion? This cannot be undone.', 'newsroom-ai-assistant' ) ); ?>;
					if (!window.confirm(confirmMsg)) { return; }
					var $clearBtn = $(this).prop('disabled', true);
					$.post(newsai_task_vars.ajax_url, {
						action: 'newsai_task_messages_clear',
						nonce: newsai_task_vars.nonce,
						alert_id: currentAlert
					}).done(function (resp) {
						if (resp && resp.success) {
							clearBadgeForAlert(currentAlert);
							loadThread();
						} else {
							alert((resp && resp.data && resp.data.message) ? resp.data.message : 'Error');
						}
					}).fail(function () {
						alert('Network error');
					}).always(function () {
						$clearBtn.prop('disabled', false);
					});
				});
				// Close: delegated on the document so a re-render of the drawer wouldn't break it.
				$(document).off('click.newsaiTaskThreadClose').on('click.newsaiTaskThreadClose',
					'#newsai-task-thread-close, #newsai-task-thread-backdrop',
					function (e) { e.preventDefault(); closeDrawer(); }
				);
				// ESC anywhere on the page.
				$(document).off('keydown.newsaiTaskThread').on('keydown.newsaiTaskThread', function (e) {
					if (e.key === 'Escape' && isOpen) { closeDrawer(); }
				});
				// Form submit (always re-bound on ready).
				$form.off('submit.newsaiTaskThread').on('submit.newsaiTaskThread', function (e) {
					e.preventDefault();
					var body = $.trim($input.val());
					if (!body || !currentAlert) { return; }
					var $btn = $form.find('button[type="submit"]');
					$btn.prop('disabled', true);
					$.post(newsai_task_vars.ajax_url, {
						action: 'newsai_task_messages_post',
						nonce: newsai_task_vars.nonce,
						alert_id: currentAlert,
						body: body
					}).done(function (resp) {
						if (resp && resp.success) {
							$input.val('');
							loadThread();
						} else {
							alert((resp && resp.data && resp.data.message) ? resp.data.message : 'Error');
						}
					}).fail(function () {
						alert('Network error');
					}).always(function () {
						$btn.prop('disabled', false);
					});
				});

				// Deep-link: ?open_task=ID — only when the requested row exists, so a stray param
				// can never auto-open an empty drawer.
				try {
					var url  = new URL(window.location.href);
					var open = parseInt(url.searchParams.get('open_task') || '0', 10);
					if (open > 0) {
						var $row  = $('tr[data-alert-id="' + open + '"]');
						if ($row.length) {
							openDrawer(open, $row.data('alert-title') || '');
						}
					}
				} catch (e) { /* IE / restricted env — skip silently */ }
			});
		})();
		</script>
		<?php
	}
}
add_action( 'admin_footer', 'newsai_task_thread_drawer_script', 20 );
