import { useState, useEffect, useRef } from 'react';
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  TouchSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
} from '@dnd-kit/core';
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
  arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { SPECIALIZATIONS } from '../data/specializations';
import { courseById } from '../data/lookups';
import type { SpecStatus, AllocationResult } from '../data/types';

export const STATUS_STYLES: Record<SpecStatus, { bg: string; color: string; label: string }> = {
  achieved: { bg: '#dcfce7', color: '#16a34a', label: 'Achieved' },
  achievable: { bg: '#dbeafe', color: '#2563eb', label: 'Achievable' },
  missing_required: { bg: '#fef3c7', color: '#d97706', label: 'Missing Req.' },
  unreachable: { bg: '#f3f4f6', color: '#9ca3af', label: 'Unreachable' },
};

function CreditBar({ allocated, potential, threshold }: { allocated: number; potential: number; threshold: number }) {
  const maxWidth = Math.max(potential, threshold);
  const allocPct = Math.min((allocated / maxWidth) * 100, 100);
  const potentialPct = Math.min((potential / maxWidth) * 100, 100);

  // Generate tick marks at 2.5 credit intervals
  const ticks: number[] = [];
  for (let t = 2.5; t < maxWidth; t += 2.5) {
    ticks.push(t);
  }

  return (
    <div style={{ position: 'relative', height: '6px', background: '#e5e7eb', borderRadius: '3px', marginTop: '4px' }}>
      {potential > allocated && (
        <div
          style={{
            position: 'absolute', left: 0, top: 0, height: '100%',
            width: `${potentialPct}%`, background: '#bfdbfe', borderRadius: '3px',
            transition: 'width 300ms ease-out',
          }}
        />
      )}
      <div
        style={{
          position: 'absolute', left: 0, top: 0, height: '100%',
          width: `${allocPct}%`, background: allocated >= threshold ? '#22c55e' : '#3b82f6',
          borderRadius: '3px', transition: 'width 300ms ease-out',
        }}
      />
      {/* Tick marks at 2.5 credit intervals — rendered above bar fills */}
      {ticks.map((t) => (
        <div
          key={t}
          style={{
            position: 'absolute', left: `${(t / maxWidth) * 100}%`, top: 0,
            width: '1px', height: '6px', background: 'rgba(0,0,0,0.2)',
            zIndex: 1, transition: 'left 300ms ease-out',
          }}
        />
      ))}
      <div
        style={{
          position: 'absolute', left: `${(threshold / maxWidth) * 100}%`, top: '-2px',
          width: '2px', height: '10px', background: '#666',
          zIndex: 2, transition: 'left 300ms ease-out',
        }}
      />
    </div>
  );
}

function AllocationBreakdown({ specId, allocations }: { specId: string; allocations: Record<string, Record<string, number>> }) {
  const contributions: { courseName: string; credits: number }[] = [];
  for (const [courseId, specAlloc] of Object.entries(allocations)) {
    const credits = specAlloc[specId];
    if (credits && credits > 0) {
      const course = courseById[courseId];
      contributions.push({ courseName: course?.name ?? courseId, credits });
    }
  }
  if (contributions.length === 0) return null;

  return (
    <div style={{ marginTop: '6px', paddingLeft: '28px', fontSize: '12px', color: '#555' }}>
      {contributions.map((c, i) => (
        <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
          <span>{c.courseName}</span>
          <span style={{ fontWeight: 600 }}>{c.credits.toFixed(1)}</span>
        </div>
      ))}
    </div>
  );
}

interface SortableItemProps {
  id: string;
  rank: number;
  total: number;
  name: string;
  status: SpecStatus;
  allocated: number;
  potential: number;
  isExpanded: boolean;
  allocations: Record<string, Record<string, number>>;
  onMoveUp: () => void;
  onMoveDown: () => void;
  onToggleExpand: () => void;
}

function SortableItem({ id, rank, total, name, status, allocated, potential, isExpanded, allocations, onMoveUp, onMoveDown, onToggleExpand }: SortableItemProps) {
  const {
    attributes,
    listeners,
    setNodeRef,
    setActivatorNodeRef,
    transform,
    transition: dndTransition,
    isDragging,
  } = useSortable({ id });

  const style = STATUS_STYLES[status] || STATUS_STYLES.unreachable;
  const isAchieved = status === 'achieved';

  // Track changes and flash on status/credit updates
  const prevStatusRef = useRef(status);
  const prevAllocatedRef = useRef(allocated);
  const [flash, setFlash] = useState(false);

  useEffect(() => {
    const statusChanged = prevStatusRef.current !== status;
    const creditsChanged = prevAllocatedRef.current !== allocated;
    prevStatusRef.current = status;
    prevAllocatedRef.current = allocated;
    if (statusChanged || creditsChanged) {
      setFlash(true);
      const timer = setTimeout(() => setFlash(false), 400);
      return () => clearTimeout(timer);
    }
  }, [status, allocated]);

  const rowStyle: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition: [dndTransition, 'background 400ms ease-out, box-shadow 400ms ease-out'].filter(Boolean).join(', '),
    opacity: isDragging ? 0.5 : 1,
    marginBottom: '4px',
    borderRadius: '6px',
    background: isDragging ? '#e8e8e8' : flash ? '#fef9c3' : style.bg,
    border: '1px solid #ddd',
    padding: '6px 10px',
    boxShadow: flash ? '0 0 0 2px #facc15' : 'none',
  };

  const arrowBtn: React.CSSProperties = {
    border: 'none',
    background: 'none',
    cursor: 'pointer',
    padding: '0 2px',
    fontSize: '14px',
    color: '#999',
    lineHeight: 1,
  };

  return (
    <div ref={setNodeRef} style={rowStyle} {...attributes}>
      <div
        style={{ display: 'flex', alignItems: 'center', gap: '6px', cursor: isAchieved ? 'pointer' : 'default' }}
        onClick={isAchieved ? onToggleExpand : undefined}
      >
        <div style={{ display: 'flex', flexDirection: 'column' }} onClick={(e) => e.stopPropagation()}>
          <button style={{ ...arrowBtn, visibility: rank === 1 ? 'hidden' : 'visible' }} onClick={onMoveUp} aria-label="Move up">▲</button>
          <button style={{ ...arrowBtn, visibility: rank === total ? 'hidden' : 'visible' }} onClick={onMoveDown} aria-label="Move down">▼</button>
        </div>
        <span
          ref={setActivatorNodeRef}
          {...listeners}
          style={{ cursor: 'grab', color: '#ccc', fontSize: '14px', touchAction: 'none' }}
          aria-label="Drag to reorder"
          onClick={(e) => e.stopPropagation()}
        >⠿</span>
        <span style={{ color: '#999', fontSize: '12px', minWidth: '20px' }}>{rank}.</span>
        <span style={{ flex: 1, fontSize: '13px', color: '#333' }}>{name}</span>
        <span style={{ fontSize: '11px', color: '#888', whiteSpace: 'nowrap' }}>
          {allocated > 0 ? allocated.toFixed(1) : '0'} / 9.0
        </span>
        <span
          style={{
            fontSize: '11px', padding: '2px 6px', borderRadius: '10px',
            background: style.color + '20', color: style.color, fontWeight: 600,
            whiteSpace: 'nowrap', transition: 'background 200ms, color 200ms',
          }}
        >
          {style.label}
        </span>
      </div>
      <CreditBar allocated={allocated} potential={potential} threshold={9} />
      <div style={{
        maxHeight: isAchieved && isExpanded ? '200px' : '0',
        overflow: 'hidden',
        transition: 'max-height 200ms ease-out',
      }}>
        <AllocationBreakdown specId={id} allocations={allocations} />
      </div>
    </div>
  );
}

interface SpecializationRankingProps {
  ranking: string[];
  result: AllocationResult;
  onReorder: (ranking: string[]) => void;
}

export function SpecializationRanking({ ranking, result, onReorder }: SpecializationRankingProps) {
  const [expanded, setExpanded] = useState<Set<string>>(() => new Set(result.achieved));
  const prevAchievedRef = useRef(result.achieved);

  useEffect(() => {
    const prev = prevAchievedRef.current;
    if (prev !== result.achieved) {
      prevAchievedRef.current = result.achieved;
      const newlyAchieved = result.achieved.filter((id) => !prev.includes(id));
      if (newlyAchieved.length > 0) {
        setExpanded((s) => {
          const next = new Set(s);
          for (const id of newlyAchieved) next.add(id);
          return next;
        });
      }
    }
  }, [result.achieved]);
  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 5 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  function handleDragEnd(event: DragEndEvent) {
    const { active, over } = event;
    if (over && active.id !== over.id) {
      const oldIndex = ranking.indexOf(active.id as string);
      const newIndex = ranking.indexOf(over.id as string);
      onReorder(arrayMove(ranking, oldIndex, newIndex));
    }
  }

  function toggleExpand(specId: string) {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(specId)) next.delete(specId);
      else next.add(specId);
      return next;
    });
  }

  function getAllocatedCredits(specId: string): number {
    let total = 0;
    for (const specAlloc of Object.values(result.allocations)) {
      total += specAlloc[specId] || 0;
    }
    return total;
  }

  const specMap = new Map(SPECIALIZATIONS.map((s) => [s.id, s]));

  return (
    <div>
      <h2 style={{ fontSize: '16px', marginBottom: '4px' }}>Specializations</h2>
      <p style={{ fontSize: '12px', color: '#888', margin: '0 0 8px' }}>Drag or use arrows to rank your preferences. The optimizer uses this order to allocate credits.</p>
      <div style={{ marginBottom: '8px', fontSize: '13px', color: '#666' }}>
        {result.achieved.length > 0
          ? `${result.achieved.length} specialization${result.achieved.length > 1 ? 's' : ''} achieved`
          : 'No specializations achieved yet'}
      </div>
      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
        <SortableContext items={ranking} strategy={verticalListSortingStrategy}>
          {ranking.map((id, i) => (
            <SortableItem
              key={id}
              id={id}
              rank={i + 1}
              total={ranking.length}
              name={specMap.get(id)?.name ?? id}
              status={result.statuses[id]}
              allocated={getAllocatedCredits(id)}
              potential={result.upperBounds[id] || 0}
              isExpanded={expanded.has(id)}
              allocations={result.allocations}
              onMoveUp={() => { if (i > 0) onReorder(arrayMove([...ranking], i, i - 1)); }}
              onMoveDown={() => { if (i < ranking.length - 1) onReorder(arrayMove([...ranking], i, i + 1)); }}
              onToggleExpand={() => toggleExpand(id)}
            />
          ))}
        </SortableContext>
      </DndContext>
    </div>
  );
}
