/**
 * 参加者リストページ
 *
 * イベント別の参加者一覧・ステータス変更・団体スタッフ展開。
 */
import { useState, useEffect, useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import {
	Button,
	TextControl,
	SelectControl,
	Notice,
	Modal,
} from '@wordpress/components';
import apiFetch from '@wordpress/api-fetch';
import downloadBase64Pdf from '../../common/pdf-download';
import useCan from '../hooks/useCan';
import GuideSection from '../components/GuideSection';
import { apiErrorMessage } from '../../common/api-error';

// 請求額（税込）。registrations.amount は**税抜**で、消費税は tax 列に別に入っている。
// 決済管理・請求書・領収書・精算の差額はすべて税込なので、ここだけ税抜で出すと
// 同じ申込が 10%（税率 8% のイベントでは 8%）少なく見える。
/**
 * サイトのタイムゾーンではなくブラウザの今日を返す
 *
 * ⚠️ 事務局はサイトと同じ地域から使う前提。ずれる運用があれば
 *    サーバーから既定値を配ること。
 */
function todayLocal() {
	const d = new Date();
	const pad = ( n ) => String( n ).padStart( 2, '0' );
	return `${ d.getFullYear() }-${ pad( d.getMonth() + 1 ) }-${ pad( d.getDate() ) }`;
}

function billedAmount( row ) {
	return Number( row?.amount || 0 ) + Number( row?.tax || 0 );
}

const STATUS_OPTIONS = [
	{ label: __( 'All Statuses', 'conferencemanager' ), value: '' },
	{ label: __( 'Pending', 'conferencemanager' ), value: 'pending' },
	{ label: __( 'Confirmed', 'conferencemanager' ), value: 'confirmed' },
	{ label: __( 'Paid', 'conferencemanager' ), value: 'paid' },
	{ label: __( 'Attended', 'conferencemanager' ), value: 'attended' },
	{ label: __( 'Cancel', 'conferencemanager' ), value: 'cancelled' },
];

const STATUS_LABELS = Object.fromEntries( STATUS_OPTIONS.filter( ( o ) => o.value ).map( ( o ) => [ o.value, o.label ] ) );

// 許可されるステータス遷移。バックエンドの RegistrationManager::is_valid_transition() と同一。
// attended は終端ステータス。cancelled は誤操作リカバリ用に pending への復元を許可。
const NEXT_STATUSES = {
	pending: [ 'confirmed', 'paid', 'cancelled' ],
	confirmed: [ 'paid', 'cancelled' ],
	paid: [ 'attended', 'cancelled' ],
	cancelled: [ 'pending' ],
	attended: [],
};

// イベント絞り込みプルダウンに載せるイベントのステータス（下書き・中止は除外）。
const SELECTABLE_EVENT_STATUSES = [ 'published', 'closed' ];

// 開催期間の表示（他ページと同じく MySQL datetime の先頭 10 文字を使う）
function formatEventPeriod( event ) {
	const start = event?.date_start ? String( event.date_start ).substring( 0, 10 ) : '';
	const end = event?.date_end ? String( event.date_end ).substring( 0, 10 ) : '';
	if ( start && end && start !== end ) {
		return `${ start } 〜 ${ end }`;
	}
	return start;
}

function StatusBadge( { status } ) {
	return (
		<span className={ `conf-manager-status-badge conf-manager-status-badge--${ status }` }>
			{ STATUS_LABELS[ status ] || status }
		</span>
	);
}

// 精算（要返金・要追加徴収）バッジ。窓口で対象者だと分かるようにする。
const SETTLEMENT_LABELS = {
	refund_due: __( 'Refund due', 'conferencemanager' ),
	surcharge_due: __( 'Surcharge due', 'conferencemanager' ),
};

function SettlementBadge( { state, amount } ) {
	if ( ! SETTLEMENT_LABELS[ state ] ) {
		return null;
	}
	return (
		<span className={ `conf-manager-settlement-badge conf-manager-settlement-badge--${ state }` }>
			{ SETTLEMENT_LABELS[ state ] }
			{ amount > 0 && ` ¥${ Number( amount ).toLocaleString() }` }
		</span>
	);
}

const COLUMNS = [
	{ key: 'registration_no', label: __( 'Reg. No.', 'conferencemanager' ) },
	{ key: 'member_name', label: __( 'Name', 'conferencemanager' ),
		render: ( val, row ) => {
			if ( row.group_id && row.is_representative ) {
				return (
					<span>
						{ val }
						<span className="conf-manager-badge conf-manager-badge--group" title={ __( 'Group Representative', 'conferencemanager' ) }>
							{ __( 'Group', 'conferencemanager' ) }
							{ row.staff_count > 0 && ` (${ row.staff_count })` }
						</span>
					</span>
				);
			}
			return val;
		},
	},
	{ key: 'member_org', label: __( 'Organization', 'conferencemanager' ) },
	{ key: 'fee_type_label', label: __( 'Fee Category', 'conferencemanager' ),
		render: ( val ) => val || '—',
	},
	{ key: 'participation_type', label: __( 'Participation Type', 'conferencemanager' ),
		render: ( val ) => val === 'online' ? __( 'Online', 'conferencemanager' ) : __( 'Venue', 'conferencemanager' ),
	},
	{ key: 'amount', label: __( 'Billed Amount', 'conferencemanager' ),
		render: ( val, row ) => `¥${ billedAmount( row ).toLocaleString() }`,
	},
	{ key: 'transfer_note', label: __( 'Transfer Account Name Note', 'conferencemanager' ),
		render: ( val ) => val || '—',
	},
	{ key: 'status', label: __( 'Status', 'conferencemanager' ),
		render: ( val, row ) => (
			<>
				<StatusBadge status={ val } />
				<SettlementBadge state={ row.settlement_state } amount={ row.settlement_amount } />
			</>
		),
	},
];

const EMPTY_STAFF = { name: '', email: '', participation_type: 'onsite', fee_type: '' };

export default function Participants() {
	const [ items, setItems ] = useState( [] );
	const [ total, setTotal ] = useState( 0 );
	const [ page, setPage ] = useState( 1 );
	const [ status, setStatus ] = useState( '' );
	const [ keyword, setKeyword ] = useState( '' );
	const [ eventId, setEventId ] = useState( '' );
	const [ events, setEvents ] = useState( [] );
	const [ eventsLoaded, setEventsLoaded ] = useState( false );
	const [ loading, setLoading ] = useState( false );
	const [ notice, setNotice ] = useState( null );
	// 押している間は同じ行の 2 つのボタンを止める（連打で採番が走らないように）
	const [ issuingId, setIssuingId ] = useState( null );
	// 入金日（受け取り側の通帳に入った日）。「入金済にする」で使う。
	const [ paidAtInput, setPaidAtInput ] = useState( '' );

	// 団体スタッフ展開
	const [ expandedGroupId, setExpandedGroupId ] = useState( null );
	const [ staffList, setStaffList ] = useState( [] );
	const [ staffLoading, setStaffLoading ] = useState( false );

	// スタッフ追加モーダル
	const [ addStaffModal, setAddStaffModal ] = useState( null ); // registration_id or null
	const [ newStaff, setNewStaff ] = useState( { ...EMPTY_STAFF } );

	// 修正/入金処理モーダル
	const [ feeEditReg, setFeeEditReg ] = useState( null ); // registration row or null
	const [ feeOptions, setFeeOptions ] = useState( null );
	const [ feeForm, setFeeForm ] = useState( { fee_type: '', participation_type: 'onsite' } );
	const [ feeLoading, setFeeLoading ] = useState( false );
	const [ feeSaving, setFeeSaving ] = useState( false );
	const [ feeError, setFeeError ] = useState( null );
	const [ statusSaving, setStatusSaving ] = useState( null ); // 遷移中のステータス

	useEffect( () => {
		apiFetch( { path: '/conf-manager/v1/events?per_page=100&orderby=date_start&order=DESC' } )
			.then( ( data ) => {
				const all = Array.isArray( data ) ? data : [];
				const selectable = all.filter( ( e ) => SELECTABLE_EVENT_STATUSES.includes( e.status ) );
				// 公開中・受付終了が 1 件も無いサイトでは選択肢が空になるのを避けるため全件にフォールバック
				const list = selectable.length > 0 ? selectable : all;
				setEvents( list );
				if ( list.length > 0 ) {
					// 既定は最新イベント（date_start DESC の先頭）
					setEventId( ( prev ) => prev || String( list[ 0 ].id ) );
				}
			} )
			.catch( () => {} )
			.finally( () => setEventsLoaded( true ) );
	}, [] );

	const eventOptions = events.map( ( e ) => ( { label: e.title, value: String( e.id ) } ) );
	const selectedEvent = events.find( ( e ) => String( e.id ) === String( eventId ) ) || null;

	const fetchItems = useCallback( async () => {
		// イベント一覧の取得前に「全イベント」を読みに行かない
		if ( ! eventsLoaded ) return;
		if ( ! eventId ) {
			setItems( [] );
			setTotal( 0 );
			return;
		}
		setLoading( true );
		try {
			const params = new URLSearchParams( { page, per_page: 20, event_id: eventId } );
			if ( status ) params.set( 'status', status );
			if ( keyword ) params.set( 'keyword', keyword );

			const response = await apiFetch( {
				path: `/conf-manager/v1/registrations?${ params }`,
				parse: false,
			} );
			const data = await response.json();
			setItems( data );
			setTotal( parseInt( response.headers.get( 'X-WP-Total' ) || '0' ) );
		} catch ( err ) {
			// ⚠️ parse:false の失敗は Response がそのまま throw されるので err.message は undefined。
			setNotice( {
				status: 'error',
				message: await apiErrorMessage( err, __( 'Failed to load the participant list.', 'conferencemanager' ) ),
			} );
		} finally {
			setLoading( false );
		}
	}, [ page, status, keyword, eventId, eventsLoaded ] );

	useEffect( () => {
		fetchItems();
	}, [ fetchItems ] );

	const closeEditModal = () => {
		setFeeEditReg( null );
		setFeeOptions( null );
		setFeeError( null );
		setStatusSaving( null );
	};

	const handleStatusChange = async ( regId, newStatus, currentStatus = '' ) => {
		// キャンセルは決済側にも及ぶので、何が起きるかを押す前に書く。
		// 🔴 入金済かどうかで結末が違う。未入金の請求は取り下げられて未入金の
		// 集計から外れるが、**受け取った入金は自動では戻らない**（返金扱いに
		// すると「起きていない返金」が台帳に載る）。
		if ( 'cancelled' === newStatus ) {
			const message = 'paid' === currentStatus
				? __( 'Cancel this registration? The payment already received will NOT be refunded automatically — handle the refund separately.', 'conferencemanager' )
				: __( 'Cancel this registration? The unpaid invoice will be withdrawn and will no longer be counted as unpaid.', 'conferencemanager' );
			if ( ! window.confirm( message ) ) {
				return;
			}
		}
		setStatusSaving( newStatus );
		try {
			await apiFetch( {
				path: `/conf-manager/v1/registrations/${ regId }/status`,
				method: 'PUT',
				// 入金日は paid にするときだけ意味を持つ。空なら従来どおり「いま」。
				data: 'paid' === newStatus && paidAtInput
					? { status: newStatus, paid_at: paidAtInput }
					: { status: newStatus },
			} );
			setNotice( { status: 'success', message: __( 'Status updated.', 'conferencemanager' ) } );
			closeEditModal();
			fetchItems();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
			setStatusSaving( null );
		}
	};

	// スタッフ一覧の取得/展開
	const toggleStaff = async ( regId ) => {
		if ( expandedGroupId === regId ) {
			setExpandedGroupId( null );
			setStaffList( [] );
			return;
		}
		setExpandedGroupId( regId );
		setStaffLoading( true );
		try {
			const data = await apiFetch( { path: `/conf-manager/v1/registrations/${ regId }/staff` } );
			setStaffList( data );
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
			setStaffList( [] );
		} finally {
			setStaffLoading( false );
		}
	};

	// スタッフ追加
	const handleAddStaff = async () => {
		if ( ! addStaffModal ) return;
		try {
			await apiFetch( {
				path: `/conf-manager/v1/registrations/${ addStaffModal }/staff`,
				method: 'POST',
				data: newStaff,
			} );
			setNotice( { status: 'success', message: __( 'Staff added.', 'conferencemanager' ) } );
			setAddStaffModal( null );
			setNewStaff( { ...EMPTY_STAFF } );
			// リロード
			toggleStaff( expandedGroupId );
			fetchItems();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		}
	};

	// スタッフ削除
	const handleCancelStaff = async ( regId, staffId, staffName ) => {
		if ( ! window.confirm( `${ staffName } ${ __( 'Cancel this item?', 'conferencemanager' ) }` ) ) return;
		try {
			await apiFetch( {
				path: `/conf-manager/v1/registrations/${ regId }/staff/${ staffId }`,
				method: 'DELETE',
			} );
			setNotice( { status: 'success', message: __( 'Staff cancelled.', 'conferencemanager' ) } );
			// リロード
			toggleStaff( regId );
			fetchItems();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		}
	};

	// 修正/入金処理モーダルを開く
	const openEditModal = async ( row ) => {
		setFeeEditReg( row );
		// 既定は今日。既に入金日が入っていればそれを出す（サーバは GMT 保存なので
		// 現地日付を添えた paid_at_local を使う）。
		setPaidAtInput( ( row.paid_at_local || '' ).substring( 0, 10 ) || todayLocal() );
		setFeeOptions( null );
		setFeeError( null );
		setStatusSaving( null );
		setFeeLoading( true );
		try {
			const data = await apiFetch( { path: `/conf-manager/v1/registrations/${ row.id }/fee-options` } );
			setFeeOptions( data );
			setFeeForm( {
				fee_type: data.current.fee_type || '',
				participation_type: data.current.participation_type || 'onsite',
			} );
		} catch ( err ) {
			// 区分の選択肢が取れなくてもステータス操作はできるようモーダルは開いたままにする
			setFeeError( err.message );
		} finally {
			setFeeLoading( false );
		}
	};

	// 参加区分の修正を保存
	const handleSaveFeeClassification = async () => {
		if ( ! feeEditReg ) return;
		setFeeSaving( true );
		try {
			await apiFetch( {
				path: `/conf-manager/v1/registrations/${ feeEditReg.id }/fee-classification`,
				method: 'PUT',
				data: feeForm,
			} );
			setNotice( { status: 'success', message: __( 'Fee classification updated.', 'conferencemanager' ) } );
			closeEditModal();
			fetchItems();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		} finally {
			setFeeSaving( false );
		}
	};

	const canEdit = useCan( 'conf_manager_edit_registrations' );

	// 帳票の発行。POST なのは、帳票行が無ければ発番して作るため。
	// 申込に決済が複数ぶら下がるときはサーバーが 409 で断る（決済管理から選ぶ）。
	const issueDocument = async ( registrationId, type ) => {
		setIssuingId( registrationId );
		setNotice( null );
		try {
			const data = await apiFetch( {
				path: `/conf-manager/v1/registrations/${ registrationId }/${ type }-pdf`,
				method: 'POST',
			} );
			downloadBase64Pdf( data.pdf_b64, data.filename );
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		} finally {
			setIssuingId( null );
		}
	};

	const columnsWithAction = canEdit ? [
		...COLUMNS,
		{
			key: 'actions',
			label: __( 'Actions', 'conferencemanager' ),
			// 操作欄は 2 段に分ける。
			//   1 段目＝この行に対して「する」こと（団体の展開・修正/入金処理）
			//   2 段目＝帳票の発行（請求書・領収書）
			// 🔴 1 行に 4 つ並べると横に広がり、狭い画面では表が横スクロールになって
			//    右端の帳票ボタンが見えなくなる。段を分けると縦に伸びるだけで済む。
			// ⚠️ 2 段目が空のときは <div> ごと出さない（空の行の分だけ間が空く）。
			render: ( _, row ) => {
				const buttons = [];
				const docButtons = [];

				// 団体展開ボタン
				if ( row.group_id && row.is_representative ) {
					buttons.push(
						<Button
							key="expand"
							variant="secondary"
							size="small"
							className="conf-manager-btn--staff-toggle"
							onClick={ ( e ) => { e.stopPropagation(); toggleStaff( row.id ); } }
						>
							{ expandedGroupId === row.id ? __( 'Close', 'conferencemanager' ) : __( 'Staff', 'conferencemanager' ) }
						</Button>
					);
				}

				// 修正/入金処理（区分の修正とステータス変更をモーダルに集約）
				buttons.push(
					<Button
						key="edit"
						variant="primary"
						size="small"
						className="conf-manager-btn--action"
						onClick={ ( e ) => { e.stopPropagation(); openEditModal( row ); } }
					>
						{ __( 'Edit / Payment', 'conferencemanager' ) }
					</Button>
				);

				// 請求書は請求額がある行だけ（¥0 の申込には決済レコードが無い）
				if ( billedAmount( row ) > 0 ) {
					docButtons.push(
						<Button
							key="invoice"
							size="small"
							className="conf-manager-btn--mini conf-manager-btn--mini-blue"
							disabled={ issuingId === row.id }
							onClick={ ( e ) => { e.stopPropagation(); issueDocument( row.id, 'invoice' ); } }
						>
							{ __( 'Invoice', 'conferencemanager' ) }
						</Button>
					);
				}

				// 領収書は入金済・参加済の行だけ
				if ( [ 'paid', 'attended' ].includes( row.status ) ) {
					docButtons.push(
						<Button
							key="receipt"
							size="small"
							className="conf-manager-btn--mini conf-manager-btn--mini-green"
							disabled={ issuingId === row.id }
							onClick={ ( e ) => { e.stopPropagation(); issueDocument( row.id, 'receipt' ); } }
						>
							{ __( 'Receipt', 'conferencemanager' ) }
						</Button>
					);
				}

				return (
					<div className="conf-manager-row-actions-stack">
						<div className="conf-manager-row-actions">{ buttons }</div>
						{ docButtons.length > 0 && (
							<div className="conf-manager-row-actions">{ docButtons }</div>
						) }
					</div>
				);
			},
		},
	] : COLUMNS;

	// テーブル行の後にスタッフ展開行を挿入するためのカスタムレンダリング
	const renderStaffRow = ( regId ) => {
		if ( expandedGroupId !== regId ) return null;

		return (
			<tr key={ `staff-${ regId }` } className="conf-manager-staff-row">
				<td colSpan={ columnsWithAction.length } style={ { padding: 0 } }>
					<div className="conf-manager-staff-panel">
						<div className="conf-manager-staff-panel__header">
							<strong>{ __( 'Group Staff List', 'conferencemanager' ) }</strong>
							{ canEdit && (
								<Button
									variant="primary"
									size="small"
									onClick={ () => { setNewStaff( { ...EMPTY_STAFF } ); setAddStaffModal( regId ); } }
								>
									{ __( '+ Add Staff', 'conferencemanager' ) }
								</Button>
							) }
						</div>
						{ staffLoading ? (
							<p style={ { padding: '8px 16px' } }>{ __( 'Loading...', 'conferencemanager' ) }</p>
						) : staffList.length === 0 ? (
							<p style={ { padding: '8px 16px', color: '#757575' } }>{ __( 'No staff members.', 'conferencemanager' ) }</p>
						) : (
							<table className="widefat striped conf-manager-staff-table">
								<thead>
									<tr>
										<th>{ __( 'Name', 'conferencemanager' ) }</th>
										<th>{ __( 'Email', 'conferencemanager' ) }</th>
										<th>{ __( 'Fee Category', 'conferencemanager' ) }</th>
										<th>{ __( 'Participation Type', 'conferencemanager' ) }</th>
										{ canEdit && <th>{ __( 'Actions', 'conferencemanager' ) }</th> }
									</tr>
								</thead>
								<tbody>
									{ staffList.map( ( s ) => (
										<tr key={ s.id }>
											<td>{ s.name }</td>
											<td>{ s.email }</td>
											<td>{ s.fee_type || '—' }</td>
											<td>{ s.participation_type === 'online' ? __( 'Online', 'conferencemanager' ) : __( 'Venue', 'conferencemanager' ) }</td>
											{ canEdit && (
												<td>
													<Button
														variant="secondary"
														isDestructive
														size="small"
														onClick={ () => handleCancelStaff( regId, s.id, s.name ) }
													>
														{ __( 'Delete', 'conferencemanager' ) }
													</Button>
												</td>
											) }
										</tr>
									) ) }
								</tbody>
							</table>
						) }
					</div>
				</td>
			</tr>
		);
	};

	return (
		<div>
			<div className="conf-manager-page-header">
				<h2>{ __( 'Participants', 'conferencemanager' ) }</h2>
			</div>

			<GuideSection
				hero={ {
					title: __( 'Centralized Registration Management', 'conferencemanager' ),
					description: __( 'Track event registrations in real-time with status management and group registration support.', 'conferencemanager' ),
				} }
				cards={ [
					{
						icon: <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="1.5"><polyline points="9 11 12 14 22 4" /><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" /></svg>,
						title: __( 'Status Management', 'conferencemanager' ),
						description: __( 'Track participant progress through statuses: pending → confirmed → paid → attended.', 'conferencemanager' ),
						tips: [
							__( 'Automatically update status after payment confirmation', 'conferencemanager' ),
							__( 'Process cancellations easily from the list', 'conferencemanager' ),
							__( 'Filter by event and status', 'conferencemanager' ),
						],
					},
					{
						icon: <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 00-3-3.87" /><path d="M16 3.13a4 4 0 010 7.75" /></svg>,
						title: __( 'Group Registration', 'conferencemanager' ),
						description: __( 'Group registration allows a representative to register multiple participants at once.', 'conferencemanager' ),
						tips: [
							__( 'Representative registers all staff members together', 'conferencemanager' ),
							__( 'Individual certificates for each person', 'conferencemanager' ),
							__( 'Invoices are issued collectively to the representative', 'conferencemanager' ),
						],
					},
					{
						icon: <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M21.21 15.89A10 10 0 118 2.83" /><path d="M22 12A10 10 0 0012 2v10z" /></svg>,
						title: __( 'Data Utilization', 'conferencemanager' ),
						description: __( 'Leverage registration data for efficient event management.', 'conferencemanager' ),
						tips: [
							__( 'Quickly find participants by registration number', 'conferencemanager' ),
							__( 'Monitor the ratio of onsite vs. online participation', 'conferencemanager' ),
							__( 'Status changes are recorded in the audit log', 'conferencemanager' ),
						],
					},
				] }
			/>

			{ notice && (
				<Notice status={ notice.status } onDismiss={ () => setNotice( null ) }>
					{ notice.message }
				</Notice>
			) }

			<form onSubmit={ ( e ) => { e.preventDefault(); setPage( 1 ); fetchItems(); } } className="conf-manager-search">
				<SelectControl value={ eventId } onChange={ ( val ) => { setEventId( val ); setPage( 1 ); } } options={ eventOptions } />
				<TextControl
					placeholder={ __( 'Search by registration no.', 'conferencemanager' ) }
					value={ keyword }
					onChange={ setKeyword }
				/>
				<SelectControl value={ status } onChange={ setStatus } options={ STATUS_OPTIONS } />
				<Button type="submit" className="conf-manager-btn--primary">
					{ __( 'Search', 'conferencemanager' ) }
				</Button>
			</form>

			{ eventsLoaded && ! selectedEvent && (
				<Notice status="warning" isDismissible={ false }>
					{ __( 'There are no events to display. Please create an event first.', 'conferencemanager' ) }
				</Notice>
			) }

			{ selectedEvent && (
				<div className="conf-manager-event-heading">
					<h3>{ selectedEvent.title }</h3>
					<span className="conf-manager-event-heading__meta">
						{ formatEventPeriod( selectedEvent ) }
						{ total > 0 && sprintf(
							/* translators: %d: number of participants */
							__( ' / %d participants', 'conferencemanager' ),
							total
						) }
					</span>
				</div>
			) }

			{ /* 一覧テーブル + スタッフ展開行 */ }
			<div className="conf-manager-table">
				{ loading && (
					<div className="conf-manager-table__loading">
						{ __( 'Loading...', 'conferencemanager' ) }
					</div>
				) }
				<table className="widefat striped">
					<thead>
						<tr>
							{ columnsWithAction.map( ( col ) => (
								<th key={ col.key }>{ col.label }</th>
							) ) }
						</tr>
					</thead>
					<tbody>
						{ items.length === 0 && ! loading ? (
							<tr>
								<td colSpan={ columnsWithAction.length }>
									{ __( 'No data available.', 'conferencemanager' ) }
								</td>
							</tr>
						) : (
							items.flatMap( ( row, idx ) => {
								const mainRow = (
									<tr key={ row.id || idx }>
										{ columnsWithAction.map( ( col ) => (
											<td key={ col.key }>
												{ col.render
													? col.render( row[ col.key ], row )
													: row[ col.key ] ?? '' }
											</td>
										) ) }
									</tr>
								);
								const staffRow = ( row.group_id && row.is_representative )
									? renderStaffRow( row.id )
									: null;
								return staffRow ? [ mainRow, staffRow ] : [ mainRow ];
							} )
						) }
					</tbody>
				</table>

				{ total > 20 && (
					<div className="conf-manager-table__pagination">
						<Button
							variant="secondary"
							disabled={ page <= 1 }
							onClick={ () => setPage( page - 1 ) }
						>
							{ __( 'Previous', 'conferencemanager' ) }
						</Button>
						<span className="conf-manager-table__page-info">
							{ page } / { Math.max( 1, Math.ceil( total / 20 ) ) }
							{ ' ' }({ total }{ __( 'items', 'conferencemanager' ) })
						</span>
						<Button
							variant="secondary"
							disabled={ page >= Math.ceil( total / 20 ) }
							onClick={ () => setPage( page + 1 ) }
						>
							{ __( 'Next', 'conferencemanager' ) }
						</Button>
					</div>
				) }
			</div>

			{ /* スタッフ追加モーダル */ }
			{ addStaffModal && (
				<Modal
					title={ __( 'Add Staff', 'conferencemanager' ) }
					onRequestClose={ () => setAddStaffModal( null ) }
				>
					<TextControl
						label={ __( 'Name', 'conferencemanager' ) }
						value={ newStaff.name }
						onChange={ ( v ) => setNewStaff( ( prev ) => ( { ...prev, name: v } ) ) }
						required
					/>
					<TextControl
						label={ __( 'Email Address', 'conferencemanager' ) }
						type="email"
						value={ newStaff.email }
						onChange={ ( v ) => setNewStaff( ( prev ) => ( { ...prev, email: v } ) ) }
						required
					/>
					<SelectControl
						label={ __( 'Participation Type', 'conferencemanager' ) }
						value={ newStaff.participation_type }
						onChange={ ( v ) => setNewStaff( ( prev ) => ( { ...prev, participation_type: v } ) ) }
						options={ [
							{ label: __( 'Venue', 'conferencemanager' ), value: 'onsite' },
							{ label: __( 'Online', 'conferencemanager' ), value: 'online' },
						] }
					/>
					<TextControl
						label={ __( 'Fee Category (Optional)', 'conferencemanager' ) }
						value={ newStaff.fee_type }
						onChange={ ( v ) => setNewStaff( ( prev ) => ( { ...prev, fee_type: v } ) ) }
						help={ __( 'e.g., regular, student, general (blank applies representative\'s member type)', 'conferencemanager' ) }
					/>
					<div style={ { marginTop: '16px', display: 'flex', gap: '8px' } }>
						<Button variant="primary" onClick={ handleAddStaff }>
							{ __( 'Add', 'conferencemanager' ) }
						</Button>
						<Button variant="tertiary" onClick={ () => setAddStaffModal( null ) }>
							{ __( 'Cancel', 'conferencemanager' ) }
						</Button>
					</div>
				</Modal>
			) }

			{ /* 修正/入金処理モーダル */ }
			{ feeEditReg && (
				<Modal
					title={ __( 'Edit / Payment', 'conferencemanager' ) }
					onRequestClose={ closeEditModal }
					className="conf-manager-edit-modal"
				>
					<p className="conf-manager-edit-modal__target">
						<strong>{ feeEditReg.registration_no }</strong>
						{ ` / ${ feeEditReg.member_name || '' }` }
						<StatusBadge status={ feeEditReg.status } />
						<span className="conf-manager-edit-modal__amount">
							{ `¥${ billedAmount( feeEditReg ).toLocaleString() }` }
						</span>
					</p>

					<h3 className="conf-manager-edit-modal__section">{ __( 'Edit Participation Category', 'conferencemanager' ) }</h3>
					{ feeLoading ? (
						<p>{ __( 'Loading...', 'conferencemanager' ) }</p>
					) : feeError ? (
						<Notice status="error" isDismissible={ false }>{ feeError }</Notice>
					) : ! feeOptions ? null : ! feeOptions.editable ? (
						<>
							<p style={ { fontSize: '13px', margin: '8px 0' } }>
								{ __( 'Fee Category', 'conferencemanager' ) }: { feeEditReg.fee_type_label || feeOptions.current?.fee_type || '—' }
								{ ' / ' }
								{ __( 'Participation Type', 'conferencemanager' ) }: { feeOptions.current?.participation_type === 'online' ? __( 'Online', 'conferencemanager' ) : __( 'Venue', 'conferencemanager' ) }
							</p>
							<Notice status="warning" isDismissible={ false }>
								{ __( 'This registration cannot be edited (it may be refunded, cancelled, or a group registration).', 'conferencemanager' ) }
							</Notice>
						</>
					) : (
						<>
							<SelectControl
								label={ __( 'Fee Category', 'conferencemanager' ) }
								value={ feeForm.fee_type }
								onChange={ ( v ) => setFeeForm( ( prev ) => ( { ...prev, fee_type: v } ) ) }
								options={ feeOptions.fee_types.map( ( t ) => ( { label: t.label, value: t.key } ) ) }
							/>
							<SelectControl
								label={ __( 'Participation Type', 'conferencemanager' ) }
								value={ feeForm.participation_type }
								onChange={ ( v ) => setFeeForm( ( prev ) => ( { ...prev, participation_type: v } ) ) }
								options={ [
									{ label: __( 'Venue', 'conferencemanager' ), value: 'onsite' },
									{ label: __( 'Online', 'conferencemanager' ), value: 'online' },
								] }
							/>
							<p style={ { fontSize: '13px', margin: '8px 0' } }>
								{ sprintf(
									/* translators: %s: base fee amount */
									__( 'Base fee (excl. tax): ¥%s', 'conferencemanager' ),
									Number( feeOptions.fee_matrix?.[ feeForm.fee_type ]?.[ feeForm.participation_type ] ?? 0 ).toLocaleString()
								) }
							</p>
							<p style={ { fontSize: '12px', color: '#757575' } }>
								{ __( 'Session option fees are recalculated as well. If an invoice was already issued, it will be regenerated. The final amount appears in the list after saving.', 'conferencemanager' ) }
							</p>
							<Button variant="primary" isBusy={ feeSaving } disabled={ feeSaving || !! statusSaving } onClick={ handleSaveFeeClassification }>
								{ __( 'Save category', 'conferencemanager' ) }
							</Button>
						</>
					) }

					<h3 className="conf-manager-edit-modal__section">{ __( 'Change Status', 'conferencemanager' ) }</h3>
					{ ( NEXT_STATUSES[ feeEditReg.status ] || [] ).includes( 'paid' ) && (
						<div style={ { marginBottom: '12px', maxWidth: '220px' } }>
							<TextControl
								label={ __( 'Payment Date', 'conferencemanager' ) }
								type="date"
								value={ paidAtInput }
								onChange={ setPaidAtInput }
								help={ __( 'The date the money arrived in your account (as shown on your bank statement), not the date you checked it. This is what appears on the invoice and receipt.', 'conferencemanager' ) }
							/>
						</div>
					) }
					{ ( NEXT_STATUSES[ feeEditReg.status ] || [] ).length === 0 ? (
						<p style={ { fontSize: '13px', color: '#757575' } }>
							{ __( 'Attended is the final status and cannot be changed.', 'conferencemanager' ) }
						</p>
					) : (
						<div style={ { display: 'flex', gap: '8px', flexWrap: 'wrap' } }>
							{ NEXT_STATUSES[ feeEditReg.status ].map( ( s ) => (
								<Button
									key={ s }
									variant={ 'cancelled' === s ? 'secondary' : 'primary' }
									isDestructive={ 'cancelled' === s }
									isBusy={ statusSaving === s }
									disabled={ !! statusSaving || feeSaving }
									onClick={ () => handleStatusChange( feeEditReg.id, s, feeEditReg.status ) }
								>
									{ sprintf(
										/* translators: %s: target status label (e.g. Paid) */
										__( 'Mark as %s', 'conferencemanager' ),
										STATUS_LABELS[ s ]
									) }
								</Button>
							) ) }
						</div>
					) }

					<div style={ { marginTop: '20px' } }>
						<Button variant="tertiary" onClick={ closeEditModal }>
							{ __( 'Close', 'conferencemanager' ) }
						</Button>
					</div>
				</Modal>
			) }
		</div>
	);
}
