// The Ampelos profile panel.
//
// ONE component behind three surfaces: the page at /profile, the fragment
// charon iframes at /embed/profile, and -- through getProfile -- the JSON at
// /api/profile. The `embedded` prop changes only chrome and navigation, never
// what is shown, so the panel accounts.sticknife.com displays is the panel here
// and there is no second implementation to keep in step.
//
// Everything is a server component and every control is a plain form. That is
// worth keeping: the embed is a cross-origin iframe, and a panel that needs no
// client JavaScript is one that cannot be broken by charon's CSP.

import Image from "next/image";
import Link from "next/link";

import type { CatalogRef, HistoryEntry, Profile, WatchNowList } from "@/lib/profile";

import { startPlexLinkAction, unlinkPlexAction } from "./actions";
import { removeFromWatchNow } from "../watch-now-actions";

type PanelProps = {
  profile: Profile;
  /** Rendered inside charon's iframe: no in-app navigation, links escape the frame. */
  embedded?: boolean;
};

/**
 * Anchors inside the embed must break out of the frame.
 *
 * Without target="_top" a click loads Ampelos's full page inside charon's
 * collapsible panel, which looks like the site has broken. Rendering the whole
 * app in a 400px box is not a state anybody meant to reach.
 */
function frameTarget(embedded?: boolean) {
  return embedded ? { target: "_top" as const } : {};
}

function whenText(iso: string) {
  const then = new Date(iso);
  const days = Math.floor((Date.now() - then.getTime()) / 86_400_000);

  if (days <= 0) return "today";
  if (days === 1) return "yesterday";
  if (days < 30) return `${days} days ago`;

  return then.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
}

function episodeLabel(entry: HistoryEntry) {
  if (entry.seasonNumber === null || entry.episodeNumber === null) return null;

  const code = `S${String(entry.seasonNumber).padStart(2, "0")}E${String(entry.episodeNumber).padStart(2, "0")}`;
  return entry.episodeTitle ? `${code} · ${entry.episodeTitle}` : code;
}

/**
 * Wrap a row so clicking it opens the catalog's detail panel for that title.
 *
 * The panel lives in the catalog board and is driven by TMDB ids, so this is a
 * link to the board with `?open=` rather than a second copy of the panel here.
 * That also happens to be the only version that works inside charon's iframe,
 * where a modal would open in a 400px box.
 *
 * A title with no usable TMDB id -- never linked, or linked to an id somebody
 * rejected -- renders as plain text rather than a link that would open the
 * wrong thing or nothing at all.
 */
function CatalogLink({
  catalog,
  title,
  embedded,
  className,
  children,
}: {
  catalog: CatalogRef;
  title: string;
  embedded?: boolean;
  className?: string;
  children: React.ReactNode;
}) {
  if (!catalog) return <div className={className}>{children}</div>;

  return (
    <Link
      href={`/?tab=${catalog.kind}&open=${encodeURIComponent(catalog.tmdbId)}`}
      {...frameTarget(embedded)}
      className={`${className ?? ""} rounded transition hover:bg-ampelos-parchment/5 focus:outline-none focus:ring-2 focus:ring-ampelos-gold`}
      aria-label={`Open details for ${title}`}
    >
      {children}
    </Link>
  );
}

function Poster({ url, alt, size = 40 }: { url: string | null; alt: string; size?: number }) {
  return (
    <div
      className="relative shrink-0 overflow-hidden rounded border border-ampelos-clay/60 bg-ampelos-ink/60"
      style={{ width: size, height: Math.round(size * 1.5) }}
    >
      {url && <Image src={url} alt={alt} fill sizes={`${size}px`} className="object-cover" />}
    </div>
  );
}

function SectionHeading({ title, aside }: { title: string; aside?: React.ReactNode }) {
  return (
    <div className="flex items-baseline justify-between gap-3">
      <h3 className="font-serif text-lg font-semibold text-ampelos-parchment">{title}</h3>
      {aside && <span className="text-xs text-ampelos-muted">{aside}</span>}
    </div>
  );
}

function PlexSection({ profile, embedded }: PanelProps) {
  const { plex } = profile;

  return (
    <section className="space-y-3">
      <SectionHeading
        title="Plex"
        aside={plex.linked ? `linked ${whenText(plex.linkedAt)}` : "not linked"}
      />

      {plex.linked ? (
        <>
          <p className="text-sm text-ampelos-parchment">
            Linked as <strong className="text-ampelos-gold">{plex.username}</strong>
            {plex.email ? <span className="text-ampelos-muted"> · {plex.email}</span> : null}
            {plex.isServerOwner ? (
              <span className="text-ampelos-muted"> · server owner</span>
            ) : null}
          </p>
          {/*
            The owner is never invited to their own server -- Plex refuses with
            "You cannot send an invitation to yourself" -- so an unshared link
            means something completely different for them than for everybody
            else, and must not read as a failure.
          */}
          <p className="text-sm text-ampelos-muted">
            {plex.isServerOwner
              ? "You own this Plex server, so you already have every library. Nothing needed sharing."
              : plex.librariesSharedAt
                ? `${plex.sharedLibraryCount} libraries shared with you. Check your email for the invitation if you have not accepted it yet.`
                : "The libraries have not been shared yet — link again to retry."}
          </p>
          <p className="text-sm text-ampelos-muted">
            Anything you add to your Plex watchlist is treated as a request: if we already have
            it you will find it in the library, and if we do not, we will go and get it.
          </p>
          {/* Unlinking stays in place -- it navigates nowhere, so it is safe in the frame. */}
          <form action={unlinkPlexAction}>
            <button type="submit" className="ampelos-ghost-button px-3 py-2 text-sm font-medium">
              Unlink
            </button>
          </form>
        </>
      ) : (
        <>
          <p className="text-sm text-ampelos-muted">
            Link your Plex account to get access to the Movies, TV Shows and Music libraries.
            Once linked, your Plex watchlist becomes your request list — add something there and
            it will be found for you.
          </p>
          <p className="text-xs text-ampelos-muted">
            You sign in at plex.tv, not here. Ampelos never sees your Plex password, and the
            sign-in token is discarded as soon as Plex confirms who you are.
          </p>
          {/*
            Linking is a round trip through plex.tv, and plex.tv refuses to be
            framed. Inside the embed the button therefore hands the person over
            to the real profile page at top level rather than starting a flow
            that would dead-end in charon's panel.
          */}
          {embedded ? (
            <Link
              href={profile.links.self}
              target="_top"
              className="ampelos-stone-button inline-block px-4 py-2 text-sm font-medium"
            >
              Link my Plex account on Ampelos
            </Link>
          ) : (
            <form action={startPlexLinkAction}>
              <button type="submit" className="ampelos-stone-button px-4 py-2 text-sm font-medium">
                Link my Plex account
              </button>
            </form>
          )}
        </>
      )}
    </section>
  );
}

function WatchNowGroup({
  label,
  list,
  embedded,
}: {
  label: string;
  list: WatchNowList;
  embedded?: boolean;
}) {
  return (
    <div className="space-y-2">
      <div className="flex items-baseline justify-between gap-3">
        <h4 className="text-sm font-semibold uppercase tracking-[0.18em] text-ampelos-gold">
          {label}
        </h4>
        <span className="text-xs text-ampelos-muted">
          {list.used} of {list.quota} slots
        </span>
      </div>

      {list.items.length === 0 ? (
        <p className="text-sm text-ampelos-muted">
          Nothing here yet. Add something from the{" "}
          <Link
            href="/"
            {...frameTarget(embedded)}
            className="text-ampelos-gold underline-offset-2 hover:underline"
          >
            catalog
          </Link>{" "}
          to keep it in high quality on live storage.
        </p>
      ) : (
        <ul className="space-y-2">
          {list.items.map((item) => (
            <li key={item.id} className="flex items-center gap-3">
              <CatalogLink
                catalog={item.catalog}
                title={item.title}
                embedded={embedded}
                className="flex min-w-0 flex-1 items-center gap-3 p-1"
              >
                <Poster url={item.posterUrl} alt="" />
                <span className="min-w-0 flex-1">
                  <span className="block truncate text-sm font-medium text-ampelos-parchment">
                    {item.title}
                    {item.year ? <span className="text-ampelos-muted"> ({item.year})</span> : null}
                  </span>
                  <span className="block text-xs text-ampelos-muted">
                    {item.slotNumber ? `Slot ${item.slotNumber}` : "Unslotted"} · added{" "}
                    {whenText(item.addedAt)}
                  </span>
                </span>
              </CatalogLink>
              <form action={removeFromWatchNow}>
                <input type="hidden" name="watchNowItemId" value={item.id} />
                <button
                  type="submit"
                  className="ampelos-ghost-button px-2 py-1 text-xs font-medium"
                  aria-label={`Remove ${item.title} from Watch Now`}
                >
                  Remove
                </button>
              </form>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

function WatchNowSection({ profile, embedded }: PanelProps) {
  const { television, movies } = profile.watchNow;

  return (
    <section className="space-y-4">
      <SectionHeading
        title="Watch Now"
        aside={`${television.used + movies.used} of ${television.quota + movies.quota} slots used`}
      />
      <p className="text-sm text-ampelos-muted">
        Watch Now is what keeps a title on live storage in full quality. Everything else falls
        back to the 720p archive over time.
      </p>
      <WatchNowGroup label="Television" list={television} embedded={embedded} />
      <WatchNowGroup label="Movies" list={movies} embedded={embedded} />
    </section>
  );
}

function WatchHistorySection({ profile, embedded }: PanelProps) {
  const { history } = profile;

  return (
    <section className="space-y-3">
      <SectionHeading
        title="Watch history"
        aside={
          history.total > 0
            ? `${history.playsLast30Days} plays in the last 30 days · ${history.distinctEpisodes} titles`
            : undefined
        }
      />

      {history.recent.length === 0 ? (
        <p className="text-sm text-ampelos-muted">
          {profile.plex.linked
            ? "Nothing recorded yet. History is copied from Plex about once an hour, so anything you watch will show up here shortly."
            : "Link your Plex account above and what you watch will be recorded here."}
        </p>
      ) : (
        <>
          <ul className="space-y-2">
            {history.recent.map((entry) => {
              const episode = episodeLabel(entry);
              return (
                <li key={entry.id}>
                  <CatalogLink
                    catalog={entry.catalog}
                    title={entry.title}
                    embedded={embedded}
                    className="flex items-center gap-3 p-1"
                  >
                    <Poster url={entry.posterUrl} alt="" size={32} />
                    <span className="min-w-0 flex-1">
                      <span className="block truncate text-sm font-medium text-ampelos-parchment">
                        {entry.title}
                      </span>
                      <span className="block truncate text-xs text-ampelos-muted">
                        {episode ? `${episode} · ` : ""}
                        {whenText(entry.watchedAt)}
                        {/*
                          Repeats are collapsed to the most recent play, so the
                          count is what stops that from looking like data loss.
                        */}
                        {entry.playCount > 1 ? ` · watched ${entry.playCount} times` : ""}
                      </span>
                    </span>
                  </CatalogLink>
                </li>
              );
            })}
          </ul>
          {/*
            Counted against distinctEpisodes rather than total plays: the list
            is one row per episode, so promising it is a window onto 1,668
            plays when it can only ever show 993 things is just wrong.
          */}
          {history.distinctEpisodes > history.recent.length && (
            <p className="text-xs text-ampelos-muted">
              Showing the {history.recent.length} most recently watched of{" "}
              {history.distinctEpisodes}. Repeats are collapsed to the latest play.
            </p>
          )}
        </>
      )}

      <p className="text-xs text-ampelos-muted">
        Only you can see this. Ampelos records history for linked accounts and never shows one
        person&apos;s viewing to anybody else, administrators included.
      </p>
    </section>
  );
}

export function ProfilePanel({ profile, embedded }: PanelProps) {
  return (
    <div className="space-y-8">
      <PlexSection profile={profile} embedded={embedded} />
      <hr className="border-ampelos-clay/50" />
      <WatchNowSection profile={profile} embedded={embedded} />
      <hr className="border-ampelos-clay/50" />
      <WatchHistorySection profile={profile} embedded={embedded} />
    </div>
  );
}
