import { useState, useEffect } from 'react';
import type { JourneyLeg } from '../../types';
import type { TravelTimeEntry } from '../../hooks/useTravelTime';
import { apiUrl, logNonAbortError } from '../../lib/api';
import { WalkingIcon } from '../ui/icons/WalkingIcon';
import { BicycleIcon } from '../ui/icons/BicycleIcon';

interface JourneyInstructionsProps {
  postcode: string;
  entries: TravelTimeEntry[];
  /** When set, shown as a subtitle (e.g. the central postcode for a hexagon) */
  label?: string;
}

interface JourneyData {
  slug: string;
  label: string;
  legs: JourneyLeg[] | null;
  /** Median (50th percentile) total travel time from R5, including waiting. */
  minutes: number | null;
  /** Best-case (5th percentile) total travel time from R5. */
  bestMinutes: number | null;
  loading: boolean;
}

// Official TfL line colors + other known London transit
const ROUTE_COLORS: Record<string, { color: string; darkText?: boolean }> = {
  Bakerloo: { color: '#B36305' },
  Central: { color: '#E32017' },
  Circle: { color: '#FFD300', darkText: true },
  District: { color: '#00782A' },
  'Elizabeth line': { color: '#6950A1' },
  Elizabeth: { color: '#6950A1' },
  'Hammersmith & City': { color: '#F3A9BB', darkText: true },
  'Hammersmith and City': { color: '#F3A9BB', darkText: true },
  Jubilee: { color: '#A0A5A9', darkText: true },
  Metropolitan: { color: '#9B0056' },
  Northern: { color: '#333333' },
  Piccadilly: { color: '#003688' },
  Victoria: { color: '#0098D4' },
  'Waterloo & City': { color: '#95CDBA', darkText: true },
  'Waterloo and City': { color: '#95CDBA', darkText: true },
  DLR: { color: '#00A4A7' },
  'London Overground': { color: '#EE7C0E' },
};

const NON_TUBE_NAMES = new Set(['DLR', 'London Overground', 'Elizabeth line']);

/** Strip trailing parenthesized GTFS route IDs and NaPTAN stop codes (e.g. "(6757261)", "(9400ZZLUCGT1)") */
function stripId(label: string): string {
  return label.replace(/\s+\([A-Za-z0-9]+\)$/, '');
}

function getRouteDisplay(mode: string): { label: string; color: string; darkText: boolean } {
  const clean = stripId(mode);
  const known = ROUTE_COLORS[clean];
  if (known) {
    const label = NON_TUBE_NAMES.has(clean) || clean.includes('line') ? clean : `${clean} line`;
    return { label, color: known.color, darkText: !!known.darkText };
  }
  if (/^\d+[A-Za-z]?$/.test(clean.trim())) {
    return { label: `Bus ${clean}`, color: '#0d9488', darkText: false };
  }
  return { label: clean, color: '#6b7280', darkText: false };
}

/** Returns a Unix timestamp for the next Monday at 07:30 local time. */
function nextMondayAt730(): number {
  const now = new Date();
  const day = now.getDay(); // 0=Sun … 6=Sat
  const daysUntil = day === 0 ? 1 : day === 1 ? 7 : 8 - day;
  const monday = new Date(now);
  monday.setDate(now.getDate() + daysUntil);
  monday.setHours(7, 30, 0, 0);
  return Math.floor(monday.getTime() / 1000);
}

function googleMapsUrl(postcode: string, destination: string): string {
  const params = new URLSearchParams({
    api: '1',
    origin: postcode,
    destination,
    travelmode: 'transit',
  });
  return `https://www.google.com/maps/dir/?${params}&departure_time=${nextMondayAt730()}`;
}

function invertLegs(legs: JourneyLeg[]): JourneyLeg[] {
  return [...legs]
    .reverse()
    .map((leg) => (leg.from && leg.to ? { ...leg, from: leg.to, to: leg.from } : leg));
}

function RouteBadge({ mode }: { mode: string }) {
  const { label, color, darkText } = getRouteDisplay(mode);
  return (
    <span
      className="inline-flex items-center text-[10px] font-bold px-1.5 py-px rounded-sm leading-tight tracking-wide"
      style={{ backgroundColor: color, color: darkText ? '#292524' : '#fff' }}
    >
      {label}
    </span>
  );
}

function TimelineLeg({ leg, isLast }: { leg: JourneyLeg; isLast: boolean }) {
  const isAccess = leg.mode === 'walk' || leg.mode === 'bicycle';

  if (isAccess) {
    return (
      <div className="flex">
        <div className="flex flex-col items-center w-4 mr-2">
          <div className="w-2 h-2 rounded-full border-[1.5px] border-warm-400 dark:border-warm-500 shrink-0 mt-1" />
          {!isLast && (
            <div className="flex-1 min-h-[4px] border-l border-dashed border-warm-300 dark:border-warm-600" />
          )}
        </div>
        <div className="flex items-center gap-1.5 py-0.5 min-w-0">
          {leg.mode === 'walk' ? (
            <WalkingIcon className="w-3 h-3 text-warm-400 dark:text-warm-500 shrink-0" />
          ) : (
            <BicycleIcon className="w-3 h-3 text-warm-400 dark:text-warm-500 shrink-0" />
          )}
          <span className="text-[11px] text-warm-500 dark:text-warm-400">
            {leg.mode === 'walk' ? 'Walk' : 'Cycle'} · {leg.minutes} min
          </span>
        </div>
      </div>
    );
  }

  const { color } = getRouteDisplay(leg.mode);
  return (
    <div className="flex">
      <div className="flex flex-col items-center w-4 mr-2">
        <div
          className="w-2.5 h-2.5 rounded-full shrink-0 mt-0.5"
          style={{ backgroundColor: color }}
        />
        {!isLast && <div className="flex-1 min-h-[4px] w-px bg-warm-300 dark:bg-warm-600" />}
      </div>
      <div className="pb-1.5 min-w-0 flex-1">
        <div className="flex items-center gap-1.5 flex-wrap">
          <RouteBadge mode={leg.mode} />
          <span className="text-[11px] text-warm-500 dark:text-warm-400">{leg.minutes} min</span>
        </div>
        {leg.from && leg.to && (
          <div className="text-[11px] text-warm-600 dark:text-warm-300 mt-0.5">
            {stripId(leg.from)} → {stripId(leg.to)}
          </div>
        )}
      </div>
    </div>
  );
}

export default function JourneyInstructions({
  postcode,
  entries,
  label,
}: JourneyInstructionsProps) {
  const [journeys, setJourneys] = useState<JourneyData[]>([]);

  // Only transit entries with a destination set
  const transitEntries = entries.filter((e) => e.mode === 'transit' && e.slug !== '');

  useEffect(() => {
    if (transitEntries.length === 0) {
      setJourneys([]);
      return;
    }

    const controller = new AbortController();
    const results: JourneyData[] = transitEntries.map((e) => ({
      slug: e.slug,
      label: e.label,
      legs: null,
      minutes: null,
      bestMinutes: null,
      loading: true,
    }));
    setJourneys([...results]);

    transitEntries.forEach((entry, idx) => {
      const params = new URLSearchParams({
        postcode,
        mode: 'transit',
        slug: entry.slug,
      });
      fetch(apiUrl('journey', params), { signal: controller.signal })
        .then((res) => {
          if (!res.ok) throw new Error(`HTTP ${res.status}`);
          return res.json();
        })
        .then(
          (data: {
            journey: JourneyLeg[] | null;
            minutes: number | null;
            best_minutes: number | null;
          }) => {
            setJourneys((prev) =>
              prev.map((j, i) =>
                i === idx
                  ? {
                      ...j,
                      legs: data.journey,
                      minutes: data.minutes,
                      bestMinutes: data.best_minutes,
                      loading: false,
                    }
                  : j
              )
            );
          }
        )
        .catch((err) => {
          logNonAbortError('journey', err);
          setJourneys((prev) => prev.map((j, i) => (i === idx ? { ...j, loading: false } : j)));
        });
    });

    return () => controller.abort();
  }, [postcode, transitEntries.map((e) => e.slug).join(',')]); // eslint-disable-line react-hooks/exhaustive-deps

  if (transitEntries.length === 0) return null;

  return (
    <div className="mx-3 mt-2 space-y-2">
      {label && (
        <div className="text-xs text-warm-500 dark:text-warm-400">Journeys from {label}</div>
      )}
      {journeys.map((j) => {
        const displayLegs = j.legs ? invertLegs(j.legs) : null;
        const legSum = j.legs ? j.legs.reduce((sum, l) => sum + l.minutes, 0) : 0;
        const totalMin = j.minutes ?? legSum;

        return (
          <div key={j.slug} className="bg-warm-50 dark:bg-warm-800 rounded-lg p-2.5">
            <div className="flex items-baseline justify-between mb-2">
              <span className="text-xs font-medium text-warm-700 dark:text-warm-300">
                To {j.label || j.slug}
              </span>
              {displayLegs && displayLegs.length > 0 && (
                <span className="text-xs font-semibold text-teal-700 dark:text-teal-400">
                  {totalMin} min
                </span>
              )}
            </div>
            {j.loading ? (
              <div className="flex items-center gap-2 py-1">
                <div className="w-3 h-3 border-2 border-teal-600 dark:border-teal-400 border-t-transparent rounded-full animate-spin" />
                <span className="text-xs text-warm-500 dark:text-warm-400">Loading...</span>
              </div>
            ) : displayLegs && displayLegs.length > 0 ? (
              <div>
                {displayLegs.map((leg, i) => (
                  <TimelineLeg key={i} leg={leg} isLast={i === displayLegs.length - 1} />
                ))}
                <a
                  href={googleMapsUrl(postcode, j.label || j.slug)}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="mt-2 flex items-center justify-center gap-1.5 w-full text-[11px] font-medium text-teal-600 dark:text-teal-400 hover:text-teal-800 dark:hover:text-teal-300 bg-white dark:bg-warm-900 border border-warm-200 dark:border-warm-700 rounded-md py-1.5 transition-colors"
                >
                  View on Google Maps
                  <svg className="w-3 h-3" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
                    <path d="M4.5 1.5H2a.5.5 0 00-.5.5v8a.5.5 0 00.5.5h8a.5.5 0 00.5-.5V7.5M7.5 1.5H10.5V4.5M10.5 1.5L5.5 6.5" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
                </a>
              </div>
            ) : (
              <span className="text-xs text-warm-500 dark:text-warm-400">
                No journey data available
              </span>
            )}
          </div>
        );
      })}
    </div>
  );
}
