<template>
	<div class="roulette-demo-page container-fluid">
		<header class="roulette-page-header">
			<div class="roulette-page-header-inner">
				<Logo :href="'/admin/'" />
				<h1 class="roulette-demo-title">Sesi Cabutan Bertuah — Kehadiran Fizikal</h1>
				<button type="button" class="btn btn-default btn-sm roulette-bg-music-btn"
					:aria-pressed="bgMusicPlaying ? 'true' : 'false'"
					:aria-label="bgMusicPlaying ? 'Pause Background Music' : 'Play Background Music'"
					@click="toggleBgMusic">
					{{ bgMusicPlaying ? 'Pause Background Music' : 'Play Background Music' }}
				</button>
			</div>
		</header>

		<div v-if="loadError" class="alert alert-danger">
			{{ loadError }}
		</div>
		<div v-else-if="loading" class="text-muted roulette-demo-loading">
			Memuat senarai pengundi Fizikal…
		</div>
		<div v-else-if="!apiVoters.length" class="alert alert-info">
			Tiada pengundi Fizikal direkod buat masa ini. Semak kehadiran atau tambah rekod.
		</div>

		<div v-if="!loading && !loadError && apiVoters.length" class="roulette-sync-bar">
			<span v-if="lastSyncedAt" class="roulette-sync-time">
				Senarai menunggu dikemas kini pada <strong>{{ lastSyncedFormatted }}</strong>
			</span>
			<span v-if="refreshing" class="roulette-sync-refreshing text-muted">Mengemas kini…</span>
			<span v-if="pollError" class="text-warning roulette-sync-poll-error">{{ pollError }}</span>
			<button type="button" class="btn btn-default btn-sm roulette-sync-btn" :disabled="refreshing"
				@click="fetchFizikalVoters(false)">
				Segerakan sekarang
			</button>
		</div>

		<div v-if="!loading && !loadError && apiVoters.length" class="row roulette-demo-layout">
			<aside class="col-md-3 col-sm-12 roulette-pending-sidebar text-left">
				<div class="panel panel-default roulette-pending-panel">
					<div class="panel-heading clearfix">
						<span>Menunggu sertai cabutan</span>
						<span class="badge pull-right">{{ pendingVoters.length }}</span>
					</div>
					<ul class="list-group roulette-pending-list">
						<li v-if="!pendingVoters.length" class="list-group-item text-muted roulette-pending-empty">
							Tiada dalam senarai menunggu — semua peserta telah diimport atau senarai kosong.
						</li>
						<template v-else>
							<li v-for="v in pendingVoters" :key="'p-' + v.id"
								class="list-group-item roulette-pending-row">
								<span class="roulette-pending-name">{{ labelForVoter(v) }}</span>
								<button type="button" class="btn btn-primary btn-xs roulette-pending-import"
									@click="importVoter(v.id)">
									Import
								</button>
							</li>
						</template>
					</ul>
					<div v-if="pendingVoters.length > 1" class="panel-footer text-right">
						<button type="button" class="btn btn-default btn-sm" @click="importAllPending">
							Import semua
						</button>
					</div>
				</div>
			</aside>

			<div class="col-md-9 col-sm-12 roulette-main-column">
				<div v-if="cabutanCount < 2" class="alert alert-info roulette-cabutan-hint">
					Import sekurang-kurangnya <strong>dua</strong> peserta daripada senarai menunggu untuk memulakan
					cabutan. Senarai <strong>Peserta</strong> pada roda hanya berubah apabila anda menambah peserta di
					sini; auto sync mengemas kini senarai menunggu sahaja.
				</div>

				<roulette-elimination-wheel v-if="cabutanCount >= 2" :items="cabutanItems" :full-width="true"
					persist-key="roulette_fizikal_demo" @winner="onWinner" />
			</div>
		</div>
	</div>
</template>

<script>
import RouletteEliminationWheel from './RouletteEliminationWheel.vue';
import Logo from '../common/Logo.vue';

function buildLabelsFromVoters(voters) {
	if (!voters || !voters.length) {
		return [];
	}
	var nameCount = {};
	for (var i = 0; i < voters.length; i++) {
		var raw = voters[i].name;
		var n = (raw != null ? String(raw) : '').trim();
		nameCount[n] = (nameCount[n] || 0) + 1;
	}
	return voters.map(function (v) {
		var n = (v.name != null ? String(v.name) : '').trim();
		if (!n) {
			n = '#' + v.id;
		}
		if (nameCount[n] > 1) {
			var tag = v.no_anggota != null ? String(v.no_anggota).trim() : '';
			return tag ? n + ' (' + tag + ')' : n + ' (#' + v.id + ')';
		}
		return n;
	});
}

/** Persist which voter ids were added to the cabutan (separate from wheel game state). */
var IMPORTED_IDS_STORAGE_KEY = 'roulette_fizikal_imported_ids';

/** Looping background track (`public/audio/…`). */
var BG_MUSIC_SRC = '/audio/lucky-draw-bg.mp3';

/** Stable string so we detect new/removed/changed voters from API. */
function votersSnapshotSignature(voters) {
	if (!voters || !voters.length) {
		return 'empty';
	}
	var rows = voters.map(function (v) {
		return (
			String(v.id) +
			'\t' +
			String(v.name != null ? v.name : '') +
			'\t' +
			String(v.no_anggota != null ? v.no_anggota : '')
		);
	});
	rows.sort();
	return rows.join('|');
}

export default {
	name: 'RouletteEliminationWheelDemo',

	components: {
		RouletteEliminationWheel,
		Logo,
	},

	data: function () {
		return {
			loading: true,
			loadError: '',
			/** Full list from GET …/fizikal-voters (updated by auto sync / manual refresh). */
			apiVoters: [],
			/** Voter ids explicitly imported into the cabutan / wheel; order = cabutan order. */
			importedParticipantIds: [],
			/** Last applied API snapshot — skip redundant Vue updates when unchanged. */
			lastSnapshotSig: '',
			refreshing: false,
			lastSyncedAt: null,
			pollError: '',
			hasLoadedOnce: false,
			pollTimerId: null,
			pollStarted: false,
			/** Interval in ms; set 0 to disable auto-refresh. */
			pollIntervalMs: 10000,
			/** True while background music is playing (user must click Main first — browser policy). */
			bgMusicPlaying: false,
		};
	},

	computed: {
		lastSyncedFormatted: function () {
			if (!this.lastSyncedAt) {
				return '—';
			}
			try {
				return this.lastSyncedAt.toLocaleString();
			} catch (e) {
				return String(this.lastSyncedAt);
			}
		},
		/** Label string per voter id (duplicate-name disambiguation uses full API list). */
		voterLabelsById: function () {
			var list = this.apiVoters;
			var labels = buildLabelsFromVoters(list);
			var map = {};
			for (var i = 0; i < list.length; i++) {
				var vid = list[i].id;
				var nid = typeof vid === 'number' && vid === vid ? vid : parseInt(vid, 10);
				if (nid === nid) {
					map[nid] = labels[i];
				}
			}
			return map;
		},
		pendingVoters: function () {
			var imported = this.importedParticipantIds;
			return this.apiVoters.filter(function (v) {
				var vid = typeof v.id === 'number' && v.id === v.id ? v.id : parseInt(v.id, 10);
				return imported.indexOf(vid) < 0;
			});
		},
		/** Wheel entries: stable voter id + label (order = import order). */
		cabutanItems: function () {
			var map = this.voterLabelsById;
			var out = [];
			for (var i = 0; i < this.importedParticipantIds.length; i++) {
				var id = this.importedParticipantIds[i];
				if (map[id] !== undefined) {
					out.push({ id: id, label: map[id] });
				}
			}
			return out;
		},
		cabutanCount: function () {
			return this.cabutanItems.length;
		},
	},

	created: function () {
		this.loadImportedIdsFromStorage();
		this.fetchFizikalVoters(false);
	},

	beforeDestroy: function () {
		this.stopPolling();
		this.disposeBgMusic();
	},

	methods: {
		disposeBgMusic: function () {
			this.bgMusicPlaying = false;
			if (!this._bgMusicAudio) {
				return;
			}
			try {
				this._bgMusicAudio.pause();
				this._bgMusicAudio.src = '';
			} catch (e) {
				/* ignore */
			}
			this._bgMusicAudio = null;
		},

		ensureBgMusicAudio: function () {
			if (typeof Audio === 'undefined') {
				return null;
			}
			if (!this._bgMusicAudio) {
				this._bgMusicAudio = new Audio(BG_MUSIC_SRC);
				this._bgMusicAudio.loop = true;
				this._bgMusicAudio.volume = 0.32;
				this._bgMusicAudio.preload = 'auto';
			}
			return this._bgMusicAudio;
		},

		toggleBgMusic: function () {
			var vm = this;
			var a = this.ensureBgMusicAudio();
			if (!a) {
				return;
			}
			if (this.bgMusicPlaying) {
				a.pause();
				this.bgMusicPlaying = false;
				return;
			}
			var p = a.play();
			if (p && typeof p.then === 'function') {
				p.then(function () {
					vm.bgMusicPlaying = true;
				}).catch(function () {
					vm.bgMusicPlaying = false;
				});
			} else {
				this.bgMusicPlaying = true;
			}
		},

		loadImportedIdsFromStorage: function () {
			if (typeof localStorage === 'undefined') {
				return;
			}
			try {
				var raw = localStorage.getItem(IMPORTED_IDS_STORAGE_KEY);
				if (!raw) {
					return;
				}
				var parsed = JSON.parse(raw);
				if (!Array.isArray(parsed)) {
					return;
				}
				this.importedParticipantIds = parsed.filter(function (x) {
					return typeof x === 'number' && x === x;
				});
			} catch (e) {
				this.importedParticipantIds = [];
			}
		},

		saveImportedIdsToStorage: function () {
			if (typeof localStorage === 'undefined') {
				return;
			}
			try {
				localStorage.setItem(
					IMPORTED_IDS_STORAGE_KEY,
					JSON.stringify(this.importedParticipantIds)
				);
			} catch (e) {
				// quota / private mode
			}
		},

		/** Drop imported ids that no longer exist in the latest API list. */
		pruneImportedIdsToApi: function (list) {
			var ok = {};
			for (var i = 0; i < list.length; i++) {
				var vid = list[i].id;
				var nid = typeof vid === 'number' && vid === vid ? vid : parseInt(vid, 10);
				if (nid === nid) {
					ok[nid] = true;
				}
			}
			var next = this.importedParticipantIds.filter(function (id) {
				return ok[id];
			});
			if (next.length !== this.importedParticipantIds.length) {
				this.importedParticipantIds = next;
				this.saveImportedIdsToStorage();
			}
		},

		labelForVoter: function (v) {
			var vid = typeof v.id === 'number' && v.id === v.id ? v.id : parseInt(v.id, 10);
			var m = this.voterLabelsById;
			return vid === vid && m[vid] != null ? m[vid] : '#' + v.id;
		},

		importVoter: function (id) {
			var nid = typeof id === 'number' && id === id ? id : parseInt(id, 10);
			if (nid !== nid) {
				return;
			}
			if (this.importedParticipantIds.indexOf(nid) >= 0) {
				return;
			}
			this.importedParticipantIds.push(nid);
			this.saveImportedIdsToStorage();
		},

		importAllPending: function () {
			var vm = this;
			this.pendingVoters.forEach(function (v) {
				var vid = typeof v.id === 'number' && v.id === v.id ? v.id : parseInt(v.id, 10);
				if (vid === vid && vm.importedParticipantIds.indexOf(vid) < 0) {
					vm.importedParticipantIds.push(vid);
				}
			});
			this.saveImportedIdsToStorage();
		},

		stopPolling: function () {
			if (this.pollTimerId !== null) {
				clearInterval(this.pollTimerId);
				this.pollTimerId = null;
			}
			this.pollStarted = false;
		},

		startPolling: function () {
			if (this.pollIntervalMs <= 0) {
				return;
			}
			if (this.pollStarted) {
				return;
			}
			this.pollStarted = true;
			var vm = this;
			this.pollTimerId = setInterval(function () {
				vm.fetchFizikalVoters(true);
			}, this.pollIntervalMs);
		},

		/**
		 * @param {boolean} silent - true = background poll (no full-page loading state).
		 */
		fetchFizikalVoters: function (silent) {
			var vm = this;
			if (!silent) {
				vm.loadError = '';
				vm.pollError = '';
				if (!vm.hasLoadedOnce) {
					vm.loading = true;
				} else {
					vm.refreshing = true;
				}
			} else {
				vm.pollError = '';
				vm.refreshing = true;
			}

			return axios
				.get(config.API + 'public/roulette/fizikal-voters')
				.then(function (response) {
					var list =
						response.data && response.data.voters ? response.data.voters : [];
					var sig = votersSnapshotSignature(list);
					if (silent && sig === vm.lastSnapshotSig) {
						vm.lastSyncedAt = new Date();
						return;
					}
					vm.lastSnapshotSig = sig;
					vm.apiVoters = list;
					vm.pruneImportedIdsToApi(list);
					vm.lastSyncedAt = new Date();
					vm.hasLoadedOnce = true;
				})
				.catch(function (error) {
					var msg =
						error &&
							error.response &&
							error.response.data &&
							error.response.data.message
							? error.response.data.message
							: 'Gagal memuat data.';
					if (!silent && !vm.hasLoadedOnce) {
						vm.loadError = msg;
						vm.apiVoters = [];
					} else {
						vm.pollError =
							'Tidak dapat mengemas kini: ' + (msg.length > 80 ? msg.slice(0, 80) + '…' : msg);
					}
				})
				.finally(function () {
					vm.loading = false;
					vm.refreshing = false;
					vm.startPolling();
				});
		},

		onWinner: function (payload) {
			if (typeof console !== 'undefined' && console.log) {
				console.log('[roulette demo] winner:', payload);
			}
		},
	},
};
</script>

<style scoped>
.roulette-demo-page {
	padding-top: 2rem;
	padding-bottom: 3rem;
	width: 100%;
	max-width: none;
}

.roulette-page-header {
	display: flex;
	justify-content: center;
	margin-bottom: 1rem;
	padding-bottom: 0.75rem;
	border-bottom: 1px solid #e7e7e7;
}

.roulette-page-header-inner {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 14px 18px;
	text-align: left;
}

.roulette-page-header-logo {
	display: block;
	height: 52px;
	width: auto;
	max-width: 140px;
	object-fit: contain;
	flex-shrink: 0;
}

.roulette-demo-title {
	font-size: 1.65rem;
	font-weight: 600;
	margin: 0;
	line-height: 1.3;
	max-width: min(100%, 36rem);
}

.roulette-bg-music-btn {
	flex-shrink: 0;
	white-space: nowrap;
}

.roulette-demo-path {
	margin-bottom: 1.25rem;
	font-size: 0.9rem;
	word-break: break-all;
}

.roulette-poll-help {
	margin-bottom: 0.75rem;
	line-height: 1.45;
}

.roulette-demo-loading {
	margin-bottom: 1rem;
}

.roulette-sync-bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px 16px;
	margin-bottom: 14px;
	font-size: 13px;
}

.roulette-sync-refreshing {
	font-style: italic;
}

.roulette-sync-poll-error {
	font-size: 12px;
}

.roulette-sync-btn {
	flex-shrink: 0;
}

.roulette-demo-layout {
	align-items: flex-start;
}

.roulette-pending-sidebar {
	margin-bottom: 16px;
}

@media (min-width: 992px) {
	.roulette-pending-sidebar {
		margin-bottom: 0;
	}

	.roulette-pending-panel {
		position: sticky;
		top: 12px;
	}
}

.roulette-main-column {
	min-width: 0;
}

.roulette-pending-panel {
	margin-bottom: 0;
}

.roulette-pending-list {
	margin-bottom: 0;
}

.roulette-pending-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	font-size: 13px;
}

.roulette-pending-name {
	flex: 1;
	min-width: 0;
	word-break: break-word;
}

.roulette-pending-import {
	flex-shrink: 0;
}

.roulette-cabutan-hint {
	margin-bottom: 16px;
	line-height: 1.45;
}
</style>
