/**
 * Healing Object — a Magical Object that gives health to Characters.
 *
 * Invariants enforced at construction:
 * - Health is non-negative
 * - Health never exceeds maxHealth
 */

import { Character } from './Character.ts';

export type ObjectStatus = { kind: 'alive' } | { kind: 'destroyed' };

export class HealingObject {
  readonly #health: number;
  readonly #maxHealth: number;
  readonly #status: ObjectStatus;

  private constructor(health: number, maxHealth: number, status: ObjectStatus) {
    this.#health = health;
    this.#maxHealth = maxHealth;
    this.#status = status;
  }

  static create({
    maxHealth,
    currentHealth,
  }: {
    maxHealth: number;
    currentHealth: number;
  }): HealingObject {
    if (maxHealth < 0) throw new Error('MaxHealth cannot be negative');
    if (currentHealth < 0) throw new Error('CurrentHealth cannot be negative');
    if (currentHealth > maxHealth) throw new Error('CurrentHealth cannot exceed maxHealth');
    const status =
      currentHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
    return new HealingObject(currentHealth, maxHealth, status);
  }

  get health(): number {
    return this.#health;
  }

  get maxHealth(): number {
    return this.#maxHealth;
  }

  get status(): ObjectStatus {
    return this.#status;
  }

  /** Use this object to heal a character. Returns updated object and character. */
  heal(character: Character, amount: number): { object: HealingObject; character: Character } {
    // Destroyed objects can't heal
    if (this.#status.kind === 'destroyed') {
      return { object: this, character };
    }
    // Negative amount is invalid
    if (amount < 0) throw new Error('Heal amount must be non-negative');
    // Calculate actual heal amount: min of requested, object remaining, character headroom
    const objectRemaining = this.#health;
    const characterMax = character.level.value >= 6 ? 1500 : 1000;
    const characterHeadroom = characterMax - character.health.value;
    const actualHeal = Math.min(amount, objectRemaining, characterHeadroom);
    // If actualHeal is 0, nothing changes
    if (actualHeal === 0) {
      return { object: this, character };
    }
    // Create updated object
    const newObjectHealth = this.#health - actualHeal;
    const newObjectStatus =
      newObjectHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
    // Create updated character
    const newCharacterHealth = character.health.value + actualHeal;
    const newCharacter = Character.createWithHealth({
      name: character.name,
      level: character.level,
      health: newCharacterHealth,
    });
    return {
      object: new HealingObject(newObjectHealth, this.#maxHealth, newObjectStatus),
      character: newCharacter,
    };
  }
}
