/**
 * Piggy, docked.
 *
 * The third pane. It is a column rather than a sheet because the point of
 * docking an agent is that you can read the page and the answer at the same
 * time — a sheet that covers the thing you are asking about defeats it.
 *
 * Three surfaces, one panel:
 *
 *   ≥ xl  — this permanent column, remembered between sessions.
 *   ≥ lg  — the existing right-hand Sheet, because 1024px minus a sidebar
 *           minus a 22rem dock leaves the page narrower than a phone.
 *   < lg  — the existing bottom Drawer.
 *
 * The status gate is not optional. `/api/piggy/chat` answers 503 when the
 * runtime is disabled, so a dock that renders its composer without asking
 * first is a permanent third of the window that fails on first use.
 */
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { PanelRightClose } from 'lucide-react';
import { useHasDockRoom } from '@/hooks/use-media-query';
import { PIGGY_ASK_LABEL } from '@/lib/piggy-copy';
import { useLayout } from '@/lib/layout';
import { usePiggyCurrentContext } from '@/lib/piggy-context';
import type { PiggyChatContext } from '@/lib/piggy-chat';
import { usePiggyChatSession } from './piggy/workspace/controls';
import {
  PiggyChatPanel,
  ResponsivePiggyChat,
  usePiggyAskRequests,
  usePiggyStatus,
  type PiggyAsk,
} from './PiggyChat';
import { PiggyMark } from './PiggyMark';
import { Button, EmptyState, Skeleton, cn } from './ui';

/**
 * Where Piggy is the page rather than the panel.
 *
 * The dock and the workspace are the same agent, so on `/piggy` an open dock
 * put two composers, two empty states and two conversations side by side.
 * Nothing broke; it just made the product look like it did not know what it was.
 */
const PIGGY_WORKSPACE_PATH = '/piggy';

export function PiggyDock() {
  const { dockOpen, setDockOpen } = useLayout();
  const hasRoom = useHasDockRoom();
  const status = usePiggyStatus();
  const ambient = usePiggyCurrentContext();
  const { pathname } = useLocation();
  const onWorkspace = pathname === PIGGY_WORKSPACE_PATH;

  /*
   * A question pressed on the page, taken by the column rather than by a
   * second Piggy in a sheet on top of it.
   *
   * The ask carries its own context because a record button knows something
   * the route does not: pressing Ask Piggy on a contract row must put the dock
   * on that contract, not on the page it happens to be listed under.
   *
   * Cleared on navigation. A dock still pinned to a contract while you read an
   * account is a dock whose badge is lying about what it is looking at, and
   * the cost — a record-scoped thread ending when you leave the record — is
   * the same cost the ambient key already pays for every record page.
   */
  const [ask, setAsk] = useState<PiggyAsk | null>(null);
  usePiggyAskRequests((next) => {
    if (hasRoom) setAsk(next);
  });
  useEffect(() => setAsk(null), [pathname]);

  const context = ask?.context ?? ambient;

  // The remembered `dockOpen` is deliberately left alone: the column comes back
  // by itself on the next page, so visiting the workspace does not silently
  // close a panel the user had open everywhere else.
  if (onWorkspace || !hasRoom || !dockOpen) return null;

  return (
    <aside
      // `xl:flex` as well as the hook: the media query and the class agree, so
      // there is no frame where the column exists at the wrong width.
      className={cn(
        'hidden w-[--dock-width] shrink-0 flex-col overflow-hidden border-l border-border bg-surface xl:flex',
        'pr-[var(--safe-right)]',
      )}
      style={{
        position: 'sticky',
        top: 'var(--app-header-h)',
        height: 'calc(100dvh - var(--app-header-h))',
      }}
      aria-label="Piggy"
    >
      <div className="flex h-12 shrink-0 items-center gap-2 border-b border-border px-3">
        <PiggyMark className="size-5 shrink-0 text-accent-fg" />
        <span className="min-w-0 truncate text-sm font-semibold">Piggy</span>
        <Button
          type="button"
          variant="ghost"
          size="icon"
          className="ml-auto size-9 min-h-0 min-w-0 text-muted"
          aria-label="Close the Piggy panel"
          onClick={() => setDockOpen(false)}
        >
          <PanelRightClose className="size-4" aria-hidden />
        </Button>
      </div>

      {status.isLoading ? (
        <div className="flex flex-col gap-3 p-3">
          <Skeleton className="h-20 rounded-xl" />
          <Skeleton className="h-12 rounded-xl" />
        </div>
      ) : !status.data?.canUse ? (
        <EmptyState
          icon={<PiggyMark className="size-6" />}
          title="Piggy is unavailable"
          description={
            status.data?.enabled
              ? 'This credential does not have read access.'
              : 'An administrator must enable the isolated Piggy runtime.'
          }
        />
      ) : (
        // Remounted per RECORD, so the transcript never carries an answer
        // about one row into a conversation about another. Page contexts all
        // share one key: they change on every navigation, and remounting there
        // threw away the transcript, the composer draft and any in-flight
        // stream (PiggyChat aborts on unmount) — which is the whole point of a
        // pane that stays put while you move around the app. The panel reads
        // `context` at send time, so the page it is asking about still tracks
        // the route without a remount.
        <DockThread
          key={context.type === 'page' ? 'page' : JSON.stringify(context)}
          context={context}
          seed={ask ? { id: ask.id, text: ask.prompt } : undefined}
        />
      )}
    </aside>
  );
}

/**
 * The dock's own conversation, and the controls bound to it.
 *
 * Both live here rather than inside `PiggyChatPanel` because the panel is not
 * the thing whose lifetime they follow: the key above is what decides when a
 * docked thread is thrown away and started again, and the mode and model have
 * to be bound to whichever conversation that key produced.
 */
function DockThread({
  context,
  seed,
}: {
  context: PiggyChatContext;
  seed?: { id: number; text?: string };
}) {
  const { conversation, controls } = usePiggyChatSession({ context });
  return (
    <PiggyChatPanel
      conversation={conversation}
      controls={controls}
      context={context}
      seed={seed}
      className="min-h-0 flex-1"
    />
  );
}

/**
 * The header control for Piggy, and the only thing that opens the agent.
 *
 * Below `xl` there is no column to toggle, so the same button opens the sheet
 * or drawer instead — one affordance in one place, whatever the viewport can
 * accommodate. A page's "Ask Piggy" button no longer opens a surface of its
 * own: it publishes its question through `requestPiggyAsk` and this control
 * decides where Piggy is, which is what stops a pig face and a speech bubble
 * on the same row of pixels from opening two different agents.
 */
export function PiggyDockToggle({ className }: { className?: string }) {
  const { dockOpen, setDockOpen } = useLayout();
  const hasRoom = useHasDockRoom();
  const status = usePiggyStatus();
  const ambient = usePiggyCurrentContext();
  const [overlayOpen, setOverlayOpen] = useState(false);
  /** The question that opened the overlay, when a page asked one. */
  const [ask, setAsk] = useState<PiggyAsk | null>(null);
  const unavailable = status.data && !status.data.canUse;
  const onWorkspace = useLocation().pathname === PIGGY_WORKSPACE_PATH;

  usePiggyAskRequests((next) => {
    if (unavailable) return;
    if (hasRoom) {
      // The column takes it from here — `PiggyDock` is subscribed too, and it
      // is the surface that holds the thread. All this has to do is make sure
      // the column the user is about to be answered in is actually on screen.
      setDockOpen(true);
      return;
    }
    setAsk(next);
    setOverlayOpen(true);
  });

  // Nothing for it to open: the whole page is Piggy. Left in the header as a
  // dead control it would be the only button in PIG that does nothing when
  // pressed — and pressed on the workspace it would toggle a column that
  // `PiggyDock` refuses to draw.
  if (onWorkspace) return null;

  const overlayContext = ask?.context ?? ambient;

  return (
    <>
      <Button
        type="button"
        variant="ghost"
        size="icon"
        className={cn('text-muted', dockOpen && hasRoom && 'bg-accent-subtle text-accent-fg', className)}
        disabled={Boolean(unavailable)}
        aria-pressed={hasRoom ? dockOpen : undefined}
        aria-label={
          unavailable
            ? 'Piggy is unavailable'
            : hasRoom
              ? dockOpen
                ? 'Close the Piggy panel'
                : 'Open the Piggy panel'
              : PIGGY_ASK_LABEL
        }
        title={unavailable ? 'Piggy is disabled or this credential lacks read access.' : 'Piggy'}
        onClick={() => {
          if (hasRoom) {
            setDockOpen(!dockOpen);
            return;
          }
          // Pressing the header control asks about the page, not about the
          // record a button last pointed at: the ask is spent.
          setAsk(null);
          setOverlayOpen(true);
        }}
      >
        <PiggyMark className="size-5" />
      </Button>
      {hasRoom ? null : (
        <ResponsivePiggyChat
          // Keyed on the record the question is about, so a question pressed on
          // a contract opens a thread about that contract — and pressing the
          // same button again returns to the thread already in progress rather
          // than discarding it.
          key={overlayContext.type === 'page' ? 'page' : JSON.stringify(overlayContext)}
          open={overlayOpen}
          onOpenChange={setOverlayOpen}
          context={overlayContext}
          initialPrompt={ask?.prompt}
          seed={ask ? { id: ask.id, text: ask.prompt } : undefined}
        />
      )}
    </>
  );
}
