import { useEffect, useState } from 'react';
import { useLiveQuery } from 'dexie-react-hooks';
import { Dices, Sparkles, Skull, History as HistoryIcon } from 'lucide-react';
import { rollDice, applyRollMode, naturalD20, DiceParseError, type RollResult } from '@/lib/dice/notation';
import { createRng } from '@/lib/rng';
import { diceRepo } from '@/lib/db/repositories';
import { broadcastGmRoll } from '@/lib/sync/wsSync';
import { useUiStore } from '@/stores/uiStore';
import { useMacroStore } from '@/stores/macroStore';
import { useRollStore } from '@/stores/rollStore';
import { useSessionStore } from '@/stores/sessionStore';
import { Page, PageHeader } from '@/components/ui/Page';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Badge } from '@/components/ui/Codex';
import { cn } from '@/lib/cn';

const DICE = ['d4', 'd6', 'd8', 'd10', 'd12', 'd20', 'd100'];
const DIE_SIDES: Record<string, number> = { d4: 4, d6: 6, d8: 8, d10: 10, d12: 12, d20: 20, d100: 100 };

/** A faceted-die glyph with the side count inscribed — used for the quick-roll pool. */
function DieGlyph({ sides, size = 44 }: { sides: number; size?: number }) {
  return (
    <span className="relative grid place-items-center text-line-strong transition-colors group-hover:text-accent" style={{ width: size, height: size }}>
      <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor" strokeWidth={1.4} strokeLinejoin="round" aria-hidden>
        <polygon points="12,2 21,7 21,17 12,22 3,17 3,7" />
      </svg>
      <span className="absolute inset-0 grid place-items-center pt-px font-mono text-[11px] font-semibold text-ink transition-colors group-hover:text-accent-deep">
        {sides}
      </span>
    </span>
  );
}

export function DicePage() {
  const campaignId = useUiStore((s) => s.activeCampaignId) ?? undefined;
  const [expr, setExpr] = useState('1d20');
  const [last, setLast] = useState<RollResult | null>(null);
  const [rollCount, setRollCount] = useState(0);
  const [error, setError] = useState<string | null>(null);

  const history = useLiveQuery(() => diceRepo.recent(campaignId, 30), [campaignId], []);

  const macroKey = campaignId ?? '';
  const macros = useMacroStore((s) => s.byCampaign[macroKey]) ?? [];
  const addMacro = useMacroStore((s) => s.add);
  const removeMacro = useMacroStore((s) => s.remove);

  const mode = useRollStore((s) => s.mode);
  const toggleMode = useRollStore((s) => s.toggleMode);
  const secret = useRollStore((s) => s.secret);
  const toggleSecret = useRollStore((s) => s.toggleSecret);
  const isGm = useSessionStore((s) => s.role) === 'gm';

  const roll = async (expression: string, label = '') => {
    try {
      const finalExpr = applyRollMode(expression, mode);
      const result = rollDice(finalExpr, createRng());
      setLast(result);
      setRollCount((n) => n + 1);
      setError(null);
      const modeTag = mode === 'advantage' ? ' (adv)' : mode === 'disadvantage' ? ' (dis)' : '';
      await diceRepo.add({
        ...(campaignId ? { campaignId } : {}),
        label: label + modeTag,
        expression: finalExpr,
        total: result.total,
        breakdown: result.breakdown,
      });
      if (!useRollStore.getState().secret) broadcastGmRoll((label + modeTag).trim() || 'Roll', finalExpr, result.total, result.breakdown);
    } catch (e) {
      setError(e instanceof DiceParseError ? e.message : 'Could not roll that expression');
    }
  };

  return (
    <Page>
      <PageHeader
        eyebrow="Cast the bones"
        title="Dice"
        subtitle="Standard notation: 2d6+3, 4d6kh3 (keep highest 3), 1d20."
        actions={
          history.length > 0 ? (
            <Button variant="ghost" onClick={() => void diceRepo.clear(campaignId)}>
              Clear history
            </Button>
          ) : null
        }
      />

      <div className="grid gap-6 lg:grid-cols-[1fr_320px]">
        <div>
          {/* Result stage */}
          {last ? (() => {
            const nat = naturalD20(last);
            const crit = nat === 20 ? 'crit' : nat === 1 ? 'fumble' : null;
            return (
              <div
                className={cn(
                  'paper-grain mb-6 overflow-hidden rounded-xl border bg-panel p-7 text-center',
                  crit === 'crit' ? 'animate-crit-glow border-accent' : crit === 'fumble' ? 'border-danger' : 'border-line',
                )}
              >
                <div className="font-mono text-xs text-faint">{last.expression}</div>
                {crit === 'crit' && (
                  <div className="mt-2 flex justify-center">
                    <Badge tone="verdigris">
                      <Sparkles size={12} aria-hidden /> Critical!
                    </Badge>
                  </div>
                )}
                {crit === 'fumble' && (
                  <div className="mt-2 flex justify-center">
                    <Badge tone="ember">
                      <Skull size={12} aria-hidden /> Fumble!
                    </Badge>
                  </div>
                )}
                <AnimatedTotal key={rollCount} result={last} />
                <div className="mt-2 font-mono text-sm text-muted">{last.breakdown}</div>
                <span className="sr-only" aria-live="polite">
                  Rolled {last.expression}: {last.total}
                </span>
              </div>
            );
          })() : (
            <div className="paper-grain mb-6 grid min-h-[140px] place-items-center rounded-xl border border-line bg-panel p-7 text-center">
              <p className="font-display text-lg italic text-muted">Choose your dice, then cast.</p>
            </div>
          )}

          {/* Expression + Roll */}
          <div className="flex gap-2">
            <Input
              value={expr}
              onChange={(e) => setExpr(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter') void roll(expr);
              }}
              placeholder="e.g. 2d6+3"
              aria-label="Dice expression"
            />
            <Button variant="primary" onClick={() => void roll(expr)}>
              <Dices size={16} aria-hidden />
              Roll
            </Button>
          </div>

          {/* Die pool */}
          <div className="mt-4 flex flex-wrap items-end justify-center gap-3 sm:justify-start">
            {DICE.map((d) => (
              <button
                key={d}
                type="button"
                onClick={() => void roll(`1${d}`)}
                aria-label={d}
                className="group grid place-items-center rounded-lg p-1 transition-transform hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/60"
              >
                <DieGlyph sides={DIE_SIDES[d] ?? 20} />
                <span className="mt-0.5 font-mono text-[11px] text-muted transition-colors group-hover:text-accent-deep">{d}</span>
              </button>
            ))}
          </div>

          {/* Advantage / Disadvantage segmented toggle + secret */}
          <div className="mt-5 flex flex-wrap items-center gap-3">
            <span className="smallcaps">Mode</span>
            <div className="inline-flex gap-0.5 rounded-lg border border-line bg-surface-2 p-0.5" role="group" aria-label="Roll mode">
              <button
                type="button"
                aria-pressed={mode === 'advantage'}
                onClick={() => toggleMode('advantage')}
                title="Toggle advantage — rolls the next die twice, keep highest"
                className={cn(
                  'whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
                  mode === 'advantage' ? 'bg-panel text-ink shadow-sm' : 'text-muted hover:text-ink',
                )}
              >
                Advantage
              </button>
              <button
                type="button"
                aria-pressed={mode === 'disadvantage'}
                onClick={() => toggleMode('disadvantage')}
                title="Toggle disadvantage — rolls the next die twice, keep lowest"
                className={cn(
                  'whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
                  mode === 'disadvantage' ? 'bg-panel text-ink shadow-sm' : 'text-muted hover:text-ink',
                )}
              >
                Disadvantage
              </button>
            </div>
            {isGm && (
              <Button
                size="sm"
                variant={secret ? 'primary' : 'secondary'}
                aria-pressed={secret}
                onClick={toggleSecret}
                title="When on, your rolls are NOT shared with the players"
              >
                🤫 Secret
              </Button>
            )}
          </div>
          {mode !== 'normal' && (
            <p className="mt-2 text-xs text-accent">
              {mode === 'advantage' ? 'Advantage' : 'Disadvantage'} is on — the next single die rolls twice, keeping the {mode === 'advantage' ? 'highest' : 'lowest'}.
            </p>
          )}
          {isGm && secret && (
            <p className="mt-2 text-xs text-warning">🤫 Secret is on — your rolls are not shared with the players.</p>
          )}

          {/* Saved rolls (macros) */}
          <div className="mt-6">
            <div className="flex items-center gap-2">
              <span className="smallcaps">Saved rolls</span>
              <Button
                size="sm"
                variant="ghost"
                disabled={expr.trim() === ''}
                onClick={() => addMacro(macroKey, expr.trim(), expr.trim())}
              >
                + Save “{expr.trim() || '…'}”
              </Button>
            </div>
            {macros.length > 0 && (
              <div className="mt-2 flex flex-wrap gap-1.5">
                {macros.map((m) => (
                  <span key={m.id} className="inline-flex items-center overflow-hidden rounded-md border border-line bg-panel">
                    <button
                      className="px-2.5 py-1 font-mono text-sm text-ink hover:bg-elevated"
                      onClick={() => { setExpr(m.expression); void roll(m.expression, m.label !== m.expression ? m.label : ''); }}
                    >
                      {m.label}
                    </button>
                    <button
                      className="px-1.5 py-1 text-muted hover:text-danger"
                      onClick={() => removeMacro(macroKey, m.id)}
                      aria-label={`Remove ${m.label}`}
                    >
                      ✕
                    </button>
                  </span>
                ))}
              </div>
            )}
          </div>

          {error && <p className="mt-4 text-sm text-danger">{error}</p>}
        </div>

        {/* History rail */}
        <aside>
          <div className="mb-2 flex items-center gap-2">
            <HistoryIcon size={14} className="text-accent" aria-hidden />
            <h2 className="smallcaps">History</h2>
          </div>
          <hr className="gilt-rule mb-3" />
          {history.length === 0 ? (
            <p className="text-sm text-muted">No rolls yet.</p>
          ) : (
            <ul className="space-y-1.5" aria-live="polite">
              {history.map((h) => (
                <li
                  key={h.id}
                  className="flex items-center justify-between gap-3 rounded-lg border border-line bg-panel px-3 py-2 text-sm"
                >
                  <span className="min-w-0 truncate text-muted">
                    {h.label ? `${h.label}: ` : ''}
                    <span className="font-mono">{h.expression}</span>
                  </span>
                  <span className="shrink-0 font-display text-lg font-semibold text-accent-deep">{h.total}</span>
                </li>
              ))}
            </ul>
          )}
        </aside>
      </div>
    </Page>
  );
}

/**
 * Shows the rolling die "tumbling" through random numbers before settling on the
 * real total. Mounted with a per-roll key so each roll replays the animation.
 */
function AnimatedTotal({ result }: { result: RollResult }) {
  const [display, setDisplay] = useState(result.total);
  const [rolling, setRolling] = useState(true);

  useEffect(() => {
    setRolling(true);
    let frames = 0;
    const maxFrames = 9;
    const span = Math.max(6, Math.abs(result.total) + 4);
    const id = setInterval(() => {
      frames++;
      if (frames >= maxFrames) {
        clearInterval(id);
        setDisplay(result.total);
        setRolling(false);
      } else {
        setDisplay(Math.floor(Math.random() * span) + 1);
      }
    }, 45);
    return () => clearInterval(id);
  }, [result]);

  return (
    <div
      aria-hidden
      className={cn(
        'mt-2 font-display text-7xl font-bold text-accent tabular-nums',
        rolling ? 'animate-dice-rolling opacity-80' : 'animate-dice-settle',
      )}
    >
      {display}
    </div>
  );
}
