import { useState, useEffect, useCallback, type CSSProperties } from 'react';
import type { NPC, Quest, HeroQuest } from '../game/types';
import {
  ApiError,
  getNPCQuests,
  acceptQuest,
  claimQuest,
  buyPotion,
  buyTownMerchantGear,
  healAtNPC,
  setNPCDialogPause,
  fetchMerchantStock,
} from '../network/api';
import type { HeroResponse, MerchantGearOfferItem } from '../network/api';
import { RARITY_COLORS, RARITY_GLOW } from '../shared/constants';
import { getTelegramUserId } from '../shared/telegram';
import { hapticImpact } from '../shared/telegram';
import { useT, t, useLocale } from '../i18n';
import { localizedQuestText } from '../i18n/questCopy';
import { npcLabel } from '../i18n/contentLabels';

// ---- Types ----

interface NPCDialogProps {
  npc: NPC;
  heroQuests: HeroQuest[];
  heroGold: number;
  potionCost: number;
  healCost: number;
  getHeroWorldPosition: () => { x: number; y: number };
  onClose: () => void;
  onQuestsChanged: () => void;
  onHeroUpdated: (hero: HeroResponse) => void;
  onToast: (message: string, color: string) => void;
  /** Block quest reward claim while hero is dead. */
  questClaimDisabled?: boolean;
  /**
   * When set, notifies the server for ExcursionKindTown: interaction opened on mount,
   * dialog + interaction closed on unmount (WebSocket commands).
   */
  townTourWs?: {
    onInteractionOpened: () => void;
    onDialogAndInteractionClosed: () => void;
  };
}

// ---- Styles ----

const overlayStyle: CSSProperties = {
  position: 'absolute',
  inset: 0,
  zIndex: 700,
  pointerEvents: 'auto',
};

const backdropStyle: CSSProperties = {
  position: 'absolute',
  inset: 0,
  backgroundColor: 'rgba(0, 0, 0, 0.6)',
};

const dialogStyle: CSSProperties = {
  position: 'absolute',
  bottom: 0,
  left: 0,
  right: 0,
  maxHeight: '55vh',
  backgroundColor: 'rgba(15, 15, 25, 0.96)',
  borderTop: '2px solid rgba(255, 215, 0, 0.35)',
  borderRadius: '16px 16px 0 0',
  display: 'flex',
  flexDirection: 'column',
  animation: 'npc-dialog-slide-up 0.3s ease-out',
  overflow: 'hidden',
};

const headerStyle: CSSProperties = {
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'space-between',
  padding: '12px 16px 8px',
  borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
};

const npcNameStyle: CSSProperties = {
  fontSize: 15,
  fontWeight: 700,
  color: '#e8e8e8',
};

const npcTypeTag: CSSProperties = {
  fontSize: 10,
  fontWeight: 600,
  color: '#999',
  textTransform: 'uppercase',
  letterSpacing: 0.5,
  marginLeft: 8,
};

const closeBtnStyle: CSSProperties = {
  background: 'none',
  border: 'none',
  color: '#888',
  fontSize: 20,
  cursor: 'pointer',
  padding: '4px 8px',
  lineHeight: 1,
};

const bodyStyle: CSSProperties = {
  overflowY: 'auto',
  padding: '10px 14px 18px',
  flex: 1,
};

const sectionTitleStyle: CSSProperties = {
  fontSize: 11,
  fontWeight: 700,
  color: '#888',
  textTransform: 'uppercase',
  letterSpacing: 0.5,
  marginTop: 10,
  marginBottom: 6,
};

const questCardStyle: CSSProperties = {
  backgroundColor: 'rgba(255, 255, 255, 0.04)',
  border: '1px solid rgba(255, 255, 255, 0.08)',
  borderRadius: 10,
  padding: '10px 12px',
  marginBottom: 8,
};

const questTitleRow: CSSProperties = {
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'space-between',
  gap: 8,
};

const questTitleText: CSSProperties = {
  fontSize: 13,
  fontWeight: 600,
  color: '#e8e8e8',
  flex: 1,
};

const questDescStyle: CSSProperties = {
  fontSize: 11,
  color: '#999',
  marginTop: 4,
  lineHeight: 1.4,
};

const rewardsRow: CSSProperties = {
  display: 'flex',
  gap: 10,
  marginTop: 6,
  fontSize: 11,
  fontWeight: 600,
};

const rewardChip: CSSProperties = {
  display: 'inline-flex',
  alignItems: 'center',
  gap: 3,
};

const acceptBtnStyle: CSSProperties = {
  marginTop: 8,
  width: '100%',
  padding: '8px 0',
  border: 'none',
  borderRadius: 8,
  backgroundColor: 'rgba(68, 170, 255, 0.25)',
  color: '#66bbff',
  fontSize: 12,
  fontWeight: 700,
  cursor: 'pointer',
  transition: 'background-color 150ms ease',
};

const claimBtnStyle: CSSProperties = {
  marginTop: 8,
  width: '100%',
  padding: '8px 0',
  border: 'none',
  borderRadius: 8,
  backgroundColor: 'rgba(255, 215, 0, 0.25)',
  color: '#ffd700',
  fontSize: 12,
  fontWeight: 700,
  cursor: 'pointer',
  textShadow: '0 0 6px rgba(255, 215, 0, 0.4)',
  animation: 'npc-claim-glow 1.5s ease-in-out infinite',
};

const progressBarOuter: CSSProperties = {
  height: 6,
  borderRadius: 3,
  backgroundColor: 'rgba(255, 255, 255, 0.08)',
  marginTop: 6,
  overflow: 'hidden',
};

const serviceBtnStyle: CSSProperties = {
  width: '100%',
  padding: '12px 0',
  border: 'none',
  borderRadius: 10,
  fontSize: 14,
  fontWeight: 700,
  cursor: 'pointer',
  marginBottom: 8,
  transition: 'background-color 150ms ease',
};

const disabledBtnStyle: CSSProperties = {
  ...serviceBtnStyle,
  opacity: 0.4,
  cursor: 'default',
};

// ---- NPC Type Info ----

const GEAR_VENDOR_TYPES = new Set(['merchant', 'armorer', 'weapon', 'jeweler']);
const QUEST_NPC_TYPES = new Set(['quest_giver', 'bounty_hunter', 'elder']);

function isGearVendorType(t: string): boolean {
  return GEAR_VENDOR_TYPES.has(t);
}

function isQuestNPCType(t: string): boolean {
  return QUEST_NPC_TYPES.has(t);
}

function npcTypeIcon(type: string): string {
  switch (type) {
    case 'quest_giver':
    case 'bounty_hunter':
      return '\u2694\uFE0F';
    case 'elder':
      return '\uD83D\uDCDC';
    case 'merchant':
    case 'armorer':
    case 'weapon':
    case 'jeweler':
      return '\uD83D\uDCB0';
    case 'healer':
      return '\u2764\uFE0F';
    default:
      return '\uD83D\uDDE3\uFE0F';
  }
}

function npcTypeLabel(type: string, tr: ReturnType<typeof useT>): string {
  switch (type) {
    case 'quest_giver':
      return tr.questGiver;
    case 'bounty_hunter':
      return tr.bountyHunter;
    case 'elder':
      return tr.elder;
    case 'merchant':
      return tr.merchant;
    case 'armorer':
      return tr.armorer;
    case 'weapon':
      return tr.weaponSmith;
    case 'jeweler':
      return tr.jeweler;
    case 'healer':
      return tr.healer;
    default:
      return tr.npc;
  }
}

function questTypeIcon(type: string): string {
  switch (type) {
    case 'kill_count': return '\u2694\uFE0F';
    case 'visit_town': return '\uD83E\uDDED';
    case 'collect_item': return '\uD83D\uDC8E';
    default: return '\uD83D\uDCDC';
  }
}

function prettyEquipmentSlot(slot: string): string {
  return slot
    .split('_')
    .map((p) => (p.length ? p.charAt(0).toUpperCase() + p.slice(1) : p))
    .join(' ');
}

function gearStatLabel(tr: ReturnType<typeof useT>, statType: string): string {
  switch (statType) {
    case 'attack':
      return tr.atk;
    case 'defense':
      return tr.def;
    case 'speed':
      return tr.spd;
    default:
      return tr.stat;
  }
}

function offerRarityStyle(rarity: string): { color: string; textShadow?: string } {
  const r = rarity.toLowerCase();
  const color = RARITY_COLORS[r] ?? '#9d9d9d';
  const glow = RARITY_GLOW[r];
  if (glow && glow !== 'none') {
    return { color, textShadow: glow };
  }
  return { color };
}

// ---- Component ----

export function NPCDialog({
  npc,
  heroQuests,
  heroGold,
  potionCost,
  healCost,
  getHeroWorldPosition,
  onClose,
  onQuestsChanged,
  onHeroUpdated,
  onToast,
  questClaimDisabled = false,
  townTourWs,
}: NPCDialogProps) {
  const tr = useT();
  const { locale } = useLocale();
  const npcDisplayName = npcLabel(locale, npc.nameKey, npc.name);
  const [availableQuests, setAvailableQuests] = useState<Quest[]>([]);
  const [loading, setLoading] = useState(false);
  const [merchantOffers, setMerchantOffers] = useState<MerchantGearOfferItem[]>([]);
  const [merchantStockLoading, setMerchantStockLoading] = useState(false);
  const [merchantStockError, setMerchantStockError] = useState(false);

  const telegramId = getTelegramUserId() ?? 1;

  // Pause town tour timers while any NPC dialog (shop / quests / healer) is open.
  useEffect(() => {
    let cancelled = false;
    setNPCDialogPause(true, telegramId).catch((err) => {
      if (!cancelled) console.warn('[NPCDialog] npc-dialog-pause (open) failed:', err);
    });
    townTourWs?.onInteractionOpened();
    return () => {
      cancelled = true;
      setNPCDialogPause(false, telegramId).catch((err) => {
        console.warn('[NPCDialog] npc-dialog-pause (close) failed:', err);
      });
      townTourWs?.onDialogAndInteractionClosed();
    };
  }, [telegramId, townTourWs]);

  // Gear vendors: roll stock when the shop opens (server-tier gear for this town).
  useEffect(() => {
    if (!isGearVendorType(npc.type)) return;
    let cancelled = false;
    setMerchantStockLoading(true);
    setMerchantStockError(false);
    const pos = getHeroWorldPosition();
    fetchMerchantStock({ npcId: npc.id, positionX: pos.x, positionY: pos.y }, telegramId)
      .then((res) => {
        if (cancelled) return;
        setMerchantOffers(Array.isArray(res.items) ? res.items : []);
      })
      .catch((err) => {
        if (cancelled) return;
        console.warn('[NPCDialog] merchant stock failed:', err);
        setMerchantStockError(true);
        setMerchantOffers([]);
      })
      .finally(() => {
        if (!cancelled) setMerchantStockLoading(false);
      });
    return () => {
      cancelled = true;
    };
    // Intentionally depend on npc identity only — position is read once when the dialog opens.
    // eslint-disable-next-line react-hooks/exhaustive-deps -- getHeroWorldPosition may be unstable from parent
  }, [npc.id, npc.type, telegramId]);

  // Fetch available quests for bounty / elder (and legacy quest_giver).
  useEffect(() => {
    if (!isQuestNPCType(npc.type)) return;
    setLoading(true);
    getNPCQuests(npc.id, telegramId)
      .then((qs) => setAvailableQuests(Array.isArray(qs) ? qs : []))
      .catch((err) => {
        console.warn('[NPCDialog] Failed to fetch quests:', err);
        setAvailableQuests([]);
      })
      .finally(() => setLoading(false));
  }, [npc.id, npc.type, telegramId]);

  // Close on Escape
  useEffect(() => {
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', handleKey);
    return () => window.removeEventListener('keydown', handleKey);
  }, [onClose]);

  const handleAcceptQuest = useCallback(
    (questId: number) => {
      acceptQuest(questId, telegramId)
        .then(() => {
          hapticImpact('medium');
          onToast(tr.questAccepted, '#44aaff');
          onQuestsChanged();
          // Remove from available list
          setAvailableQuests((prev) => (Array.isArray(prev) ? prev : []).filter((q) => q.id !== questId));
        })
        .catch((err) => {
          console.warn('[NPCDialog] Failed to accept quest:', err);
          onToast(tr.failedToAcceptQuest, '#ff4444');
        });
    },
    [telegramId, onQuestsChanged, onToast],
  );

  const handleClaimQuest = useCallback(
    (heroQuestId: number) => {
      claimQuest(heroQuestId, telegramId)
        .then((hero) => {
          hapticImpact('heavy');
          onToast(tr.questRewardsClaimed, '#ffd700');
          onHeroUpdated(hero);
          onQuestsChanged();
        })
        .catch((err) => {
          console.warn('[NPCDialog] Failed to claim quest:', err);
          onToast(tr.failedToClaimRewards, '#ff4444');
        });
    },
    [telegramId, onQuestsChanged, onHeroUpdated, onToast],
  );

  const handleBuyPotion = useCallback(() => {
    if (heroGold < potionCost) {
      onToast(tr.notEnoughGold, '#ff4444');
      return;
    }
    const pos = getHeroWorldPosition();
    buyPotion({ npcId: npc.id, positionX: pos.x, positionY: pos.y }, telegramId)
      .then((hero) => {
        hapticImpact('medium');
        onToast(t(tr.boughtPotion, { cost: potionCost }), '#88dd88');
        onHeroUpdated(hero);
      })
      .catch((err) => {
        console.warn('[NPCDialog] Failed to buy potion:', err);
        if (err instanceof ApiError) {
          try {
            const j = JSON.parse(err.body) as { error?: string };
            onToast(j.error ?? tr.failedToBuyPotion, '#ff4444');
          } catch {
            onToast(tr.failedToBuyPotion, '#ff4444');
          }
        } else {
          onToast(tr.failedToBuyPotion, '#ff4444');
        }
      });
  }, [telegramId, heroGold, potionCost, npc.id, getHeroWorldPosition, onHeroUpdated, onToast, tr]);

  const handleBuyTownGear = useCallback(
    (offerIndex: number) => {
      const row = merchantOffers[offerIndex];
      const rowCost = typeof row?.cost === 'number' ? row.cost : 0;
      if (rowCost <= 0) {
        onToast(tr.failedToBuyGear, '#ff4444');
        return;
      }
      if (heroGold < rowCost) {
        onToast(tr.notEnoughGold, '#ff4444');
        return;
      }
      const pos = getHeroWorldPosition();
      buyTownMerchantGear(
        { npcId: npc.id, positionX: pos.x, positionY: pos.y, offerIndex },
        telegramId,
      )
        .then((hero) => {
          hapticImpact('medium');
          onToast(t(tr.boughtGearFromMerchant, { cost: rowCost }), '#c9a227');
          onHeroUpdated(hero);
          setMerchantOffers((prev) => prev.filter((_, i) => i !== offerIndex));
        })
        .catch((err) => {
          console.warn('[NPCDialog] Failed to buy gear:', err);
          if (err instanceof ApiError) {
            try {
              const j = JSON.parse(err.body) as { error?: string };
              onToast(j.error ?? tr.failedToBuyGear, '#ff4444');
            } catch {
              onToast(tr.failedToBuyGear, '#ff4444');
            }
          } else {
            onToast(tr.failedToBuyGear, '#ff4444');
          }
        });
    },
    [telegramId, heroGold, merchantOffers, npc.id, getHeroWorldPosition, onHeroUpdated, onToast, tr],
  );

  const handleHeal = useCallback(() => {
    if (heroGold < healCost) {
      onToast(tr.notEnoughGold, '#ff4444');
      return;
    }
    const pos = getHeroWorldPosition();
    healAtNPC({ npcId: npc.id, positionX: pos.x, positionY: pos.y }, telegramId)
      .then((hero) => {
        hapticImpact('medium');
        onToast(tr.healedToFull, '#44cc44');
        onHeroUpdated(hero);
      })
      .catch((err) => {
        console.warn('[NPCDialog] Failed to heal:', err);
        if (err instanceof ApiError) {
          try {
            const j = JSON.parse(err.body) as { error?: string };
            onToast(j.error ?? tr.failedToHeal, '#ff4444');
          } catch {
            onToast(tr.failedToHeal, '#ff4444');
          }
        } else {
          onToast(tr.failedToHeal, '#ff4444');
        }
      });
  }, [telegramId, heroGold, healCost, npc.id, getHeroWorldPosition, onHeroUpdated, onToast, tr]);

  // Quests relevant to this NPC
  const npcHeroQuests = heroQuests.filter(
    (hq) => hq.status !== 'claimed',
  );
  // Filter to quests from this NPC based on npcName matching
  const npcInProgressQuests = npcHeroQuests.filter(
    (hq) => hq.questNpcId === npc.id && hq.status === 'accepted',
  );
  const npcCompletedQuests = npcHeroQuests.filter(
    (hq) => hq.questNpcId === npc.id && hq.status === 'completed',
  );

  return (
    <>
      <style>{`
        @keyframes npc-dialog-slide-up {
          0%   { transform: translateY(100%); }
          100% { transform: translateY(0); }
        }
        @keyframes npc-claim-glow {
          0%, 100% { box-shadow: 0 0 8px rgba(255, 215, 0, 0.15); }
          50%      { box-shadow: 0 0 18px rgba(255, 215, 0, 0.4); }
        }
      `}</style>
      <div style={overlayStyle}>
        <div style={backdropStyle} onClick={onClose} />
        <div style={dialogStyle}>
          {/* Header */}
          <div style={headerStyle}>
            <div style={{ display: 'flex', alignItems: 'center' }}>
              <span style={{ fontSize: 18, marginRight: 8 }}>{npcTypeIcon(npc.type)}</span>
              <span style={npcNameStyle}>{npcDisplayName}</span>
              <span style={npcTypeTag}>{npcTypeLabel(npc.type, tr)}</span>
            </div>
            <button style={closeBtnStyle} onClick={onClose}>{'\u2715'}</button>
          </div>

          {/* Body */}
          <div style={bodyStyle}>
            {/* ---- Quest NPCs ---- */}
            {isQuestNPCType(npc.type) && (
              <>
                {/* Completed quests — claim first */}
                {npcCompletedQuests.length > 0 && (
                  <>
                    <div style={sectionTitleStyle}>{tr.completed}</div>
                    {npcCompletedQuests.map((hq) => (
                      <div
                        key={hq.id}
                        style={{
                          ...questCardStyle,
                          borderColor: 'rgba(255, 215, 0, 0.3)',
                        }}
                      >
                        <div style={questTitleRow}>
                          <span style={{ fontSize: 14 }}>{questTypeIcon(hq.type)}</span>
                          <span style={questTitleText}>
                            {localizedQuestText(locale, hq.questKey, 'title', hq.title)}
                          </span>
                          <span style={{ fontSize: 11, fontWeight: 700, color: '#ffd700' }}>
                            {hq.progress}/{hq.targetCount}
                          </span>
                        </div>
                        <div style={rewardsRow}>
                          {hq.rewardXp > 0 && (
                            <span style={{ ...rewardChip, color: '#44aaff' }}>
                              {'\u2728'} {hq.rewardXp} XP
                            </span>
                          )}
                          {hq.rewardGold > 0 && (
                            <span style={{ ...rewardChip, color: '#ffd700' }}>
                              {'\uD83D\uDCB0'} {hq.rewardGold}
                            </span>
                          )}
                          {hq.rewardPotions > 0 && (
                            <span style={{ ...rewardChip, color: '#88dd88' }}>
                              {'\uD83E\uDDEA'} {hq.rewardPotions}
                            </span>
                          )}
                        </div>
                        <button
                          type="button"
                          style={questClaimDisabled ? { ...claimBtnStyle, opacity: 0.45, cursor: 'not-allowed', animation: 'none' } : claimBtnStyle}
                          disabled={questClaimDisabled}
                          title={questClaimDisabled ? tr.claimRewardsDisabledDead : undefined}
                          onClick={() => {
                            if (questClaimDisabled) return;
                            handleClaimQuest(hq.id);
                          }}
                        >
                          {tr.claimRewards}
                        </button>
                      </div>
                    ))}
                  </>
                )}

                {/* In-progress quests */}
                {npcInProgressQuests.length > 0 && (
                  <>
                    <div style={sectionTitleStyle}>{tr.inProgressQuests}</div>
                    {npcInProgressQuests.map((hq) => {
                      const pct = hq.targetCount > 0
                        ? Math.min(100, (hq.progress / hq.targetCount) * 100)
                        : 0;
                      return (
                        <div key={hq.id} style={questCardStyle}>
                          <div style={questTitleRow}>
                            <span style={{ fontSize: 14 }}>{questTypeIcon(hq.type)}</span>
                            <span style={questTitleText}>
                            {localizedQuestText(locale, hq.questKey, 'title', hq.title)}
                          </span>
                            <span style={{ fontSize: 11, fontWeight: 700, color: '#aaa' }}>
                              {hq.progress}/{hq.targetCount}
                            </span>
                          </div>
                          <div style={progressBarOuter}>
                            <div
                              style={{
                                height: '100%',
                                width: `${pct}%`,
                                borderRadius: 3,
                                backgroundColor: '#4a90d9',
                                transition: 'width 300ms ease',
                              }}
                            />
                          </div>
                        </div>
                      );
                    })}
                  </>
                )}

                {/* Available quests */}
                {loading ? (
                  <div style={{ color: '#666', fontSize: 12, textAlign: 'center', padding: 16 }}>
                    {tr.loadingQuests}
                  </div>
                ) : (availableQuests?.length ?? 0) > 0 ? (
                  <>
                    <div style={sectionTitleStyle}>{tr.availableQuestsSection}</div>
                    {(availableQuests ?? []).map((q) => (
                      <div key={q.id} style={questCardStyle}>
                        <div style={questTitleRow}>
                          <span style={{ fontSize: 14 }}>{questTypeIcon(q.type)}</span>
                          <span style={questTitleText}>
                            {localizedQuestText(locale, q.questKey, 'title', q.title)}
                          </span>
                        </div>
                        <div style={questDescStyle}>
                          {localizedQuestText(locale, q.questKey, 'description', q.description)}
                        </div>
                        <div style={rewardsRow}>
                          {q.rewardXp > 0 && (
                            <span style={{ ...rewardChip, color: '#44aaff' }}>
                              {'\u2728'} {q.rewardXp} XP
                            </span>
                          )}
                          {q.rewardGold > 0 && (
                            <span style={{ ...rewardChip, color: '#ffd700' }}>
                              {'\uD83D\uDCB0'} {q.rewardGold}
                            </span>
                          )}
                          {q.rewardPotions > 0 && (
                            <span style={{ ...rewardChip, color: '#88dd88' }}>
                              {'\uD83E\uDDEA'} {q.rewardPotions}
                            </span>
                          )}
                        </div>
                        <button
                          style={acceptBtnStyle}
                          onClick={() => handleAcceptQuest(q.id)}
                        >
                          {tr.acceptQuest}
                        </button>
                      </div>
                    ))}
                  </>
                ) : (
                  npcInProgressQuests.length === 0 &&
                  npcCompletedQuests.length === 0 && (
                    <div style={{ color: '#666', fontSize: 12, textAlign: 'center', padding: 16 }}>
                      {tr.noQuestsRightNow}
                    </div>
                  )
                )}
              </>
            )}

            {/* ---- Gear vendors ---- */}
            {isGearVendorType(npc.type) && (
              <>
                <div style={sectionTitleStyle}>{tr.shopLabel}</div>
                {merchantStockLoading ? (
                  <div style={{ color: '#666', fontSize: 12, textAlign: 'center', padding: 16 }}>
                    {tr.merchantStockLoading}
                  </div>
                ) : merchantStockError ? (
                  <div style={{ color: '#c66', fontSize: 12, textAlign: 'center', padding: 16 }}>
                    {tr.merchantStockFailed}
                  </div>
                ) : merchantOffers.length === 0 ? (
                  <div style={{ color: '#666', fontSize: 12, textAlign: 'center', padding: 16 }}>
                    {tr.merchantEmptyStock}
                  </div>
                ) : (
                  merchantOffers.map((item, offerIndex) => {
                    const rc = offerRarityStyle(item.rarity);
                    const critPct = Math.round((item.critChance ?? 0) * 100);
                    const spd = typeof item.speedModifier === 'number' ? item.speedModifier.toFixed(2) : '?';
                    const rowCost = typeof item.cost === 'number' ? item.cost : 0;
                    const canBuy = rowCost > 0 && heroGold >= rowCost;
                    return (
                      <div
                        key={`${item.formId}-${offerIndex}-${item.name}`}
                        style={{
                          ...questCardStyle,
                          borderColor: 'rgba(200, 180, 80, 0.2)',
                        }}
                      >
                        <div style={questTitleRow}>
                          <span style={{ fontSize: 14 }}>{'\u2694\uFE0F'}</span>
                          <span style={{ ...questTitleText, ...rc }}>{item.name}</span>
                        </div>
                        <div style={{ fontSize: 11, color: '#888', marginTop: 2 }}>
                          {item.rarity.charAt(0).toUpperCase() + item.rarity.slice(1)} · ilvl {item.ilvl} ·{' '}
                          {prettyEquipmentSlot(item.slot)}
                          {item.subtype ? ` · ${item.subtype}` : ''}
                        </div>
                        <div style={{ ...questDescStyle, marginTop: 6 }}>
                          {gearStatLabel(tr, item.statType)} {item.primaryStat}
                          {' · '}
                          crit {critPct}%
                          {' · '}
                          {tr.spd} ×{spd}
                          {(item.agilityBonus ?? 0) > 0 ? ` · +${item.agilityBonus} AGI` : ''}
                        </div>
                        <button
                          type="button"
                          style={
                            canBuy
                              ? { ...serviceBtnStyle, backgroundColor: 'rgba(200, 180, 80, 0.2)', color: '#e8c547' }
                              : { ...disabledBtnStyle, backgroundColor: 'rgba(200, 180, 80, 0.1)', color: '#e8c547' }
                          }
                          onClick={() => handleBuyTownGear(offerIndex)}
                          disabled={!canBuy}
                        >
                          {rowCost > 0 ? t(tr.buyGearForGold, { cost: rowCost }) : tr.failedToBuyGear}
                        </button>
                      </div>
                    );
                  })
                )}
                <div style={{ fontSize: 11, color: '#666', textAlign: 'center', marginTop: 6 }}>
                  {t(tr.yourGoldLabel, { amount: heroGold })}
                </div>
              </>
            )}

            {/* ---- Healer ---- */}
            {npc.type === 'healer' && (
              <>
                <div style={sectionTitleStyle}>{tr.servicesSection}</div>
                <button
                  style={
                    heroGold >= potionCost
                      ? { ...serviceBtnStyle, backgroundColor: 'rgba(68, 200, 68, 0.2)', color: '#88dd88' }
                      : { ...disabledBtnStyle, backgroundColor: 'rgba(68, 200, 68, 0.1)', color: '#88dd88' }
                  }
                  onClick={handleBuyPotion}
                  disabled={heroGold < potionCost}
                >
                  {'\uD83E\uDDEA'} {tr.buyPotion} &mdash; {potionCost} {tr.gold}
                </button>
                <button
                  style={
                    heroGold >= healCost
                      ? { ...serviceBtnStyle, backgroundColor: 'rgba(200, 68, 68, 0.2)', color: '#ff8888' }
                      : { ...disabledBtnStyle, backgroundColor: 'rgba(200, 68, 68, 0.1)', color: '#ff8888' }
                  }
                  onClick={handleHeal}
                  disabled={heroGold < healCost}
                >
                  {'\u2764\uFE0F'} {tr.healToFull} &mdash; {healCost} {tr.gold}
                </button>
                <div style={{ fontSize: 11, color: '#666', textAlign: 'center' }}>
                  {t(tr.yourGoldLabel, { amount: heroGold })}
                </div>
              </>
            )}
          </div>
        </div>
      </div>
    </>
  );
}
