"use client";

import type { ReactNode } from "react";
import { isChartFailed } from "@enode/core/api/charts";
import type { ChartPreview as ChartPreviewData, ChartType } from "@enode/core/api/dtos/charts";
import { classifyError } from "@enode/core/errors/classify";
import { useTranslation, type TranslateFn } from "@enode/core/translations/hooks";
import { Button } from "@enode/ui/button";
import { formatDay, today } from "@enode/ui/calendar-shared";
import { InlineAlert } from "@enode/ui/feedback/inline-alert";
import { openSupportReport } from "@enode/ui/feedback/support-report-store";
import { backendMessage } from "@enode/ui/feedback/use-error-presenter";
import { ChartSpotlightIsolation } from "../../../../components/charts/chart-card-actions";
import { InsightsChartTile } from "../../../../components/insights-chart-tile";
import { useRecipeNames } from "./builder-recipe";
import { namesSummary } from "./builder-steps";
import { ResultCard } from "./builder-stepper";
import type { ChartRequest, FlatDraft, GroupDraft, ProfileDraft } from "./chart-draft";
import { chartShape } from "./chart-shape";
import { ChartPreview } from "./chart-shape-preview";
import { sizeAdvice } from "./chart-size";
import { PROFILE_AXES_MAX, PROFILE_AXES_MIN } from "./guided-steps";
import { matchingPreset, type Timeframe } from "./timeframe";
import { presetLabel } from "./timeframe-field";
import { useChartPreview } from "./use-chart-preview";
import { useCoveredNames, useSelectionSize } from "./use-selection-size";

// "What you'll get": the real chart built from the picks (the layout named from
// them until every step is filled), what it opens as and its size warnings — shared by the full builder's last card and the guided
// check step, plus the plain-language summaries both build it from.

/** A timeframe in words: the preset's name, else the custom dates. */
export function timeframeSummary(timeframe: Timeframe, t: TranslateFn): string {
  const preset = matchingPreset(timeframe, today());
  if (preset) return presetLabel(preset, t);
  if (timeframe.mode === "upToToday")
    return timeframe.from ? t("Since {date}", { date: formatDay(timeframe.from) }) : t("Required");
  return timeframe.from && timeframe.to
    ? t("{from} – {to}", { from: formatDay(timeframe.from), to: formatDay(timeframe.to) })
    : t("Required");
}

// Where a metric's numbers come from. Literal `t` calls per level so every
// string is extractable.
function levelPhrase(level: string | undefined, t: TranslateFn): string | null {
  switch (level) {
    case "rep":
      return t("per rep");
    case "set":
      return t("per set");
    case "session":
      return t("per session");
    case "user":
      return t("per athlete");
    default:
      return null;
  }
}

/** A metric with where its numbers come from: "Mean velocity, per rep". */
export function metricWithLevel(label: string | null, level: string | undefined, t: TranslateFn): string {
  if (!label) return "";
  const phrase = levelPhrase(level, t);
  return phrase ? t("{metric}, {level}", { metric: label, level: phrase }) : label;
}

type Scope = {
  userIDs: readonly string[];
  userTagIDs: readonly string[];
  exerciseDefinitionIDs: readonly string[];
  exerciseTagIDs: readonly string[];
};

/**
 * Who and which lifts in words: "U18 squad, Lena +2 · 9 athletes today" and
 * "Back squat, Bench press". Empty when nothing is picked.
 */
export function useScopeText(scope: Scope): { athletes: string; exercises: string } {
  const { t } = useTranslation();
  const names = useRecipeNames(scope);
  const size = useSelectionSize(scope);
  const athleteTotal = scope.userIDs.length + scope.userTagIDs.length;
  const exerciseTotal = scope.exerciseDefinitionIDs.length + scope.exerciseTagIDs.length;
  return {
    athletes: [
      namesSummary(
        [...names.athleteTagNames, ...names.athleteNames],
        athleteTotal,
        t("{count, plural, one {# athlete} other {# athletes}}", { count: athleteTotal }),
        t,
      ),
      scope.userTagIDs.length > 0
        ? t("{count, plural, one {# athlete today} other {# athletes today}}", { count: size.athletes })
        : "",
    ]
      .filter(Boolean)
      .join(" · "),
    exercises: namesSummary(
      [...names.exerciseTagNames, ...names.exerciseNames],
      exerciseTotal,
      t("{count, plural, one {# exercise} other {# exercises}}", { count: exerciseTotal }),
      t,
    ),
  };
}

/**
 * A Profile's two groups in words: `name` is how the preview labels a group
 * (its label, else who is in it, else the fallback); `summary` is a folded
 * step's "U18 squad · U18 squad, Lena +2 · Back squat".
 */
export function useProfileGroupText(
  draft: ProfileDraft,
  fallback: { profile: string; reference: string },
) {
  const { t } = useTranslation();
  const names = { profile: useRecipeNames(draft.profile), reference: useRecipeNames(draft.reference) };
  const text = (key: "profile" | "reference") => {
    const group: GroupDraft = draft[key];
    const athletes = group.userIDs.length + group.userTagIDs.length;
    const exercises = group.exerciseDefinitionIDs.length + group.exerciseTagIDs.length;
    const who = namesSummary(
      [...names[key].athleteTagNames, ...names[key].athleteNames],
      athletes,
      t("{count, plural, one {# athlete} other {# athletes}}", { count: athletes }),
      t,
    );
    const which = namesSummary(
      [...names[key].exerciseTagNames, ...names[key].exerciseNames],
      exercises,
      t("{count, plural, one {# exercise} other {# exercises}}", { count: exercises }),
      t,
    );
    return {
      name: group.label.trim() || who || fallback[key],
      summary: [group.label.trim(), who, which].filter(Boolean).join(" · "),
    };
  };
  const profile = text("profile");
  const reference = text("reference");
  return {
    name: { profile: profile.name, reference: reference.name },
    summary: { profile: profile.summary, reference: reference.summary },
  };
}

// The preview has no id, and the chart components never read it.
const PREVIEW_ID = "preview";

function PreviewErrorAlert({ error, onRetry }: { error: unknown; onRetry: () => void }) {
  const { t } = useTranslation();
  const offline = classifyError(error).disposition === "offline";
  return (
    <InlineAlert
      variant={offline ? "offline" : "error"}
      className="mt-3"
      onRetry={onRetry}
      retryLabel={t("Try again")}
      onReport={offline ? undefined : () => openSupportReport(error)}
      reportLabel={t("Report")}
    >
      {backendMessage(error) ?? t("We couldn't build the preview. You can still save the chart.")}
    </InlineAlert>
  );
}

/**
 * The real chart once every step is filled (see {@link useChartPreview}), else
 * `template` — the layout named from the picks — and the status line for it.
 * Title and note aren't sent with the preview; they're shown from the draft,
 * and an empty title falls back to the server's own name, as on a saved chart.
 */
function useLivePreview(
  type: ChartType,
  request: ChartRequest | null,
  text: { title: string; note: string },
  template: ReactNode,
): { preview: ReactNode; status: string } {
  const { t } = useTranslation();
  const { state, retry } = useChartPreview(request);
  // The preview only reads (hover): no open, focus, zoom or athlete actions,
  // and kept out of the board's spotlight; it isn't a saved chart yet.
  const tile = (chart: ChartPreviewData<ChartType>) => (
    <ChartSpotlightIsolation>
    <InsightsChartTile
      type={type}
      item={{
        ...chart,
        id: PREVIEW_ID,
        chartConfigDto: {
          ...chart.chartConfigDto,
          title: text.title.trim() || null,
          note: text.note.trim() || null,
        },
      }}
      failedDetail={t("Building its data went wrong this time. You can still save the chart.")}
      emptyAction={
        isChartFailed(chart) ? (
          <Button variant="neutral" size="rounded" onClick={retry}>
            {t("Try again")}
          </Button>
        ) : undefined
      }
    />
    </ChartSpotlightIsolation>
  );
  switch (state.status) {
    case "off":
      return { preview: template, status: t("Values appear once every step is filled") };
    case "loading":
      return state.previous
        ? {
            preview: (
              <div aria-busy="true" className="opacity-50 transition-opacity">
                {tile(state.previous)}
              </div>
            ),
            status: t("Updating preview…"),
          }
        : {
            preview: (
              <div aria-busy="true" className="animate-pulse">
                {template}
              </div>
            ),
            status: t("Building preview…"),
          };
    case "ready":
      return { preview: tile(state.chart), status: t("Preview with your data") };
    case "error":
      return {
        preview: (
          <>
            {template}
            <PreviewErrorAlert error={state.error} onRetry={retry} />
          </>
        ),
        status: t("Preview unavailable"),
      };
  }
}

/** The preview of a Trend, Ranking, Distribution or Correlation. */
export function FlatPreviewCard({
  type,
  request,
  draft,
  title,
  metricLabel,
  metricYLabel,
  metricLevel,
  metricYLevel,
  exercisesOptional,
  complete,
  onShowWalkthrough,
}: {
  type: Exclude<ChartType, "profile">;
  /** The chart as it would be saved, once nothing blocks Save; null until then. */
  request: ChartRequest | null;
  draft: FlatDraft;
  /** The chart's title, or the automatic one; null while there's none. */
  title: string | null;
  metricLabel: string | null;
  /** A Correlation's second (Y) metric. */
  metricYLabel: string | null;
  metricLevel?: string;
  metricYLevel?: string;
  exercisesOptional: boolean;
  /** Nothing blocks Save: say what it opens as. */
  complete: boolean;
  onShowWalkthrough?: () => void;
}) {
  const { t } = useTranslation();
  const size = useSelectionSize(draft);
  const covered = useCoveredNames(draft);
  const scope = useScopeText(draft);
  const isCorrelation = type === "correlation";
  const hasAthletes = draft.userIDs.length > 0 || draft.userTagIDs.length > 0;
  const layout =
    size.athletes > 0 && (size.lifts > 0 || exercisesOptional) ? sizeAdvice(type, size, t) : null;
  const timeframe = timeframeSummary(draft.timeframe, t);
  const metricFact = isCorrelation
    ? metricLabel && metricYLabel
      ? t("{x} vs {y}", {
          x: metricWithLevel(metricLabel, metricLevel, t),
          y: metricWithLevel(metricYLabel, metricYLevel, t),
        })
      : ""
    : metricWithLevel(metricLabel, metricLevel, t);
  const live = useLivePreview(
    type,
    request,
    draft,
    <ChartPreview
      shape={chartShape(type, hasAthletes ? size : null)}
      title={title}
      facts={[metricFact, scope.athletes, scope.exercises, timeframe]}
      labels={{
        metric: isCorrelation ? metricYLabel : metricLabel,
        metricX: isCorrelation ? metricLabel : null,
        athletes: covered.athletes,
        lifts: covered.lifts,
        timeframe,
      }}
    />,
  );
  return (
    <ResultCard
      preview={live.preview}
      status={live.status}
      opensAs={complete && layout ? layout.opensAs : null}
      series={complete ? (layout?.series ?? null) : null}
      warning={complete ? (layout?.warning ?? null) : null}
      complete={complete}
      onShowWalkthrough={onShowWalkthrough}
    />
  );
}

/** The preview of a Profile: the radar with its axes and both groups named. */
export function ProfilePreviewCard({
  request,
  draft,
  title,
  metricNames,
  groupNames,
  axesSummary,
  warning,
  complete,
  onShowWalkthrough,
}: {
  /** The chart as it would be saved, once nothing blocks Save; null until then. */
  request: ChartRequest | null;
  draft: ProfileDraft;
  title: string | null;
  /** One name per radar axis. */
  metricNames: readonly string[];
  /** The groups as named by {@link useProfileGroupText}. */
  groupNames: { profile: string; reference: string };
  /** "Mean velocity, Peak power +1 · 3 axes". */
  axesSummary: string;
  /** The 3-to-6-axes hint, or null. */
  warning: string | null;
  complete: boolean;
  onShowWalkthrough?: () => void;
}) {
  const { t } = useTranslation();
  const timeframe = timeframeSummary(draft.timeframe, t);
  const axes = metricNames.length;
  const live = useLivePreview(
    "profile",
    request,
    draft,
    <ChartPreview
      shape={chartShape("profile", null, metricNames.length)}
      title={title}
      facts={[axesSummary, t("{x} vs {y}", { x: groupNames.profile, y: groupNames.reference }), timeframe]}
      labels={{
        metric: null,
        athletes: [],
        lifts: [],
        timeframe,
        axes: metricNames,
        groups: groupNames,
      }}
    />,
  );
  return (
    <ResultCard
      preview={live.preview}
      status={live.status}
      opensAs={
        complete
          ? t("{count, plural, one {a radar with # axis} other {a radar with # axes}}", { count: axes })
          : null
      }
      warning={complete ? warning : null}
      complete={complete}
      onShowWalkthrough={onShowWalkthrough}
    />
  );
}

/** The radar reads best with a handful of axes; outside that range, say so (never a block). */
export function profileAxesHint(count: number, t: TranslateFn): string | null {
  if (count === 0) return null;
  if (count < PROFILE_AXES_MIN) return t("With fewer than 3 metrics the shape says little. Add one or two more.");
  if (count > PROFILE_AXES_MAX) return t("More than 6 metrics get hard to read. Consider leaving some out.");
  return null;
}

/** "Mean velocity, Peak power +1 · 3 axes" — a Profile's metrics in words. */
export function axesSummaryText(metricNames: readonly string[], count: number, t: TranslateFn): string {
  if (metricNames.length === 0) return "";
  return `${namesSummary(metricNames, count, "", t)} · ${t("{count, plural, one {# axis} other {# axes}}", { count })}`;
}
