"use client";

import { useState } from "react";
import { Button } from "@enode/ui/button";
import { ScaleRow } from "../scale-row";
import { useSessionRpeCopy } from "./copy";
import { RPE_DESCRIPTORS, RPE_VALUES, rpeAccent, rpeColor } from "./scale";

// Session RPE (sRPE) — the post-workout check-in.
//
// ONE question: how hard was the whole session, 0–10. That single number is the
// entire contribution of this questionnaire. Training load is `sRPE × volume`,
// but the volume is not asked for — the app already records the session's sets
// and reps, so asking the athlete to retype them would invite a worse answer
// than the one already stored.
//
// Like wellness, this component does no I/O. Submit hands the payload to
// `onSubmitted` and the service page decides what that means, which keeps the
// flow testable and lets a different host do something else with the answers.

/** The shape a finished session-RPE logs — and what the host receives. */
export type SessionRpePayload = {
  /** 0–10, the whole session rated once. */
  rpe: number;
  /** The athlete, when the host handed the service a token. Null otherwise —
   *  this makes no backend call, so an anonymous rating still completes. */
  userId: string | null;
  /** When they logged it, ISO-8601. The flow has no other notion of time. */
  recordedAt: string;
};

export function sessionRpePayload(
  rpe: number,
  userId: string | null,
): SessionRpePayload {
  return { rpe, userId, recordedAt: new Date().toISOString() };
}

/**
 * What became of a logged rating, as the confirmation panel needs to say it.
 *
 * `undefined` — the prop left off — means no storage was attempted at all,
 * which is the anonymous case: the flow itself makes no backend call, so a
 * rating without a token still completes and simply claims nothing.
 *
 * The two halves of `saved` are separate because the server can do one without
 * the other. `sessionCount` of 0 means the rating was filed against the athlete
 * but matched no workout — nothing trained in the last day, or that workout was
 * already rated. A null `trainingLoad` means there was nothing to multiply.
 * Neither is a failure, and neither may be shown as a load of zero.
 */
export type SessionRpeOutcome =
  | { status: "saving" }
  | { status: "saved"; sessionCount: number; trainingLoad: number | null }
  /** `queued` means it was kept for a later retry, not lost. */
  | { status: "not-saved"; queued: boolean };

// One selectable step of the scale: a compact card carrying the step's heat on
// its LEFT RULE and its number, the wording beside it, and the house selected
// treatment when picked.
//
// The accent-rule shape is the repo pattern — `workout-card.tsx`, `SlotCard`,
// the notification and roster peeks all use the same `border-l-[3px]` card. It
// replaced a full-bleed fill, which at eleven steps read as one gradient block
// rather than a list, and forced the text to flip to light halfway down.
//
/**
 * What the confirmation panel says about where the rating went.
 *
 * Split out because it is the only part of this flow with real branching in it,
 * and every branch is a sentence that must not overclaim: a rating that reached
 * no workout, or a workout with no recorded volume, produces no training load
 * at all — not a load of zero.
 */
function OutcomeLine({
  outcome,
  t,
}: {
  outcome: SessionRpeOutcome | undefined;
  t: ReturnType<typeof useSessionRpeCopy>["t"];
}) {
  // Nothing was attempted — no token, so no backend call. Say what the rating is
  // FOR rather than what happened to it, which is all that is honest here.
  if (!outcome) {
    return (
      <span className="body-small text-foreground-more-muted">
        {t("This feeds your weekly training load, monotony and strain.")}
      </span>
    );
  }

  if (outcome.status === "saving") {
    return (
      <span className="body-small text-foreground-more-muted">
        {t("Saving…")}
      </span>
    );
  }

  if (outcome.status === "not-saved") {
    return (
      <span className="body-small text-foreground-muted">
        {outcome.queued
          ? t("Not saved yet — kept on this device and sent when you're back online.")
          : t("This couldn't be saved. Your rating wasn't recorded.")}
      </span>
    );
  }

  // Stored, but attached to nothing: no workout in the last day, or the one
  // there was had already been rated. The rating still counts for the athlete.
  if (outcome.sessionCount === 0) {
    return (
      <span className="body-small text-foreground-muted">
        {t(
          "Saved. It wasn't matched to a workout, so there's no training load for it — either nothing was recorded in the last day, or that session was already rated.",
        )}
      </span>
    );
  }

  // A workout, but nothing to multiply by: no sets, reps or durations on it.
  if (outcome.trainingLoad === null) {
    return (
      <span className="body-small text-foreground-muted">
        {t(
          "Saved. No training load though — that session has no recorded sets or reps to weigh it against.",
        )}
      </span>
    );
  }

  return (
    <div className="flex flex-col gap-1">
      <span className="stat-normal text-foreground-default">
        {t("Training load {load}", { load: Math.round(outcome.trainingLoad) })}
      </span>
      <span className="body-small text-foreground-more-muted">
        {t(
          "{count, plural, one {Across # workout} other {Across # workouts}} — this feeds your weekly load, monotony and strain.",
          { count: outcome.sessionCount },
        )}
      </span>
    </div>
  );
}

/**
 * The whole session-RPE check-in: rate the session, log it.
 *
 * Submit fires `onSubmitted` directly — the service turns it into whatever the
 * host needs. There is no confirmation step: with a single question the athlete
 * can already see exactly what they are about to log.
 *
 * Fills its parent, which must be a bounded flex column.
 */
export function SessionRpeCheckIn({
  onSubmitted,
  outcome,
  submitLabel,
  userId = null,
}: {
  onSubmitted?: (payload: SessionRpePayload) => void;
  /** Where the last logged rating ended up. Omit it when nothing is stored —
   *  the panel then claims nothing. See {@link SessionRpeOutcome}. */
  outcome?: SessionRpeOutcome;
  submitLabel?: string;
  /** Stamped into the payload. Null when the host handed over no token. */
  userId?: string | null;
}) {
  const { t, localize } = useSessionRpeCopy();
  const [rpe, setRpe] = useState<number | undefined>(undefined);
  // The logged confirmation is a terminal panel rather than another step —
  // same arrangement as the wellness summary.
  const [logged, setLogged] = useState<SessionRpePayload | null>(null);

  const reset = () => {
    setRpe(undefined);
    setLogged(null);
  };

  function log(): void {
    if (rpe === undefined) return;
    const payload = sessionRpePayload(rpe, userId);
    setLogged(payload);
    onSubmitted?.(payload);
  }

  if (logged) {
    return (
      <div className="scrollbar-none min-h-0 flex-1 overflow-y-auto px-6 pb-8">
        <div className="mx-auto flex w-full max-w-3xl flex-col gap-6">
          <div className="flex items-center gap-4 rounded-2xl border border-surface-stroke bg-surface-base p-6">
            {/* `text-foreground-default` throughout: the ramp's fills are the
                pale `-surface` feedback tints, which carry the default
                foreground in both themes. The old single-hue fill went solid at
                the top of the scale and needed the text to flip to light
                halfway up — one contrast ratio is one fewer thing to get
                wrong. */}
            <span
              className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl text-xl font-bold text-foreground-default"
              style={{ backgroundColor: rpeColor(logged.rpe) }}
            >
              {logged.rpe}
            </span>
            <div className="flex flex-col">
              <span className="title-small text-foreground-default">
                {t("Session logged — nice work")}
              </span>
              {/* The rating in words, so the confirmation says what was logged
                  rather than only that something was. The step's own wording —
                  never a neighbour's, which would read a word back to someone
                  that they did not pick. */}
              <span className="body-small text-foreground-muted">
                {localize(RPE_DESCRIPTORS[logged.rpe] ?? "")}
              </span>
            </div>
          </div>

          <OutcomeLine outcome={outcome} t={t} />

          {/* Only offered while nothing has been stored. Once a rating is on its
              way to the server these would be a lie: the server attaches a
              rating to a workout and then refuses to attach a second one, so
              "Edit" would not replace the first rating — it would file an
              orphan alongside it. */}
          {!outcome && (
            <div className="flex flex-wrap items-center gap-3">
              <Button
                variant="tertiary"
                size="rounded"
                onClick={() => setLogged(null)}
              >
                {t("Edit")}
              </Button>
              <Button variant="tertiary" size="rounded" onClick={reset}>
                {t("Log another")}
              </Button>
            </div>
          )}
        </div>
      </div>
    );
  }

  // ONE scroll container, with the action as its last child rather than as a
  // fixed bar. `min-h-full` on the column plus `mt-auto` on the action does the
  // whole job: where the scale fits, the button is pushed to the bottom of the
  // view and reads as a bar; where it does not, it sits in flow at the end of
  // the content and scrolls with it. No measurement, no breakpoint, and no
  // fixed chrome eating height from the viewport that has the least of it —
  // which is the one where eleven steps and a button have to coexist.
  //
  // A pinned bar was the previous answer. It cost a fixed ~76px on every screen
  // to solve a problem only small screens have, and drew a hard edge across the
  // frame to do it.
  return (
    <div className="scrollbar-none min-h-0 flex-1 overflow-y-auto">
      <div className="mx-auto flex min-h-full w-full max-w-3xl flex-col gap-6 px-6 py-6">
        <div className="flex flex-col gap-1">
          <h2 className="heading-normal text-foreground-default">
            {t("How was your workout?")}
          </h2>
          <p className="body-small text-foreground-muted">
            {t(
              "Rate the whole session from 0 (rest) to 10 (maximal exertion) — one number for the day.",
            )}
          </p>
        </div>

        {/* One column, hardest first — the shape of the printed scale, and the
              only arrangement in which the steps read as one ramp. Fitting all
              eleven in a phone view without scrolling is then the ROW's job,
              not the list's: a scale you have to scroll is one you cannot
              compare against itself, which is the whole reason it has anchors.
              No card around the list — each step carries its own rule and
              hairline, the same reasoning as the wellness meter. */}
        <div className="flex flex-col gap-1.5">
          {RPE_VALUES.map((value) => (
            <ScaleRow
              key={value}
              value={value}
              label={localize(RPE_DESCRIPTORS[value] ?? "")}
              accent={rpeAccent(value)}
              fill={rpeColor(value)}
              selected={rpe === value}
              onPick={setRpe}
            />
          ))}
        </div>

        {/* `mt-auto` is what makes the layout adaptive: it absorbs the slack
            when there is any, so the action lands at the bottom of a tall
            screen and immediately after the scale on a short one. `size="large"`
            is the design system's primary action — full width, `clickable-large`.

            No "Clear" beside it. Clearing a rating leaves no rating, which is
            not a state the athlete can do anything with — and every step is one
            tap away, so changing an answer never needed a detour through empty. */}
        <div className="mt-auto pt-2">
          <Button
            variant="primary"
            size="large"
            disabled={rpe === undefined}
            onClick={log}
          >
            {submitLabel ?? t("Log session")}
          </Button>
        </div>
      </div>
    </div>
  );
}
