import {
  CartesianGrid,
  Line,
  LineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from 'recharts';

type ChartTheme = 'dark' | 'light';

type ComplexityProfile = {
  key: string;
  label: string;
  color: string;
  inputSizes: number[];
  getGrowth: (n: number) => number;
  getAuxiliaryLabel?: (n: number) => string;
  inputLabel?: string;
  note?: string;
};

type ComplexityPoint = {
  n: number;
  estimatedGrowth: number;
  normalizedGrowth: number;
  auxiliaryLabel?: string;
};

type ComplexityGrowthChartProps = {
  complexity?: string | null;
  theme?: ChartTheme;
  className?: string;
};

const STANDARD_INPUT_SIZES = [1, 2, 4, 8, 16, 32, 64, 128];
const EXPONENTIAL_INPUT_SIZES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const normalizeComplexity = (value?: string | null) =>
  (value ?? '').replace(/\s+/g, '').toLowerCase();

const getComplexityProfile = (complexity?: string | null): ComplexityProfile | null => {
  const normalized = normalizeComplexity(complexity);

  if (!normalized) {
    return null;
  }

  if (/^o\(1\)$/.test(normalized)) {
    return {
      key: 'constant',
      label: 'O(1)',
      color: '#22c55e',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: () => 1,
    };
  }

  if (/nlogn/.test(normalized)) {
    return {
      key: 'linearithmic',
      label: 'O(n log n)',
      color: '#a855f7',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: (n) => n * Math.log2(Math.max(2, n)),
    };
  }

  if (/nlogk/.test(normalized)) {
    return {
      key: 'k-way-heap',
      label: 'O(N log k)',
      color: '#8b5cf6',
      inputSizes: STANDARD_INPUT_SIZES,
      inputLabel: 'Total elements (N)',
      getGrowth: (n) => {
        const k = Math.max(2, Math.round(Math.sqrt(n)));
        return n * Math.log2(k);
      },
      getAuxiliaryLabel: (n) => `representative k = ${Math.max(2, Math.round(Math.sqrt(n)))}`,
      note: 'Representative multi-list plot: k grows with sqrt(N)',
    };
  }

  if (/2\^n/.test(normalized)) {
    return {
      key: 'exponential',
      label: 'O(2^n)',
      color: '#e11d48',
      inputSizes: EXPONENTIAL_INPUT_SIZES,
      getGrowth: (n) => 2 ** n,
    };
  }

  if (/n\^3/.test(normalized)) {
    return {
      key: 'cubic',
      label: 'O(n^3)',
      color: '#f97316',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: (n) => n ** 3,
    };
  }

  if (/n\^2|m\*n|mn/.test(normalized)) {
    return {
      key: 'quadratic',
      label: normalized.includes('m') ? 'O(m * n)' : 'O(n^2)',
      color: '#ef4444',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: (n) => n ** 2,
      note: normalized.includes('m') ? 'Representative grid case: m = n' : undefined,
    };
  }

  if (/^o\(n\)$/.test(normalized)) {
    return {
      key: 'linear',
      label: 'O(n)',
      color: '#60a5fa',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: (n) => n,
    };
  }

  if (/logn/.test(normalized)) {
    return {
      key: 'logarithmic',
      label: 'O(log n)',
      color: '#eab308',
      inputSizes: STANDARD_INPUT_SIZES,
      getGrowth: (n) => Math.log2(n),
    };
  }

  return null;
};

const buildComplexityData = (profile: ComplexityProfile): ComplexityPoint[] => {
  const rawPoints = profile.inputSizes.map((n) => ({
    n,
    estimatedGrowth: profile.getGrowth(n),
    auxiliaryLabel: profile.getAuxiliaryLabel?.(n),
  }));
  const maxGrowth = Math.max(...rawPoints.map((point) => point.estimatedGrowth));
  const minGrowth = Math.min(...rawPoints.map((point) => point.estimatedGrowth));
  const hasFlatGrowth = maxGrowth === minGrowth;

  return rawPoints.map((point) => ({
    ...point,
    normalizedGrowth: hasFlatGrowth ? 48 : (point.estimatedGrowth / maxGrowth) * 100,
  }));
};

const formatGrowth = (value: number) => {
  if (!Number.isFinite(value)) {
    return 'very high';
  }

  if (Math.abs(value) < 1000) {
    return Number.isInteger(value) ? String(value) : value.toFixed(value < 10 ? 2 : 1);
  }

  if (Math.abs(value) < 1_000_000) {
    return value.toLocaleString(undefined, { maximumFractionDigits: 0 });
  }

  return value.toExponential(2);
};

function ComplexityTooltip({
  active,
  payload,
  label,
  complexityLabel,
  inputLabel,
  theme,
}: {
  active?: boolean;
  payload?: Array<{ payload?: ComplexityPoint }>;
  label?: number | string;
  complexityLabel: string;
  inputLabel: string;
  theme: ChartTheme;
}) {
  if (!active || !payload?.length) {
    return null;
  }

  const point = payload[0]?.payload;
  const isDark = theme === 'dark';

  return (
    <div
      className={[
        'rounded-md border px-3 py-2 text-xs shadow-xl',
        isDark
          ? 'border-white/10 bg-[#111827] text-slate-100'
          : 'border-slate-200 bg-white text-slate-900',
      ].join(' ')}
    >
      <div className="font-mono font-black">{complexityLabel}</div>
      <div className={isDark ? 'mt-1 text-slate-300' : 'mt-1 text-slate-600'}>
        {inputLabel} = {label}
      </div>
      {point?.auxiliaryLabel && (
        <div className={isDark ? 'text-slate-300' : 'text-slate-600'}>{point.auxiliaryLabel}</div>
      )}
      <div className={isDark ? 'text-slate-300' : 'text-slate-600'}>
        estimated growth = {formatGrowth(point?.estimatedGrowth ?? 0)}
      </div>
    </div>
  );
}

export default function ComplexityGrowthChart({
  complexity,
  theme = 'dark',
  className = '',
}: ComplexityGrowthChartProps) {
  const profile = getComplexityProfile(complexity);
  const isDark = theme === 'dark';
  const axisColor = isDark ? '#94a3b8' : '#475569';
  const gridColor = isDark ? 'rgba(148,163,184,0.16)' : 'rgba(100,116,139,0.18)';
  const fallbackClass = isDark ? 'text-slate-400' : 'text-slate-500';
  const inputLabel = profile?.inputLabel ?? 'Input size (n)';

  if (!profile) {
    return (
      <div className={`flex h-full min-h-[220px] items-center justify-center rounded-md ${fallbackClass} ${className}`}>
        <div className="text-center text-xs font-semibold">
          No graph available for {complexity ?? 'unknown complexity'}.
        </div>
      </div>
    );
  }

  const data = buildComplexityData(profile);

  return (
    <div className={`flex h-full min-h-[220px] flex-col ${className}`}>
      <div className="min-h-0 flex-1">
        <ResponsiveContainer width="100%" height="100%">
          <LineChart data={data} margin={{ top: 18, right: 22, left: 8, bottom: 24 }}>
            <CartesianGrid stroke={gridColor} strokeDasharray="4 6" />
            <XAxis
              dataKey="n"
              axisLine={{ stroke: axisColor }}
              tickLine={{ stroke: axisColor }}
              tick={{ fill: axisColor, fontSize: 11, fontWeight: 700 }}
              label={{
                value: inputLabel,
                position: 'insideBottom',
                offset: -14,
                fill: axisColor,
                fontSize: 12,
                fontWeight: 800,
              }}
            />
            <YAxis
              domain={[0, 100]}
              axisLine={{ stroke: axisColor }}
              tickLine={{ stroke: axisColor }}
              tick={{ fill: axisColor, fontSize: 11, fontWeight: 700 }}
              tickFormatter={(value) => `${value}%`}
              label={{
                value: 'Normalized growth',
                angle: -90,
                position: 'insideLeft',
                fill: axisColor,
                fontSize: 12,
                fontWeight: 800,
              }}
            />
            <Tooltip
              cursor={{ stroke: profile.color, strokeWidth: 1.5, strokeDasharray: '4 6' }}
              content={<ComplexityTooltip complexityLabel={profile.label} inputLabel={inputLabel} theme={theme} />}
            />
            <Line
              type="monotone"
              dataKey="normalizedGrowth"
              name={profile.label}
              stroke={profile.color}
              strokeWidth={4}
              dot={{ r: 3.5, fill: profile.color, strokeWidth: 0 }}
              activeDot={{ r: 6, fill: profile.color, stroke: isDark ? '#111827' : '#ffffff', strokeWidth: 2 }}
              isAnimationActive={false}
            />
          </LineChart>
        </ResponsiveContainer>
      </div>
      <div className={`mt-2 flex items-center justify-between gap-3 text-[11px] font-bold ${isDark ? 'text-slate-400' : 'text-slate-600'}`}>
        <span className="font-mono" style={{ color: profile.color }}>
          {profile.label}
        </span>
        {profile.note && <span>{profile.note}</span>}
      </div>
    </div>
  );
}
