'use client';

/**
 * `QuestionnaireCard` — the agent "ask" form as ONE controlled, presentation-
 * only card. Decomposed compound design: an internal context (context.tsx)
 * carries view state to the subcomponents; the DRAFT stays host-owned.
 *
 * Multi-question flows step ONE question at a time on the ui-core Stepper —
 * three stacked fieldsets read as a tax form; three quick steps read as a
 * conversation (2026-07-22 design review). The free-text "Other" field only
 * appears while its option row is engaged (QuestionField.tsx).
 */

import { useMemo, type KeyboardEvent } from 'react';
import {
  Button,
  Spinner,
  Stepper,
  StepperContent,
  StepperIndicator,
  StepperItem,
  StepperList,
  StepperSeparator,
  StepperTitle,
  StepperTrigger,
} from '@djangocfg/ui-core';
import { cn } from '@djangocfg/ui-core/lib';
import { QuestionnaireProvider, useQuestionnaireContext } from './context';
import { QuestionField } from './QuestionField';
import type {
  Questionnaire,
  QuestionnaireDraft,
  QuestionnaireLabels,
  QuestionnaireStatus,
} from './types';

const DEFAULT_LABELS: QuestionnaireLabels = {
  title: 'Question from the agent',
  needsYou: 'Needs your answer',
  sending: 'Sending…',
  retryStatus: 'Retry needed',
  required: 'Required',
  optional: 'Optional',
  otherAnswer: 'Other…',
  yourAnswer: 'Your answer',
  otherPlaceholder: 'Describe another option',
  textPlaceholder: 'Type your response',
  recommended: 'Recommended',
  submit: 'Send answer',
  submitting: 'Sending…',
  decline: 'Skip',
  retry: 'Retry',
  dismiss: 'Dismiss',
  completeOrDecline: 'Answer every required question, or skip.',
  draftPreserved: 'Your draft is preserved.',
  answered: 'Answered',
  declined: 'Skipped',
  expired: 'No longer active',
  selectionRange: (min, max) => `Choose ${min} to ${max}.`,
};

export type QuestionnaireCardSize = 'sm' | 'md';

export interface QuestionnaireCardProps {
  questionnaire: Questionnaire;
  draft: QuestionnaireDraft;
  status: QuestionnaireStatus;
  /** Host-translated error line; shown with the draft-preserved note. */
  errorMessage?: string;
  size?: QuestionnaireCardSize;
  labels?: Partial<QuestionnaireLabels>;
  /** Cap for the scrolling body; keep the transcript breathing. */
  maxBodyHeight?: number | string;
  onSelect: (questionId: string, optionId: string) => void;
  onOtherChange: (questionId: string, value: string) => void;
  onSubmit: () => void;
  onDecline: () => void;
  onRetry?: () => void;
  onDismiss?: () => void;
  className?: string;
}

function Receipt({
  title,
  status,
  labels,
  onDismiss,
  className,
}: {
  title: string;
  status: 'answered' | 'declined' | 'expired';
  labels: QuestionnaireLabels;
  onDismiss?: () => void;
  className?: string;
}) {
  const receipt =
    status === 'answered' ? labels.answered : status === 'declined' ? labels.declined : labels.expired;
  return (
    <div className={cn('flex overflow-hidden rounded-lg bg-muted/30', className)} data-status={status}>
      <div aria-hidden className={cn('w-1 shrink-0', status === 'answered' ? 'bg-success' : 'bg-border')} />
      <div className="flex min-w-0 flex-1 items-center justify-between gap-2 px-3 py-1.5">
        <p role="status" aria-live="polite" className="truncate text-xs text-muted-foreground">
          <span className="font-medium text-foreground/80">{title}</span>
          <span className="mx-1.5">·</span>
          {receipt}
        </p>
        {onDismiss ? (
          <Button type="button" size="sm" variant="ghost" onClick={onDismiss}>
            {labels.dismiss}
          </Button>
        ) : null}
      </div>
    </div>
  );
}

/** Compact step flow: the rail and every ARIA-owned question panel. */
function QuestionStepper() {
  const { questionnaire, activeId, setActiveId, questionComplete, small, busy } =
    useQuestionnaireContext();
  const questions = questionnaire.questions;
  if (questions.length < 2) return null;
  // The item IS the flex row (trigger + its trailing separator); the kit's
  // StepperSeparator must stay INSIDE its StepperItem (context requirement)
  // and self-hides on the last step. The 2026-07-22 invisible-rail bug was a
  // content-sized wrapper div: the item stretched (flex-1) but its inner div
  // did not, so the separator's flex-1 resolved to zero width.
  const items = questions.map((question, index) => (
    <StepperItem
      key={question.id}
      value={question.id}
      completed={questionComplete(question)}
      disabled={busy}
      className={cn(
        'flex items-center gap-1.5',
        index < questions.length - 1 && 'flex-1',
      )}
    >
      <StepperTrigger className="flex shrink-0 items-center gap-1.5 rounded-md px-1 py-0.5">
        <StepperIndicator
          className={cn('shrink-0', small ? 'size-4 text-[10px]' : 'size-5 text-[11px]')}
        />
        <StepperTitle className={cn('font-medium', small ? 'text-[11px]' : 'text-xs')}>
          {question.header || String(index + 1)}
        </StepperTitle>
      </StepperTrigger>
      <StepperSeparator className="min-w-3" />
    </StepperItem>
  ));
  // StepperTrigger owns aria-controls for its matching StepperContent. Keep
  // every target mounted while rendering only the active field, preserving the
  // card's existing one-question-at-a-time behavior.
  const panels = questions.map((question) => {
    const active = question.id === activeId;
    return (
      <StepperContent key={question.id} value={question.id} forceMount hidden={!active}>
        {active ? <QuestionField question={question} /> : null}
      </StepperContent>
    );
  });
  return (
    <Stepper
      value={activeId}
      onValueChange={setActiveId}
      activationMode="manual"
      className="w-full gap-2"
    >
      <StepperList
        className={cn('flex w-full items-center gap-1.5', small ? 'pb-1' : 'pb-1.5')}
      >
        {items}
      </StepperList>
      {panels}
    </Stepper>
  );
}

function Body({ maxBodyHeight }: { maxBodyHeight: number | string }) {
  const ctx = useQuestionnaireContext();
  const { questionnaire, labels, small, failed, allComplete } = ctx;
  const questions = questionnaire.questions;
  const active = questions[ctx.stepIndex] ?? questions[0];
  if (!active) return null;
  return (
    <div
      className={cn('space-y-2 overflow-y-auto', small ? 'px-2.5 pb-2' : 'px-3 pb-2.5')}
      style={{ maxHeight: maxBodyHeight }}
    >
      {questions.length > 1 ? <QuestionStepper /> : <QuestionField question={active} />}
      {!allComplete && !failed ? (
        <p className="text-[11px] text-muted-foreground">{labels.completeOrDecline}</p>
      ) : null}
    </div>
  );
}

function Footer({ errorMessage }: { errorMessage?: string }) {
  const ctx = useQuestionnaireContext();
  const { labels, small, busy, failed, allComplete, stepIndex, stepCount, questionnaire } = ctx;
  const questions = questionnaire.questions;
  const lastStep = stepIndex >= stepCount - 1;
  const activeComplete = questions[stepIndex] ? ctx.questionComplete(questions[stepIndex]) : false;
  const back = () => ctx.setActiveId(questions[Math.max(0, stepIndex - 1)].id);
  const next = () => ctx.setActiveId(questions[Math.min(stepCount - 1, stepIndex + 1)].id);
  return (
    <div className={cn('space-y-1', small ? 'px-2.5 py-1' : 'px-3 py-1.5', 'bg-muted/30')}>
      {failed && errorMessage ? (
        <p role="alert" className="text-[11px] text-destructive">
          {errorMessage} {labels.draftPreserved}
        </p>
      ) : null}
      <div className="flex flex-wrap items-center justify-end gap-1.5">
        <Button type="button" size="sm" variant="ghost" disabled={busy || failed} onClick={ctx.onDecline}>
          {labels.decline}
        </Button>
        {failed && ctx.onRetry ? (
          <Button type="button" size="sm" variant="outline" onClick={ctx.onRetry}>
            {labels.retry}
          </Button>
        ) : null}
        {stepCount > 1 && stepIndex > 0 ? (
          <Button type="button" size="sm" variant="outline" disabled={busy} onClick={back}>
            ←
          </Button>
        ) : null}
        {stepCount > 1 && !lastStep ? (
          <Button type="button" size="sm" disabled={busy || !activeComplete} onClick={next}>
            →
          </Button>
        ) : (
          <Button type="button" size="sm" disabled={!allComplete || busy || failed} onClick={ctx.onSubmit}>
            {busy ? <Spinner aria-hidden className="mr-1 size-3.5" /> : null}
            {busy ? labels.submitting : labels.submit}
          </Button>
        )}
      </div>
    </div>
  );
}

function Frame({
  errorMessage,
  maxBodyHeight,
  className,
}: {
  errorMessage?: string;
  maxBodyHeight: number | string;
  className?: string;
}) {
  const ctx = useQuestionnaireContext();
  const { questionnaire, labels, small, failed, busy, status } = ctx;
  const title = questionnaire.title || labels.title;
  const statusLabel = busy ? labels.sending : failed ? labels.retryStatus : labels.needsYou;
  const questions = questionnaire.questions;

  const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
    const target = event.target as HTMLElement;
    if (target.matches("input, textarea, [contenteditable='true']")) return;
    if (event.altKey || event.metaKey || event.ctrlKey) return;
    const question = questions[ctx.stepIndex];
    if (!question || question.mode === 'text' || busy) return;
    const option = question.options?.[Number(event.key) - 1];
    if (!option) return;
    event.preventDefault();
    ctx.pickOption(question, option.id);
  };

  return (
    <div
      className={cn('flex overflow-hidden rounded-lg bg-muted/30', className)}
      data-status={status}
      onKeyDown={handleKeyDown}
    >
      <div aria-hidden className={cn('w-1 shrink-0', failed ? 'bg-destructive' : 'bg-warning')} />
      <div className="flex min-w-0 flex-1 flex-col">
        <div className={cn('flex items-center gap-2', small ? 'px-2.5 py-1.5' : 'px-3 py-2')}>
          <span className={cn('min-w-0 flex-1 truncate font-medium text-foreground', small ? 'text-[13px]' : 'text-sm')}>
            {title}
          </span>
          <span
            role="status"
            className={cn(
              'inline-flex shrink-0 items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium',
              failed ? 'bg-destructive/10 text-destructive' : 'bg-warning/10 text-warning',
            )}
          >
            {statusLabel}
          </span>
        </div>
        <Body maxBodyHeight={maxBodyHeight} />
        <Footer errorMessage={errorMessage} />
      </div>
    </div>
  );
}

export function QuestionnaireCard({
  questionnaire,
  draft,
  status,
  errorMessage,
  size = 'sm',
  labels: labelOverrides,
  maxBodyHeight = '40vh',
  onSelect,
  onOtherChange,
  onSubmit,
  onDecline,
  onRetry,
  onDismiss,
  className,
}: QuestionnaireCardProps) {
  const labels = useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
  if (status === 'answered' || status === 'declined' || status === 'expired') {
    return (
      <Receipt
        title={questionnaire.title || labels.title}
        status={status}
        labels={labels}
        onDismiss={onDismiss}
        className={className}
      />
    );
  }
  return (
    <QuestionnaireProvider
      questionnaire={questionnaire}
      draft={draft}
      status={status}
      labels={labels}
      small={size === 'sm'}
      onSelect={onSelect}
      onOtherChange={onOtherChange}
      onSubmit={onSubmit}
      onDecline={onDecline}
      onRetry={onRetry}
      onDismiss={onDismiss}
    >
      <Frame errorMessage={errorMessage} maxBodyHeight={maxBodyHeight} className={className} />
    </QuestionnaireProvider>
  );
}
