'use client';

import { useQueryClient } from '@tanstack/react-query';
import { format } from 'date-fns';
import { Bar, BarChart, CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import Link from 'next/link';
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { BarChart3, RefreshCcw, Search, X } from 'lucide-react';
import { AppShell } from '@/components/shell/app-shell';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Panel } from '@/components/ui/panel';
import { useAuthGuard } from '@/hooks/use-auth-guard';
import { useLinkPrefetch } from '@/hooks/use-link-prefetch';
import {
  GRAPH_CADENCE_OPTIONS,
  GRAPH_CHART_OPTIONS,
  GRAPH_SCALE_OPTIONS,
  GRAPH_SURFACE_LABELS,
  buildGraphingHref,
  getGraphMetricDefinition,
  metricsForSurfaceAndCadence,
  normalizeGraphTickers,
  parseGraphingParams,
  resolveGraphMetric,
  serializeGraphingParams,
  type GraphChartKind,
  type GraphMetricDefinition,
  type GraphingUrlState
} from '@/lib/graphing/catalog';
import {
  buildGraphingComparisonData,
  type GraphingChartDatum,
  type GraphingFetchResult,
  type GraphingLatestValueRow,
  type GraphingSeriesPoint
} from '@/lib/graphing/series';
import { ApiError } from '@/lib/api';
import {
  formatCurrencyByScale,
  formatPercent,
  type NumberScaleUnit
} from '@/lib/format';
import type {
  FinancialCadence,
  FinancialUnit
} from '@/lib/types';
import { companyFinancialStatementsQueryOptions } from '@/lib/query/options';
import { cn } from '@/lib/utils';

const CHART_COLORS = ['#d9dee5', '#c7cdd5', '#b5bcc5', '#a4acb6', '#939ca7'] as const;
const CHART_MUTED = '#a1a9b3';
const CHART_GRID = 'rgba(196, 202, 211, 0.18)';
const CHART_TOOLTIP_BG = 'rgba(31, 34, 39, 0.96)';
const CHART_TOOLTIP_BORDER = 'rgba(220, 226, 234, 0.24)';

type TooltipEntry = {
  dataKey?: string | number;
  color?: string;
  value?: number | string | null;
  payload?: GraphingChartDatum;
};

function formatLongDate(value: string | null) {
  if (!value) {
    return 'n/a';
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return 'n/a';
  }

  return format(parsed, 'MMM dd, yyyy');
}

function formatShortDate(value: string) {
  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return value;
  }

  return format(parsed, 'MMM yyyy');
}

function formatMetricValue(
  value: number | null | undefined,
  unit: FinancialUnit,
  scale: NumberScaleUnit
) {
  if (value === null || value === undefined) {
    return 'n/a';
  }

  switch (unit) {
    case 'currency':
      return formatCurrencyByScale(value, scale);
    case 'percent':
      return formatPercent(value * 100);
    case 'ratio':
      return `${value.toFixed(2)}x`;
    case 'shares':
    case 'count':
      return new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 2 }).format(value);
    default:
      return String(value);
  }
}

function formatChangeValue(
  value: number | null,
  unit: FinancialUnit,
  scale: NumberScaleUnit
) {
  if (value === null) {
    return 'n/a';
  }

  if (unit === 'percent') {
    const signed = value >= 0 ? '+' : '';
    return `${signed}${formatPercent(value * 100)}`;
  }

  if (unit === 'ratio') {
    const signed = value >= 0 ? '+' : '';
    return `${signed}${value.toFixed(2)}x`;
  }

  if (unit === 'currency') {
    const formatted = formatCurrencyByScale(Math.abs(value), scale);
    return value >= 0 ? `+${formatted}` : `-${formatted}`;
  }

  const formatted = new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 2 }).format(Math.abs(value));
  return value >= 0 ? `+${formatted}` : `-${formatted}`;
}

function tickerPillClass(disabled?: boolean) {
  return cn(
    'inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs',
    disabled
      ? 'border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] text-[color:var(--terminal-muted)]'
      : 'border-[color:var(--line-strong)] bg-[color:var(--panel-bright)] text-[color:var(--terminal-bright)]'
  );
}

function ControlSection<T extends string>(props: {
  label: string;
  value: T;
  options: Array<{ value: T; label: string }>;
  onChange: (value: T) => void;
  ariaLabel: string;
}) {
  return (
    <div className="space-y-2">
      <p className="panel-heading text-[11px] uppercase tracking-[0.18em] text-[color:var(--terminal-muted)]">{props.label}</p>
      <div className="flex flex-wrap gap-2">
        {props.options.map((option) => (
          <Button
            key={option.value}
            type="button"
            aria-label={`${props.ariaLabel} ${option.label}`}
            variant={option.value === props.value ? 'primary' : 'ghost'}
            className="px-3 py-1.5 text-xs"
            onClick={() => props.onChange(option.value)}
          >
            {option.label}
          </Button>
        ))}
      </div>
    </div>
  );
}

function ComparisonTooltip(props: {
  active?: boolean;
  payload?: TooltipEntry[];
  metric: GraphMetricDefinition;
  scale: NumberScaleUnit;
}) {
  if (!props.active || !props.payload || props.payload.length === 0) {
    return null;
  }

  const datum = props.payload[0]?.payload;
  if (!datum) {
    return null;
  }

  const entries = props.payload
    .filter((entry) => typeof entry.dataKey === 'string')
    .map((entry) => {
      const ticker = entry.dataKey as string;
      const meta = datum[`meta__${ticker}`] as GraphingSeriesPoint | undefined;

      return {
        ticker,
        color: entry.color ?? CHART_MUTED,
        value: typeof entry.value === 'number' ? entry.value : null,
        meta
      };
    });

  return (
    <div
      className="min-w-[220px] rounded-xl border px-3 py-3 text-sm shadow-[0_16px_40px_rgba(0,0,0,0.34)]"
      style={{
        backgroundColor: CHART_TOOLTIP_BG,
        borderColor: CHART_TOOLTIP_BORDER
      }}
    >
      <p className="text-xs uppercase tracking-[0.16em] text-[color:var(--terminal-muted)]">
        {formatLongDate(entries[0]?.meta?.dateKey ?? null)}
      </p>
      <div className="mt-3 space-y-2">
        {entries.map((entry) => (
          <div key={entry.ticker} className="rounded-lg border border-[color:var(--line-weak)] bg-[color:rgba(45,49,55,0.72)] px-2 py-2">
            <div className="flex items-center justify-between gap-3">
              <div className="flex items-center gap-2">
                <span className="size-2 rounded-full" style={{ backgroundColor: entry.color }} aria-hidden="true" />
                <span className="font-medium text-[color:var(--terminal-bright)]">{entry.ticker}</span>
              </div>
              <span className="text-[color:var(--terminal-bright)]">
                {formatMetricValue(entry.value, props.metric.unit, props.scale)}
              </span>
            </div>
            {entry.meta ? (
              <p className="mt-1 text-xs text-[color:var(--terminal-muted)]">
                {entry.meta.filingType} · {entry.meta.periodLabel}
              </p>
            ) : null}
          </div>
        ))}
      </div>
    </div>
  );
}

export default function GraphingPage() {
  return (
    <Suspense fallback={<div className="flex min-h-screen items-center justify-center text-sm text-[color:var(--terminal-muted)]">Loading graphing desk...</div>}>
      <GraphingPageContent />
    </Suspense>
  );
}

function GraphingPageContent() {
  const { isPending, isAuthenticated } = useAuthGuard();
  const searchParams = useSearchParams();
  const router = useRouter();
  const queryClient = useQueryClient();
  const { prefetchResearchTicker } = useLinkPrefetch();

  const graphState = useMemo(() => parseGraphingParams(searchParams), [searchParams]);
  const canonicalQuery = useMemo(() => serializeGraphingParams(graphState), [graphState]);
  const currentQuery = searchParams.toString();

  const [tickerInput, setTickerInput] = useState(graphState.tickers.join(', '));
  const [results, setResults] = useState<GraphingFetchResult[]>([]);
  const [loading, setLoading] = useState(true);
  const [refreshNonce, setRefreshNonce] = useState(0);

  useEffect(() => {
    setTickerInput(graphState.tickers.join(', '));
  }, [graphState.tickers]);

  useEffect(() => {
    if (currentQuery !== canonicalQuery) {
      router.replace(`/graphing?${canonicalQuery}`, { scroll: false });
    }
  }, [canonicalQuery, currentQuery, router]);

  const replaceGraphState = useCallback((patch: Partial<GraphingUrlState>) => {
    const nextSurface = patch.surface ?? graphState.surface;
    const nextCadence = patch.cadence ?? graphState.cadence;
    const nextTickers = patch.tickers && patch.tickers.length > 0 ? patch.tickers : graphState.tickers;
    const nextMetric = resolveGraphMetric(
      nextSurface,
      nextCadence,
      patch.metric ?? graphState.metric
    );

    const nextState: GraphingUrlState = {
      tickers: nextTickers,
      surface: nextSurface,
      cadence: nextCadence,
      metric: nextMetric,
      chart: patch.chart ?? graphState.chart,
      scale: patch.scale ?? graphState.scale
    };

    router.replace(`/graphing?${serializeGraphingParams(nextState)}`, { scroll: false });
  }, [graphState, router]);

  useEffect(() => {
    if (isPending || !isAuthenticated) {
      return;
    }

    let cancelled = false;

    async function loadComparisonSet() {
      setLoading(true);

      const settled = await Promise.allSettled(graphState.tickers.map(async (ticker) => {
        const response = await queryClient.ensureQueryData(companyFinancialStatementsQueryOptions({
          ticker,
          surfaceKind: graphState.surface,
          cadence: graphState.cadence,
          includeDimensions: false,
          includeFacts: false,
          limit: 16
        }));

        return response.financials;
      }));

      if (cancelled) {
        return;
      }

      setResults(settled.map((entry, index) => {
        const ticker = graphState.tickers[index] ?? `ticker-${index + 1}`;

        if (entry.status === 'fulfilled') {
          return {
            ticker,
            financials: entry.value
          } satisfies GraphingFetchResult;
        }

        const reason = entry.reason instanceof ApiError
          ? entry.reason.message
          : entry.reason instanceof Error
            ? entry.reason.message
            : 'Unable to load financial history';

        return {
          ticker,
          error: reason
        } satisfies GraphingFetchResult;
      }));
      setLoading(false);
    }

    void loadComparisonSet();

    return () => {
      cancelled = true;
    };
  }, [graphState, isAuthenticated, isPending, queryClient, refreshNonce]);

  const metricOptions = useMemo(() => metricsForSurfaceAndCadence(graphState.surface, graphState.cadence), [graphState.cadence, graphState.surface]);
  const selectedMetric = useMemo(
    () => getGraphMetricDefinition(graphState.surface, graphState.cadence, graphState.metric),
    [graphState.cadence, graphState.metric, graphState.surface]
  );
  const comparison = useMemo(() => buildGraphingComparisonData({
    results,
    surface: graphState.surface,
    metric: graphState.metric
  }), [graphState.metric, graphState.surface, results]);

  const hasCurrencyScale = selectedMetric?.unit === 'currency';

  if (isPending || !isAuthenticated) {
    return <div className="flex min-h-screen items-center justify-center text-sm text-[color:var(--terminal-muted)]">Loading graphing desk...</div>;
  }

  return (
    <AppShell
      title="Graphing"
      subtitle="Compare one normalized filing metric across multiple companies with shareable chart state."
      activeTicker={graphState.tickers[0] ?? null}
      actions={(
        <div className="flex flex-wrap items-center gap-2">
          <Button
            variant="secondary"
            onClick={() => setRefreshNonce((current) => current + 1)}
          >
            <RefreshCcw className="size-4" />
            Refresh
          </Button>
          <Button
            variant="secondary"
            onClick={() => replaceGraphState(parseGraphingParams(new URLSearchParams()))}
          >
            <BarChart3 className="size-4" />
            Reset View
          </Button>
        </div>
      )}
    >
      <Panel title="Compare Set" subtitle="Enter up to five tickers. Duplicates are removed and the first ticker anchors research context.">
        <form
          className="flex flex-col gap-4"
          onSubmit={(event) => {
            event.preventDefault();
            const nextTickers = normalizeGraphTickers(tickerInput);
            replaceGraphState({ tickers: nextTickers.length > 0 ? nextTickers : [...graphState.tickers] });
          }}
        >
          <div className="flex flex-wrap items-center gap-3">
            <Input
              aria-label="Compare tickers"
              value={tickerInput}
              onChange={(event) => setTickerInput(event.target.value.toUpperCase())}
              placeholder="MSFT, AAPL, NVDA"
              className="min-w-[260px] flex-1"
            />
            <Button type="submit">
              <Search className="size-4" />
              Update Compare Set
            </Button>
            <Link
              href={buildGraphingHref(graphState.tickers[0] ?? null)}
              onMouseEnter={() => {
                if (graphState.tickers[0]) {
                  prefetchResearchTicker(graphState.tickers[0]);
                }
              }}
              onFocus={() => {
                if (graphState.tickers[0]) {
                  prefetchResearchTicker(graphState.tickers[0]);
                }
              }}
              className="text-sm text-[color:var(--accent)] hover:text-[color:var(--accent-strong)]"
            >
              Open canonical graphing URL
            </Link>
          </div>
          <div className="flex flex-wrap gap-2">
            {graphState.tickers.map((ticker, index) => (
              <span key={ticker} className={tickerPillClass(index === 0)}>
                {ticker}
                <button
                  type="button"
                  aria-label={`Remove ${ticker}`}
                  disabled={graphState.tickers.length === 1}
                  className="rounded-full p-0.5 text-[color:var(--terminal-muted)] transition hover:text-[color:var(--terminal-bright)] disabled:cursor-not-allowed disabled:opacity-40"
                  onClick={() => {
                    const nextTickers = graphState.tickers.filter((entry) => entry !== ticker);
                    if (nextTickers.length > 0) {
                      replaceGraphState({ tickers: nextTickers });
                    }
                  }}
                >
                  <X className="size-3" />
                </button>
              </span>
            ))}
          </div>
        </form>
      </Panel>

      <Panel title="Chart Controls" subtitle="Surface, metric, cadence, chart style, and scale stay in the URL for deep-linking.">
        <div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
          <div className="space-y-5">
            <ControlSection
              label="Surface"
              ariaLabel="Graph surface"
              value={graphState.surface}
              options={Object.entries(GRAPH_SURFACE_LABELS).map(([value, label]) => ({ value: value as GraphingUrlState['surface'], label }))}
              onChange={(value) => replaceGraphState({ surface: value })}
            />
            <ControlSection
              label="Cadence"
              ariaLabel="Graph cadence"
              value={graphState.cadence}
              options={GRAPH_CADENCE_OPTIONS}
              onChange={(value) => replaceGraphState({ cadence: value as FinancialCadence })}
            />
            <ControlSection
              label="Chart Type"
              ariaLabel="Chart type"
              value={graphState.chart}
              options={GRAPH_CHART_OPTIONS}
              onChange={(value) => replaceGraphState({ chart: value as GraphChartKind })}
            />
            {hasCurrencyScale ? (
              <ControlSection
                label="Scale"
                ariaLabel="Value scale"
                value={graphState.scale}
                options={GRAPH_SCALE_OPTIONS}
                onChange={(value) => replaceGraphState({ scale: value as NumberScaleUnit })}
              />
            ) : null}
          </div>

          <div className="space-y-2">
            <p className="panel-heading text-[11px] uppercase tracking-[0.18em] text-[color:var(--terminal-muted)]">Metric</p>
            <select
              aria-label="Metric selector"
              value={graphState.metric}
              onChange={(event) => replaceGraphState({ metric: event.target.value })}
              className="w-full rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-3 py-2.5 text-sm text-[color:var(--terminal-bright)] outline-none transition focus:border-[color:var(--line-strong)] focus:shadow-[0_0_0_3px_var(--focus-ring)]"
            >
              {metricOptions.map((option) => (
                <option key={option.key} value={option.key} className="bg-[#1f2227]">
                  {option.label}
                </option>
              ))}
            </select>
            {selectedMetric ? (
              <div className="rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-3 text-sm text-[color:var(--terminal-muted)]">
                <p className="text-[color:var(--terminal-bright)]">{selectedMetric.label}</p>
                <p className="mt-1">
                  {GRAPH_SURFACE_LABELS[selectedMetric.surface]} · {selectedMetric.category.replace(/_/g, ' ')} · unit {selectedMetric.unit}
                </p>
              </div>
            ) : null}
          </div>
        </div>
      </Panel>

      <Panel title="Comparison Chart" subtitle="One metric, multiple companies, aligned by actual reported dates.">
        {loading ? (
          <p className="text-sm text-[color:var(--terminal-muted)]">Loading comparison chart...</p>
        ) : !selectedMetric || !comparison.hasAnyData ? (
          <div className="rounded-xl border border-dashed border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-4 py-6">
            <p className="text-sm text-[color:var(--terminal-bright)]">No chart data available for the selected compare set.</p>
            <p className="mt-2 text-sm text-[color:var(--terminal-muted)]">Try a different metric, cadence, or company basket.</p>
          </div>
        ) : (
          <>
            {comparison.hasPartialData ? (
              <div className="mb-4 rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-3 py-3 text-sm text-[color:var(--terminal-muted)]">
                Partial coverage detected. Some companies are missing values for this metric or failed to load, but the remaining series still render.
              </div>
            ) : null}
            <div className="mb-4 flex flex-wrap gap-2">
              {comparison.companies.map((company, index) => (
                <span key={company.ticker} className={tickerPillClass(company.status !== 'ready')}>
                  <span
                    className="size-2 rounded-full"
                    style={{ backgroundColor: CHART_COLORS[index % CHART_COLORS.length] }}
                    aria-hidden="true"
                  />
                  {company.ticker}
                  <span className="text-[color:var(--terminal-muted)]">{company.companyName}</span>
                </span>
              ))}
            </div>
            <div className="h-[360px]">
              <ResponsiveContainer width="100%" height="100%">
                {graphState.chart === 'line' ? (
                  <LineChart data={comparison.chartData}>
                    <CartesianGrid strokeDasharray="2 2" stroke={CHART_GRID} />
                    <XAxis
                      dataKey="dateKey"
                      stroke={CHART_MUTED}
                      fontSize={12}
                      minTickGap={20}
                      tickFormatter={(value: string) => formatShortDate(value)}
                    />
                    <YAxis
                      stroke={CHART_MUTED}
                      fontSize={12}
                      tickFormatter={(value: number) => formatMetricValue(value, selectedMetric.unit, graphState.scale)}
                    />
                    <Tooltip content={<ComparisonTooltip metric={selectedMetric} scale={graphState.scale} />} />
                    {comparison.companies.map((company, index) => (
                      <Line
                        key={company.ticker}
                        type="monotone"
                        dataKey={company.ticker}
                        connectNulls={false}
                        stroke={CHART_COLORS[index % CHART_COLORS.length]}
                        strokeWidth={2.5}
                        dot={false}
                        name={company.ticker}
                      />
                    ))}
                  </LineChart>
                ) : (
                  <BarChart data={comparison.chartData}>
                    <CartesianGrid strokeDasharray="2 2" stroke={CHART_GRID} />
                    <XAxis
                      dataKey="dateKey"
                      stroke={CHART_MUTED}
                      fontSize={12}
                      minTickGap={20}
                      tickFormatter={(value: string) => formatShortDate(value)}
                    />
                    <YAxis
                      stroke={CHART_MUTED}
                      fontSize={12}
                      tickFormatter={(value: number) => formatMetricValue(value, selectedMetric.unit, graphState.scale)}
                    />
                    <Tooltip content={<ComparisonTooltip metric={selectedMetric} scale={graphState.scale} />} />
                    {comparison.companies.map((company, index) => (
                      <Bar
                        key={company.ticker}
                        dataKey={company.ticker}
                        fill={CHART_COLORS[index % CHART_COLORS.length]}
                        radius={[6, 6, 0, 0]}
                        name={company.ticker}
                      />
                    ))}
                  </BarChart>
                )}
              </ResponsiveContainer>
            </div>
          </>
        )}
      </Panel>

      <Panel title="Latest Values" subtitle="Most recent reported point per company, plus the prior point and one-step change.">
        <div className="overflow-x-auto">
          <table className="data-table min-w-[920px]">
            <thead>
              <tr>
                <th>Company</th>
                <th>Latest</th>
                <th>Prior</th>
                <th>Change</th>
                <th>Period</th>
                <th>Filing</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              {comparison.latestRows.map((row, index) => (
                <tr key={row.ticker}>
                  <td>
                    <div className="flex flex-col gap-1">
                      <div className="flex items-center gap-2">
                        <span
                          className="size-2 rounded-full"
                          style={{ backgroundColor: CHART_COLORS[index % CHART_COLORS.length] }}
                          aria-hidden="true"
                        />
                        <span>{row.ticker}</span>
                      </div>
                      <span className="text-xs text-[color:var(--terminal-muted)]">{row.companyName}</span>
                    </div>
                  </td>
                  <td>{selectedMetric ? formatMetricValue(row.latestValue, selectedMetric.unit, graphState.scale) : 'n/a'}</td>
                  <td>{selectedMetric ? formatMetricValue(row.priorValue, selectedMetric.unit, graphState.scale) : 'n/a'}</td>
                  <td className={cn(row.changeValue !== null && row.changeValue >= 0 ? 'text-[color:var(--accent)]' : row.changeValue !== null ? 'text-[#ffb5b5]' : 'text-[color:var(--terminal-muted)]')}>
                    {selectedMetric ? formatChangeValue(row.changeValue, selectedMetric.unit, graphState.scale) : 'n/a'}
                  </td>
                  <td>{formatLongDate(row.latestDateKey)} {row.latestPeriodLabel ? <span className="text-xs text-[color:var(--terminal-muted)]">· {row.latestPeriodLabel}</span> : null}</td>
                  <td>{row.latestFilingType ?? 'n/a'}</td>
                  <td>
                    {row.status === 'ready' ? (
                      <span className="text-[color:var(--accent)]">Ready</span>
                    ) : row.status === 'no_metric_data' ? (
                      <span className="text-[color:var(--terminal-muted)]">No metric data</span>
                    ) : (
                      <span className="text-[#ffb5b5]">{row.errorMessage ?? 'Load failed'}</span>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </Panel>
    </AppShell>
  );
}
