<template>
  <div id="outBlock">
    <div id="outBlock2">
      <nav class="navbar navbar-expand navbar-dark bg-dark d-sm-block p-0">
        <div class="container-fluid px-0">
          <div class="collapse navbar-collapse">
            <ul class="navbar-nav me-auto mx-auto">
              <li class="nav-item">테스트</li>
              <li class="nav-item">테스트</li>
              <li class="nav-item">테스트</li>
            </ul>
          </div>
        </div>
      </nav>
      <!-- eslint-disable-next-line vue/max-attributes-per-line -->
      <BContainer v-if="asyncReady" id="container" :position="'position-relative'" :toast="{ root: true }" class="bg0">
        <main class="gx-0">
          <div class="commonToolbar">
            <GlobalMenu
              v-if="globalMenu && globalInfo"
              :globalInfo="globalInfo"
              :modelValue="globalMenu"
              variant="sammo-base2"
            />
          </div>
          <h3 class="scenarioName center">
            {{ gameConstStore?.gameConst.title }} {{ serverName }}{{ frontInfo?.global.serverCnt }}기
            <span class="avoid-wrap" style="color: cyan">{{ frontInfo?.global.scenarioText }}</span>
          </h3>
          <div v-if="frontInfo" class="gameInfo row gx-0">
            <div class="s-border-t col py-2 col-8 col-md-4 subScenarioName" style="color: cyan">
              {{ globalInfo.scenarioText }}
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subNPCType" style="color: cyan">
              NPC 수, 상성:
              {{ globalInfo.extendedGeneral ? "확장" : "표준" }}
              {{ globalInfo.isFiction ? "가상" : "사실" }}
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subNPCMode" style="color: cyan">
              NPC선택: {{ ["불가능", "가능", "선택 생성"][globalInfo.npcMode] }}
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subTournamentMode" style="color: cyan">
              토너먼트: 경기당 {{ calcTournamentTerm(globalInfo.turnterm) }}분
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subOtherSetting" style="color: cyan">
              기타 설정:
              <AutorunInfo :autorunMode="globalInfo.autorunUser" />
            </div>
            <div class="s-border-t col py-2 col-8 col-md-4 subYearMonth">
              현재: {{ globalInfo.year }}年 {{ globalInfo.month }}月 ({{ globalInfo.turnterm }}분 턴 서버)
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subOnlineUserCnt">
              전체 접속자 수: {{ globalInfo.onlineUserCnt.toLocaleString() }}명
            </div>
            <div class="s-border-t col py-2 col-4 col-md-2 subAPILimit">
              턴당 갱신횟수: {{ globalInfo.apiLimit.toLocaleString() }}회
            </div>
            <div class="s-border-t col py-2 col-8 col-md-4 subGeneralCnt">
              등록 장수: 유저
              {{ createdUserCnt.toLocaleString() }} / {{ globalInfo.generalCntLimit.toLocaleString() }} +
              <span style="color: cyan">NPC {{ createdNPCCnt.toLocaleString() }} 명</span>
            </div>
            <div class="s-border-t py-2 col col-6 col-md-4 subTournamentState">
              <span v-if="frontInfo.global.tournamentType">
                <a v-if="tournamentStep.availableJoin" href="b_tournament.php" target="_blank">
                  ↑<span style="color: cyan"
                    >{{ formatTournamentType(frontInfo.global.tournamentType) }}
                    <span style="color: orange">{{ tournamentStep.state }}</span> {{ tournamentStep.nextText }}
                    {{ formatTime(tournamentTime).substring(11, 16) }}</span
                  >↑
                </a>
                <span v-else>
                  ↑<span style="color: cyan"
                    >{{ formatTournamentType(frontInfo.global.tournamentType) }}
                    <span style="color: magenta">{{ tournamentStep.state }}</span> {{ tournamentStep.nextText }}
                    {{ formatTime(tournamentTime).substring(11, 16) }}</span
                  >↑
                </span>
              </span>
              <span v-else style="color: magenta"> 현재 토너먼트 경기 없음 </span>
            </div>
            <div
              class="s-border-t py-2 col col-6 col-md-2 subLastExecuted"
              :style="{ color: serverLocked ? 'magenta' : 'cyan' }"
            >
              동작 시각: {{ formatTime(lastExecuted).substring(5) }}
            </div>
            <div class="s-border-t py-2 col col-6 col-md-2 subAuctionState">
              <a v-if="globalInfo.auctionCount" href="v_auction.php" target="_blank" style="color: cyan">
                {{ globalInfo.auctionCount.toLocaleString() }}건 거래 진행중
              </a>
              <span v-else style="color: magenta">진행중인 거래 없음</span>
            </div>
            <div class="s-border-t py-2 col col-6 col-md-4 subVoteState">
              <a v-if="globalInfo.lastVote" href="v_vote.php" target="_blank">
                <span style="color: cyan">설문 진행 중: </span><span>{{ globalInfo.lastVote.title }}</span>
              </a>
              <span v-else style="color: magenta">진행중인 설문 없음</span>
            </div>
          </div>
          <div class="s-border-t px-2 py-2 onlineNations">접속중인 국가: {{ globalInfo.onlineNations }}</div>
          <div class="s-border-t px-2 py-2 onlineUsers">【 접속자 】 {{ nationInfo?.onlineGen }}</div>
          <div class="s-border-t py-2 nationNotice">
            <div class="px-2">【 국가방침 】</div>
            <!-- eslint-disable-next-line vue/no-v-html -->
            <div v-if="nationInfo" class="nationNoticeBody" v-html="nationInfo.notice.msg" />
          </div>
          <div id="ingameBoard">
            <!-- TODO: 운영자 툴바는 어디에?-->
            <div class="mapView">
              <MapViewer
                v-if="map"
                :serverNick="serverNick"
                :serverID="serverID"
                :mapName="unwrap(gameConstStore?.gameConst.mapName)"
                :isDetailMap="true"
                :cityPosition="cityPosition"
                :imagePath="imagePath"
                :formatCityInfo="formatCityInfoText"
                :mapData="map"
                :disallowClick="false"
                :genHref="genMapCityHref"
                @city-click="onCityClick"
              />
            </div>
            <div class="reservedCommandZone">
              <PartialReservedCommand id="reservedCommandPanel" ref="reservedCommandPanel" />
            </div>
            <div id="actionMiniPlate" class="gx-0 row">
              <div class="col">
                <div class="gx-1 row">
                  <div class="col-8 d-grid">
                    <button type="button" class="btn btn-sammo-base2" @click="tryRefresh">갱 신</button>
                  </div>
                  <div class="col-4 d-grid">
                    <button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
                  </div>
                </div>
              </div>
            </div>
            <div v-if="frontInfo" class="cityInfo">
              <CityBasicCard :city="frontInfo.city" />
            </div>
            <div v-if="frontInfo" class="nationInfo">
              <NationBasicCard :nation="frontInfo.nation" :global="globalInfo" />
            </div>
            <div v-if="frontInfo && generalInfo && nationStaticInfo" class="generalInfo">
              <GeneralBasicCard
                :general="generalInfo"
                :nation="nationStaticInfo"
                :troopInfo="frontInfo.general.troopInfo"
                :turnTerm="globalInfo.turnterm"
                :lastExecuted="lastExecuted"
              />
            </div>

            <div class="generalCommandToolbar">
              <MainControlBar
                v-if="generalInfo"
                :permission="generalInfo.permission"
                :showSecret="showSecret"
                :myLevel="generalInfo.officerLevel"
                :nationLevel="nationStaticInfo?.level ?? 0"
                :nationColor="nationStaticInfo?.color.substring(1, 7) ?? '000000'"
              />
            </div>
            <div id="actionMiniPlateSub" class="gx-0 row">
              <div class="col">
                <div class="gx-1 row">
                  <div class="col-3 d-grid">
                    <button type="button" class="btn btn-dark" @click="scrollHardTo('reservedCommandPanel')">
                      명령으로
                    </button>
                  </div>
                  <div class="col-5 d-grid">
                    <button type="button" class="btn btn-sammo-base2 refreshPage">갱 신</button>
                  </div>
                  <div class="col-4 d-grid">
                    <button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="PublicRecord">
            <div class="title">장수 동향</div>
            <template v-for="[idx, rawText] of globalRecords.toArray()" :key="idx">
              <!-- eslint-disable-next-line vue/no-v-html -->
              <div :v-data-idx="idx" v-html="formatLog(rawText)" />
            </template>
          </div>
          <div class="GeneralLog">
            <div class="title">개인 기록</div>
            <template v-for="[idx, rawText] of generalRecords.toArray()" :key="idx">
              <!-- eslint-disable-next-line vue/no-v-html -->
              <div :v-data-idx="idx" v-html="formatLog(rawText)" />
            </template>
          </div>
          <div class="WorldHistory">
            <div class="title">중원 정세</div>
            <template v-for="[idx, rawText] of worldHistory.toArray()" :key="idx">
              <!-- eslint-disable-next-line vue/no-v-html -->
              <div :v-data-idx="idx" v-html="formatLog(rawText)" />
            </template>
          </div>
          <div class="commonToolbar">툴바?</div>
          <MessagePanel
            v-if="generalInfo"
            :generalID="generalInfo.no"
            :generalName="generalInfo.name"
            :nationID="generalInfo.nation"
            :permissionLevel="generalInfo.permission"
            ref="msgPanel"
          />
          <div class="commonToolbar">
            <GlobalMenu
              v-if="globalMenu && globalInfo"
              :globalInfo="globalInfo"
              :modelValue="globalMenu"
              variant="sammo-base2"
            />
          </div>
        </main>
      </BContainer>
      <div v-else>서버 갱신 중입니다.</div>
    </div>
    <nav id="mobileBottomBar">
      <BButton>하단 바</BButton>
      <BButton>하단 바2</BButton>
    </nav>
  </div>
</template>
<script lang="ts">
declare const staticValues: {
  serverName: string;
  serverNick: string;
  serverID: string;
  mapName: string;
  unitSet: string;
};

declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import { BContainer, BButton, useToast } from "bootstrap-vue-3";
import { isString } from "lodash";
import { computed, onMounted, provide, ref, watch } from "vue";
import { GameConstStore, getGameConstStore } from "./GameConstStore";
import { SammoAPI, type InvalidResponse } from "./SammoAPI";
import { parseTime } from "./util/parseTime";
import { unwrap } from "./util/unwrap";
import Denque from "denque";
import { formatLog } from "@/utilGame/formatLog";
import type { ExecuteResponse, GetFrontInfoResponse, GetMenuResponse } from "./defs/API/Global";
import { delay } from "./util/delay";
import CityBasicCard from "./components/CityBasicCard.vue";
import NationBasicCard from "./components/NationBasicCard.vue";
import GeneralBasicCard from "./components/GeneralBasicCard.vue";
import MessagePanel from "./components/MessagePanel.vue";
import type { NationInfoFull } from "./defs/API/Nation";
import type { MapResult, NationStaticItem } from "./defs";
import { formatTime } from "./util/formatTime";
import { calcTournamentTerm } from "./utilGame";
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "./components/MapViewer.vue";
import PartialReservedCommand from "./PartialReservedCommand.vue";
import AutorunInfo from "./components/AutorunInfo.vue";
import { scrollHardTo } from "./util/scrollHardTo";
import MainControlBar from "./components/MainControlBar.vue";
import GlobalMenu from "./components/GlobalMenu.vue";
import { formatTournamentStep, formatTournamentType, type TournamentStepType } from "./utilGame/formatTournament";

const { serverName, serverNick, serverID } = staticValues;

const asyncReady = ref(false);
const gameConstStore = ref<GameConstStore>();

const toasts = unwrap(useToast());

provide("gameConstStore", gameConstStore);

const lastExecuted = ref<Date>(parseTime("2022-08-15 00:00:00"));
const serverLocked = ref(true);
const refreshCounter = ref(0);

const storeP = getGameConstStore().then((store) => {
  gameConstStore.value = store;
});

const showSecret = computed(() => {
  if (!frontInfo.value) {
    return false;
  }
  if (frontInfo.value.general.permission >= 1) {
    return true;
  }
  if (frontInfo.value.general.officerLevel >= 2) {
    return true;
  }
  return false;
});

let responseLock = false;

const msgPanel = ref<InstanceType<typeof MessagePanel>>();

function moveLobby() {
  location.replace("../");
}

async function tryRefresh() {
  msgPanel.value?.tryRefresh();

  if (responseLock) {
    return;
  }
  try {
    responseLock = true;
    const responseP = SammoAPI.Global.ExecuteEngine({ serverID }, true).then((response) => {
      if (response.result) {
        lastExecuted.value = parseTime(response.lastExecuted);
        serverLocked.value = response.locked;
      }
      return response;
    });

    //TODO: 갱신 알림 띄우기
    const response = await Promise.race([delay(3000), responseP]);
    responseLock = false;

    if (response === undefined) {
      //timeout이지만 일단 갱신한다.
      refreshCounter.value += 1;
      return;
    }

    if (!response.result) {
      if (response.reqRefresh) {
        alert(`갱신이 필요합니다: ${response.reason}`);
        window.location.reload();
        return;
      }

      console.error(response.reason);
      if (!asyncReady.value) {
        throw response.reason;
      }
      toasts.danger({
        title: "갱신 실패",
        body: response.reason,
      });
      return;
    }

    refreshCounter.value += 1;

    //TODO: 서버와 클라이언트 버전이 다르다면 갱신 필요
  } catch (e) {
    responseLock = false;
    //매우 심각한 버그
    console.error(e);
    alert(`서버 갱신 실패: ${e}`);
    throw e;
  }
}

void Promise.all([storeP, tryRefresh()]).then(() => {
  asyncReady.value = true;
});

const lastGeneralRecordID = ref(0);
const lastWorldHistoryID = ref(0);

const generalRecords = ref(new Denque<[number, string]>());
const globalRecords = ref(new Denque<[number, string]>());
const worldHistory = ref(new Denque<[number, string]>());

let recordLock = false;

watch(refreshCounter, async () => {
  if (recordLock) {
    return;
  }
  try {
    recordLock = true;

    const response = await SammoAPI.Global.GetRecentRecord({
      lastGeneralRecordID: lastGeneralRecordID.value,
      lastWorldHistoryID: lastWorldHistoryID.value,
    });
    recordLock = false;

    if (response.flushGeneral) {
      generalRecords.value = new Denque<[number, string]>();
    }
    if (response.flushGlobal) {
      globalRecords.value = new Denque<[number, string]>();
    }
    if (response.flushHistory) {
      worldHistory.value = new Denque<[number, string]>();
    }

    if (response.general.length) {
      lastGeneralRecordID.value = Math.max(lastGeneralRecordID.value, response.general[0][0]);
    }
    if (response.global.length) {
      lastGeneralRecordID.value = Math.max(lastGeneralRecordID.value, response.global[0][0]);
    }
    if (response.history.length) {
      lastWorldHistoryID.value = Math.max(lastWorldHistoryID.value, response.history[0][0]);
    }

    while (response.general.length) {
      const [id, record] = unwrap(response.general.pop());
      if (!generalRecords.value.isEmpty() && id <= unwrap(generalRecords.value.get(0))[0]) {
        continue;
      }
      if (generalRecords.value.length >= 15) {
        generalRecords.value.pop();
      }
      generalRecords.value.unshift([id, record]);
    }

    while (response.global.length) {
      const [id, record] = unwrap(response.global.pop());
      if (!globalRecords.value.isEmpty() && id <= unwrap(globalRecords.value.get(0))[0]) {
        continue;
      }
      if (globalRecords.value.length >= 15) {
        globalRecords.value.pop();
      }
      globalRecords.value.unshift([id, record]);
    }

    while (response.history.length) {
      const [id, record] = unwrap(response.history.pop());
      if (!worldHistory.value.isEmpty() && id <= unwrap(worldHistory.value.get(0))[0]) {
        continue;
      }
      if (worldHistory.value.length >= 15) {
        worldHistory.value.pop();
      }
      worldHistory.value.unshift([id, record]);
    }
  } catch (e) {
    recordLock = false;
    console.error(e);
    toasts.danger({
      title: "최근 기록 갱신 실패",
      body: `${e}`,
    });
  }
});

const globalMenu = ref<GetMenuResponse["menu"]>();
onMounted(async () => {
  try {
    const response = await SammoAPI.Global.GetGlobalMenu();
    globalMenu.value = response.menu;
  } catch (e) {
    if (isString(e)) {
      toasts.danger({
        title: "메뉴 갱신 실패",
        body: `${e}`,
      });
    }
    console.error(e);
  }
});

const frontInfo = ref<GetFrontInfoResponse>();
const globalInfo = ref<GetFrontInfoResponse["global"]>({} as GetFrontInfoResponse["global"]);
const generalInfo = ref<GetFrontInfoResponse["general"]>();
const nationStaticInfo = ref<NationStaticItem>();
const nationInfo = ref<GetFrontInfoResponse['nation']>();
const generalCnt = ref(new Map<number, number>());
const createdUserCnt = ref(0);
const createdNPCCnt = ref(0);

const tournamentStep = ref<TournamentStepType>({
  availableJoin: false,
  state: "초기화 중",
  nextText: "",
});
const tournamentTime = ref<Date>(new Date());

let generalInfoLock = false;

watch(refreshCounter, async () => {
  if (generalInfoLock) {
    return;
  }
  try {
    generalInfoLock = true;
    const response = await SammoAPI.General.GetFrontInfo();
    generalInfoLock = false;

    frontInfo.value = response;
    globalInfo.value = response.global;
    generalInfo.value = response.general;

    const newLastExecuted = parseTime(response.global.lastExecuted);
    if (newLastExecuted.getTime() > lastExecuted.value.getTime()) {
      lastExecuted.value = newLastExecuted;
    }

    const rawNation = response.nation;
    nationInfo.value = rawNation;
    nationStaticInfo.value = {
      nation: rawNation.id,
      name: rawNation.name,
      color: rawNation.color,
      type: rawNation.type.raw,
      level: rawNation.level,
      capital: rawNation.capital,
      gennum: rawNation.gennum,
      power: rawNation.power,
    };
  } catch (e) {
    generalInfoLock = false;
    console.error(e);
    toasts.danger({
      title: "최근 정보 갱신 실패",
      body: `${e}`,
    });
  }
});

watch(globalInfo, (global) => {
  const value = new Map<number, number>();
  let userCnt = 0;
  let npcCnt = 0;
  for (const [npcType, cnt] of global.genCount) {
    value.set(npcType, cnt);
    if (npcType < 2) {
      userCnt += cnt;
    } else {
      npcCnt += cnt;
    }
  }
  generalCnt.value = value;
  createdUserCnt.value = userCnt;
  createdNPCCnt.value = npcCnt;

  tournamentStep.value = formatTournamentStep(global.tournamentState);
  tournamentTime.value = parseTime(global.tournamentTime);
});

const map = ref<MapResult>();

const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;

function genMapCityHref(cityID: number) {
  return `b_currentCity.php?citylist=${cityID}`;
}

function onCityClick(city: MapCityParsed, $event: MouseEvent | TouchEvent): void {
  if (city.id === 0) {
    return;
  }
  if ($event.ctrlKey) {
    window.open(genMapCityHref(city.id), "_blank");
    return;
  }
  window.location.href = genMapCityHref(city.id);
}

watch(refreshCounter, async () => {
  try {
    map.value = await SammoAPI.Global.GetMap({
      neutralView: 0,
      showMe: 1,
    });
  } catch (e) {
    console.error(e);
    toasts.danger({
      title: "지도 갱신 실패",
      body: `${e}`,
    });
  }
});

const reservedCommandPanel = ref<InstanceType<typeof PartialReservedCommand> | null>(null);
watch(refreshCounter, async () => {
  reservedCommandPanel.value?.reloadCommandList();
});
</script>
<style lang="scss" scoped>
@import "@scss/common/break_500px.scss";

#mobileBottomBar {
  display: none;
}

#outBlock {
  display: flex;
  flex-flow: column;
  justify-content: space-between;
}

.gameInfo {
  text-align: center;
}

.generalInfo {
  width: 500px;
}

#ingameBoard {
  display: grid;
}

.nationNoticeBody {
  :deep(p) {
    min-height: 1em;
  }
}

.subVoteState, .subAuctionState, .subTournamentState {
  a {
    text-decoration: gray underline;
  }
}

@include media-500px {
  #outBlock {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;

    #mobileBottomBar {
      display: block;
    }
  }

  #outBlock2 {
    flex-grow: 1;
    overflow-y: scroll;
  }

  #container {
    width: 500px;
  }

  #ingameBoard {
    grid-template-columns: 1fr;
  }

  #actionMiniPlate {
    display: none;
  }
}

@include media-1000px {
  #container {
    width: 1000px;
  }

  #ingameBoard {
    grid-template-columns: 500px 200px 300px;
  }

  .mapView {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  .reservedCommandZone {
    grid-column: 3;
    grid-row: 1 / 3;
  }

  .cityInfo {
    grid-column: 1 / 3;
    grid-row: 2 / 4;
  }

  #actionMiniPlate {
    grid-column: 3;
    grid-row: 3;
    padding-left: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
  }

  .generalCommandToolbar {
    grid-column: 1 / 4;
  }

  .nationInfo {
    grid-column: 1;
  }

  .generalInfo {
    grid-column: 2 / 4;
  }

  #actionMiniPlateSub {
    display: none;
  }
}
</style>
