import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';

import { fetchLanguageDailyCounts } from '../api/client';

const HEIGHT = 160;
const LABEL_HEIGHT = 16;

/** Language stream graph — stacked area showing language usage over time. */
export function LanguageStreamGraph() {
  const to = new Date();
  const from = new Date(to);
  from.setFullYear(from.getFullYear() - 1);

  const fromStr = fmt(from);
  const toStr = fmt(to);

  const langQ = useQuery({
    queryKey: ['language-daily', fromStr, toStr],
    queryFn: () => fetchLanguageDailyCounts(fromStr, toStr),
    staleTime: 5 * 60_000,
  });

  const { languages, paths, legendItems } = useMemo(() => {
    const raw = langQ.data ?? [];
    if (raw.length === 0)
      return { weeks: [], languages: [], paths: [], legendItems: [] };

    // Aggregate daily counts into weekly buckets
    const colorMap = new Map<string, string>();
    const weeklyMap = new Map<string, Map<string, number>>();

    for (const d of raw) {
      if (d.color) colorMap.set(d.language, d.color);
      // Bucket to ISO week (Monday-based, keyed by Monday date)
      const dt = new Date(d.date + 'T00:00:00Z');
      const day = dt.getUTCDay();
      const monday = new Date(dt);
      monday.setUTCDate(monday.getUTCDate() - ((day + 6) % 7));
      const weekKey = monday.toISOString().slice(0, 10);

      if (!weeklyMap.has(weekKey)) weeklyMap.set(weekKey, new Map());
      const langs = weeklyMap.get(weekKey)!;
      langs.set(d.language, (langs.get(d.language) ?? 0) + d.commits);
    }

    const weeks = [...weeklyMap.keys()].sort();

    // Rank languages by total commits to pick top N + "other"
    const totals = new Map<string, number>();
    for (const langs of weeklyMap.values()) {
      for (const [lang, count] of langs) {
        totals.set(lang, (totals.get(lang) ?? 0) + count);
      }
    }
    const ranked = [...totals.entries()].sort(([, a], [, b]) => b - a);
    const topN = 8;
    const topLangs = ranked.slice(0, topN).map(([l]) => l);
    const hasOther = ranked.length > topN;
    const languages = hasOther ? [...topLangs, 'Other'] : topLangs;

    // Build stacked data per week
    const stacked: number[][] = weeks.map((wk) => {
      const langs = weeklyMap.get(wk)!;
      const values = topLangs.map((l) => langs.get(l) ?? 0);
      if (hasOther) {
        let other = 0;
        for (const [l, c] of langs) {
          if (!topLangs.includes(l)) other += c;
        }
        values.push(other);
      }
      return values;
    });

    // Compute stream layout (centered baseline)
    const maxTotal = Math.max(...stacked.map((row) => row.reduce((a, b) => a + b, 0)), 1);
    const chartHeight = HEIGHT - LABEL_HEIGHT;

    // For each week, compute y0 (centered) then stack upward
    const layerCount = languages.length;
    const y0s: number[][] = [];
    const y1s: number[][] = [];

    for (let w = 0; w < weeks.length; w++) {
      const total = stacked[w].reduce((a, b) => a + b, 0);
      const scaledTotal = (total / maxTotal) * chartHeight;
      let baseline = (chartHeight - scaledTotal) / 2 + LABEL_HEIGHT;

      const wy0: number[] = [];
      const wy1: number[] = [];
      for (let l = 0; l < layerCount; l++) {
        const h = (stacked[w][l] / maxTotal) * chartHeight;
        wy0.push(baseline);
        baseline += h;
        wy1.push(baseline);
      }
      y0s.push(wy0);
      y1s.push(wy1);
    }

    // Build SVG paths for each language layer using smooth curves
    const xFor = (w: number) =>
      weeks.length > 1 ? (w / (weeks.length - 1)) * 100 : 50;

    const paths = languages.map((_, l) => {
      if (weeks.length === 0) return '';
      const topPts = weeks.map((_, w) => [xFor(w), y0s[w][l]] as [number, number]);
      const bottomPts = weeks
        .map((_, w) => [xFor(w), y1s[w][l]] as [number, number])
        .reverse();
      return `M${topPts[0][0]},${topPts[0][1]} ${smoothLine(topPts)} L${bottomPts[0][0]},${bottomPts[0][1]} ${smoothLine(bottomPts)} Z`;
    });

    // Default colors for "Other" and fallback
    const FALLBACK_COLORS = [
      '#e34c26', '#563d7c', '#3178c6', '#dea584',
      '#f1e05a', '#89e051', '#00ADD8', '#438eff',
    ];

    const legendItems = languages.map((lang, i) => ({
      language: lang,
      color:
        lang === 'Other'
          ? 'rgba(255,255,255,0.2)'
          : colorMap.get(lang) ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length],
      total: ranked[i]?.[1] ?? 0,
    }));

    return { weeks, languages, paths, legendItems };
  }, [langQ.data]);

  if (langQ.isLoading) return <p style={{ fontSize: '0.8rem' }}>loading language graph...</p>;
  if (langQ.isError || languages.length === 0) return null;

  return (
    <div className="contribution-graph mb-4">
      <p style={{ fontSize: '0.8rem', opacity: 0.6 }}>languages by commit activity</p>
      <svg viewBox={`0 0 100 ${HEIGHT}`} width="100%" preserveAspectRatio="none" className="d-block" style={{ height: `${HEIGHT}px` }}>
        {paths.map((d, i) => (
          <path
            key={legendItems[i].language}
            d={d}
            fill={legendItems[i].color}
            opacity={0.85}
          >
            <title>{legendItems[i].language}</title>
          </path>
        ))}
      </svg>
      <div className="d-flex flex-wrap gap-2 mt-1" style={{ fontSize: '0.75rem' }}>
        {legendItems.map(({ language, color }) => (
          <span key={language} className="d-flex align-items-center gap-1">
            <span
              style={{
                width: 10,
                height: 10,
                borderRadius: '50%',
                backgroundColor: color,
                display: 'inline-block',
              }}
            />
            {language}
          </span>
        ))}
      </div>
    </div>
  );
}

function fmt(d: Date): string {
  return d.toISOString().slice(0, 10);
}

/** Convert a series of points into smooth cubic bezier curve commands.
 *  Uses Catmull-Rom to Bezier conversion with tension 0.5. */
function smoothLine(pts: [number, number][]): string {
  if (pts.length < 2) return '';
  if (pts.length === 2)
    return `L${pts[1][0]},${pts[1][1]}`;

  const commands: string[] = [];
  for (let i = 1; i < pts.length; i++) {
    const p0 = pts[Math.max(i - 2, 0)];
    const p1 = pts[i - 1];
    const p2 = pts[i];
    const p3 = pts[Math.min(i + 1, pts.length - 1)];

    const t = 0.5;
    const cp1x = p1[0] + (p2[0] - p0[0]) * t / 3;
    const cp1y = p1[1] + (p2[1] - p0[1]) * t / 3;
    const cp2x = p2[0] - (p3[0] - p1[0]) * t / 3;
    const cp2y = p2[1] - (p3[1] - p1[1]) * t / 3;

    commands.push(`C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2[0]},${p2[1]}`);
  }
  return commands.join(' ');
}
