import { addCargoClamped, removeCargo, upsertCargo } from "./sliceEconomy";
import { getNextObjective, withCompletedObjective } from "./sliceObjectives";
import { getSlicePoiTarget, getSystemIdFromPoiId, SLICE_BELT, SLICE_BELT_POSITION, SLICE_OPEN_SPACE_POSITION, SLICE_STATION, SLICE_STATION_POSITION } from "./sliceWorld";
import type { GameSliceMode, GameSliceSession, LegacyGameSliceMode, SliceCargoItem, SliceEvent, SliceFittedModule, SliceOperation, SliceTarget, SliceVec3 } from "./types";

const STORAGE_PREFIX = "void-slice.gameSession.";

const DEFAULT_SKILLS: GameSliceSession["skills"] = {
  Mining: { level: 0, xp: 0, nextLevel: 100 },
  Industry: { level: 0, xp: 0, nextLevel: 100 },
  Trade: { level: 0, xp: 0, nextLevel: 100 },
  Gunnery: { level: 0, xp: 0, nextLevel: 100 },
  Navigation: { level: 0, xp: 0, nextLevel: 100 },
};

export const DEFAULT_SLICE_MODULES: SliceFittedModule[] = [
  { id: "laser1", name: "Mining Laser I", type: "mining", slot: "high", cpu: 30, power: 40, active: false },
];

const storageKey = (id: string) => `${STORAGE_PREFIX}${id}`;

const safeLocalStorage = () => {
  if (typeof window === "undefined") return null;
  try {
    return window.localStorage;
  } catch {
    return null;
  }
};

const createGameSliceSessionId = () => {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return `slice-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
};

const eventId = () => `evt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;

function migrateMode(mode: LegacyGameSliceMode | string | undefined): GameSliceMode {
  if (mode === "map" || mode === "warp") return "travel";
  if (mode === "market" || mode === "fitting" || mode === "refining") return "services";
  if (
    mode === "station" ||
    mode === "undocking" ||
    mode === "flight" ||
    mode === "travel" ||
    mode === "mining" ||
    mode === "docking" ||
    mode === "services" ||
    mode === "combat"
  ) {
    return mode;
  }
  return "station";
}

function positionForPoi(poiId: string | null | undefined): SliceVec3 {
  if (poiId === SLICE_BELT.poiId) return SLICE_BELT_POSITION;
  if (poiId === SLICE_STATION.poiId) return SLICE_STATION_POSITION;
  return SLICE_OPEN_SPACE_POSITION;
}

function targetForPoi(poiId: string | null | undefined, fallbackName?: string, systemId = "sol"): SliceTarget | null {
  const known = getSlicePoiTarget(poiId);
  if (known) return known;
  if (!poiId) return null;
  const inferredSystemId = getSystemIdFromPoiId(poiId, systemId);
  return {
    id: poiId,
    name: fallbackName ?? poiId,
    kind: poiId.includes("-station-") ? "station" : poiId.includes("-belt-") ? "asteroid_belt" : "manual",
    systemId: inferredSystemId,
    poiId,
    position: positionForPoi(poiId),
  };
}

function migrateOperation(operation: SliceOperation | null | undefined, sessionLike: Partial<GameSliceSession>): SliceOperation | null {
  if (!operation) return null;
  if (operation.kind === "travel") {
    const target = operation.target ?? targetForPoi(operation.targetPoiId, operation.targetPoiName, operation.targetSystemId) ?? {
      id: operation.targetPoiId,
      name: operation.targetPoiName,
      kind: "manual",
      systemId: operation.targetSystemId,
      poiId: operation.targetPoiId,
      position: positionForPoi(operation.targetPoiId),
    };
    return {
      ...operation,
      target,
      fromPosition: operation.fromPosition ?? sessionLike.shipPosition ?? positionForPoi(sessionLike.currentPoiId),
      targetPosition: operation.targetPosition ?? target.position,
    };
  }
  if (operation.kind === "mining") {
    return {
      ...operation,
      targetPoiId: operation.targetPoiId ?? SLICE_BELT.poiId,
      targetName: operation.targetName ?? SLICE_BELT.name,
      repeat: operation.repeat ?? false,
    };
  }
  return operation;
}

export function createGameSliceSession(args: Partial<GameSliceSession> = {}): GameSliceSession {
  const completedObjectives = args.completedObjectives ?? [];
  const hasCurrentPoi = Object.prototype.hasOwnProperty.call(args, "currentPoiId");
  const hasDockedStationPoi = Object.prototype.hasOwnProperty.call(args, "dockedStationPoiId");
  const hasDockedStationName = Object.prototype.hasOwnProperty.call(args, "dockedStationName");
  const currentPoiId = hasCurrentPoi ? args.currentPoiId ?? null : "sol-station-0";
  const dockedStationPoiId = hasDockedStationPoi ? args.dockedStationPoiId ?? null : "sol-station-0";
  const dockedStationName = hasDockedStationName ? args.dockedStationName ?? null : "Jita IV - Moon 4";
  const shipPosition = args.shipPosition ?? positionForPoi(currentPoiId);
  const activeOperation = migrateOperation(args.activeOperation, { ...args, currentPoiId, shipPosition });
  return {
    id: args.id ?? createGameSliceSessionId(),
    mode: migrateMode(args.mode),
    currentSystemId: args.currentSystemId ?? "sol",
    currentPoiId,
    dockedStationPoiId,
    dockedStationName,
    activeTravelSessionId: args.activeTravelSessionId ?? null,
    activeService: args.activeService ?? null,
    activeOperation,
    selectedTarget: args.selectedTarget ?? null,
    shipPosition,
    navigationMessage: args.navigationMessage ?? null,
    wallet: args.wallet ?? 25000,
    cargoCapacity: args.cargoCapacity ?? 2500,
    cargo: args.cargo ?? [],
    fittedModules: args.fittedModules ?? DEFAULT_SLICE_MODULES,
    zoraModules: args.zoraModules ?? ["comms"],
    skills: { ...DEFAULT_SKILLS, ...(args.skills ?? {}) },
    completedObjectives,
    activeObjectiveId: args.activeObjectiveId ?? getNextObjective(completedObjectives),
    eventLog: args.eventLog ?? [],
    updatedAt: args.updatedAt ?? Date.now(),
  };
}

export function loadGameSliceSession(id: string): GameSliceSession | null {
  const storage = safeLocalStorage();
  if (!storage) return null;
  try {
    const raw = storage.getItem(storageKey(id));
    if (!raw) return null;
    const parsed = JSON.parse(raw) as GameSliceSession;
    if (!parsed || parsed.id !== id || !parsed.currentSystemId) return null;
    return createGameSliceSession({
      ...parsed,
      currentPoiId: parsed.currentPoiId ?? null,
      dockedStationPoiId: parsed.dockedStationPoiId ?? null,
      dockedStationName: parsed.dockedStationName ?? null,
      activeTravelSessionId: parsed.activeTravelSessionId ?? null,
      activeService: parsed.activeService ?? null,
      activeOperation: parsed.activeOperation ?? null,
      selectedTarget: parsed.selectedTarget ?? null,
      shipPosition: parsed.shipPosition ?? positionForPoi(parsed.currentPoiId ?? null),
      navigationMessage: parsed.navigationMessage ?? null,
      cargo: parsed.cargo ?? [],
      fittedModules: parsed.fittedModules ?? [],
      zoraModules: parsed.zoraModules ?? ["comms"],
      skills: parsed.skills ?? DEFAULT_SKILLS,
      completedObjectives: parsed.completedObjectives ?? [],
      eventLog: parsed.eventLog ?? [],
      updatedAt: parsed.updatedAt ?? Date.now(),
    });
  } catch {
    return null;
  }
}

export function saveGameSliceSession(session: GameSliceSession): void {
  const storage = safeLocalStorage();
  if (!storage) return;
  storage.setItem(storageKey(session.id), JSON.stringify({ ...session, updatedAt: Date.now() }));
}

export function getGameSliceSessionIdFromUrl(): string | null {
  if (typeof window === "undefined") return null;
  return new URLSearchParams(window.location.search).get("sliceSession");
}

export function withGameSliceSession(url: string, id: string): string {
  const base = typeof window === "undefined" ? "http://void-slice.local" : window.location.origin;
  const next = new URL(url, base);
  next.searchParams.set("sliceSession", id);
  return `${next.pathname}${next.search}${next.hash}`;
}

export function resetGameSliceSession(id: string): GameSliceSession {
  const session = createGameSliceSession({ id });
  saveGameSliceSession(session);
  return session;
}

function awardXp(session: GameSliceSession, skill: string, amount: number): GameSliceSession {
  const current = session.skills[skill] ?? { level: 0, xp: 0, nextLevel: 100 };
  let xp = current.xp + amount;
  let level = current.level;
  let nextLevel = current.nextLevel;
  while (level < 5 && xp >= nextLevel) {
    xp -= nextLevel;
    level += 1;
    nextLevel = Math.round(nextLevel * 2.4);
  }
  return {
    ...session,
    skills: { ...session.skills, [skill]: { level, xp, nextLevel } },
  };
}

function messageForEvent(event: SliceEvent): string {
  switch (event.type) {
    case "station.undockStarted":
      return "Undocking sequence started.";
    case "station.dockStarted":
      return `Docking approach started for ${event.stationName}.`;
    case "navigation.started":
      return `Set course to ${event.targetPoiName}.`;
    case "navigation.targetSelected":
      return `Selected ${event.target.name}.`;
    case "navigation.manualWaypointSelected":
      return `Selected manual waypoint ${event.target.name}.`;
    case "navigation.approachStarted":
      return `Approaching ${event.target.name}.`;
    case "navigation.arrived":
      return `Arrived at ${event.poiName}.`;
    case "station.docked":
      return `Docked at ${event.stationName}.`;
    case "station.undocked":
      return `Undocked from station in ${event.systemId}.`;
    case "mining.started":
      return `Mining laser cycling on ${event.ore}.`;
    case "mining.completed":
      return `Mined ${event.quantity.toLocaleString()} ${event.ore}.`;
    case "mining.stopped":
      return "Mining lasers stopped.";
    case "station.serviceOpened":
      return `${event.service[0].toUpperCase()}${event.service.slice(1)} service opened.`;
    case "station.serviceClosed":
      return "Station service closed.";
    case "refining.completed":
      return `Refined ${event.ore} into ${event.minerals.map((item) => `${item.quantity.toLocaleString()} ${item.item}`).join(", ")}.`;
    case "market.sold":
      return `Sold ${event.quantity.toLocaleString()} ${event.item} for ${event.isk.toLocaleString()} ISK.`;
    case "fitting.changed":
      return `Fitting updated with ${event.modules.length} module${event.modules.length === 1 ? "" : "s"}.`;
    case "combat.started":
      return "Combat trial started.";
    case "combat.victory":
      return `Destroyed ${event.targetName}; bounty paid ${event.bounty.toLocaleString()} ISK.`;
    case "xp.awarded":
      return `${event.skill} gained ${event.amount} XP.`;
    case "zora.observed":
      return `Zora observed ${event.trigger}.`;
  }
}

function appendEvent(session: GameSliceSession, event: SliceEvent): GameSliceSession {
  return {
    ...session,
    eventLog: [
      { id: eventId(), at: Date.now(), event, message: messageForEvent(event) },
      ...session.eventLog,
    ].slice(0, 80),
    updatedAt: Date.now(),
  };
}

export function applySliceEvent(session: GameSliceSession, event: SliceEvent): GameSliceSession {
  let next: GameSliceSession = { ...session };

  switch (event.type) {
    case "station.undockStarted":
      next = {
        ...next,
        mode: "undocking",
        activeService: null,
        activeOperation: { kind: "undocking", startedAt: event.startedAt, durationMs: event.durationMs },
      };
      break;
    case "station.dockStarted":
      next = {
        ...next,
        mode: "docking",
        activeService: null,
        activeOperation: { kind: "docking", stationPoiId: event.stationPoiId, stationName: event.stationName, startedAt: event.startedAt, durationMs: event.durationMs },
      };
      break;
    case "navigation.started":
      {
        const target = targetForPoi(event.targetPoiId, event.targetPoiName, event.targetSystemId) ?? {
          id: event.targetPoiId,
          name: event.targetPoiName,
          kind: "manual",
          systemId: event.targetSystemId,
          poiId: event.targetPoiId,
          position: positionForPoi(event.targetPoiId),
        };
        next = {
          ...next,
          mode: "travel",
          activeService: null,
          activeOperation: {
            kind: "travel",
            targetSystemId: event.targetSystemId,
            targetPoiId: event.targetPoiId,
            targetPoiName: event.targetPoiName,
            target,
            fromPosition: next.shipPosition,
            targetPosition: target.position,
            startedAt: event.startedAt,
            durationMs: event.durationMs,
          },
          dockedStationPoiId: null,
          dockedStationName: null,
          selectedTarget: target,
          navigationMessage: `Approaching ${target.name}`,
        };
      }
      break;
    case "navigation.targetSelected":
      next = {
        ...next,
        selectedTarget: event.target,
        navigationMessage: `Selected ${event.target.name}`,
      };
      break;
    case "navigation.manualWaypointSelected":
      next = {
        ...next,
        selectedTarget: event.target,
        navigationMessage: `Selected ${event.target.name}`,
      };
      break;
    case "navigation.approachStarted":
      next = {
        ...next,
        mode: "travel",
        activeService: null,
        activeOperation: {
          kind: "travel",
          targetSystemId: event.target.systemId,
          targetPoiId: event.target.poiId ?? event.target.id,
          targetPoiName: event.target.name,
          target: event.target,
          fromPosition: event.fromPosition,
          targetPosition: event.target.position,
          startedAt: event.startedAt,
          durationMs: event.durationMs,
        },
        dockedStationPoiId: null,
        dockedStationName: null,
        selectedTarget: event.target,
        navigationMessage: `Approaching ${event.target.name}`,
      };
      break;
    case "navigation.arrived":
      {
        const arrivedTarget = next.activeOperation?.kind === "travel"
          ? next.activeOperation.target
          : targetForPoi(event.poiId, event.poiName, event.systemId);
        const currentPoiId = arrivedTarget?.poiId ?? null;
        const shipPosition = arrivedTarget?.position ?? positionForPoi(currentPoiId);
        next = {
          ...next,
          mode: "flight",
          currentSystemId: event.systemId,
          currentPoiId,
          shipPosition,
          selectedTarget: arrivedTarget,
          navigationMessage: `Holding at ${event.poiName}.`,
          dockedStationPoiId: null,
          dockedStationName: null,
          activeOperation: null,
        };
        if (currentPoiId?.includes("belt")) next = withCompletedObjective(next, "navigate_to_belt");
      }
      break;
    case "station.docked":
      {
        const dockTarget = next.selectedTarget?.poiId === event.stationPoiId
          ? next.selectedTarget
          : targetForPoi(event.stationPoiId, event.stationName, event.systemId);
        next = {
          ...next,
          currentSystemId: event.systemId,
          currentPoiId: event.stationPoiId,
          shipPosition: dockTarget?.position ?? positionForPoi(event.stationPoiId),
          selectedTarget: dockTarget,
          navigationMessage: `Docked at ${event.stationName}.`,
          mode: "station",
          dockedStationPoiId: event.stationPoiId,
          dockedStationName: event.stationName,
          activeService: null,
          activeOperation: null,
        };
      }
      next = withCompletedObjective(next, "dock_at_station");
      break;
    case "station.undocked":
      {
        const currentTarget = next.selectedTarget?.poiId === next.currentPoiId
          ? next.selectedTarget
          : targetForPoi(next.currentPoiId, undefined, next.currentSystemId);
        next = withCompletedObjective({
          ...next,
          mode: "flight",
          currentSystemId: event.systemId,
          dockedStationPoiId: null,
          dockedStationName: null,
          activeService: null,
          activeOperation: null,
          shipPosition: currentTarget?.position ?? positionForPoi(next.currentPoiId),
          selectedTarget: null,
          navigationMessage: "Undocked. Select a local target.",
        }, "undock");
      }
      break;
    case "mining.started":
      {
        const miningTarget = next.selectedTarget?.poiId === event.targetPoiId
          ? next.selectedTarget
          : targetForPoi(event.targetPoiId, event.targetName, next.currentSystemId);
        next = {
          ...next,
          mode: "mining",
          activeService: null,
          currentPoiId: event.targetPoiId,
          shipPosition: miningTarget?.position ?? positionForPoi(event.targetPoiId),
          selectedTarget: miningTarget,
          navigationMessage: `Mining ${event.targetName}.`,
          activeOperation: {
            kind: "mining",
            ore: event.ore,
            quantity: event.quantity,
            targetPoiId: event.targetPoiId,
            targetName: event.targetName,
            repeat: event.repeat,
            startedAt: event.startedAt,
            durationMs: event.durationMs,
          },
        };
      }
      break;
    case "mining.completed": {
      const added = addCargoClamped(next, { item: event.ore, category: "ore", quantity: event.quantity, unitPrice: 12 });
      next = withCompletedObjective({ ...added.session, mode: "flight", activeOperation: null, navigationMessage: `Cargo received: ${event.quantity.toLocaleString()} ${event.ore}.` }, "mine_ore");
      break;
    }
    case "mining.stopped":
      next = { ...next, mode: "flight", activeOperation: null, navigationMessage: "Mining stopped." };
      break;
    case "station.serviceOpened":
      next = { ...next, mode: "services", activeService: event.service, activeOperation: null };
      break;
    case "station.serviceClosed":
      next = { ...next, mode: next.dockedStationPoiId ? "station" : "flight", activeService: null };
      break;
    case "refining.completed":
      next = withCompletedObjective({
        ...next,
        mode: "services",
        activeService: "refining",
        activeOperation: null,
        cargo: event.minerals.reduce((cargo, mineral) => upsertCargo(cargo, mineral), removeCargo(next.cargo, event.ore, event.inputQuantity)),
      }, "refine_ore");
      break;
    case "market.sold":
      next = withCompletedObjective({
        ...next,
        mode: "services",
        activeService: "market",
        activeOperation: null,
        wallet: next.wallet + event.isk,
        cargo: removeCargo(next.cargo, event.item, event.quantity),
      }, "sell_goods");
      break;
    case "fitting.changed": {
      const zoraModules = event.modules.reduce((ids, module) => {
        const id = module.name === "Comms Uplink" ? "comms" : module.name === "Market Scanner" ? "trade" : module.name === "Event Logger" ? "memory" : null;
        return id && !ids.includes(id) ? [...ids, id] : ids;
      }, next.zoraModules);
      next = withCompletedObjective({ ...next, mode: "services", activeService: "fitting", activeOperation: null, fittedModules: event.modules, zoraModules }, "fit_module");
      break;
    }
    case "combat.started":
      next = { ...next, mode: "combat", activeService: null, activeOperation: null };
      break;
    case "combat.victory":
      next = next.completedObjectives.includes("combat_trial")
        ? { ...next, mode: "combat", activeService: null, activeOperation: null }
        : withCompletedObjective({
            ...next,
            mode: "combat",
            activeService: null,
            activeOperation: null,
            wallet: next.wallet + event.bounty,
            cargo: event.loot.reduce((cargo, item) => upsertCargo(cargo, item), next.cargo),
          }, "combat_trial");
      break;
    case "xp.awarded":
      next = awardXp(next, event.skill, event.amount);
      break;
    case "zora.observed":
      next = { ...next };
      break;
  }

  return appendEvent(next, event);
}
