"use client";

/**
 * Position history line chart. Y-axis runs 1 (best) at top → N (worst)
 * at the bottom, matching every SEO rank tracker convention. Two line
 * series: live SERP-provider checks (solid blue) and GSC-backfill
 * estimates (dashed gray). No fill areas — a clean line chart reads
 * as a single ranking trend, not a surface plot.
 */

import { useEffect, useState } from "react";
import {
  ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid,
  Tooltip as RTooltip,
} from "recharts";
import { Loader2 } from "lucide-react";

interface Props {
  keywordId: string;
  days?: number;
}

interface DataPoint {
  date: string;
  /** Position from a live SERP-provider check — solid line + filled dot. */
  live: number | null;
  /** Position inferred from GscQuery backfill — dashed line + hollow dot. */
  backfill: number | null;
  url?: string;
}

export default function PositionChart({ keywordId, days = 90 }: Props) {
  const [data, setData] = useState<DataPoint[]>([]);
  const [hasBackfill, setHasBackfill] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/rank-tracking/history?keywordId=${keywordId}&days=${days}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((result) => {
        if (result?.rankings) {
          // Split into two series so live checks and GSC backfill render
          // as visually distinct line segments (solid vs dashed) — see
          // the two <Line> elements below. A day with only one source
          // will show that line alone; a day with both shows both.
          let anyBackfill = false;
          const mapped: DataPoint[] = result.rankings.map((r: { checkedAt: string; position: number | null; url?: string; source?: string }) => {
            const isBackfill = r.source === "gsc_backfill";
            if (isBackfill && r.position != null) anyBackfill = true;
            return {
              date: new Date(r.checkedAt).toISOString().split("T")[0],
              live: isBackfill ? null : r.position,
              backfill: isBackfill ? r.position : null,
              url: r.url,
            };
          });
          setData(mapped);
          setHasBackfill(anyBackfill);
        }
      })
      .finally(() => setLoading(false));
  }, [keywordId, days]);

  if (loading) return <div className="flex items-center justify-center h-52 text-slate-400"><Loader2 className="w-4 h-4 animate-spin" /></div>;
  if (data.length === 0) return <div className="text-center py-8 text-xs text-slate-400">No ranking history yet.</div>;

  // Y-axis domain runs high-number → 1 so position 1 is literally at the
  // top of the plot. Using a descending domain instead of Recharts'
  // `reversed` prop because `reversed` + single-data-point renders
  // with a degenerate axis (floated ticks, no integer snapping). A
  // direct [max, 1] with allowDecimals={false} gives clean 1/5/10/15
  // ticks even when only one data point exists.
  //
  // Sparse-data tuning: when there's only one ranking point, the default
  // range (max + 5, capped at 100) makes a position-2 dot look like it's
  // glued to the top of an empty plot. Tighten the bound: cap the lower
  // edge of the y-domain to `bestPos + 8` (or 10, whichever is bigger)
  // when total points are 1-2, so the dot sits in the visual centre.
  const positions = data
    .flatMap((d) => [d.live, d.backfill])
    .filter((p): p is number => p != null);
  const totalPoints = positions.length;
  const bestPos = positions.length > 0 ? Math.min(...positions) : 1;
  const worstPos = positions.length > 0 ? Math.max(...positions) : 10;
  const maxPos = totalPoints <= 2
    ? Math.max(bestPos + 8, 10)
    : Math.min(worstPos + 5, 100);

  // Single-point or two-point histories deserve a heads-up: the shape of
  // a chart with one dot can be misleading without context.
  const sparseNotice = totalPoints > 0 && totalPoints <= 2
    ? `Limited history — ${totalPoints} data point${totalPoints === 1 ? "" : "s"} so far. More points will appear as daily checks run.`
    : null;

  return (
    <div className="h-64">
      <ResponsiveContainer width="100%" height="100%">
        <LineChart data={data} margin={{ top: 10, right: 10, bottom: 0, left: 0 }}>
          <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" vertical={false} />
          <XAxis
            dataKey="date"
            tick={{ fill: "#94a3b8", fontSize: 11 }}
            stroke="#94a3b8"
            tickFormatter={(v) => new Date(v).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
          />
          <YAxis
            domain={[maxPos, 1]}
            allowDecimals={false}
            tick={{ fill: "#94a3b8", fontSize: 11 }}
            stroke="#94a3b8"
            label={{ value: "Position", angle: -90, position: "insideLeft", style: { fontSize: 11, fill: "#94a3b8" } }}
          />
          <RTooltip
            contentStyle={{ fontSize: "11px", borderRadius: "8px", border: "1px solid #1E293B", background: "#0F172A", color: "#F1F5F9" }}
            labelStyle={{ color: "#94A3B8" }}
            labelFormatter={(v) => new Date(String(v)).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
            formatter={(v: unknown, name: unknown) => {
              const pos = typeof v === "number" ? `#${v}` : "Not ranking";
              const source = name === "backfill" ? "GSC (est.)" : "Tracked";
              return [pos, source];
            }}
          />
          {/* Live daily SERP-provider checks — solid blue line, filled dot.
              Sparse points get a bigger dot so they read as the headline
              data instead of getting lost on an empty axis. */}
          <Line
            type="monotone"
            dataKey="live"
            name="live"
            stroke="#3B82F6"
            strokeWidth={2}
            dot={{ r: totalPoints <= 2 ? 6 : 4, fill: "#3B82F6" }}
            connectNulls={false}
          />
          {/* GSC backfill — dashed gray, smaller dot. Lower strokeWidth
              and a muted hue so users immediately read it as an estimate
              derived from impression-weighted avg position, not a true
              SERP check. */}
          <Line
            type="monotone"
            dataKey="backfill"
            name="backfill"
            stroke="#94A3B8"
            strokeWidth={1.5}
            strokeDasharray="4 4"
            dot={{ r: totalPoints <= 2 ? 5 : 3, fill: "#fff", stroke: "#94A3B8", strokeWidth: 1.5 }}
            connectNulls={false}
          />
        </LineChart>
      </ResponsiveContainer>
      {sparseNotice && (
        <p className="mt-2 text-[10px] text-amber-600 text-center">{sparseNotice}</p>
      )}
      {hasBackfill && (
        <p className="mt-2 text-[10px] text-slate-400 flex items-center gap-2 justify-center">
          <span className="inline-block w-4 border-t border-dashed border-slate-400" />
          Dashed gray = GSC historical estimate · Solid blue = tracked check
        </p>
      )}
    </div>
  );
}
