<template>
	<MkSpacer :content-max="980">
		<div class="shogi">
			<section v-if="!gameId" class="panel">
				<div class="head">
					<div>
						<h1>将棋</h1>
						<p>{{ $i ? "SNS上のユーザーと対局できます。" : "ログインすると対局できます。" }}</p>
					</div>
					<div class="actions">
						<MkButton v-if="$i" primary @click="inviteUser">ユーザーを招待</MkButton>
						<MkButton v-if="$i" :disabled="matching" @click="matchAny">ランダム対局</MkButton>
					</div>
				</div>

				<div v-if="invitations.length > 0" class="block">
					<h2>招待</h2>
					<div class="list">
						<button v-for="user in invitations" :key="user.id" class="row" @click="accept(user)">
							<MkAvatar :user="user" class="avatar"/>
							<span>{{ user.name || user.username }}</span>
							<i class="ph-caret-right ph-bold"></i>
						</button>
					</div>
				</div>

				<div class="block">
					<h2>対局</h2>
					<div v-if="games.length === 0" class="empty">対局はまだありません。</div>
					<div v-else class="list">
						<button v-for="g in games" :key="g.id" class="row" @click="router.push(`/shogi/${g.id}`)">
							<span class="players">
								{{ g.user1?.name || g.user1?.username }} vs {{ g.user2?.name || g.user2?.username }}
							</span>
							<span class="status">{{ g.isEnded ? "終了" : g.isStarted ? "対局中" : "準備中" }}</span>
						</button>
					</div>
				</div>
			</section>

			<section v-else-if="game" class="game">
				<div class="gameHeader">
					<MkButton @click="router.push('/shogi')"><i class="ph-arrow-left ph-bold"></i></MkButton>
					<div>
						<h1>{{ game.user1?.name || game.user1?.username }} vs {{ game.user2?.name || game.user2?.username }}</h1>
						<p>{{ gameStatus }}</p>
					</div>
					<MkButton v-if="canPlay && !game.isEnded" danger @click="surrenderGame">投了</MkButton>
				</div>

				<div class="playArea">
					<div class="boardWrap" :class="{ flipped: myColor === 'gote' }">
						<div class="hand opponent">
							<span>{{ goteUserName }}</span>
							<button
								v-for="piece in goteHand"
								:key="`gote-${piece.role}`"
								class="handPiece"
								:disabled="!canDrop(piece.role, 'gote')"
								@click="selectDrop(piece.role, 'gote')"
							>
								{{ roleLabel[piece.role] }}<b>{{ piece.count }}</b>
							</button>
						</div>

						<div class="board">
							<button
								v-for="cell in boardCells"
								:key="cell.square"
								class="cell"
								:class="{
									selected: selectedSquare === cell.square,
									target: targetSquares.includes(cell.square),
									last: lastSquare === cell.square,
								}"
								@click="clickSquare(cell.square)"
							>
								<span v-if="cell.piece" class="piece" :class="cell.piece.color">
									{{ pieceLabel(cell.piece) }}
								</span>
							</button>
						</div>

						<div class="hand mine">
							<span>{{ senteUserName }}</span>
							<button
								v-for="piece in senteHand"
								:key="`sente-${piece.role}`"
								class="handPiece"
								:disabled="!canDrop(piece.role, 'sente')"
								@click="selectDrop(piece.role, 'sente')"
							>
								{{ roleLabel[piece.role] }}<b>{{ piece.count }}</b>
							</button>
						</div>
					</div>

					<div class="side panel">
						<div class="playersInfo">
							<div>
								<MkAvatar v-if="senteUser" :user="senteUser" class="scoreAvatar"/>
								<span>先手 {{ senteUserName }}</span>
							</div>
							<div>
								<MkAvatar v-if="goteUser" :user="goteUser" class="scoreAvatar"/>
								<span>後手 {{ goteUserName }}</span>
							</div>
						</div>

						<div v-if="!game.isStarted && canPlay" class="ready">
							<MkButton :primary="!myReady" @click="setReady(!myReady)">
								{{ myReady ? "準備解除" : "準備完了" }}
							</MkButton>
							<div class="readyStates">
								<span :class="{ on: game.user1Ready }">{{ game.user1?.username }}</span>
								<span :class="{ on: game.user2Ready }">{{ game.user2?.username }}</span>
							</div>
						</div>

						<div class="history">
							<h2>棋譜</h2>
							<div v-if="(game.logs ?? []).length === 0" class="empty">まだ指し手はありません。</div>
							<ol v-else>
								<li v-for="log in game.logs" :key="log.id || `${log.at}-${log.usi}`">{{ log.usi }}</li>
							</ol>
						</div>
					</div>
				</div>
			</section>
		</div>
	</MkSpacer>
</template>

<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { v4 as uuid } from "uuid";
import { parseSfen } from "shogiops/sfen";
import { makeUsi, parseUsi } from "shogiops/util";
import type { Color, Piece, Role, Square } from "shogiops/types";
import MkButton from "@/components/MkButton.vue";
import * as os from "@/os";
import { $i } from "@/account";
import { stream } from "@/stream";
import { useRouter } from "@/router";
import { definePageMetadata } from "@/scripts/page-metadata";

const props = defineProps<{ gameId?: string }>();
const router = useRouter();

const games = ref<any[]>([]);
const invitations = ref<any[]>([]);
const game = ref<any | null>(null);
const matching = ref(false);
const busy = ref(false);
const conn = ref<any | null>(null);
const lobbyConn = ref<any | null>(null);
const selectedSquare = ref<Square | null>(null);
const selectedDropRole = ref<Role | null>(null);

const roleLabel: Record<string, string> = {
	pawn: "歩",
	lance: "香",
	knight: "桂",
	silver: "銀",
	gold: "金",
	bishop: "角",
	rook: "飛",
	king: "玉",
	tokin: "と",
	promotedpawn: "と",
	promotedlance: "成香",
	promotedknight: "成桂",
	promotedsilver: "成銀",
	horse: "馬",
	dragon: "龍",
};
const handRoles: Role[] = ["rook", "bishop", "gold", "silver", "knight", "lance", "pawn"];
const ranks = [0, 1, 2, 3, 4, 5, 6, 7, 8];
const files = [8, 7, 6, 5, 4, 3, 2, 1, 0];

const position = computed(() => {
	if (!game.value?.sfen) return null;
	try {
		return parseSfen("standard", game.value.sfen).unwrap();
	} catch {
		return null;
	}
});

const boardCells = computed(() => {
	const pos = position.value;
	return ranks.flatMap((rank) => files.map((file) => {
		const square = rank * 16 + file;
		return {
			square,
			piece: pos?.board.get(square),
		};
	}));
});

const canPlay = computed(() => $i && game.value && (game.value.user1Id === $i.id || game.value.user2Id === $i.id));
const myReady = computed(() => $i && game.value && (game.value.user1Id === $i.id ? game.value.user1Ready : game.value.user2Ready));
const senteUser = computed(() => game.value?.sente === 2 ? game.value?.user2 : game.value?.user1);
const goteUser = computed(() => game.value?.sente === 2 ? game.value?.user1 : game.value?.user2);
const senteUserName = computed(() => senteUser.value?.name || senteUser.value?.username || "先手");
const goteUserName = computed(() => goteUser.value?.name || goteUser.value?.username || "後手");
const myColor = computed<Color | null>(() => {
	if (!$i || !game.value || game.value.sente == null) return null;
	return senteUser.value?.id === $i.id ? "sente" : goteUser.value?.id === $i.id ? "gote" : null;
});
const isMyTurn = computed(() => canPlay.value && position.value?.turn === myColor.value && game.value?.isStarted && !game.value?.isEnded);

const senteHand = computed(() => handPieces("sente"));
const goteHand = computed(() => handPieces("gote"));
const targetSquares = computed(() => {
	const pos = position.value;
	if (!pos || !isMyTurn.value) return [];
	if (selectedSquare.value != null) return [...pos.moveDests(selectedSquare.value)];
	if (selectedDropRole.value) return [...pos.dropDests({ color: pos.turn, role: selectedDropRole.value })];
	return [];
});
const lastSquare = computed(() => {
	const logs = game.value?.logs ?? [];
	const last = logs.length ? parseUsi(logs[logs.length - 1].usi) : null;
	return last ? last.to : null;
});
const gameStatus = computed(() => {
	if (!game.value) return "";
	if (game.value.isEnded) return game.value.winnerId ? `${game.value.winner?.username ?? "勝者"} の勝ち` : "引き分け";
	if (!game.value.isStarted) return "準備中";
	if (!canPlay.value) return position.value?.turn === "sente" ? "先手の番です" : "後手の番です";
	return isMyTurn.value ? "あなたの番です" : "相手の番です";
});

function handPieces(color: Color) {
	const hand = position.value?.hands.color(color);
	if (!hand) return [];
	return handRoles
		.map((role) => ({ role, count: hand.get(role) }))
		.filter((piece) => piece.count > 0);
}

function pieceLabel(piece: Piece) {
	return roleLabel[piece.role] ?? piece.role;
}

function canDrop(role: Role, color: Color) {
	return isMyTurn.value && position.value?.turn === color && (position.value?.hands.color(color).get(role) ?? 0) > 0;
}

function selectDrop(role: Role, color: Color) {
	if (!canDrop(role, color)) return;
	selectedSquare.value = null;
	selectedDropRole.value = selectedDropRole.value === role ? null : role;
}

async function clickSquare(square: Square) {
	if (!position.value || !isMyTurn.value || busy.value) return;

	if (targetSquares.value.includes(square)) {
		if (selectedDropRole.value) {
			sendMove(makeUsi({ role: selectedDropRole.value, to: square }));
			return;
		}
		if (selectedSquare.value != null) {
			const base = { from: selectedSquare.value, to: square };
			const promote = { ...base, promotion: true };
			const canMove = position.value.isLegal(base);
			const canPromote = position.value.isLegal(promote);
			let move = base;
			if (canMove && canPromote) {
				const { canceled } = await os.confirm({ type: "question", text: "成りますか？" });
				move = canceled ? base : promote;
			} else if (canPromote) {
				move = promote;
			}
			sendMove(makeUsi(move));
			return;
		}
	}

	const piece = position.value.board.get(square);
	selectedDropRole.value = null;
	selectedSquare.value = piece?.color === position.value.turn ? square : null;
}

function sendMove(usi: string) {
	busy.value = true;
	selectedSquare.value = null;
	selectedDropRole.value = null;
	conn.value?.send("move", { usi, id: uuid() });
	window.setTimeout(() => busy.value = false, 250);
}

async function loadLobby() {
	if (!$i) return;
	invitations.value = await os.api("shogi/invitations");
	games.value = await os.api("shogi/games", { my: true, limit: 30 });
}

async function loadGame() {
	if (!props.gameId) return;
	game.value = await os.api("shogi/show-game", { gameId: props.gameId });
	connectGame();
}

function connectGame() {
	conn.value?.dispose();
	if (!props.gameId) return;
	conn.value = stream.useChannel("shogiGame" as any, { gameId: props.gameId } as any);
	conn.value.on("changeReadyStates", ({ user1, user2 }) => {
		if (!game.value) return;
		game.value.user1Ready = user1;
		game.value.user2Ready = user2;
	});
	conn.value.on("started", ({ game: g }) => game.value = g);
	conn.value.on("log", (log) => {
		if (!game.value) return;
		game.value.sfen = log.sfen;
		game.value.logs.push(log);
	});
	conn.value.on("ended", ({ game: g }) => game.value = g);
	conn.value.on("canceled", () => router.push("/shogi"));
}

async function inviteUser() {
	const user = await os.selectUser();
	if (!user) return;
	matching.value = true;
	const matched = await os.api("shogi/match", { userId: user.id });
	matching.value = false;
	if (matched) router.push(`/shogi/${matched.id}`);
}

async function matchAny() {
	matching.value = true;
	const matched = await os.api("shogi/match", {});
	matching.value = false;
	if (matched) router.push(`/shogi/${matched.id}`);
}

async function accept(user: any) {
	const matched = await os.api("shogi/match", { userId: user.id });
	if (matched) router.push(`/shogi/${matched.id}`);
}

function setReady(value: boolean) {
	conn.value?.send("ready", value);
}

async function surrenderGame() {
	await os.api("shogi/surrender", { gameId: props.gameId });
}

onMounted(() => {
	if (props.gameId) {
		loadGame();
	} else {
		loadLobby();
		if ($i) {
			lobbyConn.value = stream.useChannel("shogi" as any);
			lobbyConn.value.on("invited", loadLobby);
			lobbyConn.value.on("matched", ({ game }) => router.push(`/shogi/${game.id}`));
		}
	}
});

watch(() => props.gameId, () => {
	game.value = null;
	selectedSquare.value = null;
	selectedDropRole.value = null;
	conn.value?.dispose();
	if (props.gameId) loadGame();
});

onUnmounted(() => {
	conn.value?.dispose();
	lobbyConn.value?.dispose();
});

definePageMetadata({
	title: "将棋",
	icon: "ph-crown-simple ph-bold ph-lg",
});
</script>

<style lang="scss" scoped>
.shogi { display: flex; flex-direction: column; gap: 16px; min-width: 0; overflow: hidden; }
.panel { background: var(--panel); border-radius: 8px; padding: 18px; }
.head, .gameHeader { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
h1 { margin: 0; font-size: 1.35em; }
h2 { margin: 0 0 10px; font-size: 1.05em; }
p { margin: 6px 0 0; color: var(--fgTransparentWeak); }
.actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.block { margin-top: 18px; }
.list { display: grid; gap: 8px; }
.row {
	display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px;
	border: 1px solid var(--divider); border-radius: 8px; background: var(--buttonBg);
	color: var(--fg); text-align: left;
}
.row:hover { background: var(--buttonHoverBg); }
.avatar { width: 34px; height: 34px; }
.players { flex: 1; }
.status, .empty { color: var(--fgTransparentWeak); }
.empty { padding: 12px 0; }
.game,
.playArea,
.boardWrap,
.side { min-width: 0; }
.playArea { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 16px; align-items: start; }
.boardWrap { display: grid; gap: 10px; width: 100%; max-width: 680px; }
.boardWrap.flipped .board,
.boardWrap.flipped .hand { transform: rotate(180deg); }
.boardWrap.flipped .handPiece,
.boardWrap.flipped .hand > span { transform: rotate(180deg); }
.board {
	display: grid; grid-template-columns: repeat(9, 1fr); gap: 2px;
	width: min(100%, 680px); aspect-ratio: 1 / 1;
	background: #5b3a1f; padding: 8px; border-radius: 8px;
	box-sizing: border-box;
}
.cell {
	display: grid; place-items: center; position: relative;
	border: 1px solid rgba(75, 45, 20, .55); background: #d6a25f;
	aspect-ratio: 1 / 1; border-radius: 4px; color: #281709;
}
.cell.selected { box-shadow: inset 0 0 0 3px var(--accent); }
.cell.target::after {
	content: ""; position: absolute; inset: 36%; border-radius: 999px; background: rgba(37, 82, 54, .55);
}
.cell.last { box-shadow: inset 0 0 0 2px #f5c542; }
.piece {
	display: grid; place-items: center;
	width: 82%; height: 88%;
	clip-path: polygon(50% 0, 92% 20%, 84% 100%, 16% 100%, 8% 20%);
	background: #f1cf8a; border: 1px solid rgba(60, 37, 14, .5);
	font-weight: 700; font-size: clamp(13px, 4.3vw, 26px); line-height: 1;
}
.piece.gote { transform: rotate(180deg); }
.hand {
	display: flex; align-items: center; gap: 6px; min-height: 42px; flex-wrap: wrap;
	padding: 8px; border: 1px solid var(--divider); border-radius: 8px; background: var(--panel);
}
.hand > span { color: var(--fgTransparentWeak); margin-right: auto; }
.handPiece {
	display: inline-flex; align-items: center; gap: 3px; min-width: 42px; justify-content: center;
	border: 1px solid var(--divider); border-radius: 6px; background: var(--buttonBg); color: var(--fg);
	padding: 6px 8px; font-weight: 700;
}
.handPiece:disabled { opacity: .55; }
.handPiece b { color: var(--accent); }
.side { display: grid; gap: 16px; }
.playersInfo { display: grid; gap: 10px; }
.playersInfo div { display: flex; align-items: center; gap: 8px; }
.scoreAvatar { width: 28px; height: 28px; }
.ready { display: grid; gap: 10px; }
.readyStates { display: flex; gap: 8px; flex-wrap: wrap; }
.readyStates span { color: var(--fgTransparentWeak); }
.readyStates span.on { color: var(--accent); font-weight: 700; }
.history ol { margin: 0; padding-left: 24px; max-height: 260px; overflow: auto; }
.history li { padding: 2px 0; font-family: monospace; }
@media (max-width: 760px) {
	.head, .gameHeader { align-items: stretch; flex-direction: column; }
	.playArea { grid-template-columns: 1fr; }
	.panel { padding: 14px; }
	.board { padding: 4px; gap: 1px; }
	.cell { border-radius: 2px; }
	.piece { font-size: clamp(11px, 5.5vw, 20px); }
	.hand { padding: 6px; }
	.handPiece { min-width: 36px; padding: 5px 6px; }
}
</style>
