/**
 * The two decisions a person makes before they press send: which model answers,
 * and what it is allowed to do.
 *
 * They are one component because they are one row on every surface that shows
 * them — the workspace header, the docked panel's composer, the phone drawer —
 * and because they have to agree with each other and with what is actually put
 * on the wire. `usePiggyControls` is the half that guarantees the last part: the
 * preferences live per user in localStorage, the conversation holds what the
 * next turn will send, and this binds one to the other so the header cannot show
 * "Ask first" while the composer sends `read_only`.
 *
 * The mode control is behind a popover rather than sitting inline. Its segments
 * carry a consequence sentence that changes with the selection — three lines of
 * it in `auto` — which is exactly right in a panel and impossible in a header
 * strip. The trigger names the mode in the same words the segments use, so
 * nothing is hidden except the explanation, which is one press away.
 */
import { useEffect, type ReactNode } from 'react';
import { ChevronDown } from 'lucide-react';
import type { PiggyChatContext, PiggyMode } from '@pig/core';
import { useOptionalIdentity } from '@/lib/identity';
import { canAny } from '@/lib/permissions';
import {
  usePiggyConversation,
  type PiggyConversation,
  type TranscriptMessage,
} from '@/lib/piggy-chat';
import { PiggyModeControl, piggyModeSummary, usePiggyMode } from '@/components/piggy/mode-control';
import { PiggyModelPicker, usePiggyModelChoice } from '@/components/piggy/model-picker';
import { Button, cn } from '@/components/ui';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';

/**
 * The grants Piggy's write tools actually enforce, server-side, one per tool
 * family. Held here because `GET /api/piggy/status` does not report whether the
 * caller may write, and a mode control offered to someone without any of these
 * is three segments, two of which turn every proposed change into a 403.
 *
 * Display only. The tools check capabilities themselves against the team the
 * record belongs to; this asks the weaker question — could this person write
 * anywhere at all — because the mode is chosen before any record is named.
 */
const PIGGY_WRITE_CAPABILITIES = [
  'deal:write',
  'activity:write',
  'commitment:write',
  'contract:sign',
] as const;

export interface PiggyControlsState {
  mode: PiggyMode;
  setMode: (mode: PiggyMode) => void;
  modelId: string | null;
  setModelId: (modelId: string) => void;
  canWrite: boolean;
}

/**
 * A conversation and the controls that decide what it may do, created together.
 *
 * They are one hook because the order matters and getting it wrong is invisible
 * until it costs a write. The conversation owns `mode` and `modelId` — it
 * outlives every panel that draws a control for them — while the preferences
 * own the same two values because they outlive the conversation. If the
 * conversation is created first and corrected by an effect afterwards, there is
 * a window one render wide in which it holds `read_only` while the header says
 * `Ask first`, and anything that sends inside that window (a suggestion pressed
 * on mount, a thread resumed with a question already in hand) sends the mode
 * from before the correction. Seeding at construction closes the window; the
 * effects below then only carry later changes.
 */
export function usePiggyChatSession(options: {
  context?: PiggyChatContext;
  initialPrompt?: string;
  /** A stored transcript being resumed. See `usePiggyConversation`. */
  initialMessages?: TranscriptMessage[];
  initialConversationId?: string;
} = {}): { conversation: PiggyConversation; controls: PiggyControlsState } {
  const { mode, setMode } = usePiggyMode();
  const model = usePiggyModelChoice();
  const identity = useOptionalIdentity();
  const canWrite = PIGGY_WRITE_CAPABILITIES.some((capability) =>
    canAny(identity ?? undefined, capability),
  );

  const conversation = usePiggyConversation({
    ...options,
    initialMode: canWrite ? mode : 'read_only',
    // Null is "no stored preference", which the relay reads as its own default.
    // Resolving it to a model id here would be this file guessing which one.
    initialModelId: model.modelId ?? undefined,
  });

  const { setMode: setConversationMode, setModelId: setConversationModelId } = conversation;

  /*
   * The correction B2's control performs while it is on screen, performed here
   * as well because on this surface the control is inside a popover and spends
   * almost all of its life unmounted. A stored `auto` that outlived the grant
   * that justified it would otherwise sit in localStorage and go on the wire.
   */
  useEffect(() => {
    if (!canWrite && mode !== 'read_only') setMode('read_only');
  }, [canWrite, mode, setMode]);

  useEffect(() => {
    setConversationMode(canWrite ? mode : 'read_only');
  }, [canWrite, mode, setConversationMode]);

  useEffect(() => {
    // `undefined` is "whatever the deployment's default is" — never a guess at
    // which model that is, which is why the picker's null is passed through
    // rather than resolved to `defaultModelId` here.
    setConversationModelId(model.modelId ?? undefined);
  }, [model.modelId, setConversationModelId]);

  return {
    conversation,
    controls: { mode, setMode, modelId: model.modelId, setModelId: model.setModelId, canWrite },
  };
}

/**
 * The mode, as a header-sized control.
 *
 * Disabled rather than hidden when nothing may be written: "you cannot change
 * this" is information, and a control that vanishes reads as a missing feature.
 */
export function PiggyModeButton({
  mode,
  setMode,
  canWrite,
  compact = false,
  disabled = false,
}: {
  mode: PiggyMode;
  setMode: (mode: PiggyMode) => void;
  canWrite: boolean;
  compact?: boolean;
  disabled?: boolean;
}) {
  const shown = canWrite ? mode : 'read_only';
  const { label, icon: Icon } = piggyModeSummary(shown);
  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          type="button"
          variant={compact ? 'ghost' : 'outline'}
          disabled={disabled}
          className={cn(
            'min-w-0 gap-1.5 font-medium',
            // `text-xs` in both branches: 11px is the micro-LABEL size, and this
            // is a control's own word, not a label naming one. It was the last
            // non-Label 11px string in the product. Only the padding gives.
            compact ? 'h-11 px-2 text-xs' : 'h-11 px-2.5 text-xs',
          )}
          aria-label={`What Piggy may do: ${label}`}
        >
          <Icon
            aria-hidden
            className={cn('shrink-0', shown === 'auto' && 'text-warning')}
          />
          <span className="truncate">{label}</span>
          <ChevronDown aria-hidden className="size-3 shrink-0 opacity-60" />
        </Button>
      </PopoverTrigger>
      {/* Above the sheet and drawer primitives at z-50, so the same trigger
          works inside the phone overlays as it does in the header. */}
      {/* 24rem, because the three segments are a grid of equal thirds and
          "Read only" needs about 7rem of it: at 20rem the control opened with
          two of its three labels truncated to "Read …" and "Ask fir…". */}
      <PopoverContent align="end" className="z-[60] w-[min(24rem,calc(100vw-1.5rem))]">
        <PiggyModeControl value={mode} onChange={setMode} canWrite={canWrite} />
      </PopoverContent>
    </Popover>
  );
}

/**
 * Both controls, in the order they are decided in: what may it do, then which
 * model does it. They wrap rather than shrink — at 22rem the pair is a whisker
 * over one line, and a truncated model name is worse than a second row.
 */
export function PiggyControls({
  controls,
  compact = false,
  disabled = false,
  children,
  className,
}: {
  controls: PiggyControlsState;
  compact?: boolean;
  /** A turn is running: the next one's settings are already fixed. */
  disabled?: boolean;
  children?: ReactNode;
  className?: string;
}) {
  return (
    <div className={cn('flex min-w-0 flex-wrap items-center gap-1.5', className)}>
      <PiggyModeButton
        mode={controls.mode}
        setMode={controls.setMode}
        canWrite={controls.canWrite}
        compact={compact}
        disabled={disabled}
      />
      <PiggyModelPicker
        value={controls.modelId}
        onChange={controls.setModelId}
        compact={compact}
        disabled={disabled}
      />
      {children}
    </div>
  );
}
