import type { BattleBond, UnitData } from '../data/scenario';

export type BattleObjectiveSnapshot = {
  id: string;
  label: string;
  achieved: boolean;
  detail: string;
  rewardGold: number;
};

export type CampBondSnapshot = BattleBond & {
  battleExp: number;
};

export type CampMvpSnapshot = {
  unitId: string;
  name: string;
  damageDealt: number;
  defeats: number;
};

export type FirstBattleReport = {
  battleId: string;
  battleTitle: string;
  outcome: 'victory' | 'defeat';
  turnNumber: number;
  rewardGold: number;
  defeatedEnemies: number;
  totalEnemies: number;
  objectives: BattleObjectiveSnapshot[];
  units: UnitData[];
  bonds: CampBondSnapshot[];
  mvp?: CampMvpSnapshot;
  itemRewards: string[];
  completedCampDialogues: string[];
  createdAt: string;
};

export type CampaignStep =
  | 'new'
  | 'prologue'
  | 'first-battle'
  | 'first-camp'
  | 'first-victory-story'
  | 'second-battle'
  | 'second-camp'
  | 'third-battle'
  | 'third-camp'
  | 'fourth-battle'
  | 'fourth-camp'
  | 'fifth-battle'
  | 'fifth-camp'
  | 'sixth-battle'
  | 'sixth-camp'
  | 'seventh-battle'
  | 'seventh-camp'
  | 'eighth-battle'
  | 'eighth-camp'
  | 'ninth-battle'
  | 'ninth-camp'
  | 'tenth-battle'
  | 'tenth-camp';

export type CampaignUnitProgressSnapshot = {
  unitId: string;
  name: string;
  level: number;
  exp: number;
  hp: number;
  maxHp: number;
  equipment: UnitData['equipment'];
};

export type CampaignBondProgressSnapshot = {
  id: string;
  title: string;
  level: number;
  exp: number;
  battleExp: number;
};

export type CampaignBattleSettlement = {
  battleId: string;
  battleTitle: string;
  outcome: FirstBattleReport['outcome'];
  rewardGold: number;
  itemRewards: string[];
  objectives: BattleObjectiveSnapshot[];
  units: CampaignUnitProgressSnapshot[];
  bonds: CampaignBondProgressSnapshot[];
  completedAt: string;
};

export type CampaignState = {
  version: 1;
  updatedAt: string;
  step: CampaignStep;
  activeSaveSlot: number;
  gold: number;
  roster: UnitData[];
  bonds: CampBondSnapshot[];
  inventory: Record<string, number>;
  selectedSortieUnitIds: string[];
  completedCampDialogues: string[];
  battleHistory: Record<string, CampaignBattleSettlement>;
  latestBattleId?: string;
  firstBattleReport?: FirstBattleReport;
};

export const campaignStorageKey = 'heros-web:campaign-state';
export const campaignSaveSlotCount = 3;

const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: CampaignStep }> = {
  'first-battle-zhuo-commandery': { victory: 'first-camp', retry: 'first-battle' },
  'second-battle-yellow-turban-pursuit': { victory: 'second-camp', retry: 'second-battle' },
  'third-battle-guangzong-road': { victory: 'third-camp', retry: 'third-battle' },
  'fourth-battle-guangzong-camp': { victory: 'fourth-camp', retry: 'fourth-battle' },
  'fifth-battle-sishui-vanguard': { victory: 'fifth-camp', retry: 'fifth-battle' },
  'sixth-battle-jieqiao-relief': { victory: 'sixth-camp', retry: 'sixth-battle' },
  'seventh-battle-xuzhou-rescue': { victory: 'seventh-camp', retry: 'seventh-battle' },
  'eighth-battle-xiaopei-supply-road': { victory: 'eighth-camp', retry: 'eighth-battle' },
  'ninth-battle-xuzhou-gate-night-raid': { victory: 'ninth-camp', retry: 'ninth-battle' },
  'tenth-battle-xuzhou-breakout': { victory: 'tenth-camp', retry: 'tenth-battle' }
};

export type CampaignSaveSlotSummary = {
  slot: number;
  exists: boolean;
  updatedAt?: string;
  step?: CampaignStep;
  gold?: number;
  battleTitle?: string;
};

let campaignState: CampaignState | undefined;

export function createInitialCampaignState(): CampaignState {
  return {
    version: 1,
    updatedAt: new Date().toISOString(),
    step: 'new',
    activeSaveSlot: 1,
    gold: 0,
    roster: [],
    bonds: [],
    inventory: {},
    selectedSortieUnitIds: [],
    completedCampDialogues: [],
    battleHistory: {}
  };
}

export function startNewCampaign() {
  const state = createInitialCampaignState();
  state.step = 'prologue';
  return setCampaignState(state);
}

export function getCampaignState() {
  return cloneCampaignState(ensureCampaignState());
}

export function setCampaignState(state: CampaignState) {
  campaignState = normalizeCampaignState(state);
  campaignState.updatedAt = new Date().toISOString();
  persistCampaignState(campaignState);
  return cloneCampaignState(campaignState);
}

export function loadCampaignState(slot?: number) {
  campaignState = readStoredCampaignState(slot) ?? readStoredCampaignState() ?? readLatestSlottedCampaignState() ?? createInitialCampaignState();
  return cloneCampaignState(campaignState);
}

export function saveCampaignState(state = ensureCampaignState(), slot = state.activeSaveSlot) {
  campaignState = normalizeCampaignState(state);
  campaignState.activeSaveSlot = normalizeSlot(slot);
  campaignState.updatedAt = new Date().toISOString();
  persistCampaignState(campaignState);
  return cloneCampaignState(campaignState);
}

export function resetCampaignState() {
  campaignState = createInitialCampaignState();
  tryStorage()?.removeItem(campaignStorageKey);
  for (let slot = 1; slot <= campaignSaveSlotCount; slot += 1) {
    tryStorage()?.removeItem(campaignSaveSlotKey(slot));
  }
  return cloneCampaignState(campaignState);
}

export function hasCampaignSave() {
  return Boolean(tryStorage()?.getItem(campaignStorageKey) ?? readLatestSlottedCampaignState());
}

export function listCampaignSaveSlots(): CampaignSaveSlotSummary[] {
  return Array.from({ length: campaignSaveSlotCount }, (_, index) => {
    const slot = index + 1;
    const state = readStoredCampaignState(slot);
    if (!state) {
      return { slot, exists: false };
    }
    return {
      slot,
      exists: true,
      updatedAt: state.updatedAt,
      step: state.step,
      gold: state.gold,
      battleTitle: state.firstBattleReport?.battleTitle
    };
  });
}

export function markCampaignStep(step: CampaignStep) {
  const state = ensureCampaignState();
  state.step = step;
  state.updatedAt = new Date().toISOString();
  persistCampaignState(state);
  return cloneCampaignState(state);
}

export function setFirstBattleReport(report: FirstBattleReport) {
  const state = ensureCampaignState();
  const reportClone = cloneReport(report);
  const battleId = reportClone.battleId;
  const previousSettlement = state.battleHistory[battleId];
  const completedCampDialogues =
    battleId === 'first-battle-zhuo-commandery' ? [...reportClone.completedCampDialogues] : [...state.completedCampDialogues];

  reportClone.completedCampDialogues = completedCampDialogues;
  state.firstBattleReport = reportClone;
  const stepRule = campaignBattleSteps[battleId] ?? campaignBattleSteps['first-battle-zhuo-commandery'];
  const victoryStep: CampaignStep = stepRule.victory;
  const retryStep: CampaignStep = stepRule.retry;
  state.step = reportClone.outcome === 'victory' ? victoryStep : retryStep;
  state.gold = Math.max(0, state.gold - (previousSettlement?.rewardGold ?? 0) + reportClone.rewardGold);
  state.roster = mergeRosterProgress(state.roster, reportClone.units.filter((unit) => unit.faction === 'ally'));
  state.bonds = reportClone.bonds.map(cloneBondSnapshot);
  state.completedCampDialogues = completedCampDialogues;
  state.inventory = applyRewardDelta(state.inventory, previousSettlement?.itemRewards ?? [], -1);
  state.inventory = applyRewardDelta(state.inventory, reportClone.itemRewards, 1);
  state.battleHistory[battleId] = createBattleSettlement(reportClone);
  state.latestBattleId = battleId;
  state.updatedAt = new Date().toISOString();

  persistCampaignState(state);
}

export function getFirstBattleReport() {
  const report = ensureCampaignState().firstBattleReport;
  return report ? cloneReport(report) : undefined;
}

export function applyCampBondExp(dialogueId: string, bondId: string, amount: number) {
  const state = ensureCampaignState();
  if (!state.firstBattleReport) {
    return undefined;
  }

  if (!state.completedCampDialogues.includes(dialogueId)) {
    state.completedCampDialogues.push(dialogueId);
  }
  if (!state.firstBattleReport.completedCampDialogues.includes(dialogueId)) {
    state.firstBattleReport.completedCampDialogues.push(dialogueId);
  }

  const campaignBond = state.bonds.find((candidate) => candidate.id === bondId);
  const reportBond = state.firstBattleReport.bonds.find((candidate) => candidate.id === bondId);
  if (!campaignBond || !reportBond) {
    return undefined;
  }

  advanceBond(campaignBond, amount);
  reportBond.level = campaignBond.level;
  reportBond.exp = campaignBond.exp;
  reportBond.battleExp = campaignBond.battleExp;
  state.updatedAt = new Date().toISOString();

  persistCampaignState(state);
  return cloneReport(state.firstBattleReport);
}

export function ensureCampaignRosterUnits(units: UnitData[], bonds: BattleBond[] = []) {
  const state = ensureCampaignState();
  let changed = false;
  const existingUnitIds = new Set(state.roster.map((unit) => unit.id));

  units.forEach((unit) => {
    if (existingUnitIds.has(unit.id)) {
      return;
    }
    state.roster.push(cloneUnit(unit));
    existingUnitIds.add(unit.id);
    changed = true;
  });

  const existingBondIds = new Set(state.bonds.map((bond) => bond.id));
  bonds.forEach((bond) => {
    if (existingBondIds.has(bond.id)) {
      return;
    }
    state.bonds.push(createCampBondSnapshot(bond));
    existingBondIds.add(bond.id);
    changed = true;
  });

  if (state.firstBattleReport) {
    const reportUnitIds = new Set(state.firstBattleReport.units.map((unit) => unit.id));
    units.forEach((unit) => {
      if (reportUnitIds.has(unit.id)) {
        return;
      }
      state.firstBattleReport?.units.push(cloneUnit(unit));
      reportUnitIds.add(unit.id);
      changed = true;
    });

    const reportBondIds = new Set(state.firstBattleReport.bonds.map((bond) => bond.id));
    bonds.forEach((bond) => {
      if (reportBondIds.has(bond.id)) {
        return;
      }
      state.firstBattleReport?.bonds.push(createCampBondSnapshot(bond));
      reportBondIds.add(bond.id);
      changed = true;
    });
  }

  if (changed) {
    state.updatedAt = new Date().toISOString();
    persistCampaignState(state);
  }

  return cloneCampaignState(state);
}

function ensureCampaignState() {
  if (!campaignState) {
    campaignState = readStoredCampaignState() ?? createInitialCampaignState();
  }
  return campaignState;
}

function normalizeCampaignState(state: CampaignState): CampaignState {
  const normalized = cloneCampaignState(state);
  normalized.version = 1;
  normalized.updatedAt = normalized.updatedAt || new Date().toISOString();
  normalized.activeSaveSlot = normalizeSlot(normalized.activeSaveSlot);
  normalized.gold = normalized.gold ?? 0;
  normalized.roster = (normalized.roster ?? []).map(cloneUnit);
  normalized.bonds = (normalized.bonds ?? []).map(cloneBondSnapshot);
  normalized.completedCampDialogues = [...new Set(normalized.completedCampDialogues ?? [])];
  normalized.inventory = { ...(normalized.inventory ?? {}) };
  normalized.selectedSortieUnitIds = [...new Set(normalized.selectedSortieUnitIds ?? [])];
  normalized.battleHistory = normalized.battleHistory ?? {};
  if (normalized.firstBattleReport) {
    normalized.firstBattleReport = cloneReport(normalized.firstBattleReport);
  }
  return normalized;
}

function readStoredCampaignState(slot?: number) {
  const raw = tryStorage()?.getItem(slot ? campaignSaveSlotKey(slot) : campaignStorageKey);
  if (!raw) {
    return undefined;
  }

  try {
    const parsed = JSON.parse(raw) as CampaignState;
    if (!parsed || parsed.version !== 1) {
      return undefined;
    }
    return normalizeCampaignState(parsed);
  } catch {
    return undefined;
  }
}

function persistCampaignState(state: CampaignState) {
  const storage = tryStorage();
  storage?.setItem(campaignStorageKey, JSON.stringify(state));
  storage?.setItem(campaignSaveSlotKey(state.activeSaveSlot), JSON.stringify(state));
}

function tryStorage() {
  if (typeof window === 'undefined') {
    return undefined;
  }
  return window.localStorage;
}

function campaignSaveSlotKey(slot: number) {
  return `${campaignStorageKey}:slot-${normalizeSlot(slot)}`;
}

function normalizeSlot(slot: number | undefined) {
  if (!slot || Number.isNaN(slot)) {
    return 1;
  }
  return Math.min(campaignSaveSlotCount, Math.max(1, Math.floor(slot)));
}

function readLatestSlottedCampaignState() {
  const latest = listCampaignSaveSlots()
    .filter((slot) => slot.exists && slot.updatedAt)
    .sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))[0];
  return latest ? readStoredCampaignState(latest.slot) : undefined;
}

function advanceBond(bond: CampBondSnapshot, amount: number) {
  bond.battleExp += amount;
  bond.exp += amount;
  while (bond.exp >= 100) {
    bond.exp -= 100;
    bond.level = Math.min(100, bond.level + 1);
  }
}

function createBattleSettlement(report: FirstBattleReport): CampaignBattleSettlement {
  return {
    battleId: report.battleId,
    battleTitle: report.battleTitle,
    outcome: report.outcome,
    rewardGold: report.rewardGold,
    itemRewards: [...report.itemRewards],
    objectives: report.objectives.map((objective) => ({ ...objective })),
    units: report.units
      .filter((unit) => unit.faction === 'ally')
      .map((unit) => ({
        unitId: unit.id,
        name: unit.name,
        level: unit.level,
        exp: unit.exp,
        hp: unit.hp,
        maxHp: unit.maxHp,
        equipment: cloneUnit(unit).equipment
      })),
    bonds: report.bonds.map((bond) => ({
      id: bond.id,
      title: bond.title,
      level: bond.level,
      exp: bond.exp,
      battleExp: bond.battleExp
    })),
    completedAt: report.createdAt
  };
}

function applyRewardDelta(inventory: Record<string, number>, rewards: string[], direction: 1 | -1) {
  return rewards.reduce<Record<string, number>>((nextInventory, reward) => {
    const { label, amount } = parseRewardLabel(reward);
    nextInventory[label] = Math.max(0, (nextInventory[label] ?? 0) + amount * direction);
    if (nextInventory[label] === 0) {
      delete nextInventory[label];
    }
    return nextInventory;
  }, { ...inventory });
}

function mergeRosterProgress(currentRoster: UnitData[], deployedUnits: UnitData[]) {
  const merged = new Map<string, UnitData>();
  currentRoster.forEach((unit) => merged.set(unit.id, cloneUnit(unit)));
  deployedUnits.forEach((unit) => merged.set(unit.id, cloneUnit(unit)));
  return Array.from(merged.values());
}

function parseRewardLabel(reward: string) {
  const normalized = reward.replace(/\s+/g, ' ').trim();
  const match = normalized.match(/^(.*?)(?:\s*[+xX]?\s*(\d+))$/);

  if (!match) {
    return { label: normalized, amount: 1 };
  }

  return {
    label: match[1].trim() || normalized,
    amount: Number(match[2] ?? 1)
  };
}

function cloneCampaignState(state: CampaignState): CampaignState {
  return JSON.parse(JSON.stringify(state)) as CampaignState;
}

function cloneReport(report: FirstBattleReport): FirstBattleReport {
  return JSON.parse(JSON.stringify(report)) as FirstBattleReport;
}

function cloneUnit(unit: UnitData): UnitData {
  return JSON.parse(JSON.stringify(unit)) as UnitData;
}

function cloneBondSnapshot(bond: CampBondSnapshot): CampBondSnapshot {
  return {
    ...bond,
    unitIds: [...bond.unitIds] as [string, string]
  };
}

function createCampBondSnapshot(bond: BattleBond): CampBondSnapshot {
  return {
    ...bond,
    unitIds: [...bond.unitIds] as [string, string],
    battleExp: 0
  };
}
