import { useEffect, useState } from 'react';
import { Link } from '@tanstack/react-router';
import { ArrowLeft, Check, Shield, Gauge, Footprints, Award } from 'lucide-react';
import type { Character } from '@/lib/schemas';
import type { AbilityKey, CharacterRulesInput, ProficiencyRank, SystemId } from '@/lib/rules';
import { getSystem, ABILITY_ABBR } from '@/lib/rules';
import { allArmorMechanics5e, type ArmorMechanics } from '@/lib/mechanics';
import { charactersRepo, campaignsRepo } from '@/lib/db/repositories';
import { encodeClaim } from '@/lib/sync/playerLink';
import { fileToDataUrl, squareThumbnail } from '@/lib/img/resize';
import { PF2E_SAVES } from '@/lib/rules/pf2e/skills';
import { useDebouncedCallback } from '@/lib/useDebouncedCallback';
import { rollCheck } from '@/lib/useRoll';
import { cn } from '@/lib/cn';
import { Page } from '@/components/ui/Page';
import { Button } from '@/components/ui/Button';
import { Modal } from '@/components/ui/Modal';
import { Badge, Meter } from '@/components/ui/Codex';
import { Input, Select } from '@/components/ui/Input';
import { NumberField } from '@/components/ui/NumberField';
import { formatModifier } from '@/lib/format';
import { InventorySection } from './sheet/InventorySection';
import { AttacksSection } from './sheet/AttacksSection';
import { ResourcesSection } from './sheet/ResourcesSection';
import { SpellcastingSection } from './sheet/SpellcastingSection';
import { DefensesSection } from './sheet/DefensesSection';
import { FeatsSection } from './sheet/FeatsSection';
import { AbilityGenModal } from './sheet/AbilityGenModal';
import { LevelUpModal } from './sheet/LevelUpModal';

const ABILITIES: AbilityKey[] = ['str', 'dex', 'con', 'int', 'wis', 'cha'];

const RANKS_5E: ProficiencyRank[] = ['untrained', 'trained', 'expert'];
const RANKS_PF2E: ProficiencyRank[] = ['untrained', 'trained', 'expert', 'master', 'legendary'];
const RANK_LABEL: Record<ProficiencyRank, string> = {
  untrained: 'Untrained',
  trained: 'Trained',
  expert: 'Expert',
  master: 'Master',
  legendary: 'Legendary',
};

export function CharacterSheet({ character }: { character: Character }) {
  // Local editable copy; write-through to the DB on change (debounced + flush on unmount).
  const [c, setC] = useState<Character>(character);
  const [levelUp, setLevelUp] = useState(false);
  const [genScores, setGenScores] = useState(false);
  const [shared, setShared] = useState(false);
  // Manual fallback: holds the link to show in a selectable dialog when neither
  // Web Share nor Clipboard is available (e.g. iPad without a secure context).
  const [shareLink, setShareLink] = useState<string | null>(null);

  const onPortrait = async (file: File) => {
    const thumb = await squareThumbnail(await fileToDataUrl(file), 256);
    update({ portrait: thumb });
  };

  const shareWithPlayer = async () => {
    const campaign = await campaignsRepo.get(c.campaignId);
    const link = `${location.origin}/player?c=${encodeClaim({ character: c, campaignName: campaign?.name ?? 'Campaign', campaignSystem: c.system })}`;
    // Prefer the native share sheet (best on iOS/iPadOS), then the clipboard,
    // then a selectable dialog so the link is always recoverable.
    if (typeof navigator.share === 'function') {
      try {
        await navigator.share({ title: `${c.name} — character link`, url: link });
        return;
      } catch (err) {
        // User dismissed the share sheet: respect that and do nothing further.
        if (err instanceof DOMException && err.name === 'AbortError') return;
        // Otherwise fall through to clipboard / manual fallback.
      }
    }
    try {
      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(link);
        setShared(true);
        setTimeout(() => setShared(false), 1500);
        return;
      }
    } catch { /* clipboard blocked (e.g. insecure context on iPad) */ }
    setShareLink(link);
  };
  const save = useDebouncedCallback((next: Character) => {
    // Persist everything except identity/timestamps (update() stamps updatedAt).
    const { id, campaignId: _campaignId, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = next;
    void charactersRepo.update(id, rest);
  }, 350);

  const update = (patch: Partial<Character>) => {
    setC((prev) => {
      const next = { ...prev, ...patch };
      save(next);
      return next;
    });
  };

  const sys = getSystem(c.system);
  const rulesInput: CharacterRulesInput = {
    level: c.level,
    abilities: c.abilities,
    skillRanks: c.skillRanks as Record<string, ProficiencyRank>,
    saveRanks: c.saveRanks as Partial<Record<AbilityKey, ProficiencyRank>>,
    armorBonus: c.armorBonus,
    ...(c.equippedArmor ? { equippedArmor: c.equippedArmor } : {}),
    perceptionRank: c.perceptionRank,
    ...(c.spellcastingRank ? { spellcastingRank: c.spellcastingRank } : {}),
  };

  const ac = sys.baseArmorClass(rulesInput);
  const initiative = sys.initiativeModifier(rulesInput);
  const skills = sys.skillModifiers(rulesInput);
  const saves = sys.saveModifiers(rulesInput);
  const profLabel = c.system === '5e' ? `+${sys.proficiencyValue(c.level, 'trained')} prof` : `level ${c.level}`;
  const ranks = c.system === 'pf2e' ? RANKS_PF2E : RANKS_5E;

  return (
    <Page>
      <div className="mb-4">
        <Link to="/characters" className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-ink">
          <ArrowLeft className="h-4 w-4" aria-hidden />
          Back to characters
        </Link>
      </div>

      {/* Header hero */}
      <div className="paper-grain mb-6 overflow-hidden rounded-xl border border-line bg-panel">
        <div className="flex flex-wrap items-center gap-x-5 gap-y-4 p-5">
          <label className="group relative h-20 w-20 shrink-0 cursor-pointer overflow-hidden rounded-2xl border border-line bg-surface-2" title="Upload portrait (also used as the map token)">
            {c.portrait
              ? <img src={c.portrait} alt="" className="h-full w-full object-cover" />
              : <span className="grid h-full w-full place-items-center text-[10px] text-muted">+ art</span>}
            <span className="absolute inset-x-0 bottom-0 bg-black/55 text-center text-[9px] text-white opacity-0 transition group-hover:opacity-100">edit</span>
            <input type="file" accept="image/*" className="hidden" aria-label="Portrait" onChange={(e) => { const f = e.target.files?.[0]; if (f) void onPortrait(f); e.target.value = ''; }} />
          </label>
          <div className="min-w-0 flex-1">
            <div className="font-display text-sm italic text-accent">{c.ancestry || sys.label}</div>
            <Input
              className="mt-0.5 w-full max-w-md truncate border-transparent bg-transparent px-0 font-display text-2xl font-semibold leading-tight tracking-tight focus-visible:border-line sm:text-3xl"
              value={c.name}
              aria-label="Character name"
              onChange={(e) => update({ name: e.target.value })}
            />
            <div className="mt-2 flex flex-wrap items-center gap-2">
              <Badge tone="gold">Level {c.level}</Badge>
              <Badge>{c.kind === 'pc' ? 'PC' : 'NPC'} · {sys.label}</Badge>
              <Badge tone="default">
                <Award className="h-3 w-3" aria-hidden />
                {profLabel}
              </Badge>
            </div>
          </div>
          <div className="flex w-full items-center gap-2 sm:w-auto">
            {[
              { icon: Shield, value: ac, label: 'Armor' },
              { icon: Gauge, value: formatModifier(initiative), label: 'Init' },
              { icon: Footprints, value: c.speed, label: 'Speed' },
            ].map(({ icon: Ic, value, label }) => (
              <div key={label} className="flex flex-1 flex-col items-center gap-1 rounded-xl border border-line bg-surface-2 px-3 py-2.5 text-center sm:w-[4.5rem] sm:flex-none">
                <Ic className="h-4 w-4 text-accent-deep" aria-hidden />
                <span className="font-mono font-display text-xl font-semibold leading-none text-ink">{value}</span>
                <span className="smallcaps text-[8.5px]">{label}</span>
              </div>
            ))}
          </div>
        </div>
        <div className="flex flex-wrap items-center justify-end gap-2 border-t border-line bg-surface-2 px-5 py-3">
          {c.kind === 'pc' && <Button size="sm" variant="ghost" onClick={() => void shareWithPlayer()} title="Copy a link the player opens to manage this character">{shared ? <><Check size={12} aria-hidden /> Link copied</> : 'Share with player'}</Button>}
          <Button size="sm" variant="secondary" onClick={() => setLevelUp(true)}>Level up</Button>
          <Button size="sm" variant="ghost" onClick={() => window.print()} title="Print / save as PDF">Print</Button>
        </div>
      </div>

      <div className="paper-grain mb-6 grid gap-4 rounded-xl border border-line bg-panel p-4 sm:grid-cols-2 lg:grid-cols-4">
        <Labeled label={c.system === 'pf2e' ? 'Ancestry' : 'Race'}>
          <Input value={c.ancestry} onChange={(e) => update({ ancestry: e.target.value })} />
        </Labeled>
        <Labeled label="Class">
          <Input value={c.className} onChange={(e) => update({ className: e.target.value })} />
        </Labeled>
        <Labeled label="Level">
          <NumberField value={c.level} min={1} max={20} onChange={(level) => update({ level })} />
          <p className="mt-1 text-[10px] leading-tight text-muted">Direct edits update proficiency-based stats only — HP and spell slots aren’t recalculated. Use <span className="text-ink">Level Up</span> to advance a level.</p>
        </Labeled>
        <Labeled label="Speed">
          <NumberField value={c.speed} min={0} onChange={(speed) => update({ speed })} />
        </Labeled>
      </div>

      {/* Vital stats */}
      <div className="mb-6 grid gap-3 sm:grid-cols-3">
        <StatCard label="Armor Class" value={ac} hint={c.equippedArmor ? `${c.equippedArmor.name}${c.armorBonus ? ` + ${c.armorBonus}` : ''}` : `10 + DEX${c.armorBonus ? ` + ${c.armorBonus}` : ''}`}>
          <div className="mt-2 flex flex-col items-center gap-2 text-xs text-muted">
            {c.system === '5e' && <ArmorPicker c={c} update={update} />}
            <div className="flex items-center gap-2">
              <span>Shield/misc bonus</span>
              <NumberField className="w-16" value={c.armorBonus} onChange={(armorBonus) => update({ armorBonus })} aria-label="Armor bonus" />
            </div>
          </div>
        </StatCard>

        <StatCard label="Initiative" value={formatModifier(initiative)} hint={c.system === 'pf2e' ? 'Perception' : 'DEX mod'} />

        <HpCard c={c} update={update} />
      </div>

      {/* Abilities */}
      <section className="mb-6">
        <div className="mb-3 flex items-center justify-between">
          <SectionTitle>Ability Scores</SectionTitle>
          <Button size="sm" variant="ghost" onClick={() => setGenScores(true)}>Generate</Button>
        </div>
        <div className="grid grid-cols-3 gap-3 sm:grid-cols-6">
          {ABILITIES.map((a) => {
            const mod = sys.abilityModifier(c.abilities[a]);
            return (
              <div key={a} className="flex flex-col items-center gap-1 rounded-xl border border-line bg-panel px-2 py-3 text-center">
                <div className="smallcaps text-[10px]">{ABILITY_ABBR[a]}</div>
                <div className="font-mono font-display text-2xl font-semibold leading-none text-accent-deep">{formatModifier(mod)}</div>
                <NumberField
                  className="mt-1"
                  value={c.abilities[a]}
                  min={1}
                  max={30}
                  aria-label={`${ABILITY_ABBR[a]} score`}
                  onChange={(v) => update({ abilities: { ...c.abilities, [a]: v } })}
                />
              </div>
            );
          })}
        </div>
      </section>

      {/* Perception (pf2e core proficiency — advances to legendary; drives initiative) */}
      {c.system === 'pf2e' && (
        <section className="mb-6">
          <SectionTitle>Perception</SectionTitle>
          <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
            <RankRow
              label="Perception (WIS)"
              modifier={sys.passivePerception(rulesInput) - 10}
              rank={c.perceptionRank}
              ranks={ranks}
              onRank={(rank) => update({ perceptionRank: rank })}
              system={c.system}
            />
          </div>
        </section>
      )}

      {/* Saves */}
      <section className="mb-6">
        <SectionTitle>Saving Throws</SectionTitle>
        <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
          {c.system === 'pf2e'
            ? PF2E_SAVES.map((s) => (
                <RankRow
                  key={s.key}
                  label={s.label}
                  modifier={saves[s.ability]}
                  rank={(c.saveRanks[s.ability] as ProficiencyRank) ?? 'untrained'}
                  ranks={ranks}
                  onRank={(rank) => update({ saveRanks: { ...c.saveRanks, [s.ability]: rank } })}
                  system={c.system}
                />
              ))
            : ABILITIES.map((a) => (
                <RankRow
                  key={a}
                  label={`${ABILITY_ABBR[a]} save`}
                  modifier={saves[a]}
                  rank={(c.saveRanks[a] as ProficiencyRank) ?? 'untrained'}
                  ranks={ranks}
                  onRank={(rank) => update({ saveRanks: { ...c.saveRanks, [a]: rank } })}
                  system={c.system}
                />
              ))}
        </div>
      </section>

      {/* Skills */}
      <section className="mb-6">
        <SectionTitle>Skills</SectionTitle>
        <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
          {skills.map((s) => (
            <RankRow
              key={s.key}
              label={`${s.label} (${ABILITY_ABBR[s.ability]})`}
              modifier={s.modifier}
              rank={(c.skillRanks[s.key] as ProficiencyRank) ?? 'untrained'}
              ranks={ranks}
              onRank={(rank) => update({ skillRanks: { ...c.skillRanks, [s.key]: rank } })}
              system={c.system}
            />
          ))}
        </div>
      </section>

      {/* Status & defenses (system-specific) */}
      <DefensesSection c={c} update={update} />

      {/* Attacks + passive perception */}
      <AttacksSection c={c} update={update} />

      {/* Spellcasting */}
      <SpellcastingSection c={c} update={update} />

      {/* Class resources + rest */}
      <ResourcesSection c={c} update={update} />

      {/* Feats & features */}
      <FeatsSection c={c} update={update} />

      {/* Inventory, currency, encumbrance */}
      <InventorySection c={c} update={update} />

      {/* Notes */}
      <section className="mb-6">
        <SectionTitle>Notes</SectionTitle>
        <textarea
          value={c.notes}
          onChange={(e) => update({ notes: e.target.value })}
          placeholder="Backstory, bonds, reminders…"
          className="min-h-32 w-full resize-y rounded-md border border-line bg-surface px-3 py-2 text-sm text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/60"
        />
      </section>

      {genScores && (
        <AbilityGenModal onClose={() => setGenScores(false)} onApply={(abilities) => update({ abilities })} />
      )}
      {levelUp && (
        <LevelUpModal character={c} onClose={() => setLevelUp(false)} onApply={(patch) => update(patch)} />
      )}
      {shareLink !== null && (
        <Modal
          open
          onClose={() => setShareLink(null)}
          title="Share with player"
          footer={<Button size="sm" variant="ghost" onClick={() => setShareLink(null)}>Done</Button>}
        >
          <p className="mb-2 text-sm text-muted">Copy this link and send it to the player:</p>
          <Input
            data-autofocus
            readOnly
            value={shareLink}
            aria-label="Player link"
            className="font-mono text-xs"
            onFocus={(e) => e.currentTarget.select()}
          />
        </Modal>
      )}
    </Page>
  );
}

function HpCard({ c, update }: { c: Character; update: (p: Partial<Character>) => void }) {
  const [delta, setDelta] = useState(0);
  const apply = (mode: 'damage' | 'heal') => {
    if (!Number.isFinite(delta) || delta <= 0) return;
    if (mode === 'damage') {
      const absorbed = Math.min(c.hp.temp, delta);
      update({ hp: { ...c.hp, temp: c.hp.temp - absorbed, current: c.hp.current - (delta - absorbed) } });
    } else {
      update({ hp: { ...c.hp, current: Math.min(c.hp.max, c.hp.current + delta) } });
    }
    setDelta(0);
  };
  const ratio = c.hp.max > 0 ? c.hp.current / c.hp.max : 0;
  return (
    <div className="rounded-xl border border-line bg-panel p-4 text-center">
      <div className="smallcaps">Hit Points</div>
      <div className="my-1 font-mono font-display text-2xl font-semibold text-ink">
        {c.hp.current}
        <span className="text-base text-muted"> / {c.hp.max}</span>
        {c.hp.temp > 0 && <span className="ml-1 text-base text-info">(+{c.hp.temp})</span>}
      </div>
      <div className="mb-2">
        <Meter value={c.hp.current} max={c.hp.max} tone={ratio < 0.35 ? 'var(--app-danger)' : 'var(--app-verdigris)'} height={8} />
      </div>
      <div className="grid grid-cols-3 gap-1 text-[11px] text-muted">
        <label>Cur<NumberField value={c.hp.current} onChange={(current) => update({ hp: { ...c.hp, current } })} aria-label="Current HP" /></label>
        <label>Max<NumberField value={c.hp.max} min={0} onChange={(max) => update({ hp: { ...c.hp, max } })} aria-label="Max HP" /></label>
        <label>Temp<NumberField value={c.hp.temp} min={0} onChange={(temp) => update({ hp: { ...c.hp, temp } })} aria-label="Temp HP" /></label>
      </div>
      <div className="mt-2 flex items-center gap-1">
        <NumberField className="w-16" value={delta} min={0} onChange={setDelta} aria-label="HP change amount" />
        <Button size="sm" variant="danger" className="flex-1" onClick={() => apply('damage')}>
          Damage
        </Button>
        <Button size="sm" variant="secondary" className="flex-1" onClick={() => apply('heal')}>
          Heal
        </Button>
      </div>
    </div>
  );
}

function RankRow({
  label,
  modifier,
  rank,
  ranks,
  onRank,
  system,
}: {
  label: string;
  modifier: number;
  rank: ProficiencyRank;
  ranks: ProficiencyRank[];
  onRank: (r: ProficiencyRank) => void;
  system: SystemId;
}) {
  const proficient = rank !== 'untrained';
  return (
    <div className="flex items-center gap-2 rounded-lg border border-line bg-panel px-3 py-1.5">
      <span
        className={cn('h-2 w-2 shrink-0 rounded-full', proficient ? 'bg-accent' : 'bg-line-strong')}
        aria-hidden
      />
      <button
        onClick={() => rollCheck(modifier, label, { system })}
        className="w-9 rounded font-mono font-display text-lg font-semibold text-accent-deep hover:bg-accent/10"
        title={`Roll ${label}`}
      >
        {formatModifier(modifier)}
      </button>
      <span className="flex-1 truncate text-sm text-ink">{label}</span>
      <Select className="w-auto py-1 text-xs" value={rank} onChange={(e) => onRank(e.target.value as ProficiencyRank)}>
        {ranks.map((r) => (
          <option key={r} value={r}>
            {RANK_LABEL[r]}
          </option>
        ))}
      </Select>
    </div>
  );
}

function Labeled({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <label className="block">
      <span className="smallcaps mb-1 block">{label}</span>
      {children}
    </label>
  );
}

function StatCard({ label, value, hint, children }: { label: string; value: string | number; hint?: string; children?: React.ReactNode }) {
  return (
    <div className="rounded-xl border border-line bg-panel p-4 text-center">
      <div className="smallcaps">{label}</div>
      <div className="my-1 font-mono font-display text-3xl font-semibold text-accent-deep">{value}</div>
      {hint && <div className="text-xs text-muted">{hint}</div>}
      {children}
    </div>
  );
}

function ArmorPicker({ c, update }: { c: Character; update: (p: Partial<Character>) => void }) {
  const [armors, setArmors] = useState<ArmorMechanics[]>([]);
  useEffect(() => { void allArmorMechanics5e().then(setArmors); }, []);
  // Body armor only — shields are handled via the misc bonus field.
  const body = armors.filter((a) => a.category !== 'shield');
  return (
    <Select
      className="w-auto py-1 text-xs"
      aria-label="Equipped armor"
      value={c.equippedArmor?.name ?? ''}
      onChange={(e) => {
        const name = e.target.value;
        const found = body.find((a) => a.name === name);
        update({ equippedArmor: found ? { name: found.name, category: found.category, baseAc: found.baseAc, dexCap: found.dexCap, stealthDisadvantage: found.stealthDisadvantage } : null });
      }}
    >
      <option value="">Unarmored</option>
      {body.map((a) => <option key={a.name} value={a.name}>{a.name} (AC {a.baseAc}{a.dexCap === 0 ? '' : a.dexCap === null ? ' + Dex' : ` + Dex≤${a.dexCap}`})</option>)}
    </Select>
  );
}

function SectionTitle({ children }: { children: React.ReactNode }) {
  return <h2 className="mb-3 font-display text-lg font-semibold text-ink">{children}</h2>;
}
