import { useEffect, useState } from 'react';
import { Crosshair, RefreshCw, TriangleAlert } from 'lucide-react';
import {
  defaultGuideWindowStart,
  GUIDE_WINDOW_MS,
  useGuideScreenQuery,
  type ChannelState,
  type ChannelGuideChannel,
  type ChannelGuideProgramme
} from '../api';
import {
  Badge,
  Button,
  Card,
  ChannelLogo,
  Select,
  Spinner,
  StatusDot
} from '../components';

const EPG_SLOT_MINUTES = 30;
const EPG_SLOT_WIDTH = 156;
const EPG_RAIL_WIDTH = 196;
const EPG_ROW_HEIGHT = 82;
const EPG_HEADER_HEIGHT = 34;
function GuideLoadingState() {
  return (
    <Card title={<h2>Loading guide</h2>} subtitle="Fetching a bounded JSON guide window and live channel state.">
      <div className="ctv-dashboard-state">
        <Spinner tone="muted" />
        <span>Loading guide</span>
      </div>
    </Card>
  );
}

function GuideErrorState({ error, refresh }: { error: string; refresh: () => void }) {
  return (
    <Card
      title={<h2>Guide unavailable</h2>}
      subtitle="The API returned an error while loading the JSON guide."
      actions={<Button onClick={refresh} startIcon={<RefreshCw aria-hidden="true" size={15} />} variant="secondary">Retry</Button>}
    >
      <div className="ctv-channels-error" role="alert">
        <TriangleAlert aria-hidden="true" size={15} />
        <span>{error}</span>
      </div>
    </Card>
  );
}

export function GuideScreen() {
  const query = useGuideScreenQuery();
  const [now, setNow] = useState(() => new Date());

  useEffect(() => {
    const intervalId = window.setInterval(() => {
      setNow(new Date());
    }, 60000);

    return () => {
      window.clearInterval(intervalId);
    };
  }, []);

  if (query.status === 'loading') {
    return <GuideLoadingState />;
  }

  if (query.status === 'error') {
    return <GuideErrorState error={query.error} refresh={query.refresh} />;
  }

  const channels = query.data.guide.channels;
  const channelStatesByNumber = new Map(query.data.channelStates.map((state) => [state.channelNumber, state]));
  const windowStart = new Date(query.data.guide.start);
  const windowEnd = new Date(query.data.guide.end);
  const slots = guideSlots(windowStart, windowEnd);
  const totalWidth = EPG_RAIL_WIDTH + slots.length * EPG_SLOT_WIDTH;
  const nowOffset = offsetPx(now, windowStart);
  const nowInWindow = now >= windowStart && now <= windowEnd;

  const moveWindow = (delta: number) => {
    query.setWindowStart(new Date(query.windowStart.getTime() + delta * GUIDE_WINDOW_MS));
  };

  const jumpToNow = () => {
    query.setWindowStart(defaultGuideWindowStart(now));
  };

  return (
    <div className="ctv-epg-screen">
      <section className="ctv-epg-toolbar" aria-label="Guide controls">
        <div className="ctv-epg-filter">
          <Select disabled label="Channel group" options={['All channels']} size="sm" value="All channels" />
        </div>
        <div className="ctv-epg-window">
          <span>{formatGuideTime(windowStart)}</span>
          <input
            aria-label="Guide window position"
            disabled
            max="100"
            min="0"
            type="range"
            value={nowInWindow ? Math.round((now.getTime() - windowStart.getTime()) / (windowEnd.getTime() - windowStart.getTime()) * 100) : 0}
            readOnly
          />
          <span>{formatGuideTime(windowEnd)}</span>
        </div>
        <Badge tone={nowInWindow ? 'accent' : 'neutral'} dot={nowInWindow}>Now {formatGuideTime(now)}</Badge>
        <Button onClick={() => moveWindow(-1)} variant="secondary">Previous</Button>
        <Button onClick={() => moveWindow(1)} variant="secondary">Next guide window</Button>
        <Button onClick={jumpToNow} startIcon={<Crosshair aria-hidden="true" size={15} />} variant="primary">Jump to now</Button>
      </section>

      <section className="ctv-epg-grid-shell" aria-label="Guide timeline">
        <div className="ctv-epg-scroll">
          <div
            aria-label="Channel guide"
            className="ctv-epg-grid"
            role="grid"
            style={{ minWidth: totalWidth }}
          >
            <div className="ctv-epg-time-head" role="row" style={{ height: EPG_HEADER_HEIGHT }}>
              <div className="ctv-epg-rail-head" style={{ width: EPG_RAIL_WIDTH }} />
              {slots.map((slot) => (
                <div className="ctv-epg-time-slot" key={slot.toISOString()} role="columnheader" style={{ width: EPG_SLOT_WIDTH }}>
                  {formatGuideTime(slot)}
                </div>
              ))}
            </div>

            {nowInWindow && (
              <div
                aria-hidden="true"
                className="ctv-epg-now-marker"
                style={{
                  bottom: 0,
                  left: EPG_RAIL_WIDTH + nowOffset,
                  top: EPG_HEADER_HEIGHT
                }}
              >
                <span />
              </div>
            )}

            {channels.map((channel, index) => (
              <GuideChannelRow
                channel={channel}
                channelState={channelStatesByNumber.get(channel.number) ?? null}
                index={index}
                key={channel.number}
                windowEnd={windowEnd}
                windowStart={windowStart}
              />
            ))}
          </div>
        </div>
      </section>
    </div>
  );
}

function GuideChannelRow({
  channel,
  channelState,
  index,
  windowEnd,
  windowStart
}: {
  channel: ChannelGuideChannel;
  channelState: ChannelState | null;
  index: number;
  windowEnd: Date;
  windowStart: Date;
}) {
  const visibleProgrammes = channel.programmes
    .map((programme) => clipProgramme(programme, windowStart, windowEnd))
    .filter((programme): programme is ClippedProgramme => programme !== null);

  return (
    <div className="ctv-epg-row" role="row" style={{ height: EPG_ROW_HEIGHT }}>
      <div className="ctv-epg-channel-rail" role="rowheader" style={{ width: EPG_RAIL_WIDTH }}>
        <code>{channel.number}</code>
        <ChannelLogo name={channel.name} size={30} src={channel.logo} />
        <span>{channel.name}</span>
        {channelState?.onAir && <StatusDot status="live" size={7} />}
      </div>
      <div className={`ctv-epg-track${index % 2 ? ' ctv-epg-track-alt' : ''}`} role="gridcell">
        {visibleProgrammes.length === 0 ? (
          <span className="ctv-epg-empty">No programmes in this window</span>
        ) : visibleProgrammes.map((programme) => (
          <GuideProgrammeBlock
            key={`${programme.title}-${programme.start.getTime()}-${programme.stop.getTime()}`}
            programme={programme}
            channelState={channelState}
            windowStart={windowStart}
          />
        ))}
      </div>
    </div>
  );
}

interface ClippedProgramme {
  category: null | string;
  fillerKind: string;
  start: Date;
  stop: Date;
  subTitle: null | string;
  title: string;
  visibleStart: Date;
  visibleStop: Date;
}

function GuideProgrammeBlock({
  channelState,
  programme,
  windowStart
}: {
  channelState: ChannelState | null;
  programme: ClippedProgramme;
  windowStart: Date;
}) {
  const filler = programme.fillerKind !== 'None';
  const live = Boolean(channelState?.onAir && programmeMatchesNowPlaying(programme, channelState.nowPlaying));
  const left = offsetPx(programme.visibleStart, windowStart) + 3;
  const width = Math.max(24, offsetPx(programme.visibleStop, programme.visibleStart) - 6);

  return (
    <article
      className={`ctv-epg-programme${live ? ' ctv-epg-programme-live' : ''}${filler ? ' ctv-epg-programme-filler' : ''}`}
      style={{ left, width }}
      title={`${programme.title}${programme.subTitle ? ` - ${programme.subTitle}` : ''}`}
    >
      <div className="ctv-epg-programme-title">
        {live && <StatusDot status="live" size={6} />}
        <strong>{programme.title}</strong>
      </div>
      {programme.subTitle && <span>{programme.subTitle}</span>}
      {filler ? <Badge tone="neutral">Filler</Badge> : programme.category && <small>{programme.category}</small>}
    </article>
  );
}

function guideSlots(start: Date, end: Date): Date[] {
  const slots: Date[] = [];
  const slotMs = EPG_SLOT_MINUTES * 60 * 1000;

  for (let value = start.getTime(); value < end.getTime(); value += slotMs) {
    slots.push(new Date(value));
  }

  return slots;
}

function formatGuideTime(value: Date): string {
  return value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}

function offsetPx(value: Date, start: Date): number {
  return (value.getTime() - start.getTime()) / (EPG_SLOT_MINUTES * 60 * 1000) * EPG_SLOT_WIDTH;
}

function clipProgramme(programme: ChannelGuideProgramme, windowStart: Date, windowEnd: Date): ClippedProgramme | null {
  const start = new Date(programme.start);
  const stop = new Date(programme.stop);

  if (Number.isNaN(start.getTime()) || Number.isNaN(stop.getTime()) || stop <= windowStart || start >= windowEnd) {
    return null;
  }

  return {
    category: programme.category,
    fillerKind: programme.fillerKind,
    start,
    stop,
    subTitle: programme.subTitle,
    title: programme.title,
    visibleStart: start < windowStart ? windowStart : start,
    visibleStop: stop > windowEnd ? windowEnd : stop
  };
}

function programmeMatchesNowPlaying(programme: ClippedProgramme, nowPlaying: ChannelState['nowPlaying'] | undefined): boolean {
  if (!nowPlaying) {
    return false;
  }

  const nowPlayingStart = new Date(nowPlaying.startUtc);
  const nowPlayingFinish = new Date(nowPlaying.finishUtc);

  return programme.start.getTime() === nowPlayingStart.getTime() &&
    programme.stop.getTime() === nowPlayingFinish.getTime();
}
