/**
 * @file Exploration panel component for exploring areas and collecting materials.
 * @copyright nhcarrigan
 * @license Naomi's Public License
 * @author Naomi Carrigan
 */
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
/* eslint-disable complexity -- Complex component with many conditional render paths */
/* eslint-disable max-lines -- Exploration panel requires many render paths and result display */
/* eslint-disable max-statements -- Component function requires many state declarations and handlers */
import { type JSX, useEffect, useRef, useState } from "react";
import { checkExplorationClaimable } from "../../api/client.js";
import { useGame } from "../../context/gameContext.js";
import { EXPLORATION_AREAS } from "../../data/explorations.js";
import { cdnImage } from "../../utils/cdn.js";
import { ZoneSelector } from "./zoneSelector.js";
import type {
  ExploreClaimableResponse,
  ExploreCollectResponse,
} from "@elysium/types";

/**
 * Formats a duration in seconds to a human-readable string.
 * @param seconds - The total number of seconds to format.
 * @returns The formatted duration string.
 */
const formatDuration = (seconds: number): string => {
  const secondsPerDay = 86_400;
  const secondsPerHour = 3600;
  const secondsPerMinute = 60;
  if (seconds >= secondsPerDay) {
    const days = Math.floor(seconds / secondsPerDay);
    const remainingAfterDays = seconds % secondsPerDay;
    const hours = Math.floor(remainingAfterDays / secondsPerHour);
    return hours > 0
      ? `${String(days)}d ${String(hours)}h`
      : `${String(days)}d`;
  }
  if (seconds >= secondsPerHour) {
    const hours = Math.floor(seconds / secondsPerHour);
    const remainingAfterHours = seconds % secondsPerHour;
    const minutes = Math.floor(remainingAfterHours / secondsPerMinute);
    return `${String(hours)}h ${String(minutes)}m`;
  }
  if (seconds >= secondsPerMinute) {
    const minutes = Math.floor(seconds / secondsPerMinute);
    const secs = seconds % secondsPerMinute;
    return `${String(minutes)}m ${String(secs)}s`;
  }
  return `${String(seconds)}s`;
};

/**
 * Computes the time remaining for an exploration in progress.
 * Uses endsAt (server-computed) when available to avoid client/server clock drift.
 * Falls back to startedAt + durationSeconds for saves predating the endsAt field.
 * @param endsAt - The server-computed completion timestamp, if available.
 * @param startedAt - The timestamp when exploration started.
 * @param durationSeconds - The total duration in seconds.
 * @returns The remaining seconds.
 */
const timeRemaining = (
  endsAt: number | undefined,
  startedAt: number,
  durationSeconds: number,
): number => {
  if (endsAt !== undefined) {
    return Math.max(0, (endsAt - Date.now()) / 1000);
  }
  const elapsed = (Date.now() - startedAt) / 1000;
  return Math.max(0, durationSeconds - elapsed);
};

interface CollectResult {
  areaId:   string;
  response: ExploreCollectResponse;
}

/**
 * Renders the exploration panel for managing area explorations.
 * @returns The JSX element.
 */
const ExplorationPanel = (): JSX.Element => {
  const { state, startExploration, collectExploration, formatNumber }
    = useGame();
  const [ activeZoneId, setActiveZoneId ] = useState(() => {
    return sessionStorage.getItem("elysium_explore_zone") ?? "verdant_vale";
  });
  const [ pendingAreaId, setPendingAreaId ] = useState<string | null>(null);
  const [ lastResult, setLastResult ] = useState<CollectResult | null>(null);
  const [ claimableAreaIds, setClaimableAreaIds ]
    = useState<ReadonlySet<string>>(new Set());

  const stateReference = useRef(state);
  stateReference.current = state;

  const claimableReference = useRef(claimableAreaIds);
  claimableReference.current = claimableAreaIds;

  useEffect(() => {
    const pollClaimable = async(): Promise<void> => {
      const currentState = stateReference.current;
      if (currentState === null) {
        return;
      }
      const inProgressArea = currentState.exploration?.areas.find((a) => {
        return a.status === "in_progress";
      });
      if (inProgressArea === undefined) {
        return;
      }
      if (claimableReference.current.has(inProgressArea.id)) {
        return;
      }
      const areaData = EXPLORATION_AREAS.find((a) => {
        return a.id === inProgressArea.id;
      });
      if (areaData === undefined) {
        return;
      }
      const remaining = timeRemaining(
        inProgressArea.endsAt,
        inProgressArea.startedAt ?? 0,
        areaData.durationSeconds,
      );
      if (remaining > 0) {
        return;
      }
      const result: ExploreClaimableResponse
        = await checkExplorationClaimable(inProgressArea.id);
      if (result.claimable) {
        setClaimableAreaIds((previous) => {
          return new Set([ ...previous, inProgressArea.id ]);
        });
      }
    };

    const intervalId = setInterval(() => {
      void pollClaimable();
    }, 1000);

    return (): void => {
      clearInterval(intervalId);
    };
  }, []);

  if (state === null) {
    return (
      <section className="panel">
        <p>{"Loading..."}</p>
      </section>
    );
  }

  const { zones, exploration: explorationState, bosses, quests } = state;

  const activeZone = zones.find((zone) => {
    return zone.id === activeZoneId;
  });
  const zoneIsLocked = activeZone?.status === "locked";
  const unlockBoss = activeZone?.unlockBossId === null
    || activeZone?.unlockBossId === undefined
    ? undefined
    : bosses.find((boss) => {
      return boss.id === activeZone.unlockBossId;
    });
  const unlockQuest = activeZone?.unlockQuestId === null
    || activeZone?.unlockQuestId === undefined
    ? undefined
    : quests.find((quest) => {
      return quest.id === activeZone.unlockQuestId;
    });

  const zoneAreas = EXPLORATION_AREAS.filter((area) => {
    return area.zoneId === activeZoneId;
  });

  const hasActiveExploration
    = explorationState?.areas.some((area) => {
      return area.status === "in_progress";
    }) ?? false;

  async function handleStart(areaId: string): Promise<void> {
    setPendingAreaId(areaId);
    try {
      await startExploration(areaId);
    } finally {
      setPendingAreaId(null);
    }
  }

  async function handleCollect(areaId: string): Promise<void> {
    setPendingAreaId(areaId);
    try {
      const result = await collectExploration(areaId);
      setLastResult({ areaId: areaId, response: result });
      setClaimableAreaIds((previous) => {
        const next = new Set(previous);
        next.delete(areaId);
        return next;
      });
    } finally {
      setPendingAreaId(null);
    }
  }

  function handleDismissResult(): void {
    setLastResult(null);
  }

  function handleZoneSelect(id: string): void {
    setActiveZoneId(id);
    setLastResult(null);
    sessionStorage.setItem("elysium_explore_zone", id);
  }

  const goldChange = lastResult?.response.event?.goldChange ?? 0;
  const essenceChange = lastResult?.response.event?.essenceChange ?? 0;

  return (
    <section className="panel exploration-panel">
      <div className="panel-header">
        <h2>{"🗺️ Exploration"}</h2>
      </div>

      {lastResult === null
        ? null
        : <div className="exploration-result">
          <button
            className="exploration-result-close"
            onClick={handleDismissResult}
            type="button"
          >
            {"✕"}
          </button>
          {lastResult.response.foundNothing
            ? <p className="exploration-nothing">
              {lastResult.response.nothingMessage}
            </p>
            : <>
              {lastResult.response.event === null
                ? null
                : <p className="exploration-event-text">
                  {lastResult.response.event.text}
                </p>
              }
              <div className="exploration-rewards">
                {goldChange !== 0
                  && <span
                    className={`reward-tag ${goldChange > 0
                      ? ""
                      : "negative"}`}
                  >
                    {"🪙 "}
                    {goldChange > 0
                      ? "+"
                      : ""}
                    {formatNumber(goldChange)}
                    {" gold"}
                  </span>
                }
                {essenceChange > 0
                  && <span className="reward-tag">
                    {"✨ +"}
                    {formatNumber(essenceChange)}
                    {" essence"}
                  </span>
                }
                {lastResult.response.event?.materialGained !== null
                && lastResult.response.event?.materialGained !== undefined
                  ? <span className="reward-tag material-tag">
                    {"📦 +"}
                    {lastResult.response.event.materialGained.quantity}{" "}
                    {/* eslint-disable-next-line stylistic/max-len -- long property chain cannot be shortened */}
                    {lastResult.response.event.materialGained.materialId.replaceAll(
                      "_",
                      " ",
                    )}
                    {" (event)"}
                  </span>
                  : null}
                {lastResult.response.materialsFound.map((foundMaterial) => {
                  return (
                    <span
                      className="reward-tag material-tag"
                      key={foundMaterial.materialId}
                    >
                      {"📦 +"}
                      {foundMaterial.quantity}{" "}
                      {foundMaterial.materialId.replaceAll("_", " ")}
                    </span>
                  );
                })}
              </div>
            </>
          }
        </div>
      }

      <ZoneSelector
        activeZoneId={activeZoneId}
        onSelectZone={handleZoneSelect}
        zones={zones}
      />

      {zoneIsLocked && (unlockBoss !== undefined || unlockQuest !== undefined)
        ? <div className="exploration-zone-locked-hint">
          <p>{"🔒 This zone is locked. Unlock exploration by:"}</p>
          {unlockBoss === undefined
            ? null
            : <p>
              {"⚔️ Defeat: "}
              {unlockBoss.name}
            </p>
          }
          {unlockQuest === undefined
            ? null
            : <p>
              {"📜 Complete: "}
              {unlockQuest.name}
            </p>
          }
        </div>
        : null
      }

      <div className="exploration-list">
        {zoneAreas.map((area) => {
          const areaState = explorationState?.areas.find((explorationArea) => {
            return explorationArea.id === area.id;
          });
          const status = areaState?.status ?? "locked";
          const startedAt = areaState?.startedAt ?? 0;
          const endsAt = areaState?.endsAt;
          const isReady
            = status === "in_progress"
            && claimableAreaIds.has(area.id);
          const isPending = pendingAreaId === area.id;

          function handleStartClick(): void {
            void handleStart(area.id);
          }
          function handleCollectClick(): void {
            void handleCollect(area.id);
          }

          return (
            <div
              className={`exploration-card exploration-${status}`}
              key={area.id}
            >
              <img
                alt={area.name}
                className="card-thumbnail"
                src={cdnImage("explorations", area.id)}
              />
              <div className="exploration-info">
                <h3>
                  {area.name}
                  {areaState?.completedOnce === true
                    ? <span className="exploration-discovered">{" 📖"}</span>
                    : null}
                </h3>
                <p>{area.description}</p>
                <span className="exploration-duration">
                  {"⏱️ "}
                  {formatDuration(area.durationSeconds)}
                </span>
              </div>
              <div className="exploration-action">
                {status === "locked"
                  && <span className="quest-badge locked">{"🔒 Locked"}</span>
                }
                {status === "available"
                  && <button
                    className="start-quest-button"
                    disabled={isPending || hasActiveExploration}
                    onClick={handleStartClick}
                    title={
                      hasActiveExploration
                        ? "An exploration is already in progress"
                        : undefined
                    }
                    type="button"
                  >
                    {isPending
                      ? "Departing..."
                      : `Explore (${formatDuration(area.durationSeconds)})`}
                  </button>
                }
                {status === "in_progress" && !isReady
                  && <span className="quest-badge active">
                    {"⏳ "}
                    {/* eslint-disable-next-line stylistic/max-len -- long call cannot be shortened */}
                    {formatDuration(Math.ceil(timeRemaining(endsAt, startedAt, area.durationSeconds)))}
                    {" remaining"}
                  </span>
                }
                {status === "in_progress" && isReady
                  ? <button
                    className="collect-button"
                    disabled={isPending}
                    onClick={handleCollectClick}
                    type="button"
                  >
                    {isPending
                      ? "Collecting..."
                      : "📦 Collect Results"}
                  </button>
                  : null}
              </div>
            </div>
          );
        })}
        {zoneAreas.length === 0
          && <p className="empty-zone">
            {"No exploration areas in this zone."}
          </p>
        }
      </div>
    </section>
  );
};

export { ExplorationPanel };
