'use client';

/**
 * Internal questionnaire context — the "smart" core the subcomponents read
 * instead of prop-drilling. VIEW state only (active step, other-field
 * engagement); the DRAFT stays host-owned and flows in as props, so the host
 * store remains the single source of truth.
 *
 * Selection-clearing convention: `onSelect(questionId, "")` clears the
 * question's selection. The engaged "Other" flow uses it so a synthetic
 * option id never reaches the host draft (hosts submit ids to the backend
 * contract verbatim).
 */

import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from 'react';
import type {
  Questionnaire,
  QuestionnaireDraft,
  QuestionnaireLabels,
  QuestionnaireQuestion,
  QuestionnaireStatus,
} from './types';

export const OTHER_MAX_LENGTH = 4000;

export interface QuestionnaireCallbacks {
  onSelect: (questionId: string, optionId: string) => void;
  onOtherChange: (questionId: string, value: string) => void;
  onSubmit: () => void;
  onDecline: () => void;
  onRetry?: () => void;
  onDismiss?: () => void;
}

export interface QuestionnaireContextValue extends QuestionnaireCallbacks {
  questionnaire: Questionnaire;
  draft: QuestionnaireDraft;
  status: QuestionnaireStatus;
  busy: boolean;
  failed: boolean;
  labels: QuestionnaireLabels;
  small: boolean;
  /** Active step (question id) when stepping; single-question flows skip the chrome. */
  activeId: string;
  setActiveId: (id: string) => void;
  stepIndex: number;
  stepCount: number;
  /** True when the Other free-text field is revealed for the question. */
  otherEngaged: (question: QuestionnaireQuestion) => boolean;
  engageOther: (question: QuestionnaireQuestion) => void;
  disengageOther: (question: QuestionnaireQuestion) => void;
  /** Pick a REAL option — also resolves the Other flow for the question. */
  pickOption: (question: QuestionnaireQuestion, optionId: string) => void;
  questionComplete: (question: QuestionnaireQuestion) => boolean;
  allComplete: boolean;
}

const Context = createContext<QuestionnaireContextValue | null>(null);

export function useQuestionnaireContext(): QuestionnaireContextValue {
  const value = useContext(Context);
  if (!value) throw new Error('Questionnaire subcomponents need <QuestionnaireProvider>');
  return value;
}

function isComplete(question: QuestionnaireQuestion, draft: QuestionnaireDraft): boolean {
  if (!question.required) return true;
  const other = (draft.other[question.id] ?? '').trim();
  if (question.mode === 'text') return other.length > 0;
  if (other.length > 0) return true;
  const min = question.mode === 'multiple_choice' ? question.minSelections ?? 1 : 1;
  return (draft.selectedIds[question.id]?.length ?? 0) >= min;
}

export function QuestionnaireProvider({
  children,
  questionnaire,
  draft,
  status,
  labels,
  small,
  ...callbacks
}: QuestionnaireCallbacks & {
  children: ReactNode;
  questionnaire: Questionnaire;
  draft: QuestionnaireDraft;
  status: QuestionnaireStatus;
  labels: QuestionnaireLabels;
  small: boolean;
}) {
  const questions = questionnaire.questions;
  const [activeId, setActiveId] = useState(questions[0]?.id ?? '');
  // Local engagement survives an emptied textarea; draft text re-engages after
  // a remount so a saved draft never hides its own field.
  const [engaged, setEngaged] = useState<Record<string, boolean>>({});

  const { onSelect, onOtherChange } = callbacks;
  const otherEngaged = useCallback(
    (question: QuestionnaireQuestion) =>
      Boolean(engaged[question.id]) || (draft.other[question.id] ?? '').trim().length > 0,
    [engaged, draft.other],
  );
  const engageOther = useCallback(
    (question: QuestionnaireQuestion) => {
      setEngaged((prev) => ({ ...prev, [question.id]: true }));
      // Radio semantics: Other replaces the picked option; the "" convention
      // clears the host selection without inventing an option id.
      if (question.mode === 'single_choice') onSelect(question.id, '');
    },
    [onSelect],
  );
  const disengageOther = useCallback(
    (question: QuestionnaireQuestion) => {
      setEngaged((prev) => ({ ...prev, [question.id]: false }));
      if ((draft.other[question.id] ?? '') !== '') onOtherChange(question.id, '');
    },
    [draft.other, onOtherChange],
  );
  const pickOption = useCallback(
    (question: QuestionnaireQuestion, optionId: string) => {
      // A real single-choice pick resolves the Other flow — hidden text must
      // never silently override the visible answer.
      if (question.mode === 'single_choice' && otherEngaged(question)) {
        setEngaged((prev) => ({ ...prev, [question.id]: false }));
        if ((draft.other[question.id] ?? '') !== '') onOtherChange(question.id, '');
      }
      onSelect(question.id, optionId);
    },
    [draft.other, onOtherChange, onSelect, otherEngaged],
  );

  const questionComplete = useCallback(
    (question: QuestionnaireQuestion) => isComplete(question, draft),
    [draft],
  );

  const value = useMemo<QuestionnaireContextValue>(() => {
    const stepIndex = Math.max(0, questions.findIndex((question) => question.id === activeId));
    return {
      ...callbacks,
      questionnaire,
      draft,
      status,
      busy: status === 'submitting',
      failed: status === 'error',
      labels,
      small,
      activeId: questions[stepIndex]?.id ?? '',
      setActiveId,
      stepIndex,
      stepCount: questions.length,
      otherEngaged,
      engageOther,
      disengageOther,
      pickOption,
      questionComplete,
      allComplete: questions.every((question) => isComplete(question, draft)),
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- callbacks spread is stable per-render by construction
  }, [
    questionnaire,
    draft,
    status,
    labels,
    small,
    activeId,
    otherEngaged,
    engageOther,
    disengageOther,
    pickOption,
    questionComplete,
    callbacks.onSelect,
    callbacks.onOtherChange,
    callbacks.onSubmit,
    callbacks.onDecline,
    callbacks.onRetry,
    callbacks.onDismiss,
  ]);

  return <Context.Provider value={value}>{children}</Context.Provider>;
}
