import React, { useState, useEffect, useRef, useMemo } from 'react';
import * as shared from '@ttrpg/shared';
import { initializeApp } from './storage';
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from './storage';
import { getFirestore, doc, setDoc, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage, getStorageMode } from './storage';
import {
  PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown,
  UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
  Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon,
  StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText,
  Maximize2, Minimize2, Moon, Coffee
} from 'lucide-react';

// Custom CSS for death animation (player view only)
const deathAnimationStyles = `
  @keyframes death-dissolve {
    0% {
      opacity: 1;
      transform: scale(1) translateY(0);
    }
    50% {
      opacity: 0.5;
      transform: scale(0.95) translateY(-5px);
      filter: blur(2px);
    }
    100% {
      opacity: 0;
      transform: scale(0.8) translateY(-10px);
      filter: blur(4px);
    }
  }
  
  .animate-death-dissolve {
    animation: death-dissolve 2s ease-in-out forwards;
  }
`;

// Inject styles
if (typeof document !== 'undefined') {
  const styleElement = document.createElement('style');
  styleElement.innerHTML = deathAnimationStyles;
  document.head.appendChild(styleElement);
}

// ============================================================================
// CONSTANTS
// ============================================================================

const APP_VERSION = 'v0.3';
const { DEFAULT_MAX_HP, DEFAULT_INIT_MOD, MONSTER_DEFAULT_INIT_MOD, generateId, rollD20, formatInitMod, sortParticipantsByInitiative, syncTurnOrder, computeTurnOrderAfterRemoval } = shared;
const ROLL_DISPLAY_DURATION = 5000;

const CONDITIONS = [
  { id: 'alchemist_fire',      label: 'Alchemist Fire',      emoji: '🔥' },
  { id: 'bardic_inspiration',  label: 'Bardic Inspiration',  emoji: '🎵' },
  { id: 'blinded',             label: 'Blinded',             emoji: '🙈' },
  { id: 'charmed',             label: 'Charmed',             emoji: '💘' },
  { id: 'deafened',            label: 'Deafened',            emoji: '🔇' },
  { id: 'exhaustion',          label: 'Exhaustion',          emoji: '😴' },
  { id: 'frightened',          label: 'Frightened',          emoji: '😱' },
  { id: 'grappled',            label: 'Grappled',            emoji: '🤜' },
  { id: 'grazed',              label: 'Grazed',              emoji: '🩹' },
  { id: 'incapacitated',       label: 'Incapacitated',       emoji: '💫' },
  { id: 'invisible',           label: 'Invisible',           emoji: '👻' },
  { id: 'paralyzed',           label: 'Paralyzed',           emoji: '⚡' },
  { id: 'petrified',           label: 'Petrified',           emoji: '🗿' },
  { id: 'poisoned',            label: 'Poisoned',            emoji: '🤢' },
  { id: 'prone',               label: 'Prone',               emoji: '⬇️' },
  { id: 'restrained',          label: 'Restrained',          emoji: '🕸️' },
  { id: 'sapped',              label: 'Sapped',              emoji: '🔨' },
  { id: 'shield',              label: 'Shield',              emoji: '🛡️' },
  { id: 'slowed',              label: 'Slowed',              emoji: '🐌' },
  { id: 'stunned',             label: 'Stunned',             emoji: '💥' },
  { id: 'unconscious',         label: 'Unconscious',         emoji: '💤' },
  { id: 'vexed',               label: 'Vexed',               emoji: '🎯' },
];

// ============================================================================
// FIREBASE CONFIGURATION
// ============================================================================

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID
};

const APP_ID = process.env.REACT_APP_TRACKER_APP_ID || 'ttrpg-initiative-tracker-default';
const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;

let app;
let db;
let auth;
let storage;

const STORAGE_MODE = getStorageMode();

// Initialize storage backend. firebase mode = real SDK init.
// ws/memory mode = mock auth, no firebase.
const initializeStorage = () => {
  if (STORAGE_MODE === 'firebase') {
    const requiredKeys = ['apiKey', 'authDomain', 'projectId', 'appId'];
    const missingKeys = requiredKeys.filter(key => !firebaseConfig[key]);
    if (missingKeys.length > 0) {
      console.error(`CRITICAL: Missing Firebase config values: ${missingKeys.join(', ')}`);
      return false;
    }
    try {
      app = initializeApp(firebaseConfig);
      db = getFirestore(app);
      auth = getAuth(app);
      storage = getStorage();
      return true;
    } catch (error) {
      console.error("Error initializing Firebase:", error);
      return false;
    }
  }

  // ws / memory mode: stub auth so App's anon-sign-in path works.
  // db stays a truthy sentinel object so legacy `if (!db) return` guards pass;
  // all real reads/writes route through `storage.*`, never the SDK `db`.
  const FAKE_USER = { uid: 'local-user', isAnonymous: true };
  auth = {
    currentUser: FAKE_USER,
  };
  db = { __localStub: true };
  storage = getStorage();
  return true;
};

const isInitialized = initializeStorage();

// ============================================================================
// FIRESTORE PATH HELPERS
// ============================================================================

const getPath = {
  campaigns: () => `${PUBLIC_DATA_PATH}/campaigns`,
  campaign: (id) => `${PUBLIC_DATA_PATH}/campaigns/${id}`,
  encounters: (campaignId) => `${PUBLIC_DATA_PATH}/campaigns/${campaignId}/encounters`,
  encounter: (campaignId, encounterId) => `${PUBLIC_DATA_PATH}/campaigns/${campaignId}/encounters/${encounterId}`,
  activeDisplay: () => `${PUBLIC_DATA_PATH}/activeDisplay/status`,
  logs: () => `${PUBLIC_DATA_PATH}/logs`
};

// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================

// generateId, rollD20, formatInitMod, sortParticipantsByInitiative,
// computeTurnOrderAfterRemoval/Addition: imported from @ttrpg/shared (1-list model).

const LOG_QUERY = [orderBy('timestamp', 'desc'), limit(500)];

const logAction = async (message, context = {}, undoData = null) => {
  if (!db) return;
  try {
    const entry = { timestamp: Date.now(), message, ...context };
    if (undoData) entry.undo = undoData;
    await storage.addDoc(getPath.logs(), entry);
  } catch (err) {
    console.error('Error writing log:', err);
  }
};


// ============================================================================
// CUSTOM HOOKS
// ============================================================================

function useFirestoreDocument(docPath) {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!docPath) {
      setData(null);
      setIsLoading(false);
      setError("Document path not provided.");
      return;
    }

    setIsLoading(true);
    setError(null);

    const storage = getStorage();
    const unsubscribe = storage.subscribeDoc(docPath, (doc) => {
      setData(doc);
      setIsLoading(false);
    });
    return () => { if (typeof unsubscribe === 'function') unsubscribe(); };
  }, [docPath]);

  return { data, isLoading, error };
}

function useFirestoreCollection(collectionPath, queryConstraints = []) {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const queryString = useMemo(() => JSON.stringify(queryConstraints), [queryConstraints]);

  useEffect(() => {
    if (!collectionPath) {
      setData([]);
      setIsLoading(false);
      setError("Collection path not provided.");
      return;
    }

    setIsLoading(true);
    setError(null);

    const storage = getStorage();
    const unsubscribe = storage.subscribeCollection(collectionPath, (items) => {
      setData(items);
      setIsLoading(false);
    });
    return () => { if (typeof unsubscribe === 'function') unsubscribe(); };
    // queryString, not array ref
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [collectionPath, queryString]);

  return { data, isLoading, error };
}

// ============================================================================
// REUSABLE COMPONENTS
// ============================================================================

function Modal({ onClose, title, children }) {
  useEffect(() => {
    const handleEsc = (event) => {
      if (event.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', handleEsc);
    return () => window.removeEventListener('keydown', handleEsc);
  }, [onClose]);

  return (
    <div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
      <div className="bg-stone-900 p-6 rounded-lg shadow-xl w-full max-w-md">
        <div className="flex justify-between items-center mb-4">
          <h2 className="text-xl font-semibold text-amber-300 font-cinzel tracking-wide">{title}</h2>
          <button onClick={onClose} className="text-stone-400 hover:text-stone-200">
            <XCircle size={24} />
          </button>
        </div>
        {children}
      </div>
    </div>
  );
}

function ConfirmationModal({ isOpen, onClose, onConfirm, title, message }) {
  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
      <div className="bg-stone-900 p-6 rounded-lg shadow-xl w-full max-w-md">
        <div className="flex items-center mb-4">
          <AlertTriangle size={24} className="text-yellow-400 mr-3 flex-shrink-0" />
          <h2 className="text-xl font-semibold text-yellow-300">{title || "Confirm Action"}</h2>
        </div>
        <p className="text-stone-300 mb-6">{message || "Are you sure you want to proceed?"}</p>
        <div className="flex justify-end space-x-3">
          <button
            onClick={onClose}
            className="px-4 py-2 text-sm font-medium text-stone-300 bg-stone-700 hover:bg-stone-600 rounded-md transition-colors"
          >
            Cancel
          </button>
          <button
            onClick={onConfirm}
            className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors"
          >
            Confirm
          </button>
        </div>
      </div>
    </div>
  );
}

function LoadingSpinner({ message = "Loading..." }) {
  return (
    <div className="min-h-screen bg-stone-950 text-white flex flex-col items-center justify-center p-4">
      <div className="animate-spin rounded-full h-16 w-16 border-t-4 border-amber-500 border-solid"></div>
      <p className="mt-4 text-xl">{message}</p>
    </div>
  );
}

function ErrorDisplay({ message, critical = false }) {
  return (
    <div className={`min-h-screen ${critical ? 'bg-red-900' : 'bg-stone-950'} text-white flex flex-col items-center justify-center p-4`}>
      <h1 className="text-3xl font-bold mb-4">
        {critical ? 'Configuration Error' : 'Error'}
      </h1>
      <p className="text-xl text-center max-w-2xl">{message}</p>
    </div>
  );
}

// ============================================================================
// FORM COMPONENTS
// ============================================================================

function CreateCampaignForm({ onCreate, onCancel }) {
  const [name, setName] = useState('');
  const [backgroundUrl, setBackgroundUrl] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (name.trim()) {
      onCreate(name, backgroundUrl);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label htmlFor="campaignName" className="block text-sm font-medium text-stone-300">
          Campaign Name
        </label>
        <input
          type="text"
          id="campaignName"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
          required
        />
      </div>
      <div>
        <label htmlFor="backgroundUrl" className="block text-sm font-medium text-stone-300">
          Player Display Background URL (Optional)
        </label>
        <input
          type="url"
          id="backgroundUrl"
          value={backgroundUrl}
          onChange={(e) => setBackgroundUrl(e.target.value)}
          placeholder="https://example.com/image.jpg"
          className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
        />
      </div>
      <div className="flex justify-end space-x-3">
        <button
          type="button"
          onClick={onCancel}
          className="px-4 py-2 text-sm font-medium text-stone-300 bg-stone-700 hover:bg-stone-600 rounded-md transition-colors"
        >
          Cancel
        </button>
        <button
          type="submit"
          className="px-4 py-2 text-sm font-medium text-white bg-red-700 hover:bg-red-800 rounded-md transition-colors"
        >
          Create
        </button>
      </div>
    </form>
  );
}

function CreateEncounterForm({ onCreate, onCancel }) {
  const [name, setName] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (name.trim()) {
      onCreate(name);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label htmlFor="encounterName" className="block text-sm font-medium text-stone-300">
          Encounter Name
        </label>
        <input
          type="text"
          id="encounterName"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
          required
        />
      </div>
      <div className="flex justify-end space-x-3">
        <button
          type="button"
          onClick={onCancel}
          className="px-4 py-2 text-sm font-medium text-stone-300 bg-stone-700 hover:bg-stone-600 rounded-md transition-colors"
        >
          Cancel
        </button>
        <button
          type="submit"
          className="px-4 py-2 text-sm font-medium text-white bg-amber-700 hover:bg-amber-800 rounded-md transition-colors"
        >
          Create
        </button>
      </div>
    </form>
  );
}

function EditParticipantModal({ participant, onClose, onSave }) {
  const [name, setName] = useState(participant.name);
  const [initiative, setInitiative] = useState(participant.initiative);
  const [currentHp, setCurrentHp] = useState(participant.currentHp);
  const [maxHp, setMaxHp] = useState(participant.maxHp);
  const [isNpc, setIsNpc] = useState(participant.type === 'monster' ? (participant.isNpc || false) : false);

  const handleSubmit = (e) => {
    e.preventDefault();
    onSave({
      name: name.trim(),
      initiative: parseInt(initiative, 10),
      currentHp: parseInt(currentHp, 10),
      maxHp: parseInt(maxHp, 10),
      isNpc: participant.type === 'monster' ? isNpc : false,
    });
  };

  return (
    <Modal onClose={onClose} title={`Edit ${participant.name}`}>
      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="block text-sm font-medium text-stone-300">Name</label>
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
          />
        </div>
        <div>
          <label className="block text-sm font-medium text-stone-300">Initiative</label>
          <input
            type="number"
            value={initiative}
            onChange={(e) => setInitiative(e.target.value)}
            className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
          />
        </div>
        <div className="flex gap-4">
          <div className="flex-1">
            <label className="block text-sm font-medium text-stone-300">Current HP</label>
            <input
              type="number"
              value={currentHp}
              onChange={(e) => setCurrentHp(e.target.value)}
              className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
            />
          </div>
          <div className="flex-1">
            <label className="block text-sm font-medium text-stone-300">Max HP</label>
            <input
              type="number"
              value={maxHp}
              onChange={(e) => setMaxHp(e.target.value)}
              className="mt-1 block w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
            />
          </div>
        </div>
        {participant.type === 'monster' && (
          <div className="flex items-center">
            <input
              type="checkbox"
              id="editIsNpc"
              checked={isNpc}
              onChange={(e) => setIsNpc(e.target.checked)}
              className="h-4 w-4 text-violet-600 border-stone-400 rounded focus:ring-violet-500"
            />
            <label htmlFor="editIsNpc" className="ml-2 block text-sm text-stone-300">
              Is NPC?
            </label>
          </div>
        )}
        <div className="flex justify-end space-x-3 pt-2">
          <button
            type="button"
            onClick={onClose}
            className="px-4 py-2 text-sm font-medium text-stone-300 bg-stone-700 hover:bg-stone-600 rounded-md transition-colors"
          >
            Cancel
          </button>
          <button
            type="submit"
            className="px-4 py-2 text-sm font-medium text-white bg-amber-700 hover:bg-amber-800 rounded-md transition-colors"
          >
            <Save size={18} className="mr-1 inline-block" /> Save
          </button>
        </div>
      </form>
    </Modal>
  );
}

// ============================================================================
// CHARACTER MANAGER COMPONENT
// ============================================================================

function CharacterManager({ campaignId, campaignCharacters }) {
  const [characterName, setCharacterName] = useState('');
  const [defaultMaxHp, setDefaultMaxHp] = useState(DEFAULT_MAX_HP);
  const [defaultInitMod, setDefaultInitMod] = useState(DEFAULT_INIT_MOD);
  const [editingCharacter, setEditingCharacter] = useState(null);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [itemToDelete, setItemToDelete] = useState(null);
  const [isOpen, setIsOpen] = useState(true);

  const handleAddCharacter = async () => {
    if (!db || !characterName.trim() || !campaignId) return;

    const hp = parseInt(defaultMaxHp, 10);
    const initMod = parseInt(defaultInitMod, 10);

    if (isNaN(hp) || hp <= 0) {
      alert("Please enter a valid positive number for Default Max HP.");
      return;
    }
    if (isNaN(initMod)) {
      alert("Please enter a valid number for Default Initiative Modifier.");
      return;
    }

    const newCharacter = {
      id: generateId(),
      name: characterName.trim(),
      defaultMaxHp: hp,
      defaultInitMod: initMod
    };

    try {
      await storage.updateDoc(getPath.campaign(campaignId), {
        players: [...campaignCharacters, newCharacter]
      });
      setCharacterName('');
      setDefaultMaxHp(DEFAULT_MAX_HP);
      setDefaultInitMod(DEFAULT_INIT_MOD);
    } catch (err) {
      console.error("Error adding character:", err);
      alert("Failed to add character. Please try again.");
    }
  };

  const handleUpdateCharacter = async (characterId, newName, newDefaultMaxHp, newDefaultInitMod) => {
    if (!db || !newName.trim() || !campaignId) return;

    const hp = parseInt(newDefaultMaxHp, 10);
    const initMod = parseInt(newDefaultInitMod, 10);

    if (isNaN(hp) || hp <= 0) {
      alert("Please enter a valid positive number for Default Max HP.");
      setEditingCharacter(null);
      return;
    }
    if (isNaN(initMod)) {
      alert("Please enter a valid number for Default Initiative Modifier.");
      setEditingCharacter(null);
      return;
    }

    const updatedCharacters = campaignCharacters.map(c =>
      c.id === characterId
        ? { ...c, name: newName.trim(), defaultMaxHp: hp, defaultInitMod: initMod }
        : c
    );

    try {
      await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
      setEditingCharacter(null);
    } catch (err) {
      console.error("Error updating character:", err);
      alert("Failed to update character. Please try again.");
    }
  };

  const requestDeleteCharacter = (characterId, charName) => {
    setItemToDelete({ id: characterId, name: charName });
    setShowDeleteConfirm(true);
  };

  const confirmDeleteCharacter = async () => {
    if (!db || !itemToDelete) return;

    const updatedCharacters = campaignCharacters.filter(c => c.id !== itemToDelete.id);

    try {
      await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
    } catch (err) {
      console.error("Error deleting character:", err);
      alert("Failed to delete character. Please try again.");
    }

    setShowDeleteConfirm(false);
    setItemToDelete(null);
  };

  return (
    <>
      <div className="p-4 bg-stone-900 rounded-lg shadow">
        <div className="flex justify-between items-center mb-3">
          <h3 className="text-xl font-semibold text-amber-300 font-cinzel tracking-wide flex items-center">
            <Users size={24} className="mr-2" /> Campaign Characters
          </h3>
          <button
            onClick={() => setIsOpen(!isOpen)}
            className="p-1 text-stone-400 hover:text-stone-200"
            aria-label={isOpen ? "Collapse" : "Expand"}
          >
            {isOpen ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
          </button>
        </div>

        {isOpen && (
          <>
            <form onSubmit={(e) => { e.preventDefault(); handleAddCharacter(); }} className="grid grid-cols-1 sm:grid-cols-3 gap-2 mb-4 items-end">
              <div className="sm:col-span-1">
                <label htmlFor="characterName" className="block text-xs font-medium text-stone-400">
                  Name
                </label>
                <input
                  type="text"
                  id="characterName"
                  value={characterName}
                  onChange={(e) => setCharacterName(e.target.value)}
                  placeholder="Character name"
                  className="w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <div className="w-full sm:w-auto">
                <label htmlFor="defaultMaxHp" className="block text-xs font-medium text-stone-400">
                  Default HP
                </label>
                <input
                  type="number"
                  id="defaultMaxHp"
                  value={defaultMaxHp}
                  onChange={(e) => setDefaultMaxHp(e.target.value)}
                  className="w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <div className="w-full sm:w-auto">
                <label htmlFor="defaultInitMod" className="block text-xs font-medium text-stone-400">
                  Init Mod
                </label>
                <input
                  type="number"
                  id="defaultInitMod"
                  value={defaultInitMod}
                  onChange={(e) => setDefaultInitMod(e.target.value)}
                  className="w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <button
                type="submit"
                className="sm:col-span-3 sm:w-auto sm:justify-self-end px-4 py-2 text-sm font-medium text-white bg-amber-700 hover:bg-amber-800 rounded-md transition-colors flex items-center justify-center"
              >
                <PlusCircle size={18} className="mr-1" /> Add Character
              </button>
            </form>

            {campaignCharacters.length === 0 && (
              <p className="text-sm text-stone-400">No characters added yet.</p>
            )}

            <ul className="space-y-2">
              {campaignCharacters.map(character => (
                <li key={character.id} className="flex justify-between items-center p-3 bg-stone-800 rounded-md">
                  {editingCharacter && editingCharacter.id === character.id ? (
                    <form
                      onSubmit={(e) => {
                        e.preventDefault();
                        handleUpdateCharacter(
                          character.id,
                          editingCharacter.name,
                          editingCharacter.defaultMaxHp,
                          editingCharacter.defaultInitMod
                        );
                      }}
                      className="flex-grow flex flex-wrap gap-2 items-center"
                    >
                      <input
                        type="text"
                        value={editingCharacter.name}
                        onChange={(e) => setEditingCharacter({ ...editingCharacter, name: e.target.value })}
                        className="flex-grow min-w-[100px] px-2 py-1 bg-stone-700 border border-stone-600 rounded-md text-white"
                      />
                      <input
                        type="number"
                        value={editingCharacter.defaultMaxHp}
                        onChange={(e) => setEditingCharacter({ ...editingCharacter, defaultMaxHp: e.target.value })}
                        className="w-20 px-2 py-1 bg-stone-700 border border-stone-600 rounded-md text-white"
                        title="Default Max HP"
                      />
                      <input
                        type="number"
                        value={editingCharacter.defaultInitMod}
                        onChange={(e) => setEditingCharacter({ ...editingCharacter, defaultInitMod: e.target.value })}
                        className="w-20 px-2 py-1 bg-stone-700 border border-stone-600 rounded-md text-white"
                        title="Default Init Mod"
                      />
                      <button type="submit" className="p-1 text-green-400 hover:text-green-300">
                        <Save size={18} />
                      </button>
                      <button
                        type="button"
                        onClick={() => setEditingCharacter(null)}
                        className="p-1 text-stone-400 hover:text-stone-200"
                      >
                        <XCircle size={18} />
                      </button>
                    </form>
                  ) : (
                    <>
                      <span className="text-stone-100">
                        {character.name}{' '}
                        <span className="text-xs text-stone-400">
                          (HP: {character.defaultMaxHp || 'N/A'}, Init Mod: {formatInitMod(character.defaultInitMod)})
                        </span>
                      </span>
                      <div className="flex space-x-2">
                        <button
                          onClick={() => setEditingCharacter({
                            id: character.id,
                            name: character.name,
                            defaultMaxHp: character.defaultMaxHp || DEFAULT_MAX_HP,
                            defaultInitMod: character.defaultInitMod || DEFAULT_INIT_MOD
                          })}
                          className="p-1 rounded transition-colors text-yellow-400 hover:text-yellow-300 bg-stone-700 hover:bg-stone-600"
                          aria-label="Edit character"
                        >
                          <Edit3 size={18} />
                        </button>
                        <button
                          onClick={() => requestDeleteCharacter(character.id, character.name)}
                          className="p-1 rounded transition-colors text-red-400 hover:text-red-300 bg-stone-700 hover:bg-stone-600"
                          aria-label="Delete character"
                        >
                          <Trash2 size={18} />
                        </button>
                      </div>
                    </>
                  )}
                </li>
              ))}
            </ul>
          </>
        )}
      </div>

      <ConfirmationModal
        isOpen={showDeleteConfirm}
        onClose={() => setShowDeleteConfirm(false)}
        onConfirm={confirmDeleteCharacter}
        title="Delete Character?"
        message={`Are you sure you want to remove "${itemToDelete?.name}" from this campaign?`}
      />
    </>
  );
}

// ============================================================================
// PARTICIPANT MANAGER COMPONENT
// ============================================================================

function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
  const [participantName, setParticipantName] = useState('');
  const [participantType, setParticipantType] = useState('monster');
  const [selectedCharacterId, setSelectedCharacterId] = useState('');
  const [monsterInitMod, setMonsterInitMod] = useState(MONSTER_DEFAULT_INIT_MOD);
  const [maxHp, setMaxHp] = useState(DEFAULT_MAX_HP);
  const [isNpc, setIsNpc] = useState(false);
  const [editingParticipant, setEditingParticipant] = useState(null);
  const [hpChangeValues, setHpChangeValues] = useState({});
  const [draggedItemId, setDraggedItemId] = useState(null);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [itemToDelete, setItemToDelete] = useState(null);
  const [lastRollDetails, setLastRollDetails] = useState(null);
  const [openConditionsId, setOpenConditionsId] = useState(null);

  const participants = encounter.participants || [];

  useEffect(() => {
    if (participantType === 'character' && selectedCharacterId) {
      const selectedChar = campaignCharacters.find(c => c.id === selectedCharacterId);
      if (selectedChar && selectedChar.defaultMaxHp) {
        setMaxHp(selectedChar.defaultMaxHp);
      } else {
        setMaxHp(DEFAULT_MAX_HP);
      }
      setIsNpc(false);
    } else if (participantType === 'monster') {
      setMaxHp(DEFAULT_MAX_HP);
      setMonsterInitMod(MONSTER_DEFAULT_INIT_MOD);
    }
  }, [selectedCharacterId, participantType, campaignCharacters]);

  const handleAddParticipant = async () => {
    if (!db) return;
    if (participantType === 'monster' && !participantName.trim()) return;
    if (participantType === 'character' && !selectedCharacterId) return;

    let nameToAdd = participantName.trim();
    const initiativeRoll = rollD20();
    let modifier = 0;
    let currentMaxHp = parseInt(maxHp, 10) || DEFAULT_MAX_HP;
    let participantIsNpc = false;

    if (participantType === 'character') {
      const character = campaignCharacters.find(c => c.id === selectedCharacterId);
      if (!character) {
        console.error("Selected character not found");
        return;
      }
      if (participants.some(p => p.type === 'character' && p.originalCharacterId === selectedCharacterId)) {
        alert(`${character.name} is already in this encounter.`);
        return;
      }
      nameToAdd = character.name;
      currentMaxHp = character.defaultMaxHp || currentMaxHp;
      modifier = character.defaultInitMod || 0;
    } else {
      modifier = parseInt(monsterInitMod, 10) || 0;
      participantIsNpc = isNpc;
    }

    const finalInitiative = initiativeRoll + modifier;
    const newParticipant = {
      id: generateId(),
      name: nameToAdd,
      type: participantType,
      originalCharacterId: participantType === 'character' ? selectedCharacterId : null,
      initiative: finalInitiative,
      maxHp: currentMaxHp,
      currentHp: currentMaxHp,
      isNpc: participantType === 'monster' ? participantIsNpc : false,
      conditions: [],
      isActive: true,
      deathSaves: 0, // Track failed death saves (0-3)
      isDying: false, // For death animation on player display
    };

    try {
      await storage.updateDoc(encounterPath, {
        participants: [...participants, newParticipant]
      });
      logAction(`${nameToAdd} added to encounter (Initiative: ${finalInitiative})`, { encounterName: encounter.name }, {
        encounterPath,
        updates: { participants: [...participants] },
      });

      setLastRollDetails({
        name: nameToAdd,
        roll: initiativeRoll,
        mod: modifier,
        total: finalInitiative,
        type: participantIsNpc ? 'NPC' : participantType
      });
      setTimeout(() => setLastRollDetails(null), ROLL_DISPLAY_DURATION);

      // Reset form
      setParticipantName('');
      setMaxHp(DEFAULT_MAX_HP);
      setSelectedCharacterId('');
      setMonsterInitMod(MONSTER_DEFAULT_INIT_MOD);
      setIsNpc(false);
    } catch (err) {
      console.error("Error adding participant:", err);
      alert("Failed to add participant. Please try again.");
    }
  };

  const handleAddAllCampaignCharacters = async () => {
    if (!db || !campaignCharacters || campaignCharacters.length === 0) return;

    const existingCharacterIds = participants
      .filter(p => p.type === 'character' && p.originalCharacterId)
      .map(p => p.originalCharacterId);

    const newParticipants = campaignCharacters
      .filter(char => !existingCharacterIds.includes(char.id))
      .map(char => {
        const initiativeRoll = rollD20();
        const modifier = char.defaultInitMod || 0;
        const finalInitiative = initiativeRoll + modifier;

        return {
          id: generateId(),
          name: char.name,
          type: 'character',
          originalCharacterId: char.id,
          initiative: finalInitiative,
          maxHp: char.defaultMaxHp || DEFAULT_MAX_HP,
          currentHp: char.defaultMaxHp || DEFAULT_MAX_HP,
          conditions: [],
          isActive: true,
          isNpc: false,
          deathSaves: 0,
          isDying: false,
        };
      });

    if (newParticipants.length === 0) {
      alert("All campaign characters are already in this encounter.");
      return;
    }

    try {
      await storage.updateDoc(encounterPath, {
        participants: [...participants, ...newParticipants]
      });
      console.log(`Added ${newParticipants.length} characters to the encounter.`);
    } catch (err) {
      console.error("Error adding all campaign characters:", err);
      alert("Failed to add all characters. Please try again.");
    }
  };

  const handleUpdateParticipant = async (updatedData) => {
    if (!db || !editingParticipant) return;

    const updatedParticipants = participants.map(p =>
      p.id === editingParticipant.id ? { ...p, ...updatedData } : p
    );

    try {
      await storage.updateDoc(encounterPath, { participants: updatedParticipants });
      setEditingParticipant(null);
    } catch (err) {
      console.error("Error updating participant:", err);
      alert("Failed to update participant. Please try again.");
    }
  };

  const requestDeleteParticipant = (participantId, participantName) => {
    setItemToDelete({ id: participantId, name: participantName });
    setShowDeleteConfirm(true);
  };

  const confirmDeleteParticipant = async () => {
    if (!db || !itemToDelete) return;

    const updatedParticipants = participants.filter(p => p.id !== itemToDelete.id);
    const deleteUndoData = {
      encounterPath,
      updates: {
        participants: [...participants],
        ...(encounter.isStarted ? {
          currentTurnParticipantId: encounter.currentTurnParticipantId,
          turnOrderIds: [...(encounter.turnOrderIds || [])],
        } : {}),
      },
    };

    try {
      await storage.updateDoc(encounterPath, {
        participants: updatedParticipants,
        ...(encounter.isStarted ? {
          ...syncTurnOrder(updatedParticipants),
          ...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants),
        } : {}),
      });
      logAction(`${itemToDelete.name} removed from encounter`, { encounterName: encounter.name }, deleteUndoData);
    } catch (err) {
      console.error("Error deleting participant:", err);
      alert("Failed to delete participant. Please try again.");
    }

    setShowDeleteConfirm(false);
    setItemToDelete(null);
  };

  const toggleParticipantActive = async (participantId) => {
    if (!db) return;

    const participant = participants.find(p => p.id === participantId);
    if (!participant) return;
    const newIsActive = !participant.isActive;

    const updatedParticipants = participants.map(p =>
      p.id === participantId ? { ...p, isActive: newIsActive } : p
    );

    // 1-list: stay in slot, flip isActive only. Sync turnOrderIds. Advance
    // current only if deact hits current.
    let turnUpdates = {};
    if (encounter.isStarted) {
      turnUpdates = syncTurnOrder(updatedParticipants);
      if (!newIsActive && encounter.currentTurnParticipantId === participantId) {
        turnUpdates = { ...turnUpdates, ...computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants) };
      }
    }

    try {
      await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
      logAction(`${participant.name} ${newIsActive ? 'reactivated' : 'deactivated'}`, { encounterName: encounter.name }, {
        encounterPath,
        updates: {
          participants: [...participants],
          ...(encounter.isStarted ? {
            currentTurnParticipantId: encounter.currentTurnParticipantId,
            turnOrderIds: [...(encounter.turnOrderIds || [])],
          } : {}),
        },
      });
    } catch (err) {
      console.error("Error toggling active state:", err);
    }
  };

  const applyHpChange = async (participantId, changeType) => {
    if (!db) return;

    const amountStr = hpChangeValues[participantId];
    if (!amountStr || amountStr.trim() === '') return;

    const amount = parseInt(amountStr, 10);
    if (isNaN(amount) || amount === 0) {
      setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
      return;
    }

    const participant = participants.find(p => p.id === participantId);
    if (!participant) return;

    let newHp = participant.currentHp;
    if (changeType === 'damage') {
      newHp = Math.max(0, participant.currentHp - amount);
    } else if (changeType === 'heal') {
      newHp = Math.min(participant.maxHp, participant.currentHp + amount);
    }

    // Determine if participant died or was resurrected
    const wasDead = participant.currentHp === 0;
    const isDead = newHp === 0;
    const wasResurrected = wasDead && newHp > 0;

    const updatedParticipants = participants.map(p => {
      if (p.id === participantId) {
        const updates = { ...p, currentHp: newHp };
        
        // Handle death - deactivate and start death saves
        if (isDead && !wasDead) {
          updates.isActive = false;
          updates.deathSaves = p.deathSaves || 0;
          updates.isDying = false;
        }
        
        // Handle resurrection - reactivate and reset death saves
        if (wasResurrected) {
          updates.isActive = true;
          updates.deathSaves = 0;
          updates.isDying = false;
        }
        
        return updates;
      }
      return p;
    });

    const turnUpdates = {};

    const hpUndoData = {
      encounterPath,
      updates: {
        participants: [...participants],
        ...((isDead && !wasDead) || wasResurrected ? {
          currentTurnParticipantId: encounter.currentTurnParticipantId,
          turnOrderIds: [...(encounter.turnOrderIds || [])],
        } : {}),
      },
    };

    try {
      await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
      setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
      const hpLine = `${participant.currentHp} → ${newHp} HP`;
      const deathSuffix = (isDead && !wasDead) ? (participant.type === 'character' ? ' — Unconscious' : ' — Defeated') : '';
      const resurSuffix = wasResurrected ? ' — Revived' : '';
      if (changeType === 'damage') {
        logAction(`${participant.name} took ${amount} damage (${hpLine})${deathSuffix}`, { encounterName: encounter.name }, hpUndoData);
      } else {
        logAction(`${participant.name} healed for ${amount} (${hpLine})${resurSuffix}`, { encounterName: encounter.name }, hpUndoData);
      }
    } catch (err) {
      console.error("Error applying HP change:", err);
    }
  };

  const handleDeathSaveChange = async (participantId, saveNumber) => {
    if (!db) return;

    const participant = participants.find(p => p.id === participantId);
    if (!participant) return;

    const currentSaves = participant.deathSaves || 0;
    const newSaves = currentSaves === saveNumber ? saveNumber - 1 : saveNumber;

    // If clicking the third death save, mark as dying (for player view animation)
    if (newSaves === 3) {
      const updatedParticipants = participants.map(p =>
        p.id === participantId ? { ...p, deathSaves: newSaves, isDying: true } : p
      );

      try {
        await storage.updateDoc(encounterPath, { participants: updatedParticipants });
        
        // Wait for animation to complete on player display (2 seconds) then remove participant
        setTimeout(async () => {
          const finalParticipants = participants.filter(p => p.id !== participantId);
          try {
            await storage.updateDoc(encounterPath, { participants: finalParticipants });
          } catch (err) {
            console.error("Error removing dead participant:", err);
          }
        }, 2000);
      } catch (err) {
        console.error("Error marking participant as dying:", err);
      }
    } else {
      // Normal death save update
      const updatedParticipants = participants.map(p =>
        p.id === participantId ? { ...p, deathSaves: newSaves } : p
      );

      try {
        await storage.updateDoc(encounterPath, { participants: updatedParticipants });
      } catch (err) {
        console.error("Error updating death saves:", err);
      }
    }
  };

  const toggleCondition = async (participantId, conditionId) => {
    if (!db) return;
    const participant = participants.find(p => p.id === participantId);
    if (!participant) return;
    const wasActive = (participant.conditions || []).includes(conditionId);
    const updatedParticipants = participants.map(p => {
      if (p.id !== participantId) return p;
      const current = p.conditions || [];
      const next = wasActive
        ? current.filter(c => c !== conditionId)
        : [...current, conditionId];
      return { ...p, conditions: next };
    });
    try {
      await storage.updateDoc(encounterPath, { participants: updatedParticipants });
      const cond = CONDITIONS.find(c => c.id === conditionId);
      const condLabel = cond ? `${cond.label} ${cond.emoji}` : conditionId;
      logAction(`${participant.name} ${wasActive ? 'lost' : 'gained'} ${condLabel}`, { encounterName: encounter.name }, {
        encounterPath,
        updates: { participants: [...participants] },
      });
    } catch (err) {
      console.error("Error updating conditions:", err);
    }
  };

  const handleDragStart = (e, id) => {
    setDraggedItemId(id);
    e.dataTransfer.effectAllowed = 'move';
  };

  const handleDragOver = (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  };

  const handleDrop = async (e, targetId) => {
    e.preventDefault();

    if (!db || draggedItemId === null || draggedItemId === targetId) {
      setDraggedItemId(null);
      return;
    }

    const currentParticipants = [...participants];
    const draggedIndex = currentParticipants.findIndex(p => p.id === draggedItemId);
    const targetIndex = currentParticipants.findIndex(p => p.id === targetId);

    if (draggedIndex === -1 || targetIndex === -1) {
      console.error("Dragged or target item not found.");
      setDraggedItemId(null);
      return;
    }

    const draggedItem = currentParticipants[draggedIndex];
    const targetItem = currentParticipants[targetIndex];

    if (draggedItem.initiative !== targetItem.initiative) {
      console.log("Drag-drop only allowed for participants with same initiative.");
      setDraggedItemId(null);
      return;
    }

    const [removedItem] = currentParticipants.splice(draggedIndex, 1);
    currentParticipants.splice(targetIndex, 0, removedItem);

    try {
      await storage.updateDoc(encounterPath, { participants: currentParticipants });
    } catch (err) {
      console.error("Error reordering participants:", err);
    }

    setDraggedItemId(null);
  };

  // 1-list model: participants[] IS the display order. No re-sort.
  const sortedParticipants = participants;

  const initiativeGroups = participants.reduce((acc, p) => {
    acc[p.initiative] = (acc[p.initiative] || 0) + 1;
    return acc;
  }, {});
  const tiedInitiatives = Object.keys(initiativeGroups)
    .filter(init => initiativeGroups[init] > 1)
    .map(Number);

  return (
    <>
      <div className="p-3 bg-stone-900 rounded-md mt-4">
        <div className="flex justify-between items-center mb-3">
          <h4 className="text-lg font-medium text-amber-200 font-cinzel tracking-wide">Add Participants</h4>
          <button
            onClick={handleAddAllCampaignCharacters}
            className="px-3 py-1.5 text-xs font-medium text-white bg-violet-700 hover:bg-violet-800 rounded-md transition-colors flex items-center"
            disabled={!campaignCharacters || campaignCharacters.length === 0 || (encounter.isStarted && !encounter.isPaused)}
          >
            <Users2 size={16} className="mr-1.5" />
            <Dices size={16} className="mr-1.5" /> Add All (Roll Init)
          </button>
        </div>

        {/* Warning when combat is active */}
        {encounter.isStarted && !encounter.isPaused && (
          <div className="mb-4 p-3 bg-yellow-900 bg-opacity-30 border border-yellow-600 rounded-md flex items-start">
            <AlertTriangle size={20} className="text-yellow-400 mr-3 flex-shrink-0 mt-0.5" />
            <div>
              <p className="text-yellow-300 font-semibold text-sm">Combat is Running</p>
              <p className="text-yellow-200 text-xs mt-1">
                Pause combat to add or remove participants. The turn order will be recalculated when combat is resumed.
              </p>
            </div>
          </div>
        )}

        <form
          onSubmit={(e) => {
            e.preventDefault();
            handleAddParticipant();
          }}
          className="grid grid-cols-1 md:grid-cols-6 gap-x-4 gap-y-2 mb-4 p-3 bg-stone-800 rounded items-end"
        >
          <div className="md:col-span-2">
            <label className="block text-sm font-medium text-stone-300">Type</label>
            <select
              value={participantType}
              onChange={(e) => {
                setParticipantType(e.target.value);
                setSelectedCharacterId('');
                setIsNpc(false);
              }}
              className="mt-1 w-full px-3 py-2 bg-stone-700 border-stone-600 rounded text-white"
            >
              <option value="monster">Monster</option>
              <option value="character">Character</option>
            </select>
          </div>

          {participantType === 'monster' ? (
            <>
              <div className="md:col-span-4">
                <label htmlFor="monsterName" className="block text-sm font-medium text-stone-300">
                  Monster Name
                </label>
                <input
                  type="text"
                  id="monsterName"
                  value={participantName}
                  onChange={(e) => setParticipantName(e.target.value)}
                  placeholder="e.g., Dire Wolf"
                  className="mt-1 w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <div className="md:col-span-2">
                <label htmlFor="monsterInitMod" className="block text-sm font-medium text-stone-300">
                  Init Mod
                </label>
                <input
                  type="number"
                  id="monsterInitMod"
                  value={monsterInitMod}
                  onChange={(e) => setMonsterInitMod(e.target.value)}
                  className="mt-1 w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <div className="md:col-span-2">
                <label htmlFor="monsterMaxHp" className="block text-sm font-medium text-stone-300">
                  Max HP
                </label>
                <input
                  type="number"
                  id="monsterMaxHp"
                  value={maxHp}
                  onChange={(e) => setMaxHp(e.target.value)}
                  className="mt-1 w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
              <div className="md:col-span-2 flex items-center pt-5">
                <input
                  type="checkbox"
                  id="isNpc"
                  checked={isNpc}
                  onChange={(e) => setIsNpc(e.target.checked)}
                  className="h-4 w-4 text-violet-600 border-stone-400 rounded focus:ring-violet-500"
                />
                <label htmlFor="isNpc" className="ml-2 block text-sm text-stone-300">
                  Is NPC?
                </label>
              </div>
            </>
          ) : (
            <>
              <div className="md:col-span-4">
                <label className="block text-sm font-medium text-stone-300">Select Character</label>
                <select
                  value={selectedCharacterId}
                  onChange={(e) => setSelectedCharacterId(e.target.value)}
                  className="mt-1 w-full px-3 py-2 bg-stone-700 border-stone-600 rounded text-white"
                >
                  <option value="">-- Select from Campaign --</option>
                  {campaignCharacters.map(c => (
                    <option key={c.id} value={c.id}>
                      {c.name} (HP: {c.defaultMaxHp || 'N/A'}, Mod: {formatInitMod(c.defaultInitMod)})
                    </option>
                  ))}
                </select>
              </div>
              <div className="md:col-span-2">
                <label className="block text-sm font-medium text-stone-300">Max HP (Encounter)</label>
                <input
                  type="number"
                  value={maxHp}
                  onChange={(e) => setMaxHp(e.target.value)}
                  className="mt-1 w-full px-3 py-2 bg-stone-800 border border-stone-700 rounded-md shadow-sm focus:outline-none focus:ring-amber-600 focus:border-amber-600 sm:text-sm text-white"
                />
              </div>
            </>
          )}

          <div className="md:col-span-6 flex justify-end mt-2">
            <button
              type="submit"
              disabled={encounter.isStarted && !encounter.isPaused}
              className={`px-4 py-2 text-sm font-medium text-white rounded-md transition-colors flex items-center ${encounter.isStarted && !encounter.isPaused ? 'bg-stone-600 cursor-not-allowed opacity-50' : 'bg-red-700 hover:bg-red-800'}`}
              title={encounter.isStarted && !encounter.isPaused ? 'Pause combat to add participants' : 'Add participant and roll initiative'}
            >
              <Dices size={18} className="mr-1.5" /> Add to Encounter (Roll Init)
            </button>
          </div>
        </form>

        {lastRollDetails && (
          <p className="text-sm text-green-400 mt-2 mb-2 text-center">
            {lastRollDetails.name} ({lastRollDetails.type === 'character' ? 'Character' : lastRollDetails.type === 'monster' ? 'Monster' : lastRollDetails.type})
            : Rolled d20 ({lastRollDetails.roll}) {formatInitMod(lastRollDetails.mod)} = {lastRollDetails.total} Initiative
          </p>
        )}

        {participants.length === 0 && <p className="text-sm text-stone-400">No participants added yet.</p>}

        <ul className="space-y-2">
          {sortedParticipants.map((p) => {
            const isCurrentTurn = encounter.isStarted && p.id === encounter.currentTurnParticipantId;
            const isDraggable = (!encounter.isStarted || encounter.isPaused) && tiedInitiatives.includes(Number(p.initiative));
            const participantDisplayType = p.type === 'monster' ? (p.isNpc ? 'NPC' : 'Monster') : 'Character';

            let bgColor = p.type === 'character' ? 'bg-indigo-950' : (p.isNpc ? 'bg-stone-700' : 'bg-[#8e351c]');
            if (isCurrentTurn && !encounter.isPaused) bgColor = 'bg-green-600';

            const isDead = p.currentHp === 0;

            return (
              <li
                key={p.id}
                draggable={isDraggable}
                onDragStart={isDraggable ? (e) => handleDragStart(e, p.id) : undefined}
                onDragOver={isDraggable ? handleDragOver : undefined}
                onDrop={isDraggable ? (e) => handleDrop(e, p.id) : undefined}
                onDragEnd={() => setDraggedItemId(null)}
                className={`p-3 rounded-md flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2 transition-all duration-300 ${bgColor} ${isCurrentTurn && !encounter.isPaused ? 'ring-2 ring-green-300 shadow-lg' : ''} ${!p.isActive && !isDead ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : ''} ${draggedItemId === p.id ? 'opacity-50 ring-2 ring-yellow-400' : ''}`}
              >
                <div className="flex-1 flex items-start sm:items-center">
                  {isDraggable && (
                    <ChevronsUpDown
                      size={18}
                      className="mr-2 text-stone-400 flex-shrink-0"
                      title="Drag to reorder in tie"
                    />
                  )}
                  <div className="flex-1">
                    <p className="font-semibold text-lg text-white">
                      {isDead && <span className="mr-2">☠️</span>}
                      {p.name} <span className="text-xs">({participantDisplayType})</span>
                      {isCurrentTurn && !encounter.isPaused && (
                        <span className="ml-2 px-2 py-0.5 bg-yellow-400 text-black text-xs font-bold rounded-full inline-flex items-center">
                          <Zap size={12} className="mr-1" /> CURRENT
                        </span>
                      )}
                      {isDead && <span className="ml-2 text-xs text-red-300 font-semibold">{p.type === 'character' ? '(Unconscious)' : '(Dead)'}</span>}
                    </p>
                    <p className={`text-sm ${isCurrentTurn && !encounter.isPaused ? 'text-green-100' : 'text-stone-200'}`}>
                      Init: {p.initiative} | HP: {p.currentHp}/{p.maxHp}
                    </p>
                    
                    {/* Death Saves - only player characters make death saving throws */}
                    {isDead && encounter.isStarted && p.type === 'character' && (
                      <div className="mt-2 flex items-center space-x-2">
                        <span className="text-xs text-red-300 font-medium">Death Saves:</span>
                        {[1, 2, 3].map(saveNum => (
                          <button
                            key={saveNum}
                            onClick={() => handleDeathSaveChange(p.id, saveNum)}
                            className={`w-6 h-6 rounded border-2 transition-all ${(p.deathSaves || 0) >= saveNum ? 'bg-red-600 border-red-500' : 'bg-stone-800 border-stone-600 hover:border-red-400'} ${saveNum === 3 && (p.deathSaves || 0) === 3 ? 'animate-pulse' : ''}`}
                            title={`Death save ${saveNum}`}
                          >
                            {(p.deathSaves || 0) >= saveNum && <span className="text-white text-sm">✕</span>}
                          </button>
                        ))}
                      </div>
                    )}

                    {/* Active condition badges */}
                    {(p.conditions || []).length > 0 && (
                      <div className="mt-1 flex flex-wrap gap-1">
                        {(p.conditions || []).map(cId => {
                          const cond = CONDITIONS.find(c => c.id === cId);
                          return cond ? (
                            <span
                              key={cId}
                              className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-full bg-purple-900 border border-purple-600 text-xs text-purple-200 cursor-pointer hover:bg-purple-800"
                              title={`Remove ${cond.label}`}
                              onClick={() => toggleCondition(p.id, cId)}
                            >
                              {cond.emoji} {cond.label}
                            </span>
                          ) : null;
                        })}
                      </div>
                    )}

                    {/* Expandable conditions picker */}
                    {openConditionsId === p.id && (
                      <div className="mt-2 p-2 bg-stone-900 rounded-md border border-stone-600">
                        <p className="text-xs text-stone-400 mb-1 font-medium">Toggle Conditions</p>
                        <div className="flex flex-wrap gap-1">
                          {CONDITIONS.map(cond => {
                            const active = (p.conditions || []).includes(cond.id);
                            return (
                              <button
                                key={cond.id}
                                onClick={() => toggleCondition(p.id, cond.id)}
                                className={`px-2 py-1 rounded text-xs transition-colors ${active ? 'bg-purple-700 border border-purple-400 text-white' : 'bg-stone-700 border border-stone-500 text-stone-300 hover:bg-stone-600'}`}
                                title={cond.label}
                              >
                                {cond.emoji} {cond.label}
                              </button>
                            );
                          })}
                        </div>
                      </div>
                    )}
                  </div>
                </div>

                <div className="flex flex-wrap items-center space-x-2 mt-2 sm:mt-0">
                  {encounter.isStarted && (
                    <div className="flex items-center space-x-1 bg-stone-800 p-1 rounded-md">
                      <input
                        type="number"
                        placeholder="HP"
                        value={hpChangeValues[p.id] || ''}
                        onChange={(e) => setHpChangeValues(prev => ({ ...prev, [p.id]: e.target.value }))}
                        className="w-16 p-1 text-sm bg-stone-700 border border-stone-600 rounded-md text-white focus:ring-amber-600 focus:border-amber-600"
                        aria-label={`HP change for ${p.name}`}
                      />
                      {!isDead && (
                        <button
                          onClick={() => applyHpChange(p.id, 'damage')}
                          className="p-1 bg-red-500 hover:bg-red-600 text-white rounded-md text-xs"
                          title="Damage"
                        >
                          <HeartCrack size={16} />
                        </button>
                      )}
                      <button
                        onClick={() => applyHpChange(p.id, 'heal')}
                        className="p-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded-md text-xs"
                        title={isDead ? "Heal / Revive" : "Heal"}
                      >
                        <HeartPulse size={16} />
                      </button>
                    </div>
                  )}
                  {!isDead && (
                    <button
                      onClick={() => toggleParticipantActive(p.id)}
                      className={`p-1 rounded transition-colors ${p.isActive ? 'text-yellow-400 hover:text-yellow-300' : 'text-stone-400 hover:text-stone-300'} bg-stone-700 hover:bg-stone-600`}
                      title={p.isActive ? "Mark Inactive" : "Mark Active"}
                    >
                      {p.isActive ? <UserCheck size={18} /> : <UserX size={18} />}
                    </button>
                  )}
                  <button
                    onClick={() => setOpenConditionsId(openConditionsId === p.id ? null : p.id)}
                    className={`p-1 rounded transition-colors bg-stone-700 hover:bg-stone-600 ${openConditionsId === p.id || (p.conditions || []).length > 0 ? 'text-purple-400 hover:text-purple-300' : 'text-stone-400 hover:text-stone-300'}`}
                    title="Conditions"
                  >
                    <span className="text-base leading-none">✨</span>
                  </button>
                  <button
                    onClick={() => setEditingParticipant(p)}
                    className="p-1 rounded transition-colors text-yellow-400 hover:text-yellow-300 bg-stone-700 hover:bg-stone-600"
                    title="Edit"
                  >
                    <Edit3 size={18} />
                  </button>
                  <button
                    onClick={() => requestDeleteParticipant(p.id, p.name)}
                    className="p-1 rounded transition-colors text-red-400 hover:text-red-300 bg-stone-700 hover:bg-stone-600"
                    title="Remove"
                  >
                    <Trash2 size={18} />
                  </button>
                </div>
              </li>
            );
          })}
        </ul>

        {editingParticipant && (
          <EditParticipantModal
            participant={editingParticipant}
            onClose={() => setEditingParticipant(null)}
            onSave={handleUpdateParticipant}
          />
        )}
      </div>

      <ConfirmationModal
        isOpen={showDeleteConfirm}
        onClose={() => setShowDeleteConfirm(false)}
        onConfirm={confirmDeleteParticipant}
        title="Delete Participant?"
        message={`Are you sure you want to remove "${itemToDelete?.name}" from this encounter?`}
      />
    </>
  );
}

// ============================================================================
// INITIATIVE CONTROLS COMPONENT
// ============================================================================

function InitiativeControls({ campaignId, encounter, encounterPath }) {
  const [showEndConfirm, setShowEndConfirm] = useState(false);
  const { data: activeDisplayData } = useFirestoreDocument(getPath.activeDisplay());
  const hidePlayerHp = activeDisplayData?.hidePlayerHp ?? true;

  const handleToggleHidePlayerHp = async () => {
    if (!db) return;
    try {
      await storage.setDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp }, { merge: true });
    } catch (err) {
      console.error("Error toggling hidePlayerHp:", err);
    }
  };

  const handleStartEncounter = async () => {
    if (!db || !encounter.participants || encounter.participants.length === 0) {
      alert("Add participants first.");
      return;
    }

    const activeParticipants = encounter.participants.filter(p => p.isActive);
    if (activeParticipants.length === 0) {
      alert("No active participants.");
      return;
    }

    // 1-list model: sort ALL participants by init (active+inactive),
    // first active = current. Matches shared.startEncounter.
    const sortedParticipants = sortParticipantsByInitiative(encounter.participants, encounter.participants);
    const firstActive = sortedParticipants.find(p => p.isActive);

    try {
      await storage.updateDoc(encounterPath, {
        isStarted: true,
        isPaused: false,
        round: 1,
        participants: sortedParticipants,
        currentTurnParticipantId: firstActive.id,
        turnOrderIds: sortedParticipants.map(p => p.id)
      });

      await storage.setDoc(getPath.activeDisplay(), {
        activeCampaignId: campaignId,
        activeEncounterId: encounter.id
      }, { merge: true });

      logAction(`Combat started: "${encounter.name}" — ${sortedParticipants[0].name}'s turn (Round 1)`, { encounterName: encounter.name }, {
        encounterPath,
        updates: {
          isStarted: encounter.isStarted ?? false,
          isPaused: encounter.isPaused ?? false,
          round: encounter.round ?? 0,
          currentTurnParticipantId: encounter.currentTurnParticipantId ?? null,
          turnOrderIds: [...(encounter.turnOrderIds || [])],
        },
      });
      console.log("Encounter started and set as active display.");
    } catch (err) {
      console.error("Error starting encounter:", err);
      alert("Failed to start encounter. Please try again.");
    }
  };

  const handleTogglePause = async () => {
    if (!db || !encounter || !encounter.isStarted) return;

    const newPausedState = !encounter.isPaused;
    let newTurnOrderIds = encounter.turnOrderIds;

    if (!newPausedState && encounter.isPaused) {
      // 1-list model: no re-sort on resume. turnOrderIds already mirrors
      // participants[] (set at start/add/reorder). Resume = unpause only.
      newTurnOrderIds = encounter.turnOrderIds;
    }

    try {
      await storage.updateDoc(encounterPath, {
        isPaused: newPausedState,
        turnOrderIds: newTurnOrderIds
      });
      logAction(`Combat ${newPausedState ? 'paused' : 'resumed'}: "${encounter.name}"`, { encounterName: encounter.name }, {
        encounterPath,
        updates: {
          isPaused: encounter.isPaused ?? false,
          turnOrderIds: [...(encounter.turnOrderIds || [])],
        },
      });
    } catch (err) {
      console.error("Error toggling pause state:", err);
    }
  };

  const handleNextTurn = async () => {
    if (!db || !encounter.isStarted || encounter.isPaused) return;
    if (!encounter.currentTurnParticipantId || !encounter.turnOrderIds || encounter.turnOrderIds.length === 0) return;

    const activePsInOrder = encounter.turnOrderIds
      .map(id => encounter.participants.find(p => p.id === id && p.isActive))
      .filter(Boolean);

    if (activePsInOrder.length === 0) {
      alert("No active participants left.");
      await storage.updateDoc(encounterPath, {
        isStarted: false,
        isPaused: false,
        currentTurnParticipantId: null,
        round: encounter.round
      });
      return;
    }

    let currentIndex = activePsInOrder.findIndex(p => p.id === encounter.currentTurnParticipantId);
    let nextRound = encounter.round;

    // Current participant was removed; find the next one after their old position in turnOrderIds
    if (currentIndex === -1) {
      const rawPos = (encounter.turnOrderIds || []).indexOf(encounter.currentTurnParticipantId);
      const candidateIds = [...(encounter.turnOrderIds || []).slice(rawPos + 1), ...(encounter.turnOrderIds || []).slice(0, rawPos)];
      const nextP = candidateIds.map(id => activePsInOrder.find(p => p.id === id)).find(Boolean);
      currentIndex = nextP ? activePsInOrder.findIndex(p => p.id === nextP.id) - 1 : -1;
    }

    let nextIndex = (currentIndex + 1) % activePsInOrder.length;
    let newTurnOrderIds = encounter.turnOrderIds;

    if (nextIndex === 0 && currentIndex !== -1) {
      nextRound += 1;
      // Rebuild turn order by initiative at the start of each new round so that participants
      // activated mid-round (appended to the end) slot into proper initiative position next round.
      const activePs = encounter.participants.filter(p => p.isActive);
      const sorted = sortParticipantsByInitiative(activePs, encounter.participants);
      newTurnOrderIds = sorted.map(p => p.id);
    }

    // When wrapping to a new round the next participant is first in the rebuilt order
    const nextParticipant = (nextIndex === 0 && currentIndex !== -1)
      ? encounter.participants.find(p => p.id === newTurnOrderIds[0])
      : activePsInOrder[nextIndex];

    if (!nextParticipant) return;

    try {
      await storage.updateDoc(encounterPath, {
        currentTurnParticipantId: nextParticipant.id,
        round: nextRound,
        turnOrderIds: newTurnOrderIds,
      });
      logAction(`${nextParticipant.name}'s turn (Round ${nextRound})`, { encounterName: encounter.name }, {
        encounterPath,
        updates: {
          currentTurnParticipantId: encounter.currentTurnParticipantId,
          round: encounter.round,
          turnOrderIds: [...encounter.turnOrderIds],
        },
      });
    } catch (err) {
      console.error("Error advancing turn:", err);
    }
  };

  const confirmEndEncounter = async () => {
    if (!db) return;

    try {
      await storage.updateDoc(encounterPath, {
        isStarted: false,
        isPaused: false,
        currentTurnParticipantId: null,
        round: 0,
        turnOrderIds: []
      });

      await storage.setDoc(getPath.activeDisplay(), {
        activeCampaignId: null,
        activeEncounterId: null
      }, { merge: true });

      logAction(`Combat ended: "${encounter.name}"`, { encounterName: encounter.name }, {
        encounterPath,
        updates: {
          isStarted: encounter.isStarted ?? false,
          isPaused: encounter.isPaused ?? false,
          round: encounter.round ?? 0,
          currentTurnParticipantId: encounter.currentTurnParticipantId ?? null,
          turnOrderIds: [...(encounter.turnOrderIds || [])],
        },
      });
      console.log("Encounter ended and deactivated from Player Display.");
    } catch (err) {
      console.error("Error ending encounter:", err);
    }

    setShowEndConfirm(false);
  };

  if (!encounter || !encounter.participants) return null;

  return (
    <>
      <div className="lg:sticky lg:top-4 p-4 bg-stone-900 rounded-md shadow-lg">
        <h4 className="text-lg font-medium text-amber-200 mb-4 text-center font-cinzel tracking-wide">Combat Controls</h4>
        <div className="flex flex-col gap-3">
          {!encounter.isStarted ? (
            <button
              onClick={handleStartEncounter}
              className="w-full px-4 py-3 text-sm font-medium text-white bg-red-700 hover:bg-red-800 rounded-md transition-colors flex items-center justify-center"
              disabled={!encounter.participants || encounter.participants.filter(p => p.isActive).length === 0}
            >
              <PlayIcon size={18} className="mr-2" /> Start Combat
            </button>
          ) : (
            <>
              <button
                onClick={handleTogglePause}
                className={`w-full px-4 py-3 text-sm font-medium text-white rounded-md transition-colors flex items-center justify-center ${encounter.isPaused ? 'bg-red-700 hover:bg-red-800' : 'bg-amber-600 hover:bg-amber-700'}`}
                title={encounter.isPaused
                  ? 'Resume combat from the current turn. The initiative order will be recalculated to include any participants added while paused.'
                  : 'Pause combat to freeze the turn order. While paused, you can add or remove participants, adjust HP, and edit initiative values. The turn order will be recalculated when you resume.'}
              >
                {encounter.isPaused ? <PlayIcon size={18} className="mr-2" /> : <PauseIcon size={18} className="mr-2" />}
                {encounter.isPaused ? 'Resume Combat' : 'Pause Combat'}
              </button>
              <button
                onClick={handleNextTurn}
                className="w-full px-4 py-3 text-sm font-medium text-white bg-purple-700 hover:bg-purple-800 rounded-md transition-colors flex items-center justify-center"
                disabled={!encounter.currentTurnParticipantId || encounter.isPaused}
              >
                <SkipForwardIcon size={18} className="mr-2" /> Next Turn
              </button>
              <button
                onClick={() => setShowEndConfirm(true)}
                className="w-full px-4 py-3 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors flex items-center justify-center"
              >
                <StopCircleIcon size={18} className="mr-2" /> End Combat
              </button>
              
              {/* Round Counter */}
              <div className="mt-2 pt-3 border-t border-stone-700">
                <p className="text-center text-lg font-semibold text-amber-300 font-cinzel">Round: {encounter.round}</p>
                {encounter.isPaused && (
                  <p className="text-center text-sm text-yellow-400 font-semibold mt-1">(Paused)</p>
                )}
              </div>
            </>
          )}
        </div>

        {/* Display Settings */}
        <div className="mt-3 pt-3 border-t border-stone-700">
          <h5 className="text-xs font-semibold text-stone-400 uppercase tracking-wider mb-2">Player Display</h5>
          <label className="flex items-center justify-between cursor-pointer gap-2">
            <span className="text-sm text-stone-300">Hide player HP</span>
            <button
              role="switch"
              aria-checked={hidePlayerHp}
              onClick={handleToggleHidePlayerHp}
              className={`relative inline-flex h-5 w-9 flex-shrink-0 rounded-full border-2 border-transparent transition-colors focus:outline-none ${hidePlayerHp ? 'bg-amber-600' : 'bg-stone-600'}`}
            >
              <span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${hidePlayerHp ? 'translate-x-4' : 'translate-x-0'}`} />
            </button>
          </label>
        </div>
      </div>

      <ConfirmationModal
        isOpen={showEndConfirm}
        onClose={() => setShowEndConfirm(false)}
        onConfirm={confirmEndEncounter}
        title="End Encounter?"
        message="Are you sure you want to end this encounter? Initiative order will be reset and it will be removed from the Player Display."
      />
    </>
  );
}

// ============================================================================
// ENCOUNTER MANAGER COMPONENT
// ============================================================================

function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharacters }) {
  const { data: encountersData, isLoading: isLoadingEncounters } = useFirestoreCollection(
    campaignId ? getPath.encounters(campaignId) : null
  );
  const { data: activeDisplayInfo } = useFirestoreDocument(getPath.activeDisplay());

  const [encounters, setEncounters] = useState([]);
  const [selectedEncounterId, setSelectedEncounterId] = useState(null);
  const [showCreateModal, setShowCreateModal] = useState(false);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [itemToDelete, setItemToDelete] = useState(null);

  const selectedEncounterIdRef = useRef(selectedEncounterId);

  useEffect(() => {
    if (encountersData) setEncounters(encountersData);
  }, [encountersData]);

  useEffect(() => {
    selectedEncounterIdRef.current = selectedEncounterId;
  }, [selectedEncounterId]);

  useEffect(() => {
    if (!campaignId) {
      setSelectedEncounterId(null);
      return;
    }

    if (encounters && encounters.length > 0) {
      const currentSelection = selectedEncounterIdRef.current;

      if (currentSelection === null || !encounters.some(e => e.id === currentSelection)) {
        if (initialActiveEncounterId && encounters.some(e => e.id === initialActiveEncounterId)) {
          setSelectedEncounterId(initialActiveEncounterId);
        } else if (
          activeDisplayInfo &&
          activeDisplayInfo.activeCampaignId === campaignId &&
          encounters.some(e => e.id === activeDisplayInfo.activeEncounterId)
        ) {
          setSelectedEncounterId(activeDisplayInfo.activeEncounterId);
        }
      }
    } else if (encounters && encounters.length === 0) {
      setSelectedEncounterId(null);
    }
  }, [campaignId, initialActiveEncounterId, activeDisplayInfo, encounters]);

  const handleCreateEncounter = async (name) => {
    if (!db || !name.trim() || !campaignId) return;

    const newEncounterId = generateId();

    try {
      await storage.setDoc(`${getPath.encounters(campaignId)}/${newEncounterId}`, {
        name: name.trim(),
        createdAt: new Date().toISOString(),
        participants: [],
        round: 0,
        currentTurnParticipantId: null,
        isStarted: false,
        isPaused: false
      });

      setShowCreateModal(false);
      setSelectedEncounterId(newEncounterId);
    } catch (err) {
      console.error("Error creating encounter:", err);
      alert("Failed to create encounter. Please try again.");
    }
  };

  const requestDeleteEncounter = (encounterId, encounterName) => {
    setItemToDelete({ id: encounterId, name: encounterName });
    setShowDeleteConfirm(true);
  };

  const confirmDeleteEncounter = async () => {
    if (!db || !itemToDelete) return;

    const encounterId = itemToDelete.id;

    try {
      await storage.deleteDoc(getPath.encounter(campaignId, encounterId));

      if (selectedEncounterId === encounterId) {
        setSelectedEncounterId(null);
      }

      if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
        await storage.updateDoc(getPath.activeDisplay(), {
          activeCampaignId: null,
          activeEncounterId: null
        });
      }
    } catch (err) {
      console.error("Error deleting encounter:", err);
      alert("Failed to delete encounter. Please try again.");
    }

    setShowDeleteConfirm(false);
    setItemToDelete(null);
  };

  const handleTogglePlayerDisplay = async (encounterId) => {
    if (!db) return;

    try {
      const currentActiveCampaign = activeDisplayInfo?.activeCampaignId;
      const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;

      if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
        await storage.setDoc(getPath.activeDisplay(), {
          activeCampaignId: null,
          activeEncounterId: null,
        }, { merge: true });
      } else {
        await storage.setDoc(getPath.activeDisplay(), {
          activeCampaignId: campaignId,
          activeEncounterId: encounterId,
        }, { merge: true });
      }
    } catch (err) {
      console.error("Error toggling Player Display:", err);
    }
  };

  const selectedEncounter = encounters?.find(e => e.id === selectedEncounterId);

  if (isLoadingEncounters && campaignId) {
    return <p className="text-center text-stone-300 mt-4">Loading encounters...</p>;
  }

  return (
    <>
      <div className="mt-6 p-4 bg-stone-900 rounded-lg shadow">
        <div className="flex justify-between items-center mb-3">
          <h3 className="text-xl font-semibold text-amber-300 font-cinzel tracking-wide flex items-center">
            <Swords size={24} className="mr-2" /> Encounters
          </h3>
          <button
            onClick={() => setShowCreateModal(true)}
            className="px-4 py-2 text-sm font-medium text-white bg-amber-700 hover:bg-amber-800 rounded-md transition-colors flex items-center"
          >
            <PlusCircle size={18} className="mr-1" /> Create Encounter
          </button>
        </div>

        {(!encounters || encounters.length === 0) && (
          <p className="text-sm text-stone-400">No encounters yet.</p>
        )}

        <div className="space-y-3">
          {encounters?.map(encounter => {
            const isLive = activeDisplayInfo &&
              activeDisplayInfo.activeCampaignId === campaignId &&
              activeDisplayInfo.activeEncounterId === encounter.id;

            return (
              <div
                key={encounter.id}
                className={`p-3 rounded-md shadow transition-all ${selectedEncounterId === encounter.id ? 'bg-amber-900 ring-2 ring-amber-500' : 'bg-stone-800 hover:bg-stone-700'} ${isLive ? 'ring-2 ring-green-500 shadow-md shadow-green-500/30' : ''}`}
              >
                <div className="flex justify-between items-center">
                  <div onClick={() => setSelectedEncounterId(encounter.id)} className="cursor-pointer flex-grow">
                    <h4 className="font-medium text-white">{encounter.name}</h4>
                    <p className="text-xs text-stone-300">
                      Participants: {encounter.participants?.length || 0}
                    </p>
                    {isLive && (
                      <span className="text-xs text-green-400 font-semibold block mt-1">
                        LIVE ON PLAYER DISPLAY
                      </span>
                    )}
                  </div>
                  <div className="flex items-center space-x-2">
                    <button
                      onClick={() => handleTogglePlayerDisplay(encounter.id)}
                      className={`p-1 rounded transition-colors ${isLive ? 'bg-red-500 hover:bg-red-600 text-white' : 'text-amber-400 hover:text-amber-300 bg-stone-700 hover:bg-stone-600'}`}
                      title={isLive ? "Deactivate for Player Display" : "Activate for Player Display"}
                    >
                      {isLive ? <EyeOff size={18} /> : <Eye size={18} />}
                    </button>
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        requestDeleteEncounter(encounter.id, encounter.name);
                      }}
                      className="p-1 rounded transition-colors text-red-400 hover:text-red-300 bg-stone-700 hover:bg-stone-600"
                      title="Delete Encounter"
                    >
                      <Trash2 size={18} />
                    </button>
                  </div>
                </div>
              </div>
            );
          })}
        </div>

        {showCreateModal && (
          <Modal onClose={() => setShowCreateModal(false)} title="Create New Encounter">
            <CreateEncounterForm
              onCreate={handleCreateEncounter}
              onCancel={() => setShowCreateModal(false)}
            />
          </Modal>
        )}

        {selectedEncounter && (
          <div className="mt-6 p-4 bg-stone-900 rounded-lg shadow-inner">
            <h3 className="text-xl font-semibold text-amber-300 mb-3 font-cinzel tracking-wide">
              Managing Encounter: {selectedEncounter.name}
            </h3>
            <div className="flex flex-col lg:flex-row gap-4">
              {/* Combat Controls - Left Side (Sticky on large screens) */}
              <div className="lg:w-64 flex-shrink-0">
                <InitiativeControls
                  campaignId={campaignId}
                  encounter={selectedEncounter}
                  encounterPath={getPath.encounter(campaignId, selectedEncounter.id)}
                />
              </div>
              
              {/* Participant Manager - Right Side */}
              <div className="flex-grow">
                <ParticipantManager
                  encounter={selectedEncounter}
                  encounterPath={getPath.encounter(campaignId, selectedEncounter.id)}
                  campaignCharacters={campaignCharacters}
                />
              </div>
            </div>
          </div>
        )}
      </div>

      <ConfirmationModal
        isOpen={showDeleteConfirm}
        onClose={() => setShowDeleteConfirm(false)}
        onConfirm={confirmDeleteEncounter}
        title="Delete Encounter?"
        message={`Are you sure you want to delete the encounter "${itemToDelete?.name}"? This action cannot be undone.`}
      />
    </>
  );
}

// ============================================================================
// ADMIN VIEW COMPONENT
// ============================================================================

function AdminView({ userId }) {
  const { data: campaignsData, isLoading: isLoadingCampaigns, error: campaignsError } = useFirestoreCollection(
    getPath.campaigns()
  );
  const { data: initialActiveInfo } = useFirestoreDocument(getPath.activeDisplay());

  const [campaignsWithDetails, setCampaignsWithDetails] = useState([]);
  const [selectedCampaignId, setSelectedCampaignId] = useState(null);
  const [showCreateModal, setShowCreateModal] = useState(false);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [itemToDelete, setItemToDelete] = useState(null);

  useEffect(() => {
    if (campaignsData && db) {
      const fetchDetails = async () => {
        const detailedCampaigns = await Promise.all(
          campaignsData.map(async (campaign) => {
            const characters = campaign.players || [];
            let encounterCount = 0;

            try {
              const encounters = await storage.getCollection(getPath.encounters(campaign.id));
              encounterCount = encounters.length;
            } catch (err) {
              console.error(`Failed to fetch encounters for campaign ${campaign.id}:`, err);
            }

            return { ...campaign, characters, encounterCount };
          })
        );

        setCampaignsWithDetails(detailedCampaigns);
      };

      fetchDetails();
    } else if (campaignsData) {
      setCampaignsWithDetails(
        campaignsData.map(c => ({ ...c, characters: c.players || [], encounterCount: 0 }))
      );
    }
  }, [campaignsData]);

  useEffect(() => {
    if (
      initialActiveInfo &&
      initialActiveInfo.activeCampaignId &&
      campaignsWithDetails.length > 0
    ) {
      const campaignExists = campaignsWithDetails.some(c => c.id === initialActiveInfo.activeCampaignId);
      if (campaignExists && selectedCampaignId !== initialActiveInfo.activeCampaignId) {
        setSelectedCampaignId(initialActiveInfo.activeCampaignId);
      }
    }
  }, [initialActiveInfo, campaignsWithDetails, selectedCampaignId]);

  const handleCreateCampaign = async (name, backgroundUrl) => {
    if (!db || !name.trim()) return;

    const newCampaignId = generateId();

    try {
      await storage.setDoc(getPath.campaign(newCampaignId), {
        name: name.trim(),
        playerDisplayBackgroundUrl: backgroundUrl.trim() || '',
        ownerId: userId,
        createdAt: new Date().toISOString(),
        players: [],
      });

      setShowCreateModal(false);
      setSelectedCampaignId(newCampaignId);
    } catch (err) {
      console.error("Error creating campaign:", err);
      alert("Failed to create campaign. Please try again.");
    }
  };

  const requestDeleteCampaign = (campaignId, campaignName) => {
    setItemToDelete({ id: campaignId, name: campaignName });
    setShowDeleteConfirm(true);
  };

  const confirmDeleteCampaign = async () => {
    if (!db || !itemToDelete) return;

    const campaignId = itemToDelete.id;

    try {
      const encountersPath = getPath.encounters(campaignId);
      const encounters = await storage.getCollection(encountersPath);
      const deleteOps = encounters.map(e => {
        const id = e.id || e.path?.split('/').pop();
        return { type: 'delete', path: `${encountersPath}/${id}` };
      });
      if (deleteOps.length > 0) await storage.batchWrite(deleteOps);

      await storage.deleteDoc(getPath.campaign(campaignId));

      if (selectedCampaignId === campaignId) {
        setSelectedCampaignId(null);
      }

      const activeDisplay = await storage.getDoc(getPath.activeDisplay());

      if (activeDisplay && activeDisplay.activeCampaignId === campaignId) {
        await storage.updateDoc(getPath.activeDisplay(), {
          activeCampaignId: null,
          activeEncounterId: null
        });
      }
    } catch (err) {
      console.error("Error deleting campaign:", err);
      alert("Failed to delete campaign. Please try again.");
    }

    setShowDeleteConfirm(false);
    setItemToDelete(null);
  };

  const selectedCampaign = campaignsWithDetails.find(c => c.id === selectedCampaignId);

  if (isLoadingCampaigns) {
    return <p className="text-center text-stone-300">Loading campaigns...</p>;
  }

  if (campaignsError) {
    return (
      <p className="text-center text-red-400">
        Error loading campaigns: {campaignsError.message || String(campaignsError)}
      </p>
    );
  }

  return (
    <>
      <div className="space-y-6">
        <div>
          <div className="flex justify-between items-center mb-4">
            <h2 className="text-2xl font-semibold text-amber-300 font-cinzel tracking-wide">Campaigns</h2>
            <button
              onClick={() => setShowCreateModal(true)}
              className="bg-red-700 hover:bg-red-800 text-white font-bold py-2 px-4 rounded-lg flex items-center transition-colors"
            >
              <PlusCircle size={20} className="mr-2" /> Create Campaign
            </button>
          </div>

          {campaignsWithDetails.length === 0 && !isLoadingCampaigns && (
            <p className="text-stone-400">No campaigns yet. Create one to get started!</p>
          )}

          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            {campaignsWithDetails.map(campaign => {
              const cardStyle = campaign.playerDisplayBackgroundUrl
                ? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` }
                : {};

              const cardClasses = `h-40 flex flex-col justify-between rounded-lg shadow-md cursor-pointer transition-all relative overflow-hidden bg-cover bg-center ${selectedCampaignId === campaign.id ? 'ring-4 ring-amber-500' : ''} ${!campaign.playerDisplayBackgroundUrl ? 'bg-stone-800 hover:bg-stone-700' : 'hover:shadow-xl'}`;

              return (
                <div
                  key={campaign.id}
                  onClick={() => setSelectedCampaignId(campaign.id)}
                  className={cardClasses}
                  style={cardStyle}
                >
                  <div
                    className={`relative z-10 flex flex-col justify-between h-full ${campaign.playerDisplayBackgroundUrl ? 'bg-black bg-opacity-60 p-3' : 'p-4'}`}
                  >
                    <div>
                      <h3 className="text-xl font-semibold text-white">{campaign.name}</h3>
                      <div className="text-xs text-stone-100 mt-1 space-x-3">
                        <span className="inline-flex items-center">
                          <Users size={12} className="mr-1" /> {campaign.characters?.length || 0} Characters
                        </span>
                        <span className="inline-flex items-center">
                          <Swords size={12} className="mr-1" /> {campaign.encounterCount === undefined ? '...' : campaign.encounterCount} Encounters
                        </span>
                        {campaign.createdAt && (
                          <span className="inline-flex items-center opacity-80">
                            {new Date(campaign.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false })}
                          </span>
                        )}
                      </div>
                    </div>
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        requestDeleteCampaign(campaign.id, campaign.name);
                      }}
                      className="mt-auto text-red-300 hover:text-red-100 text-xs flex items-center self-start bg-black bg-opacity-50 hover:bg-opacity-70 px-2 py-1 rounded"
                    >
                      <Trash2 size={14} className="mr-1" /> Delete
                    </button>
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        {showCreateModal && (
          <Modal onClose={() => setShowCreateModal(false)} title="Create New Campaign">
            <CreateCampaignForm
              onCreate={handleCreateCampaign}
              onCancel={() => setShowCreateModal(false)}
            />
          </Modal>
        )}

        {selectedCampaign && (
          <div className="mt-6 p-6 bg-stone-900 rounded-lg shadow-xl">
            <h2 className="text-2xl font-semibold text-amber-300 mb-4 font-cinzel tracking-wide">
              Managing: {selectedCampaign.name}
            </h2>
            <CharacterManager
              campaignId={selectedCampaignId}
              campaignCharacters={selectedCampaign.characters || []}
            />
            <hr className="my-6 border-stone-700" />
            <EncounterManager
              campaignId={selectedCampaignId}
              initialActiveEncounterId={
                initialActiveInfo && initialActiveInfo.activeCampaignId === selectedCampaignId
                  ? initialActiveInfo.activeEncounterId
                  : null
              }
              campaignCharacters={selectedCampaign.characters || []}
            />
          </div>
        )}
      </div>

      <ConfirmationModal
        isOpen={showDeleteConfirm}
        onClose={() => setShowDeleteConfirm(false)}
        onConfirm={confirmDeleteCampaign}
        title="Delete Campaign?"
        message={`Are you sure you want to delete the campaign "${itemToDelete?.name}" and all its encounters? This action cannot be undone.`}
      />
    </>
  );
}

// ============================================================================
// DISPLAY VIEW COMPONENT (Player View)
// ============================================================================

function DisplayView() {
  const { data: activeDisplayData, isLoading: isLoadingActiveDisplay, error: activeDisplayError } = useFirestoreDocument(
    getPath.activeDisplay()
  );

  const [activeEncounterData, setActiveEncounterData] = useState(null);
  const [isLoadingEncounter, setIsLoadingEncounter] = useState(true);
  const [encounterError, setEncounterError] = useState(null);
  const [campaignBackgroundUrl, setCampaignBackgroundUrl] = useState('');
  const [isPlayerDisplayActive, setIsPlayerDisplayActive] = useState(false);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [wakeLockEnabled, setWakeLockEnabled] = useState(false);
  const wakeLockRef = useRef(null);
  const currentParticipantRef = useRef(null);

  useEffect(() => {
    const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', onFsChange);
    return () => document.removeEventListener('fullscreenchange', onFsChange);
  }, []);

  const toggleFullscreen = () => {
    if (!document.fullscreenElement) {
      document.documentElement.requestFullscreen();
    } else {
      document.exitFullscreen();
    }
  };

  useEffect(() => {
    if (!wakeLockEnabled) {
      wakeLockRef.current?.release();
      wakeLockRef.current = null;
      return;
    }

    const acquire = async () => {
      try {
        wakeLockRef.current = await navigator.wakeLock.request('screen');
      } catch (e) {
        console.error('Wake lock failed:', e);
      }
    };

    acquire();

    // Re-acquire after tab becomes visible again (browser auto-releases on hide)
    const onVisChange = () => { if (document.visibilityState === 'visible') acquire(); };
    document.addEventListener('visibilitychange', onVisChange);
    return () => {
      document.removeEventListener('visibilitychange', onVisChange);
      wakeLockRef.current?.release();
      wakeLockRef.current = null;
    };
  }, [wakeLockEnabled]);

  useEffect(() => {
    if (!db) {
      setEncounterError("Firestore not available.");
      setIsLoadingEncounter(false);
      setIsPlayerDisplayActive(false);
      return;
    }

    let unsubscribeEncounter;
    let unsubscribeCampaign;

    if (activeDisplayData) {
      const { activeCampaignId, activeEncounterId } = activeDisplayData;

      if (activeCampaignId && activeEncounterId) {
        setIsPlayerDisplayActive(true);
        setIsLoadingEncounter(true);
        setEncounterError(null);

        unsubscribeCampaign = storage.subscribeDoc(
          getPath.campaign(activeCampaignId),
          (camp) => {
            setCampaignBackgroundUrl((camp && camp.playerDisplayBackgroundUrl) || '');
          }
        );

        unsubscribeEncounter = storage.subscribeDoc(
          getPath.encounter(activeCampaignId, activeEncounterId),
          (enc) => {
            if (enc) {
              setActiveEncounterData({ id: activeEncounterId, ...enc });
            } else {
              setActiveEncounterData(null);
              setEncounterError("Active encounter data not found.");
            }
            setIsLoadingEncounter(false);
          }
        );
      } else {
        setActiveEncounterData(null);
        setCampaignBackgroundUrl('');
        setIsPlayerDisplayActive(false);
        setIsLoadingEncounter(false);
      }
    } else if (!isLoadingActiveDisplay) {
      setActiveEncounterData(null);
      setCampaignBackgroundUrl('');
      setIsPlayerDisplayActive(false);
      setIsLoadingEncounter(false);
    }

    return () => {
      if (unsubscribeEncounter) unsubscribeEncounter();
      if (unsubscribeCampaign) unsubscribeCampaign();
    };
  }, [activeDisplayData, isLoadingActiveDisplay]);

  // Auto-scroll current participant into view
  useEffect(() => {
    if (currentParticipantRef.current && activeEncounterData?.isStarted && !activeEncounterData?.isPaused) {
      currentParticipantRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'nearest'
      });
    }
  }, [activeEncounterData?.currentTurnParticipantId, activeEncounterData?.isStarted, activeEncounterData?.isPaused]);

  if (isLoadingActiveDisplay || (isPlayerDisplayActive && isLoadingEncounter)) {
    return <div className="text-center py-10 text-2xl text-stone-300">Loading Player Display...</div>;
  }

  if (activeDisplayError || (isPlayerDisplayActive && encounterError)) {
    return <div className="text-center py-10 text-2xl text-red-400">{activeDisplayError || encounterError}</div>;
  }

  if (!isPlayerDisplayActive || !activeEncounterData) {
    return (
      <div className="min-h-screen bg-black text-stone-400 flex flex-col items-center justify-center p-4 text-center">
        <EyeOff size={64} className="mb-4 text-stone-500" />
        <h2 className="text-3xl font-semibold font-cinzel tracking-wide">Game Session Paused</h2>
        <p className="text-xl mt-2">The Dungeon Master has not activated an encounter for display.</p>
      </div>
    );
  }

  const { name, participants, round, currentTurnParticipantId, isStarted, isPaused } = activeEncounterData;
  const hidePlayerHp = activeDisplayData?.hidePlayerHp ?? true;

  let participantsToRender = [];
  if (participants) {
    // 1-list model: participants[] IS the display order (DM drag = source of
    // truth). Do NOT re-sort by initiative — that diverges from AdminView /
    // turnOrderIds after any cross-init drag (BUG-15).
    participantsToRender = participants.filter(p => p.isActive || p.type !== 'monster');
  }

  const displayStyles = campaignBackgroundUrl
    ? {
        backgroundImage: `url(${campaignBackgroundUrl})`,
        backgroundSize: 'cover',
        backgroundPosition: 'center center',
        backgroundRepeat: 'no-repeat',
        minHeight: '100vh'
      }
    : { minHeight: '100vh' };

  return (
    <div
      className={`p-4 md:p-8 rounded-xl shadow-2xl ${!campaignBackgroundUrl ? 'bg-stone-950' : ''}`}
      style={displayStyles}
    >
      <div className="fixed top-3 right-3 z-50 flex gap-2">
        <button
          onClick={() => setWakeLockEnabled(v => !v)}
          title={wakeLockEnabled ? 'Allow sleep' : 'Prevent sleep'}
          className={`p-2 rounded-lg transition-all ${wakeLockEnabled ? 'bg-amber-600 hover:bg-amber-700 text-white' : 'bg-stone-800 bg-opacity-80 hover:bg-opacity-100 text-stone-300 hover:text-white'}`}
        >
          {wakeLockEnabled ? <Coffee size={20} /> : <Moon size={20} />}
        </button>
        <button
          onClick={toggleFullscreen}
          title={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
          className="bg-stone-800 bg-opacity-80 hover:bg-opacity-100 text-stone-300 hover:text-white p-2 rounded-lg transition-all"
        >
          {isFullscreen ? <Minimize2 size={20} /> : <Maximize2 size={20} />}
        </button>
      </div>
      <div className={campaignBackgroundUrl ? 'bg-stone-950 bg-opacity-75 p-4 md:p-6 rounded-lg' : ''}>
        <h2 className="text-4xl md:text-5xl font-bold text-center text-amber-400 mb-2 font-cinzel tracking-wide">{name}</h2>

        {isStarted && <p className="text-2xl text-center text-amber-300 mb-1 font-cinzel">Round: {round}</p>}

        {isStarted && isPaused && (
          <p className="text-xl text-center text-yellow-400 mb-4 font-semibold">(Combat Paused)</p>
        )}

        {!isStarted && participants?.length > 0 && (
          <p className="text-2xl text-center text-stone-400 mb-6">Awaiting Start</p>
        )}

        {!isStarted && (!participants || participants.length === 0) && (
          <p className="text-2xl text-stone-500 mb-6">No participants.</p>
        )}

        {participantsToRender.length === 0 && isStarted && (
          <p className="text-xl text-stone-400">No active participants.</p>
        )}

        <div className="space-y-4 max-w-3xl mx-auto">
          {participantsToRender.map(p => {
            const isDead = p.currentHp === 0;
            const isDying = p.isDying || false;
            let participantBgColor = p.type === 'monster'
              ? (p.isNpc ? 'bg-stone-800' : 'bg-[#8e351c]')
              : 'bg-indigo-950';

            const isCurrentTurn = p.id === currentTurnParticipantId && isStarted && !isPaused;

            if (isCurrentTurn) {
              participantBgColor = 'bg-green-700 ring-4 ring-green-400 scale-105';
            } else if (isPaused && p.id === currentTurnParticipantId) {
              participantBgColor += ' ring-2 ring-yellow-400';
            }

            return (
              <div
                key={p.id}
                ref={isCurrentTurn ? currentParticipantRef : null}
                className={`p-4 md:p-6 rounded-lg shadow-lg transition-all ${participantBgColor} ${isDead ? 'opacity-40 grayscale' : (!p.isActive ? 'opacity-40 grayscale' : '')} ${isDying ? 'animate-death-dissolve' : ''}`}
              >
                <div className="flex justify-between items-center mb-2">
                  <h3
                    className={`text-2xl md:text-3xl font-bold font-cinzel ${isCurrentTurn ? 'text-white' : (p.type === 'character' ? 'text-amber-100' : (p.isNpc ? 'text-stone-100' : 'text-white'))}`}
                  >
                    {isDead && <span className="mr-2">☠️</span>}
                    {p.name}
                    {isCurrentTurn && (
                      <span className="text-yellow-300 animate-pulse ml-2">(Current)</span>
                    )}
                    {isDead && <span className="text-red-300 text-lg ml-2">{p.type === 'character' ? '(Unconscious)' : '(Dead)'}</span>}
                  </h3>
                  <span
                    className={`text-xl md:text-2xl font-semibold ${isCurrentTurn ? 'text-green-200' : 'text-stone-200'}`}
                  >
                    Init: {p.initiative}
                  </span>
                </div>

                {!(hidePlayerHp && p.type === 'character') && (
                  <div className="flex justify-between items-center">
                    <div className="w-full bg-stone-700 rounded-full h-6 md:h-8 relative overflow-hidden border-2 border-stone-600">
                      <div
                        className={`h-full rounded-full transition-all ${isDead ? 'bg-red-900' : (p.currentHp <= p.maxHp / 4 ? 'bg-red-500' : (p.currentHp <= p.maxHp / 2 ? 'bg-yellow-500' : 'bg-green-500'))}`}
                        style={{ width: `${Math.max(0, (p.currentHp / p.maxHp) * 100)}%` }}
                      ></div>
                      {p.type !== 'monster' && (
                        <span className="absolute inset-0 flex items-center justify-center text-xs md:text-sm font-medium text-white px-2">
                          HP: {p.currentHp} / {p.maxHp}
                        </span>
                      )}
                    </div>
                  </div>
                )}

                {p.conditions?.length > 0 && (
                  <div className="flex flex-wrap gap-1 mt-2">
                    {p.conditions.map(cId => {
                      const cond = CONDITIONS.find(c => c.id === cId);
                      return cond ? (
                        <span
                          key={cId}
                          className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-purple-900 border border-purple-500 text-xs md:text-sm text-purple-200 font-medium"
                        >
                          {cond.emoji} {cond.label}
                        </span>
                      ) : null;
                    })}
                  </div>
                )}

                {!p.isActive && !isDead && (
                  <p className="text-center text-lg font-semibold text-stone-300 mt-2">(Inactive)</p>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ============================================================================
// LOGS VIEW COMPONENT
// ============================================================================

function LogsView() {
  const { data: logs, isLoading } = useFirestoreCollection(getPath.logs(), LOG_QUERY);
  const [showClearConfirm, setShowClearConfirm] = useState(false);
  const [undoingId, setUndoingId] = useState(null);

  const handleClearLogs = async () => {
    try {
      const logs = await storage.getCollection(getPath.logs());
      if (logs.length > 0) {
        const ops = logs.map(l => {
          const id = l.id || l.path?.split('/').pop();
          return { type: 'delete', path: `${getPath.logs()}/${id}` };
        });
        await storage.batchWrite(ops);
      }
    } catch (err) {
      console.error('Error clearing logs:', err);
    }
    setShowClearConfirm(false);
  };

  const handleUndo = async (entry) => {
    if (!db || !entry.undo) return;
    setUndoingId(entry.id);
    try {
      await storage.updateDoc(entry.undo.encounterPath, entry.undo.updates);
      await storage.updateDoc(`${getPath.logs()}/${entry.id}`, { undone: true });
    } catch (err) {
      console.error('Error undoing action:', err);
      alert('Failed to roll back. The encounter may have changed or no longer exists.');
    }
    setUndoingId(null);
  };

  return (
    <div className="min-h-screen bg-stone-950 text-stone-100 font-garamond">
      <header className="bg-stone-950 p-4 shadow-lg border-b border-amber-900">
        <div className="container mx-auto flex justify-between items-center">
          <h1 className="text-3xl font-bold text-amber-400 font-cinzel tracking-wide">Combat Log</h1>
          <div className="flex gap-2">
            <a
              href="/"
              className="px-4 py-2 rounded-md text-sm font-medium bg-stone-700 hover:bg-stone-600 text-white transition-colors"
            >
              ← Back to Tracker
            </a>
            <button
              onClick={() => setShowClearConfirm(true)}
              disabled={isLoading || logs.length === 0}
              className="px-4 py-2 rounded-md text-sm font-medium bg-red-800 hover:bg-red-700 text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
            >
              Clear Log
            </button>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4 md:p-8">
        {isLoading ? (
          <LoadingSpinner message="Loading logs..." />
        ) : logs.length === 0 ? (
          <p className="text-stone-400 text-center mt-12 text-lg">No log entries yet.</p>
        ) : (
          <>
            <p className="text-stone-500 text-sm mb-4">{logs.length} entries — newest first</p>
            <div className="space-y-1 max-w-5xl">
              {logs.map(entry => (
                <div
                  key={entry.id}
                  className={`flex gap-3 items-center p-3 rounded-md border text-sm transition-opacity ${
                    entry.undone
                      ? 'bg-stone-900/50 border-stone-800/50 opacity-50'
                      : 'bg-stone-900 border-stone-800'
                  }`}
                >
                  <span className="text-stone-500 whitespace-nowrap font-mono shrink-0">
                    {new Date(entry.timestamp).toLocaleString()}
                  </span>
                  {entry.encounterName && (
                    <span className="text-amber-600 whitespace-nowrap shrink-0">[{entry.encounterName}]</span>
                  )}
                  <span className={`flex-1 ${entry.undone ? 'line-through text-stone-500' : 'text-stone-100'}`}>
                    {entry.message}
                  </span>
                  {entry.undone ? (
                    <span className="shrink-0 text-xs text-stone-600 italic">rolled back</span>
                  ) : entry.undo ? (
                    <button
                      onClick={() => handleUndo(entry)}
                      disabled={undoingId === entry.id}
                      className="shrink-0 px-2 py-0.5 text-xs rounded bg-stone-700 hover:bg-amber-800 text-stone-300 hover:text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                      title="Roll back this action"
                    >
                      {undoingId === entry.id ? '…' : '↩ Undo'}
                    </button>
                  ) : null}
                </div>
              ))}
            </div>
          </>
        )}
      </main>

      <ConfirmationModal
        isOpen={showClearConfirm}
        onClose={() => setShowClearConfirm(false)}
        onConfirm={handleClearLogs}
        title="Clear All Logs?"
        message="This will permanently delete all log entries and cannot be undone."
      />
    </div>
  );
}

// ============================================================================
// MAIN APP COMPONENT
// ============================================================================

function App() {
  const [userId, setUserId] = useState(null);
  const [isAuthReady, setIsAuthReady] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const [isPlayerViewOnlyMode, setIsPlayerViewOnlyMode] = useState(false);
  const [isLogsMode, setIsLogsMode] = useState(false);

  useEffect(() => {
    const queryParams = new URLSearchParams(window.location.search);
    if (queryParams.get('playerView') === 'true' || window.location.pathname === '/display') {
      setIsPlayerViewOnlyMode(true);
    }
    if (window.location.pathname === '/logs') {
      setIsLogsMode(true);
    }

    if (!auth) {
      setError("Auth not initialized.");
      setIsLoading(false);
      setIsAuthReady(false);
      return;
    }

    // ws/memory mode: stub auth, no SDK sign-in. Unblock UI immediately.
    if (STORAGE_MODE !== 'firebase') {
      setUserId(auth.currentUser?.uid || 'local-user');
      setIsAuthReady(true);
      setIsLoading(false);
      return;
    }

    const initAuth = async () => {
      try {
        const token = window.__initial_auth_token;
        if (token) {
          await signInWithCustomToken(auth, token);
        } else {
          await signInAnonymously(auth);
        }
      } catch (err) {
        console.error("Authentication error:", err);
        setError("Failed to authenticate. Please try again later.");
        // Auth failed and onAuthStateChanged won't fire with a user, so unblock the UI here
        setIsAuthReady(true);
        setIsLoading(false);
      }
    };

    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUserId(user ? user.uid : null);
      // Only mark auth ready once we have an actual authenticated user.
      // onAuthStateChanged fires with null before signInAnonymously completes,
      // which would cause Firestore queries to run unauthenticated.
      if (user) {
        setIsAuthReady(true);
        setIsLoading(false);
      }
    });

    initAuth();

    return () => unsubscribe();
  }, []);

  if (!isInitialized || !auth) {
    return (
      <ErrorDisplay
        critical
        message={`${STORAGE_MODE === 'firebase' ? 'Firebase' : 'Storage'} is not properly configured. Check your .env.local file and ensure all REACT_APP_* variables are correctly set.`}
      />
    );
  }

  if (isLoading || !isAuthReady) {
    return <LoadingSpinner message="Loading Initiative Tracker..." />;
  }

  if (error) {
    return <ErrorDisplay message={error} />;
  }

  const openPlayerWindow = () => {
    const playerViewUrl = window.location.origin + '/display';
    window.open(playerViewUrl, '_blank', 'noopener,noreferrer,width=1024,height=768');
  };

  if (isPlayerViewOnlyMode) {
    return (
      <div className="min-h-screen bg-stone-950 text-stone-100 font-garamond">
        {isAuthReady && <DisplayView />}
        {!isAuthReady && !error && <p>Authenticating for Player Display...</p>}
      </div>
    );
  }

  if (isLogsMode) {
    return isAuthReady ? <LogsView /> : <LoadingSpinner message="Authenticating..." />;
  }

  return (
    <div className="min-h-screen bg-stone-950 text-stone-100 font-garamond">
      <header className="bg-stone-950 p-4 shadow-lg border-b border-amber-900">
        <div className="container mx-auto flex justify-between items-center">
          <h1 className="text-3xl font-bold text-amber-400 font-cinzel tracking-wide">TTRPG Initiative Tracker</h1>
          <div className="flex gap-2">
            <a
              href="/logs"
              target="_blank"
              rel="noopener noreferrer"
              className="px-4 py-2 rounded-md text-sm font-medium transition-colors bg-stone-700 hover:bg-stone-600 text-white flex items-center"
            >
              <ScrollText size={16} className="mr-2" /> View Logs
            </a>
            <button
              onClick={openPlayerWindow}
              className="px-4 py-2 rounded-md text-sm font-medium transition-colors bg-amber-700 hover:bg-amber-800 text-white flex items-center"
            >
              <ExternalLink size={16} className="mr-2" /> Open Player Window
            </button>
          </div>
        </div>
      </header>

      <main className="container mx-auto p-4 md:p-8">
        {isAuthReady && userId && <AdminView userId={userId} />}
        {!isAuthReady && !error && <p>Authenticating...</p>}
      </main>

      <footer className="bg-stone-950 p-4 text-center text-sm text-stone-400 mt-8">
        TTRPG Initiative Tracker {APP_VERSION}
      </footer>
    </div>
  );
}

export default App;
