import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { scaleLinear } from "d3-scale";
import { interpolateRgb } from "d3-interpolate";
import { Loader2 } from "lucide-react";
import WorldMap from "../components/WorldMap.jsx";
import {
  fetchCountryDivergence,
  fetchMapArticles,
} from "../api/client.js";
import {
  getCachedMapArticles,
  getCachedMapDivergence,
  setCachedMapArticles,
  setCachedMapDivergence,
} from "../utils/investigationCache.js";
import { viewportTooltipPosition } from "../utils/viewportTooltip.js";
import "./MapView.css";

const heatScale = scaleLinear()
  .domain([0, 0.5, 1])
  .range(["#1B202C", "#C8A96E", "#E05252"])
  .interpolate(interpolateRgb)
  .clamp(true);

const NO_DATA_FILL = "#1A1E29";

export default function MapView({ query }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [hover, setHover] = useState(null);
  const [selectedIso, setSelectedIso] = useState(null);
  const tooltipRef = useRef(null);
  const lastPointerRef = useRef({ x: 0, y: 0 });
  const [articles, setArticles] = useState([]);
  const [articlesLoading, setArticlesLoading] = useState(true);

  const topic = query?.topic?.trim() ?? "";
  const newsTimeframe = query?.newsTimeframe ?? "6m";
  const qParams = { topic, newsTimeframe };

  useEffect(() => {
    let cancelled = false;
    if (!topic) {
      setData(null);
      setLoading(false);
      return () => {
        cancelled = true;
      };
    }
    const cached = getCachedMapDivergence(topic, newsTimeframe);
    if (cached) {
      setData(cached);
      setLoading(false);
      return () => {
        cancelled = true;
      };
    }
    setLoading(true);
    setData(null);
    fetchCountryDivergence(qParams)
      .then((d) => {
        if (cancelled) return;
        setData(d);
        setCachedMapDivergence(topic, newsTimeframe, d);
      })
      .finally(() => !cancelled && setLoading(false));
    return () => {
      cancelled = true;
    };
  }, [topic, newsTimeframe]);

  useEffect(() => {
    let cancelled = false;
    if (!topic) {
      setArticles([]);
      setArticlesLoading(false);
      return () => {
        cancelled = true;
      };
    }
    const cached = getCachedMapArticles(topic, newsTimeframe, selectedIso);
    if (cached) {
      setArticles(cached);
      setArticlesLoading(false);
      return () => {
        cancelled = true;
      };
    }
    setArticlesLoading(true);
    fetchMapArticles(qParams, selectedIso)
      .then((d) => {
        if (cancelled) return;
        const list = d?.articles ?? [];
        setArticles(list);
        setCachedMapArticles(topic, newsTimeframe, selectedIso, list);
      })
      .finally(() => !cancelled && setArticlesLoading(false));
    return () => {
      cancelled = true;
    };
  }, [topic, newsTimeframe, selectedIso]);

  const byNumeric = useMemo(() => {
    const m = new Map();
    data?.countries?.forEach((c) => m.set(c.numericCode, c));
    return m;
  }, [data]);

  const selectedNumeric = useMemo(() => {
    if (!selectedIso || !data) return null;
    const c = data.countries.find((x) => x.iso3 === selectedIso);
    return c?.numericCode ?? null;
  }, [selectedIso, data]);

  const colorFor = useCallback(
    (numeric) => {
      if (!data) return NO_DATA_FILL;
      const c = byNumeric.get(numeric);
      return c ? heatScale(c.divergence) : NO_DATA_FILL;
    },
    [data, byNumeric]
  );

  const applyTooltipDom = useCallback((clientX, clientY) => {
    lastPointerRef.current = { x: clientX, y: clientY };
    const el = tooltipRef.current;
    if (!el) return;
    const { width, height } = el.getBoundingClientRect();
    const estWidth = width > 0 ? width : 236;
    const estHeight = height > 0 ? height : 92;
    const { left, top } = viewportTooltipPosition(clientX, clientY, {
      estWidth,
      estHeight,
    });
    el.style.left = `${left}px`;
    el.style.top = `${top}px`;
  }, []);

  const onCountryEnter = useCallback(
    (country, e) => {
      lastPointerRef.current = { x: e.clientX, y: e.clientY };
      const c = byNumeric.get(country.numericCode);
      setHover(c ? { ...c } : { name: country.name });
    },
    [byNumeric]
  );

  const onCountryMove = useCallback(
    (e) => {
      applyTooltipDom(e.clientX, e.clientY);
    },
    [applyTooltipDom]
  );

  const onCountryLeave = useCallback(() => {
    setHover(null);
  }, []);

  const onCountryClick = useCallback(
    (country) => {
      const c = byNumeric.get(country.numericCode);
      if (c) {
        setSelectedIso((prev) => (prev === c.iso3 ? null : c.iso3));
      }
    },
    [byNumeric]
  );

  useLayoutEffect(() => {
    if (!hover) return;
    const { x, y } = lastPointerRef.current;
    applyTooltipDom(x, y);
  }, [hover, applyTooltipDom]);

  return (
    <div className="map-view-page">
      <div className="map-view">
        <MapLegend />

        <WorldMap
          colorFor={colorFor}
          selectedNumeric={selectedNumeric}
          onCountryEnter={onCountryEnter}
          onCountryMove={onCountryMove}
          onCountryLeave={onCountryLeave}
          onCountryClick={onCountryClick}
        />

        {loading && (
          <div className="map-view__loading">
            <Loader2 size={14} strokeWidth={1.5} className="spin" />
            <span>compiling coverage</span>
          </div>
        )}

        <div className="map-view__hint">drag to pan · scroll to zoom · click a country</div>

        {hover && (
          <div
            ref={tooltipRef}
            className="map-tooltip"
            style={{ position: "fixed", left: 0, top: 0, transform: "none" }}
          >
            <div className="map-tooltip__name">{hover.name}</div>
            {hover.articleCount != null ? (
              <>
                <div className="map-tooltip__row">
                  <span className="label">Articles</span>
                  <span className="mono">{hover.articleCount}</span>
                </div>
                <div className="map-tooltip__row">
                  <span className="label">Density</span>
                  <span className="mono" style={{ color: heatScale(hover.divergence) }}>
                    {hover.divergence.toFixed(2)}
                  </span>
                </div>
              </>
            ) : (
              <div className="map-tooltip__row faint">no coverage indexed</div>
            )}
          </div>
        )}
      </div>

      <section className="map-articles">
        <div className="map-articles__head">
          <div>
            <div className="label">Articles</div>
            <h2 className="map-articles__title">
              {selectedIso ? `Coverage from ${selectedIso}` : "All indexed coverage"}
            </h2>
          </div>
          {selectedIso && (
            <button className="ghost" onClick={() => setSelectedIso(null)}>
              Clear country filter
            </button>
          )}
        </div>

        {articlesLoading ? (
          <div className="map-articles__loading">
            <Loader2 size={14} className="spin" />
            <span>loading articles</span>
          </div>
        ) : !articles.length ? (
          <div className="map-articles__loading">
            <span>no articles found for this selection</span>
          </div>
        ) : (
          <ul className="map-articles__list">
            {articles.map((article) => (
              <li key={article.id} className="map-article">
                <a href={article.url} target="_blank" rel="noreferrer" className="map-article__title">
                  {article.headline}
                </a>
                <div className="map-article__meta">
                  <span>{article.source || "unknown source"}</span>
                  {article.countryName && (
                    <>
                      <span className="faint">·</span>
                      <span>{article.countryName}</span>
                    </>
                  )}
                  {article.publishedAt && (
                    <>
                      <span className="faint">·</span>
                      <span>{article.publishedAt}</span>
                    </>
                  )}
                </div>
                {article.summary && <p className="map-article__summary">{article.summary}</p>}
              </li>
            ))}
          </ul>
        )}
      </section>
    </div>
  );
}

function MapLegend() {
  const stops = [0, 0.25, 0.5, 0.75, 1];
  return (
    <div className="map-legend">
      <div className="label map-legend__title">Article density</div>
      <div className="map-legend__bar">
        {stops.map((s, i) => (
          <div
            key={i}
            className="map-legend__cell"
            style={{ background: heatScale(s) }}
          />
        ))}
      </div>
      <div className="map-legend__scale" aria-label="Article density from low to high">
        <span className="map-legend__scale-label map-legend__scale-label--low">low density</span>
        <span className="map-legend__scale-gap" aria-hidden="true" />
        <span className="map-legend__scale-label map-legend__scale-label--high">high density</span>
      </div>
    </div>
  );
}
