import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { AlertCircle, Compass } from 'lucide-react';
import { discoveryService } from '../services/discoveryService';
import type { DiscoveryRow, DiscoverySourceSummary } from '../services/discoveryService';
import { Badge } from '../components/ethos/Badge';
import { PageContainer } from '../components/PageContainer';
import { PageHeader } from '../components/PageHeader';
import { EmptyState } from '../components/EmptyState';
import { SkeletonRows } from '../components/LoadingState';

// Mirrors the probation sweep in workers/src/index.ts. Shown rather than hidden:
// a track on probation is N plays away from staying, and the operator should be
// able to see exactly how far. If the sweep's thresholds change, change these.
const KEEP_AFTER_PLAYS = 3;
const DROP_AFTER_SKIPS = 3;

type Filter = 'all' | 'probation' | 'retained' | 'retired' | 'stalled';

const FILTERS: { id: Filter; label: string }[] = [
  { id: 'all', label: 'All' },
  { id: 'probation', label: 'On probation' },
  { id: 'retained', label: 'Kept' },
  { id: 'retired', label: 'Removed' },
  { id: 'stalled', label: 'Never arrived' },
];

/** A candidate that was evaluated but never became a track. */
const isStalled = (row: DiscoveryRow) => !row.track_id && row.status !== 'candidate';

function matches(row: DiscoveryRow, filter: Filter): boolean {
  if (filter === 'all') return true;
  if (filter === 'stalled') return isStalled(row);
  return row.probation_status === filter;
}

function sourceLabel(source: string): string {
  if (source === 'new_release') return 'New release';
  if (source === 'similar_recommendation') return 'Similar';
  if (source === 'graph_exploration') return 'Graph';
  return source;
}

function statusBadge(row: DiscoveryRow) {
  if (row.probation_status === 'retained') return <Badge color="green">Kept</Badge>;
  if (row.probation_status === 'retired') return <Badge color="neutral">Removed</Badge>;
  if (row.probation_status === 'probation') return <Badge color="amber" dot>On probation</Badge>;
  if (isStalled(row)) return <Badge color="red">{row.status.replace(/_/g, ' ')}</Badge>;
  return <Badge color="neutral">Queued</Badge>;
}

function shortDate(value: string | null): string {
  if (!value) return '—';
  return new Date(value).toISOString().slice(0, 10);
}

/** Where this candidate came from, in one line, without a second request. */
function provenance(row: DiscoveryRow): string {
  const notes = row.notes ?? {};
  if (notes.album) return `from ${notes.album}`;
  if (notes.seed_artist) return `because you play ${notes.seed_artist}`;
  return sourceLabel(row.source);
}

function displayName(row: DiscoveryRow): { title: string; artist: string } {
  const credited = row.artist_credit?.[0]?.name ?? '';
  return {
    title: row.track_title ?? row.title ?? 'Untitled',
    artist: row.track_artist ?? credited,
  };
}

function SummaryStrip({ summary }: { summary: DiscoverySourceSummary[] }) {
  if (summary.length === 0) return null;
  return (
    <div className="grid gap-2 sm:grid-cols-2">
      {summary.map((s) => (
        <div key={s.source} className="rounded-lg border border-border bg-surface0/40 p-3">
          <div className="flex items-baseline justify-between gap-2">
            <span className="truncate text-sm font-medium text-text">{sourceLabel(s.source)}</span>
            <span className="flex-none font-mono text-xs tabular-nums text-machine">
              {s.candidates} found
            </span>
          </div>
          <dl className="mt-2 flex flex-wrap gap-x-4 gap-y-1 font-mono text-xs tabular-nums">
            <div className="flex gap-1.5">
              <dt className="font-sans text-secondary">kept</dt>
              <dd className="text-green">{s.retained}</dd>
            </div>
            <div className="flex gap-1.5">
              <dt className="font-sans text-secondary">trying</dt>
              <dd className="text-amber">{s.probation}</dd>
            </div>
            <div className="flex gap-1.5">
              <dt className="font-sans text-secondary">removed</dt>
              <dd className="text-machine">{s.retired}</dd>
            </div>
            <div className="flex gap-1.5">
              <dt className="font-sans text-secondary">never arrived</dt>
              <dd className={s.stalled > 0 ? 'text-red' : 'text-machine'}>{s.stalled}</dd>
            </div>
          </dl>
        </div>
      ))}
    </div>
  );
}

export default function Recommendations() {
  const [filter, setFilter] = useState<Filter>('all');
  const { data, isLoading, isError } = useQuery({
    queryKey: ['discovery-overview'],
    queryFn: () => discoveryService.overview(),
    refetchInterval: 60_000,
  });

  const rows = data?.rows ?? [];
  const visible = useMemo(() => rows.filter((row) => matches(row, filter)), [rows, filter]);

  const totals = data?.summary.reduce(
    (acc, s) => ({
      candidates: acc.candidates + s.candidates,
      retained: acc.retained + s.retained,
      probation: acc.probation + s.probation,
    }),
    { candidates: 0, retained: 0, probation: 0 }
  );

  return (
    <PageContainer>
      <PageHeader
        title="Recommendations"
        subtitle="What discovery brought in, and what your skips did with it."
        meta={
          totals
            ? `${totals.candidates} found · ${totals.retained} kept · ${totals.probation} on probation`
            : undefined
        }
      />

      {isLoading ? (
        <SkeletonRows count={5} />
      ) : isError ? (
        <EmptyState
          compact
          icon={<AlertCircle size={28} />}
          title="Couldn't load recommendations"
          subtitle="Something went wrong. Try reloading the page."
        />
      ) : rows.length === 0 ? (
        <EmptyState
          icon={<Compass size={28} />}
          title="Nothing discovered yet"
          subtitle="The new-release and similarity scans run daily. Acquisition also has to be enabled on the worker before a candidate can become a track."
        />
      ) : (
        <>
          <SummaryStrip summary={data?.summary ?? []} />

          <div className="flex flex-wrap gap-1.5">
            {FILTERS.map((f) => {
              const count = rows.filter((row) => matches(row, f.id)).length;
              const active = filter === f.id;
              return (
                <button
                  key={f.id}
                  onClick={() => setFilter(f.id)}
                  className={`flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs transition-colors ${
                    active
                      ? 'border-accent/60 bg-accent/10 text-accent'
                      : 'border-border text-secondary hover:bg-surface1'
                  }`}
                >
                  {f.label}
                  <span className="font-mono tabular-nums text-[11px] text-machine">{count}</span>
                </button>
              );
            })}
          </div>

          {visible.length === 0 ? (
            <EmptyState compact icon={<Compass size={28} />} title="Nothing in this state" />
          ) : (
            <ul className="track-list rounded-lg border border-border bg-surface0/40 px-1.5">
              {visible.map((row) => {
                const { title, artist } = displayName(row);
                const plays = row.completed_plays ?? 0;
                const skips = row.quick_skips ?? 0;
                // Stacked below 640px: at that width the name, the badge and the
                // mono readout cannot share a row without the title collapsing
                // to two characters.
                return (
                  <li
                    key={row.id}
                    className="flex flex-col gap-1 py-2 sm:min-h-14 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2.5"
                  >
                    <div className="min-w-0 flex-1">
                      <div className="flex flex-wrap items-center gap-2">
                        <span className="truncate text-sm font-medium text-text">{title}</span>
                        {statusBadge(row)}
                      </div>
                      <div className="truncate text-xs text-secondary">
                        {artist ? `${artist} · ` : ''}
                        {provenance(row)}
                      </div>
                      {row.last_error && (
                        <div className="mt-0.5 truncate font-mono text-[11px] text-red" title={row.last_error}>
                          {row.last_error}
                        </div>
                      )}
                    </div>
                    <div className="flex items-center gap-3 font-mono text-xs tabular-nums text-machine sm:flex-none">
                      {row.probation_status === 'probation' ? (
                        <span title="Completed plays that keep it, quick skips that drop it">
                          {plays}/{KEEP_AFTER_PLAYS} plays · {skips}/{DROP_AFTER_SKIPS} skips
                        </span>
                      ) : row.track_id ? (
                        <span title="Completed plays · quick skips">
                          {plays} plays · {skips} skips
                        </span>
                      ) : (
                        <span title="Acquisition attempts">
                          {row.acquisition_attempts} attempts
                        </span>
                      )}
                      <span className="text-muted" title="Acquired, or first seen">
                        {shortDate(row.acquired_at ?? row.first_seen_at)}
                      </span>
                    </div>
                  </li>
                );
              })}
            </ul>
          )}
        </>
      )}
    </PageContainer>
  );
}
