"use client";

import { useCallback, type ReactNode } from "react";
import type { SharingVisibility } from "@enode/core/api/dtos/tags";
import { useTranslation } from "@enode/core/translations/hooks";
import { Building2FillIcon, GlobeIcon, LockFillIcon } from "@enode/ui/icons";

// The ONE sharing vocabulary both apps speak: one glyph and one word per access
// level, so "Private / Organisation / Public" reads identically in the portal's
// tag editor, the workout-template drawer and the tracking app's template
// picker. Promoted out of the tags drawer, which established it — not
// reinvented.
//
// **The words are display only.** The wire values are unchanged and unchanging:
// `sharedPrivate` / `sharedInternal` / `sharedPublic` are what every DTO, store
// and table row still carries. Only the noun the reader sees moved from
// "Internal" — jargon nobody outside the codebase uses — to "Organisation",
// which is the word the product already speaks at registration
// (`auth/org-name-step.tsx` collects an organisation name).
//
// WHAT THE LABEL MUST NOT BE ASKED TO DO: state reach. The backend resolves
// `sharedInternal` against the owner's ACCEPTED RELATIONS — one hop, so two
// athletes of the same coach do not reach each other (server handoff §2.5). The
// dominant case is a coach publishing to their own athletes, where "Organisation"
// is exactly right; an athlete publishing is the case where the bucket name is
// wider than the audience. That is why the one-word label names the BUCKET and
// {@link useVisibilityHint} — the sentence that actually promises an audience —
// keeps saying "the people you work with" instead of repeating the label.
//
// Both helpers take a BARE STRING rather than the `SharingVisibility` union on
// purpose: the wire is not validated, so a value from a newer backend has to be
// representable in order to DEGRADE (no glyph / the raw value) instead of being
// silently misclassified as one of the three levels this build knows. Passing a
// `SharingVisibility` stays type-correct — the parameter is simply wider.

/** The label resolver {@link useVisibilityLabel} hands out. */
export type VisibilityLabelFn = (
  visibility: string | null | undefined,
) => string;

/**
 * The access levels a user may CHOOSE right now, in display order.
 *
 * Public is deliberately absent: publishing to every enode user is not open
 * yet, so no picker in either app offers it. This is a product gate, not a
 * capability gate — `sharedPublic` stays fully modelled everywhere else, so
 * public content that already exists (published from the native app, or seeded
 * on the backend) is still discovered, displayed, read and copied normally. A
 * level that cannot be authored is not the same as a level that cannot exist,
 * and conflating the two would make existing public content invisible.
 *
 * TO RE-ENABLE PUBLIC AUTHORING: add `"sharedPublic"` back to this array. That
 * is the whole change — both pickers derive their options from it, and
 * {@link isSelectableVisibility} gates the "already public, offer a way down"
 * branch that each section renders.
 */
export const SELECTABLE_VISIBILITIES = [
  "sharedPrivate",
  "sharedInternal",
] as const satisfies readonly SharingVisibility[];

/**
 * Whether a stored level is one the user may pick.
 *
 * `false` means the entity sits at a level this build will not author — today
 * only `sharedPublic`. The caller must then show the real current level and an
 * explicit way to move OFF it, never a picker whose options exclude the value:
 * `SegmentedPicker` marks no pill active for a value it was not given, which
 * would read as "nothing is set" about content that is in fact world-visible.
 */
export function isSelectableVisibility(
  visibility: string | null | undefined,
): boolean {
  return (SELECTABLE_VISIBILITIES as readonly string[]).includes(
    visibility ?? "",
  );
}

/**
 * The glyph for an access level: a lock (only you), an organisation building
 * (everyone in it), a globe (everyone). SF Symbols: `lock.fill` /
 * `building.2.fill` / `globe`.
 *
 * @param visibility - the raw `sharingVisibility` value.
 * @param className - sizing + colour for the glyph. Defaults to the `size-3.5`
 * the detail rows use.
 * @returns the icon, or `null` for a level this build does not recognise — a
 * wrong glyph would state a reach the entity may not have.
 */
export function visibilityIcon(
  visibility: string | null | undefined,
  className = "size-3.5",
): ReactNode {
  switch (visibility) {
    case "sharedPrivate":
      return <LockFillIcon className={className} />;
    case "sharedInternal":
      return <Building2FillIcon className={className} />;
    case "sharedPublic":
      return <GlobeIcon className={className} />;
    default:
      return null;
  }
}

/**
 * The translated one-word name of an access level, for pickers, table cells and
 * read-only value rows.
 *
 * A literal `t("…")` per branch so the extractor registers each string — never
 * `t(variable)`. An unrecognised value falls through to the raw string (and `""`
 * when there is none) so a cell shows something truthful rather than a label
 * that claims the wrong reach.
 */
export function useVisibilityLabel(): VisibilityLabelFn {
  const { t } = useTranslation();
  return useCallback<VisibilityLabelFn>(
    (visibility) => {
      switch (visibility) {
        case "sharedPrivate":
          return t("Private");
        case "sharedInternal":
          return t("Organisation");
        case "sharedPublic":
          return t("Public");
        default:
          return visibility ?? "";
      }
    },
    [t],
  );
}

/** The hint resolver {@link useVisibilityHint} hands out. */
export type VisibilityHintFn = (
  visibility: string | null | undefined,
) => string | null;

/**
 * A one-line explanation of what an access level actually does, for the picker
 * rows in the exercise-naming section and the exercise-creation wizard.
 *
 * Each line names BOTH halves of what goes out — the name and the instructions —
 * because a published description carries both. Saying only "this name" would
 * understate the reach of a coaching cue the author may not have meant to
 * publish.
 *
 * Same shape and same reasons as {@link useVisibilityLabel}: a literal `t("…")`
 * per branch so the extractor sees each string, and `null` — no line at all —
 * for a level this build cannot describe, rather than a sentence that promises
 * the wrong reach.
 */
export function useVisibilityHint(): VisibilityHintFn {
  const { t } = useTranslation();
  return useCallback<VisibilityHintFn>(
    (visibility) => {
      switch (visibility) {
        case "sharedPrivate":
          return t(
            "Only you see your name and instructions for this movement.",
          );
        case "sharedInternal":
          // "The people you work with", NOT the label's own word — deliberately
          // the one place that does not say "your organisation". The backend
          // resolves an internal description against the owner's ACCEPTED
          // RELATIONS — one hop (server response §2.5). Two athletes of the
          // same coach do not reach each other, so an organisation-wide
          // sentence would over-promise the audience. The label names the
          // bucket; this line is the one that promises an audience, so this is
          // the line that has to stay literally true.
          return t(
            "The people you work with see your name and instructions for this movement — in your shared workouts, and as an alternative name for it.",
          );
        case "sharedPublic":
          return t(
            "Every enode user sees your name and instructions for this movement — in your shared workouts, and as an alternative name for it.",
          );
        default:
          return null;
      }
    },
    [t],
  );
}
