/**
 * イベント管理ページ
 *
 * 一覧・作成・編集・料金マトリックス設定。
 */
import { useState, useEffect, useCallback } from '@wordpress/element';
import { __, _x, sprintf } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
import {
	Button,
	TextControl,
	TextareaControl,
	SelectControl,
	Modal,
	Notice,
} from '@wordpress/components';
import DataTable from '../components/DataTable';
import { useEventApi } from '../hooks/useApi';
import useCan from '../hooks/useCan';
import GuideSection from '../components/GuideSection';
import { decorateMemberTypeLabels } from '../../common/member-type-label';

const STATUS_OPTIONS = [
	{ label: __( 'Draft', 'conferencemanager' ), value: 'draft' },
	{ label: __( 'Published', 'conferencemanager' ), value: 'published' },
	{ label: __( 'Closed', 'conferencemanager' ), value: 'closed' },
	{ label: __( 'Cancel', 'conferencemanager' ), value: 'cancelled' },
];

// ⚠️ 会員管理・公開の申込フォーム・自己登録フォームと**同じ文言**にすること。
//    ここだけ 'Student' / 'Associate' / 'Honorary' と短く書いていたため、
//    料金表では「学生」なのに他の画面では「学生会員」と出て、同じ区分が
//    別のものに見えていた。
const MEMBER_TYPE_LABELS_BASE = {
	regular: __( 'Regular Member', 'conferencemanager' ),
	student: __( 'Student Member', 'conferencemanager' ),
	associate: __( 'Associate Member', 'conferencemanager' ),
	honorary: __( 'Honorary Member', 'conferencemanager' ),
	general: __( 'Non-member', 'conferencemanager' ),
};

// 🔴 表示名の装飾はここ 1 箇所だけ。**元の定義（上）は 1 文字も変えない。**
// 学会の略称（初期設定）が入っていれば標準 5 区分にだけ前置する。
// ⚠️ サーバから来る値（`fee_type_label` など）は**サーバ側でだけ**装飾している。
//    ここで二重に通さないこと。
const MEMBER_TYPE_LABELS = decorateMemberTypeLabels( MEMBER_TYPE_LABELS_BASE );

const PARTICIPATION_TYPES = [ 'onsite', 'online' ];
const MEMBER_TYPES = [ 'regular', 'student', 'associate', 'honorary', 'general' ];

/**
 * 料金表のセル（金額 ＋「無料」チェック）
 *
 * 「0 を入れると無料」が分かりづらかったので、無料はチェックで表す。
 * 空欄は **その参加形態を受け付けない**（キーごと消える）。
 */
function FeeCell( { value, onChange } ) {
	const isFree = 0 === value;
	const hasPrice = 'number' === typeof value;

	return (
		<div style={ { display: 'flex', alignItems: 'center', gap: '6px' } }>
			{ isFree ? (
				<span style={ { width: '100px', color: '#00a32a', fontWeight: 600 } }>
					{ __( '¥0 (free)', 'conferencemanager' ) }
				</span>
			) : (
				<input
					type="number"
					min="0"
					placeholder={ __( 'not offered', 'conferencemanager' ) }
					value={ hasPrice ? value : '' }
					onChange={ ( e ) => onChange( '' === e.target.value ? null : ( parseInt( e.target.value, 10 ) || 0 ) ) }
					style={ { width: '100px' } }
				/>
			) }
			<label style={ { fontSize: '11px', whiteSpace: 'nowrap', cursor: 'pointer' } }>
				<input
					type="checkbox"
					checked={ isFree }
					onChange={ ( e ) => onChange( e.target.checked ? 0 : null ) }
					style={ { marginRight: '2px' } }
				/>
				{ /* ⚠️ msgid 'Free' は What's New の「Free 版」見出しと同じ語で、
				     あちらは英語のままが正。文脈を付けて別の訳語にする。 */ }
				{ _x( 'Free', 'fee is zero', 'conferencemanager' ) }
			</label>
		</div>
	);
}

/**
 * 行はあるのに、どの参加形態にも値段が入っていない区分の表示名を返す
 *
 * 🔴 **この状態の区分は誰も申し込めない。** 行があるので「受け付けている」と
 * 判定されるが（`fee_type_is_offered()`）、参加形態のセルが 1 つも無いので
 * `fee_is_configured()` がどちらの形態でも false を返す。
 *
 * ⚠️ **行そのものが無い区分（✖️ で外したもの）は対象外。** そちらは
 * 「その区分は受け付けない」という意図した設定で、申込フォームにも出ない。
 *
 * ⚠️ 判定はサーバ側と同じ「**キーがあるか**」で見る（値ではなく）。
 *    セルを空にすると `setFeeCell()` がキーごと消すので、この画面で作れる
 *    状態では両者は一致する。
 *
 * @param {Object} rules  料金表（`fee_rules`）
 * @param {Object} labels カスタム区分の表示名（`fee_labels`）
 * @return {string[]} 値段が 1 つも入っていない区分の表示名
 */
function feeTypesWithoutAnyPrice( rules, labels ) {
	return Object.keys( rules || {} )
		.filter( ( key ) => '_labels' !== key )
		.filter( ( key ) => {
			const row = rules[ key ];
			if ( ! row || 'object' !== typeof row ) {
				return false;
			}
			return ! PARTICIPATION_TYPES.some(
				( pt ) => Object.prototype.hasOwnProperty.call( row, pt )
			);
		} )
		.map( ( key ) => ( labels || {} )[ key ] || MEMBER_TYPE_LABELS[ key ] || key );
}

const BASE_COLUMNS = [
	{ key: 'id', label: 'ID' },
	{ key: 'title', label: __( 'Title', 'conferencemanager' ) },
	{
		key: 'date_start',
		label: __( 'Event Date', 'conferencemanager' ),
		render: ( val ) => val ? val.substring( 0, 10 ) : '',
	},
	{ key: 'venue', label: __( 'Venue', 'conferencemanager' ) },
];

const EMPTY_FORM = {
	title: '',
	description: '',
	date_start: '',
	date_end: '',
	venue: '',
	venue_address: '',
	capacity: '',
	reg_start: '',
	reg_end: '',
	status: 'draft',
	fee_rules: {},
	fee_labels: {},
	advance_fee_deadline: '',
	fee_rules_day: {},
	fee_labels_day: {},
	advance_fee_notice: '',
	tax_rate: '10.0',
	payment_account_name: '',
	payment_account_info: '',
	organizer_info: '',
	submission_start: '',
	submission_end: '',
	submission_mode: 'form',
	submission_title_limit: '',
	submission_abstract_limit: '',
	submission_keywords_limit: '',
	submission_template_name: '',
	submission_template_file: null,
};

const TAX_RATE_OPTIONS = [
	{ label: __( 'Tax-exempt (0%)', 'conferencemanager' ), value: '0.0' },
	{ label: __( 'Reduced rate (8%)', 'conferencemanager' ), value: '8.0' },
	{ label: __( 'Standard rate (10%)', 'conferencemanager' ), value: '10.0' },
];

export default function Events() {
	const api = useEventApi();
	const canEdit = useCan( 'conf_manager_edit_events' );

	const [ events, setEvents ] = useState( [] );
	const [ total, setTotal ] = useState( 0 );
	const [ page, setPage ] = useState( 1 );
	const [ keyword, setKeyword ] = useState( '' );

	const [ isModalOpen, setModalOpen ] = useState( false );

	// 催しのキャンセル確認。
	// 🔴 **未選択で開く。** 既定で選ばれていると「押しただけ」で申込が全部動く。
	//    cancelModal は { id, impact } / cancelChoice は '' | 'event_only' | 'with_registrations'
	const [ cancelModal, setCancelModal ] = useState( null );
	const [ cancelChoice, setCancelChoice ] = useState( '' );
	const [ cancelBusy, setCancelBusy ] = useState( false );
	const [ editingId, setEditingId ] = useState( null );
	const [ form, setForm ] = useState( { ...EMPTY_FORM } );
	// 料金表に出す標準の区分（✖️ で外せる）
	const [ visibleTypes, setVisibleTypes ] = useState( [ ...MEMBER_TYPES ] );
	const [ notice, setNotice ] = useState( null );

	const handlePublish = async ( eventId, e ) => {
		e.stopPropagation();
		try {
			await api.updateEvent( eventId, { status: 'published' } );
			setNotice( { status: 'success', message: __( 'Event published.', 'conferencemanager' ) } );
			fetchEvents();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		}
	};

	const columns = [
		...BASE_COLUMNS,
		{
			key: 'status',
			label: __( 'Status', 'conferencemanager' ),
			// 🔴 状態の列に操作を混ぜないこと。
			//    もとは下書きの行のステータス列に「公開する」ボタンが直接置かれていて、
			//    「状態の欄を押すと状態が変えられる」ように見えていた（実際は行クリックで
			//    編集モーダルが開く）。ボタンは行末の操作列へ移した。
			//    会員管理の承認バッジと同じ系統（.conf-manager-status-badge）に寄せてある。
			render: ( val ) => {
				const s = STATUS_OPTIONS.find( ( o ) => o.value === val );
				const label = s ? s.label : val;
				const modifiers = {
					draft: 'draft',
					published: 'published',
					closed: 'closed',
					// ⚠️ 申込の --cancelled（灰）を流用しない。締切と同じ見た目になる。
					cancelled: 'event_cancelled',
				};
				const modifier = modifiers[ val ];
				if ( ! modifier ) {
					return label;
				}
				return (
					<span className={ `conf-manager-status-badge conf-manager-status-badge--${ modifier }` }>
						{ label }
					</span>
				);
			},
		},
		...( canEdit ? [ {
			key: 'actions',
			label: '',
			// 🔴 編集の入口を「行クリックだけ」にしない。入口が行クリックしか無いと
			//    「操作」と「状態」の区別が画面に出ない。
			// ⚠️ 行クリックは残す（スマホでは小さなボタンを狙うより行を叩くほうが確実）。
			//    そのぶん狭い画面でだけ指で押す高さを確保している（admin.css の 782px）。
			// ⚠️ 行内ボタンの e.stopPropagation() を外さないこと。外すとボタンの処理と
			//    行クリック（編集モーダル）が同時に走る。
			render: ( _, row ) => (
				<div className="conf-manager-row-actions">
					<Button variant="secondary" isSmall onClick={ ( e ) => { e.stopPropagation(); openEdit( row ); } }>
						{ __( 'Edit', 'conferencemanager' ) }
					</Button>
					{ row.status === 'draft' && (
						<span className="conf-manager-publish-wrap">
							{ /* 気づかせるための脈動。ボタンに付いたまま操作列へ移してある。 */ }
							<span className="conf-manager-pulse-dot" />
							<button
								type="button"
								className="conf-manager-publish-btn"
								onClick={ ( e ) => handlePublish( row.id, e ) }
							>
								{ __( 'Publish', 'conferencemanager' ) }
							</button>
						</span>
					) }
				</div>
			),
		} ] : [] ),
	];

	const fetchEvents = useCallback( async () => {
		try {
			const params = { page, per_page: 20 };
			if ( keyword ) {
				params.keyword = keyword;
			}
			const result = await api.getEvents( params );
			setEvents( result.items );
			setTotal( result.total );
		} catch ( err ) {
			// handled by hook
		}
	}, [ page, keyword ] );

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

	const openCreate = () => {
		setEditingId( null );
		setForm( { ...EMPTY_FORM } );
		setVisibleTypes( [ ...MEMBER_TYPES ] );
		setModalOpen( true );
	};

	const openEdit = ( event ) => {
		setEditingId( event.id );
		const rules = { ...( event.fee_rules || {} ) };
		const labels = { ...( rules._labels || {} ) };
		delete rules._labels;
		const rulesDay = { ...( event.fee_rules_day || {} ) };
		const labelsDay = { ...( rulesDay._labels || {} ) };
		delete rulesDay._labels;

		// 標準の区分は「この催しで実際に使っているもの」だけ出す。
		// ⚠️ 料金表そのものが空の催し（＝一度も設定していない）は全部出す。
		//    そうしないと、まだ何も入れていない催しで表が空になり操作できなくなる。
		const used = MEMBER_TYPES.filter( ( mt ) => Object.prototype.hasOwnProperty.call( rules, mt ) );
		setVisibleTypes( used.length > 0 ? used : [ ...MEMBER_TYPES ] );
		setForm( {
			title: event.title || '',
			description: event.description || '',
			date_start: event.date_start || '',
			date_end: event.date_end || '',
			venue: event.venue || '',
			venue_address: event.venue_address || '',
			capacity: event.capacity || '',
			reg_start: event.reg_start || '',
			reg_end: event.reg_end || '',
			status: event.status || 'draft',
			fee_rules: rules,
			fee_labels: labels,
			advance_fee_deadline: event.advance_fee_deadline || '',
			fee_rules_day: rulesDay,
			fee_labels_day: labelsDay,
			advance_fee_notice: event.advance_fee_notice || '',
			tax_rate: Number( event.tax_rate ?? 10.0 ).toFixed( 1 ),
			payment_account_name: event.payment_account_name || '',
			payment_account_info: event.payment_account_info || '',
			organizer_info: event.organizer_info || '',
			submission_start: event.submission_start || '',
			submission_end: event.submission_end || '',
			submission_mode: event.submission_settings?.submission_mode || 'form',
			submission_title_limit: event.submission_settings?.title_limit || '',
			submission_abstract_limit: event.submission_settings?.abstract_limit || '',
			submission_keywords_limit: event.submission_settings?.keywords_limit || '',
			submission_template_name: event.submission_settings?.template_file_name || '',
			submission_template_file: null,
		} );
		setModalOpen( true );
	};

	const handleSubmit = async () => {
		try {
			const data = { ...form };
			if ( data.capacity ) {
				data.capacity = parseInt( data.capacity );
			}

			// カスタム種別ラベルを fee_rules._labels にマージ
			if ( data.fee_labels && Object.keys( data.fee_labels ).length > 0 ) {
				data.fee_rules = { ...data.fee_rules, _labels: { ...data.fee_labels } };
			}
			delete data.fee_labels;

			// 当日料金のカスタム種別ラベルも同様にマージ
			if ( data.fee_labels_day && Object.keys( data.fee_labels_day ).length > 0 ) {
				data.fee_rules_day = { ...data.fee_rules_day, _labels: { ...data.fee_labels_day } };
			}
			delete data.fee_labels_day;

			// 演題投稿設定をまとめる
			data.submission_settings = {};
			if ( data.submission_mode ) data.submission_settings.submission_mode = data.submission_mode;
			if ( data.submission_title_limit ) data.submission_settings.title_limit = parseInt( data.submission_title_limit );
			if ( data.submission_abstract_limit ) data.submission_settings.abstract_limit = parseInt( data.submission_abstract_limit );
			if ( data.submission_keywords_limit ) data.submission_settings.keywords_limit = parseInt( data.submission_keywords_limit );
			delete data.submission_mode;
			delete data.submission_title_limit;
			delete data.submission_abstract_limit;
			delete data.submission_keywords_limit;
			delete data.submission_template_name;
			delete data.submission_template_file;

			if ( editingId ) {
				await api.updateEvent( editingId, data );
				setNotice( { status: 'success', message: __( 'Event updated.', 'conferencemanager' ) } );
			} else {
				const created = await api.createEvent( data );
				if ( created?.status !== 'published' ) {
					setNotice( {
						status: 'success',
						message: __( 'Event created.', 'conferencemanager' ),
						eventId: created?.id,
					} );
				} else {
					setNotice( { status: 'success', message: __( 'Event created.', 'conferencemanager' ) } );
				}
			}
			setModalOpen( false );
			fetchEvents();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		}
	};

	/**
	 * キャンセルの確認を開く
	 *
	 * 🔴 **押す前に件数を数えて見せる。** 件数はサーバの get_cancel_impact() が
	 * 返すもので、実際に動かす delete() と同じリゾルバから来る
	 * （別に数えると「3 件と言われたのに 2 件しか動かない」が起きる）。
	 */
	const openCancel = async ( id ) => {
		setCancelChoice( '' );
		setCancelBusy( false );
		try {
			const impact = await api.getEventCancelImpact( id );
			setCancelModal( { id, impact } );
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		}
	};

	const handleDelete = async () => {
		if ( ! cancelModal || ! cancelChoice ) {
			return;
		}
		setCancelBusy( true );
		try {
			await api.deleteEvent( cancelModal.id, 'with_registrations' === cancelChoice );
			setNotice( {
				status: 'success',
				message: 'with_registrations' === cancelChoice
					? sprintf(
						/* translators: %d: number of registrations that were cancelled with the event */
						__( 'Event cancelled, along with %d registration(s).', 'conferencemanager' ),
						cancelModal.impact.cancellable
					)
					: __( 'Event cancelled.', 'conferencemanager' ),
			} );
			setCancelModal( null );
			setCancelChoice( '' );
			fetchEvents();
		} catch ( err ) {
			setNotice( { status: 'error', message: err.message } );
		} finally {
			setCancelBusy( false );
		}
	};

	const updateField = ( field ) => ( value ) => {
		setForm( ( prev ) => ( { ...prev, [ field ]: value } ) );
	};

	/**
	 * 料金表のセルを書き換える
	 *
	 * セルは 3 状態:
	 *   数値      … 有料
	 *   0         … 無料（「無料」にチェック）
	 *   キー無し  … **その参加形態は受け付けない**
	 *
	 * ⚠️ 以前は `parseInt( value ) || 0` で、**空欄にすると 0（＝無料）が書かれていた**。
	 *    「受け付けない」を表現できず、消したつもりが無料になっていた。
	 *
	 * @param {string} field           'fee_rules' か 'fee_rules_day'
	 * @param {string} typeKey         料金区分キー
	 * @param {string} participation   'onsite' か 'online'
	 * @param {number|null} value      null でキーごと削除（受け付けない）
	 */
	const setFeeCell = ( field, typeKey, participation, value ) => {
		setForm( ( prev ) => {
			const rules = { ...prev[ field ] };
			const row = { ...( rules[ typeKey ] || {} ) };
			if ( null === value ) {
				delete row[ participation ];
			} else {
				row[ participation ] = value;
			}
			rules[ typeKey ] = row;
			return { ...prev, [ field ]: rules };
		} );
	};

	const updateFeeRule = ( memberType, participationType, value ) => {
		setFeeCell( 'fee_rules', memberType, participationType, '' === value ? null : ( parseInt( value, 10 ) || 0 ) );
	};

	const updateFeeRuleDay = ( memberType, participationType, value ) => {
		setFeeCell( 'fee_rules_day', memberType, participationType, '' === value ? null : ( parseInt( value, 10 ) || 0 ) );
	};

	const copyAdvanceToDay = () => {
		setForm( ( prev ) => ( {
			...prev,
			fee_rules_day: { ...prev.fee_rules },
			fee_labels_day: { ...prev.fee_labels },
		} ) );
	};

	/**
	 * 料金表に出す標準の区分
	 *
	 * ✖️ で消せる（使っていない区分を表から外すため）。消すと fee_rules から
	 * キーごと落ちるので、⚠️ **その区分は「参加費が未設定」になる**。
	 * 未設定の扱いは RegistrationManager::fee_is_configured() を参照。
	 * 表に残したまま無料にしたい場合は 0 を入れること。
	 */
	const removeStandardType = ( key ) => {
		// ⚠️ 外すと「この催しでは受け付けない区分」になる。該当する会員は
		//    申込フォームで**他の区分（先頭の有効なもの）の料金**で申し込むことになるので、
		//    値上げになりうる。黙って消さずに確認する。
		const message = sprintf(
			/* translators: %s: fee category name (e.g. Student Member) */
			__( 'Remove "%s" from the fee table?\n\nThis event will no longer offer this category. Members of this type will register under another category, at that category\'s price.', 'conferencemanager' ),
			MEMBER_TYPE_LABELS[ key ] || key
		);
		// eslint-disable-next-line no-alert
		if ( ! window.confirm( message ) ) {
			return;
		}

		setVisibleTypes( ( prev ) => prev.filter( ( k ) => k !== key ) );
		setForm( ( prev ) => {
			const rules = { ...prev.fee_rules };
			const rulesDay = { ...prev.fee_rules_day };
			delete rules[ key ];
			delete rulesDay[ key ];
			return { ...prev, fee_rules: rules, fee_rules_day: rulesDay };
		} );
	};

	/** 消した標準の区分を戻す（値は空から） */
	const restoreStandardType = ( key ) => {
		setVisibleTypes( ( prev ) => (
			MEMBER_TYPES.filter( ( k ) => prev.includes( k ) || k === key )
		) );
	};

	const hiddenStandardTypes = MEMBER_TYPES.filter( ( mt ) => ! visibleTypes.includes( mt ) );

	// 料金表の中で「行はあるが値段が 1 つも無い」区分（＝誰も申し込めない）
	const unpricedFeeTypes = feeTypesWithoutAnyPrice( form.fee_rules, form.fee_labels );

	const addCustomType = () => {
		setForm( ( prev ) => {
			const labels = { ...prev.fee_labels };
			const rules = { ...prev.fee_rules };
			// 次の連番を決定
			let n = 1;
			while ( labels[ `custom_${ n }` ] || rules[ `custom_${ n }` ] ) {
				n++;
			}
			const key = `custom_${ n }`;
			labels[ key ] = '';
			rules[ key ] = { onsite: 0, online: 0 };
			return { ...prev, fee_labels: labels, fee_rules: rules };
		} );
	};

	const removeCustomType = ( key ) => {
		setForm( ( prev ) => {
			const labels = { ...prev.fee_labels };
			const rules = { ...prev.fee_rules };
			delete labels[ key ];
			delete rules[ key ];
			return { ...prev, fee_labels: labels, fee_rules: rules };
		} );
	};

	// Remove a custom fee type from the on-site fee table only.
	// Operates on the on-site (_day) state and never touches advance (fee_labels / fee_rules).
	/**
	 * 当日料金表に区分を足す
	 *
	 * ⚠️ 以前は当日表に**削除しか無く**、区分を入れる手段は「前売からコピー」だけだった
	 *    （＝当日料金を全部上書きする）。一度消すと戻せない状態になっていた。
	 */
	const addCustomTypeDay = () => {
		setForm( ( prev ) => {
			const labels = { ...prev.fee_labels_day };
			const rules = { ...prev.fee_rules_day };
			let n = 1;
			while ( labels[ `custom_${ n }` ] || rules[ `custom_${ n }` ] ) {
				n++;
			}
			const key = `custom_${ n }`;
			labels[ key ] = '';
			rules[ key ] = {};
			return { ...prev, fee_labels_day: labels, fee_rules_day: rules };
		} );
	};

	const updateCustomLabelDay = ( key, value ) => {
		setForm( ( prev ) => ( {
			...prev,
			fee_labels_day: { ...prev.fee_labels_day, [ key ]: value },
		} ) );
	};

	const removeCustomTypeDay = ( key ) => {
		setForm( ( prev ) => {
			const labels = { ...prev.fee_labels_day };
			const rules = { ...prev.fee_rules_day };
			delete labels[ key ];
			delete rules[ key ];
			return { ...prev, fee_labels_day: labels, fee_rules_day: rules };
		} );
	};

	const updateCustomLabel = ( key, value ) => {
		setForm( ( prev ) => ( {
			...prev,
			fee_labels: { ...prev.fee_labels, [ key ]: value },
		} ) );
	};

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

			<GuideSection
				hero={ {
					title: __( 'Streamline Event Operations', 'conferencemanager' ),
					description: __( 'Manage everything from creating conferences, meetings, and symposiums to setting participation fees.', 'conferencemanager' ),
				} }
				cards={ [
					{
						icon: <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="3" y="4" width="18" height="18" rx="2" ry="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" /></svg>,
						title: __( 'Create Events', 'conferencemanager' ),
						description: __( 'Register basic info such as title, dates, venue, and capacity to publish an event.', 'conferencemanager' ),
						tips: [
							__( 'Set status to "Published" to start accepting registrations', 'conferencemanager' ),
							__( 'Control registration period with start and deadline dates', 'conferencemanager' ),
							__( 'Organizer info is reflected on certificates and invoices', 'conferencemanager' ),
						],
					},
					{
						icon: <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" strokeWidth="1.5"><line x1="12" y1="1" x2="12" y2="23" /><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6" /></svg>,
						title: __( 'Pricing', 'conferencemanager' ),
						description: __( 'Set flexible fees using a member type × participation type matrix.', 'conferencemanager' ),
						tips: [
							__( 'Set individual prices for regular members, students, and non-members', 'conferencemanager' ),
							__( 'Support different prices for onsite and online participation', 'conferencemanager' ),
							__( 'Choose tax rate (0% / 8% / 10%)', 'conferencemanager' ),
						],
					},
				] }
			/>

			{ notice && (
				/*
				 * key で内容が変わるたびに Notice を再マウントさせる。
				 * 同一 Notice インスタンスを子要素の形（Publish ボタンの有無）を
				 * 変えながら再描画すると、WP の Notice 内 useSpokenMessage が
				 * フック状態を壊して落ちる（areHookInputsEqual の undefined.length）ため。
				 */
				<Notice
					key={ notice.eventId ? `create-${ notice.eventId }` : `msg-${ notice.message }` }
					status={ notice.status }
					onDismiss={ () => setNotice( null ) }
				>
					{ notice.message }
					{ notice.eventId && (
						<>
							{ ' ' }
							{ __( 'To publish this event, change the status to "Published".', 'conferencemanager' ) }
							<Button
								variant="primary"
								isDestructive
								style={ { marginLeft: '8px' } }
								onClick={ async () => {
									await api.updateEvent( notice.eventId, { status: 'published' } );
									setNotice( { status: 'success', message: __( 'Event published.', 'conferencemanager' ) } );
									fetchEvents();
								} }
							>
								{ __( 'Publish', 'conferencemanager' ) }
							</Button>
						</>
					) }
				</Notice>
			) }

			<form onSubmit={ ( e ) => { e.preventDefault(); setPage( 1 ); fetchEvents(); } } className="conf-manager-search">
				<TextControl
					placeholder={ __( 'Search by title/venue', 'conferencemanager' ) }
					value={ keyword }
					onChange={ setKeyword }
				/>
				<Button type="submit" className="conf-manager-btn--primary">
					{ __( 'Search', 'conferencemanager' ) }
				</Button>
				<span className="conf-manager-search__spacer" />
				{ canEdit && (
					<Button className="conf-manager-btn--danger" onClick={ openCreate }>
						{ __( 'New Event', 'conferencemanager' ) }
					</Button>
				) }
			</form>

			<DataTable
				columns={ columns }
				rows={ events }
				total={ total }
				page={ page }
				perPage={ 20 }
				onPageChange={ setPage }
				onRowClick={ canEdit ? openEdit : undefined }
				loading={ api.loading }
			/>

			{ isModalOpen && (
				<Modal
					title={ editingId ? __( 'Edit Event', 'conferencemanager' ) : __( 'New Event', 'conferencemanager' ) }
					onRequestClose={ () => setModalOpen( false ) }
					className="conf-manager-modal--wide"
				>
					<TextControl label={ __( 'Title', 'conferencemanager' ) } value={ form.title } onChange={ updateField( 'title' ) } required />
					<TextareaControl label={ __( 'Description', 'conferencemanager' ) } value={ form.description } onChange={ updateField( 'description' ) } />
					<TextControl label={ __( 'Start Date', 'conferencemanager' ) } type="datetime-local" value={ form.date_start.replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'date_start' )( v.replace( 'T', ' ' ) + ':00' ) } required />
					<TextControl label={ __( 'End Date', 'conferencemanager' ) } type="datetime-local" value={ form.date_end.replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'date_end' )( v.replace( 'T', ' ' ) + ':00' ) } required />
					<TextControl label={ __( 'Venue', 'conferencemanager' ) } value={ form.venue } onChange={ updateField( 'venue' ) } />
					<TextControl label={ __( 'Venue Address', 'conferencemanager' ) } value={ form.venue_address } onChange={ updateField( 'venue_address' ) } />
					<TextControl label={ __( 'Capacity', 'conferencemanager' ) } type="number" value={ form.capacity } onChange={ updateField( 'capacity' ) } />
					<TextControl label={ __( 'Registration Opens', 'conferencemanager' ) } type="datetime-local" value={ ( form.reg_start || '' ).replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'reg_start' )( v.replace( 'T', ' ' ) + ':00' ) } />
					<TextControl label={ __( 'Registration Closes', 'conferencemanager' ) } type="datetime-local" value={ ( form.reg_end || '' ).replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'reg_end' )( v.replace( 'T', ' ' ) + ':00' ) } />

					<h3>{ __( 'Abstract submission settings', 'conferencemanager' ) }</h3>
					<TextControl label={ __( 'Submission Start', 'conferencemanager' ) } type="datetime-local" value={ ( form.submission_start || '' ).replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'submission_start' )( v.replace( 'T', ' ' ) + ':00' ) } />
					<TextControl label={ __( 'Submission Deadline', 'conferencemanager' ) } type="datetime-local" value={ ( form.submission_end || '' ).replace( ' ', 'T' ).substring( 0, 16 ) } onChange={ ( v ) => updateField( 'submission_end' )( v.replace( 'T', ' ' ) + ':00' ) } />

					<SelectControl
						label={ __( 'Submission mode', 'conferencemanager' ) }
						value={ form.submission_mode || 'form' }
						onChange={ updateField( 'submission_mode' ) }
						options={ [
							{ label: __( 'Form entry', 'conferencemanager' ), value: 'form' },
							{ label: __( 'Word template', 'conferencemanager' ), value: 'template' },
							{ label: __( 'Hybrid (form + template)', 'conferencemanager' ), value: 'hybrid' },
						] }
						help={ form.submission_mode === 'template'
							? __( 'Authors enter only the title in the form and upload a completed Word file.', 'conferencemanager' )
							: form.submission_mode === 'hybrid'
								? __( 'In addition to form entry (title, abstract, keywords), authors also upload a completed Word file.', 'conferencemanager' )
								: __( 'Authors enter the title, abstract, and keywords in the form (traditional method).', 'conferencemanager' )
						}
					/>

					{ /* テンプレートアップロード（template/hybridモード時） */ }
					{ ( form.submission_mode === 'template' || form.submission_mode === 'hybrid' ) && (
						<div style={ { padding: '12px', background: '#f0f6fc', border: '1px solid #c3c4c7', borderRadius: '4px', marginBottom: '12px' } }>
							<p style={ { margin: '0 0 8px', fontWeight: 'bold' } }>{ __( 'Word template (.docx)', 'conferencemanager' ) }</p>
							{ form.submission_template_name && (
								<p style={ { margin: '0 0 8px' } }>
									{ __( 'Current template:', 'conferencemanager' ) } <strong>{ form.submission_template_name }</strong>
									<Button
										isDestructive
										isSmall
										style={ { marginLeft: '8px' } }
										onClick={ async () => {
											if ( editingId ) {
												try {
													await apiFetch( { path: `/conf-manager/v1/events/${ editingId }/template`, method: 'DELETE' } );
													updateField( 'submission_template_name' )( '' );
												} catch ( err ) {
													setNotice( { status: 'error', message: err.message } );
												}
											}
										} }
									>{ __( 'Delete', 'conferencemanager' ) }</Button>
								</p>
							) }
							<input
								type="file"
								accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
								onChange={ ( e ) => updateField( 'submission_template_file' )( e.target.files[ 0 ] || null ) }
							/>
							{ form.submission_template_file && editingId && (
								<Button
									variant="secondary"
									isSmall
									style={ { marginTop: '8px' } }
									onClick={ async () => {
										const fd = new FormData();
										fd.append( 'file', form.submission_template_file );
										try {
											const res = await apiFetch( {
												path: `/conf-manager/v1/events/${ editingId }/template`,
												method: 'POST',
												body: fd,
											} );
											updateField( 'submission_template_name' )( res.template_name );
											updateField( 'submission_template_file' )( null );
											setNotice( { status: 'success', message: __( 'The template has been uploaded.', 'conferencemanager' ) } );
										} catch ( err ) {
											setNotice( { status: 'error', message: err.message } );
										}
									} }
								>{ __( 'Upload template', 'conferencemanager' ) }</Button>
							) }
							{ ! editingId && <p style={ { fontSize: '12px', color: '#757575', margin: '8px 0 0' } }>{ __( 'Note: the template can be uploaded after the event is saved.', 'conferencemanager' ) }</p> }
						</div>
					) }

					{ /* 文字数上限（formまたはhybridモード時のみ） */ }
					{ ( ! form.submission_mode || form.submission_mode === 'form' || form.submission_mode === 'hybrid' ) && (
						<>
							<p style={ { fontSize: '12px', color: '#757575', margin: '12px 0 4px' } }>{ __( 'Character limit (blank = no limit) * Simple character count (full-width and half-width each count as 1)', 'conferencemanager' ) }</p>
							<div style={ { display: 'flex', gap: '12px' } }>
								<TextControl label={ __( 'Title Limit', 'conferencemanager' ) } type="number" value={ form.submission_title_limit } onChange={ updateField( 'submission_title_limit' ) } placeholder={ __( 'e.g., 100', 'conferencemanager' ) } />
								<TextControl label={ __( 'Abstract Limit', 'conferencemanager' ) } type="number" value={ form.submission_abstract_limit } onChange={ updateField( 'submission_abstract_limit' ) } placeholder={ __( 'e.g., 800', 'conferencemanager' ) } />
								<TextControl label={ __( 'Keywords Limit', 'conferencemanager' ) } type="number" value={ form.submission_keywords_limit } onChange={ updateField( 'submission_keywords_limit' ) } placeholder={ __( 'e.g., 200', 'conferencemanager' ) } />
							</div>
						</>
					) }
					{ form.submission_mode === 'template' && (
						<>
							<p style={ { fontSize: '12px', color: '#757575', margin: '12px 0 4px' } }>{ __( 'Character limit (template mode: title only)', 'conferencemanager' ) }</p>
							<TextControl label={ __( 'Title Limit', 'conferencemanager' ) } type="number" value={ form.submission_title_limit } onChange={ updateField( 'submission_title_limit' ) } placeholder={ __( 'e.g., 100', 'conferencemanager' ) } />
						</>
					) }

					{ editingId && (
						<SelectControl label={ __( 'Status', 'conferencemanager' ) } value={ form.status } onChange={ updateField( 'status' ) } options={ STATUS_OPTIONS } />
					) }

					<SelectControl label={ __( 'Tax Rate', 'conferencemanager' ) } value={ form.tax_rate } onChange={ updateField( 'tax_rate' ) } options={ TAX_RATE_OPTIONS } />

					<h3>{ __( 'Bank Transfer & Organizer Info', 'conferencemanager' ) }</h3>
					<TextControl label={ __( 'Account Holder Name', 'conferencemanager' ) } value={ form.payment_account_name } onChange={ updateField( 'payment_account_name' ) } />
					<TextareaControl label={ __( 'Bank Transfer Details', 'conferencemanager' ) } help={ __( 'Enter bank name, branch, account number, etc. separated by line breaks', 'conferencemanager' ) } value={ form.payment_account_info } onChange={ updateField( 'payment_account_info' ) } />
					<TextareaControl label={ __( 'Organizer Information', 'conferencemanager' ) } help={ __( 'Enter organizer name, postal code, address, etc. separated by line breaks', 'conferencemanager' ) } value={ form.organizer_info } onChange={ updateField( 'organizer_info' ) } />

					<h3>{ form.advance_fee_deadline ? __( 'Advance Fee (excl. tax)', 'conferencemanager' ) : __( 'Fee Settings (excl. tax)', 'conferencemanager' ) }</h3>
					<p style={ { margin: '0 0 8px', color: '#646970' } }>
						{ __( 'Leave blank or enter 0 to treat this as free (¥0).', 'conferencemanager' ) }
					</p>
					{ /*
					 * 🔴 公開してから通知で気づくより、ここで気づくほうが早い。
					 * 管理画面全体の通知（AdminMenu::events_with_unpriced_categories）と
					 * 同じ状態を、編集している最中に出す。
					 */ }
					{ unpricedFeeTypes.length > 0 && (
						<Notice status="warning" isDismissible={ false }>
							{ sprintf(
								/* translators: %s: comma-separated fee category names */
								__( 'No price is set for any participation type: %s. Nobody can register under these categories. Enter a price (or tick Free), or remove the row with ×.', 'conferencemanager' ),
								unpricedFeeTypes.join( ', ' )
							) }
						</Notice>
					) }
					<table className="widefat" style={ { marginBottom: '16px' } }>
						<thead>
							<tr>
								<th>{ __( 'Member Type', 'conferencemanager' ) }</th>
								{ PARTICIPATION_TYPES.map( ( pt ) => (
									<th key={ pt }>{ pt === 'onsite' ? __( 'On-site', 'conferencemanager' ) : __( 'Online', 'conferencemanager' ) }</th>
								) ) }
								<th style={ { width: '40px' } }></th>
							</tr>
						</thead>
						<tbody>
							{ visibleTypes.map( ( mt ) => (
								<tr key={ mt }>
									<td>{ MEMBER_TYPE_LABELS[ mt ] }</td>
									{ PARTICIPATION_TYPES.map( ( pt ) => (
										<td key={ pt }>
											<FeeCell
												value={ form.fee_rules?.[ mt ]?.[ pt ] }
												onChange={ ( v ) => setFeeCell( 'fee_rules', mt, pt, v ) }
											/>
										</td>
									) ) }
									<td>
										<button
											type="button"
											onClick={ () => removeStandardType( mt ) }
											style={ { background: 'none', border: 'none', color: '#d63638', cursor: 'pointer', fontSize: '16px' } }
											title={ __( 'Remove this fee category from the table', 'conferencemanager' ) }
											aria-label={ MEMBER_TYPE_LABELS[ mt ] }
										>&times;</button>
									</td>
								</tr>
							) ) }
							{ Object.keys( form.fee_labels ).map( ( key ) => (
								<tr key={ key }>
									<td>
										<input
											type="text"
											value={ form.fee_labels[ key ] }
											onChange={ ( e ) => updateCustomLabel( key, e.target.value ) }
											placeholder={ __( 'Enter type name', 'conferencemanager' ) }
											style={ { width: '120px' } }
										/>
									</td>
									{ PARTICIPATION_TYPES.map( ( pt ) => (
										<td key={ pt }>
											<FeeCell
												value={ form.fee_rules?.[ key ]?.[ pt ] }
												onChange={ ( v ) => setFeeCell( 'fee_rules', key, pt, v ) }
											/>
										</td>
									) ) }
									<td>
										<button
											type="button"
											onClick={ () => removeCustomType( key ) }
											style={ { background: 'none', border: 'none', color: '#d63638', cursor: 'pointer', fontSize: '16px' } }
											title={ __( 'Delete', 'conferencemanager' ) }
										>&times;</button>
									</td>
								</tr>
							) ) }
						</tbody>
					</table>
					<p style={ { margin: '0 0 8px', fontSize: '12px', color: '#757575' } }>
						{ __( 'Tick "Free" to charge nothing (shown as ¥0). Leave a cell empty to not offer that participation type. Remove a row with × to not offer that category at all.', 'conferencemanager' ) }
					</p>
					{ hiddenStandardTypes.length > 0 && (
						<p style={ { margin: '0 0 8px', fontSize: '12px', color: '#757575' } }>
							{ __( 'Removed from the table:', 'conferencemanager' ) }{ ' ' }
							{ hiddenStandardTypes.map( ( mt ) => (
								<Button
									key={ mt }
									variant="link"
									isSmall
									onClick={ () => restoreStandardType( mt ) }
									style={ { marginRight: '8px' } }
								>
									{ `+ ${ MEMBER_TYPE_LABELS[ mt ] }` }
								</Button>
							) ) }
						</p>
					) }
					<Button variant="secondary" onClick={ addCustomType } style={ { marginBottom: '16px' } }>
						{ __( '+ Add Member Type', 'conferencemanager' ) }
					</Button>

					<h3>{ __( 'Advance & On-site Pricing', 'conferencemanager' ) }</h3>
					<TextControl
						label={ __( 'Advance Ticket Deadline', 'conferencemanager' ) }
						help={ __( 'When set, a two-tier pricing (advance / on-site) is applied. If not set, only the above pricing applies.', 'conferencemanager' ) }
						type="datetime-local"
						value={ ( form.advance_fee_deadline || '' ).replace( ' ', 'T' ).substring( 0, 16 ) }
						onChange={ ( v ) => updateField( 'advance_fee_deadline' )( v ? v.replace( 'T', ' ' ) + ':00' : '' ) }
						max={ form.date_start ? form.date_start.replace( ' ', 'T' ).substring( 0, 16 ) : undefined }
					/>

					{ form.advance_fee_deadline && (
						<>
							<h3>{ __( 'On-site Fee (excl. tax)', 'conferencemanager' ) }</h3>
							<div style={ { marginBottom: '8px' } }>
								<Button variant="secondary" isSmall onClick={ copyAdvanceToDay }>
									{ __( 'Copy from advance fees', 'conferencemanager' ) }
								</Button>
							</div>
							<table className="widefat" style={ { marginBottom: '16px' } }>
								<thead>
									<tr>
										<th>{ __( 'Member Type', 'conferencemanager' ) }</th>
										{ PARTICIPATION_TYPES.map( ( pt ) => (
											<th key={ pt }>{ pt === 'onsite' ? __( 'On-site', 'conferencemanager' ) : __( 'Online', 'conferencemanager' ) }</th>
										) ) }
										<th style={ { width: '40px' } }></th>
									</tr>
								</thead>
								<tbody>
									{ /* 前売の表で外した区分は当日料金にも出さない（同じ催しの同じ区分なので） */ }
									{ visibleTypes.map( ( mt ) => (
										<tr key={ mt }>
											<td>{ MEMBER_TYPE_LABELS[ mt ] }</td>
											{ PARTICIPATION_TYPES.map( ( pt ) => (
												<td key={ pt }>
													<FeeCell
														value={ form.fee_rules_day?.[ mt ]?.[ pt ] }
														onChange={ ( v ) => setFeeCell( 'fee_rules_day', mt, pt, v ) }
													/>
												</td>
											) ) }
											<td></td>
										</tr>
									) ) }
									{ Object.keys( form.fee_labels_day || {} ).map( ( key ) => (
										<tr key={ key }>
											<td>
												<input
													type="text"
													value={ form.fee_labels_day[ key ] }
													onChange={ ( e ) => updateCustomLabelDay( key, e.target.value ) }
													placeholder={ __( 'Enter type name', 'conferencemanager' ) }
													style={ { width: '120px' } }
												/>
											</td>
											{ PARTICIPATION_TYPES.map( ( pt ) => (
												<td key={ pt }>
													<FeeCell
														value={ form.fee_rules_day?.[ key ]?.[ pt ] }
														onChange={ ( v ) => setFeeCell( 'fee_rules_day', key, pt, v ) }
													/>
												</td>
											) ) }
											<td>
												<button
													type="button"
													onClick={ () => removeCustomTypeDay( key ) }
													style={ { background: 'none', border: 'none', color: '#d63638', cursor: 'pointer', fontSize: '16px' } }
													title={ __( 'Delete from on-site fee', 'conferencemanager' ) }
												>&times;</button>
											</td>
										</tr>
									) ) }
								</tbody>
							</table>
							<Button variant="secondary" onClick={ addCustomTypeDay } style={ { marginBottom: '16px' } }>
								{ __( '+ Add Member Type', 'conferencemanager' ) }
							</Button>

							<TextareaControl
								label={ __( 'Registration Form Notice', 'conferencemanager' ) }
								help={ __( 'Notice about advance/on-site pricing. Shown on the registration form.', 'conferencemanager' ) }
								value={ form.advance_fee_notice }
								onChange={ updateField( 'advance_fee_notice' ) }
								placeholder={ __( 'e.g., Advance pricing is available until MM/DD. After the deadline, on-site pricing applies.', 'conferencemanager' ) }
							/>
						</>
					) }

					<div style={ { display: 'flex', gap: '8px' } }>
						<Button variant="primary" onClick={ handleSubmit } isBusy={ api.loading }>
							{ editingId ? __( 'Update', 'conferencemanager' ) : __( 'Create', 'conferencemanager' ) }
						</Button>
						{ editingId && (
							<Button variant="secondary" isDestructive onClick={ () => { setModalOpen( false ); openCancel( editingId ); } }>
								{ __( 'Cancel', 'conferencemanager' ) }
							</Button>
						) }
						<Button variant="tertiary" onClick={ () => setModalOpen( false ) }>
							{ __( 'Close', 'conferencemanager' ) }
						</Button>
					</div>
				</Modal>
			) }

			{ /*
			  * 催しのキャンセル確認。
			  * 🔴 **未選択で開き、選ぶまで実行ボタンを出さない。**
			  *    既定で選ばれていると「押しただけ」で申込が全部動き、
			  *    戻すのは 1 件ずつになる。
			  */ }
			{ cancelModal && (
				<Modal
					title={ __( 'Cancel Event', 'conferencemanager' ) }
					onRequestClose={ () => { setCancelModal( null ); setCancelChoice( '' ); } }
				>
					<p>
						{ __( 'Cancelling an event stops new registrations and removes it from the public list. The event itself can be restored afterwards.', 'conferencemanager' ) }
					</p>

					<p>
						{ sprintf(
							/* translators: %d: number of registrations that can be cancelled with the event */
							__( 'This event has %d registration(s) that can be cancelled with it.', 'conferencemanager' ),
							cancelModal.impact.cancellable
						) }
						{ cancelModal.impact.attended > 0 && (
							<>
								{ ' ' }
								{ sprintf(
									/* translators: %d: number of registrations already marked as attended */
									__( '%d more have already been marked as attended and cannot be cancelled.', 'conferencemanager' ),
									cancelModal.impact.attended
								) }
							</>
						) }
					</p>

					{ cancelModal.impact.with_received_payment > 0 && (
						<Notice status="warning" isDismissible={ false }>
							{ sprintf(
								/* translators: %d: number of registrations that have a payment already received */
								__( '%d of them have a payment that has already been received. Cancelling does not refund anything — handle those refunds separately.', 'conferencemanager' ),
								cancelModal.impact.with_received_payment
							) }
						</Notice>
					) }

					<div style={ { margin: '16px 0' } }>
						<label style={ { display: 'block', marginBottom: '8px' } }>
							<input
								type="radio"
								name="conf-manager-cancel-choice"
								checked={ 'event_only' === cancelChoice }
								onChange={ () => setCancelChoice( 'event_only' ) }
							/>
							{ ' ' }
							{ __( 'Cancel the event only', 'conferencemanager' ) }
							<span style={ { display: 'block', marginLeft: '24px', color: '#646970' } }>
								{ __( 'Registrations stay as they are, and their unpaid invoices keep being counted as unpaid.', 'conferencemanager' ) }
							</span>
						</label>
						<label style={ { display: 'block' } }>
							<input
								type="radio"
								name="conf-manager-cancel-choice"
								checked={ 'with_registrations' === cancelChoice }
								onChange={ () => setCancelChoice( 'with_registrations' ) }
							/>
							{ ' ' }
							{ sprintf(
								/* translators: %d: number of registrations that will be cancelled */
								__( 'Cancel the event and its %d registration(s)', 'conferencemanager' ),
								cancelModal.impact.cancellable
							) }
							<span style={ { display: 'block', marginLeft: '24px', color: '#646970' } }>
								{ __( 'Unpaid invoices are withdrawn with them. Restoring is done one registration at a time.', 'conferencemanager' ) }
							</span>
						</label>
					</div>

					<div style={ { display: 'flex', gap: '8px' } }>
						{ /* 🔴 選ぶまで実行ボタンそのものを出さない */ }
						{ cancelChoice && (
							<Button variant="primary" isDestructive onClick={ handleDelete } isBusy={ cancelBusy } disabled={ cancelBusy }>
								{ __( 'Cancel Event', 'conferencemanager' ) }
							</Button>
						) }
						<Button variant="tertiary" onClick={ () => { setCancelModal( null ); setCancelChoice( '' ); } }>
							{ __( 'Close', 'conferencemanager' ) }
						</Button>
					</div>
				</Modal>
			) }
		</div>
	);
}
