/**
 * OpenCase — multi-step form to open a transaction case.
 *
 * Steps: category → ask → description → submit
 * Fraud/safety categories show EscapeRouteNotice and force escapeUsed.
 *
 * Renders inside the standard customer chrome (AppShell + SiteNav + BottomNav)
 * so /cases/new gets the same top navigation header as /favorites and /profile.
 */

'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useT } from '@/lib/i18n/react';
import type { StringBundle } from '@/lib/i18n/types';
import type { Locale } from '@/lib/i18n';
import { AppShell } from '@/components/ui/layout/AppShell';
import { BottomNav, useCustomerNavItems } from '@/components/ui/layout/BottomNav';
import { SiteNav } from '@/components/ui/layout/SiteNav';
import { Button } from '@/components/ui/primitives/Button';
import { Input } from '@/components/ui/primitives/Input';
import { FormField } from '@/components/ui/primitives/FormField';
import { NumberInput } from '@/components/ui/primitives/NumberInput';
import { Textarea } from '@/components/ui/primitives/Textarea';
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from '@/components/ui/primitives/Select';
import { EscapeRouteNotice } from '@/components/ui/domain/support/EscapeRouteNotice';
import { Spinner } from '@/components/ui/feedback/Spinner';
import { InlineNotice } from '@/components/ui/feedback/InlineNotice';
import { Icon } from '@/components/ui/icons/Icon';
import { X } from 'lucide-react';
import { interpolate } from '@/lib/i18n/interpolate';
import { Image } from '@/components/ui/primitives/Image';
import { IconButton } from '@/components/ui/primitives/IconButton';
import { authenticatedFetch } from '@/lib/authenticated-fetch';
import { captureCaught } from '@/lib/observability';
import { useOpenCase } from './useCaseMutations';
import { qk } from '@/lib/query/keys';
import { fetchMyPurchases } from '@/features/my-purchases/useMyPurchases';
import { ErrorBoundary } from '@/components/ui/feedback/ErrorBoundary';
import { HydratedIsland } from '@/components/HydratedIsland';
import type { CaseCategory } from '@/server/support/state-machines';

const CATEGORIES: CaseCategory[] = [
  'item_not_as_described',
  'no_show_vendor',
  'quality_issue',
  'cannot_redeem',
  'cancellation_request',
  'fraud',
  'safety',
  'other',
];

const ASK_OUTCOMES = ['refund_full', 'refund_partial', 'replacement', 'deny'] as const;
type AskOutcome = (typeof ASK_OUTCOMES)[number];

const MAX_ATTACHMENTS = 10;
const MAX_FILE_BYTES = 10 * 1024 * 1024;
const ACCEPTED_IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/avif']);

interface PendingFile {
  file: File;
  previewUrl: string;
}

async function uploadAttachment(file: File, caseId: string): Promise<string> {
  const initRes = await authenticatedFetch('/api/support/attachments/direct-upload', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ contentType: file.type, sizeBytes: file.size }),
  });
  const initJson = (await initRes.json()) as {
    ok: boolean;
    uploadToken?: string;
    data?: { uploadToken: string };
  };
  const uploadToken = initJson.uploadToken ?? initJson.data?.uploadToken;
  if (!initJson.ok || !uploadToken) throw new Error('upload_init_failed');

  const fd = new FormData();
  fd.append('uploadToken', uploadToken);
  fd.append('parentType', 'case');
  fd.append('parentId', caseId);
  fd.append('file', file);
  const completeRes = await authenticatedFetch('/api/support/attachments/complete', {
    method: 'POST',
    body: fd,
  });
  const completeJson = (await completeRes.json()) as {
    ok: boolean;
    attachmentId?: string;
    data?: { id: string };
  };
  const attachmentId = completeJson.attachmentId ?? completeJson.data?.id;
  if (!completeJson.ok || !attachmentId) throw new Error('upload_complete_failed');
  return attachmentId;
}

export interface OpenCaseProps {
  purchaseId?: string;
  /** Path prefix to redirect after opening. Default: "/cases". */
  successBasePath?: string;
  onSuccess?: (caseId: string) => void;
  /** SiteNav auth/role props (forwarded from the Astro route). */
  isGuest?: boolean;
  isAdmin?: boolean;
  isVendor?: boolean;
  userName?: string;
  /** Server-resolved locale — threads to HydratedIsland so first render uses correct locale. */
  initialLocale?: Locale;
}

export function OpenCase(props: OpenCaseProps) {
  return (
    <HydratedIsland locale={props.initialLocale}>
      <OpenCaseInner {...props} />
    </HydratedIsland>
  );
}

function OpenCaseInner({
  purchaseId: initialPurchaseId,
  successBasePath = '/cases',
  onSuccess,
  isGuest = false,
  isAdmin = false,
  isVendor = false,
  userName,
  initialLocale,
}: OpenCaseProps) {
  const t = useT('cases');
  const tCommon = useT('common');
  const tErrors = t('errors') as unknown as Record<string, string>;
  const tOpen = t('open') as unknown as StringBundle['cases']['open'];
  const tCategories = t('categories') as unknown as StringBundle['cases']['categories'];
  const mutation = useOpenCase();
  const navItems = useCustomerNavItems('/cases');

  const [selectedPurchaseId, setSelectedPurchaseId] = useState(initialPurchaseId ?? '');
  const { data: purchasesData, isLoading: purchasesLoading } = useQuery({
    queryKey: qk.purchases(),
    queryFn: fetchMyPurchases,
  });

  const [category, setCategory] = useState<CaseCategory | ''>('');
  const [askOutcome, setAskOutcome] = useState<AskOutcome | ''>('');
  const [askAmountCents, setAskAmountCents] = useState<number | null>(null);
  const [askAmountShekels, setAskAmountShekels] = useState<number>(0);
  const [description, setDescription] = useState('');
  const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);
  const [uploading, setUploading] = useState(false);
  const [uploadError, setUploadError] = useState<string | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const pendingFilesRef = useRef(pendingFiles);

  useEffect(() => {
    pendingFilesRef.current = pendingFiles;
  }, [pendingFiles]);

  useEffect(() => {
    return () => {
      for (const pending of pendingFilesRef.current) {
        URL.revokeObjectURL(pending.previewUrl);
      }
    };
  }, []);

  const escapeCategory = category === 'fraud' || category === 'safety';
  const atAttachmentLimit = pendingFiles.length >= MAX_ATTACHMENTS;
  const isBusy = mutation.isPending || uploading;
  const partialRefundInvalid =
    askOutcome === 'refund_partial' && (!askAmountCents || askAmountCents <= 0);

  const selectedPurchase = selectedPurchaseId
    ? [...(purchasesData?.active ?? []), ...(purchasesData?.history ?? [])].find(
        (p) => p.id === selectedPurchaseId,
      )
    : undefined;

  function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (fileInputRef.current) fileInputRef.current.value = '';
    if (!file || atAttachmentLimit) return;

    setUploadError(null);

    if (!ACCEPTED_IMAGE_TYPES.has(file.type)) {
      setUploadError(tOpen.attachments_upload_error);
      return;
    }
    if (file.size > MAX_FILE_BYTES) {
      setUploadError(tOpen.attachments_upload_error);
      return;
    }

    const previewUrl = URL.createObjectURL(file);
    setPendingFiles((prev) => [...prev, { file, previewUrl }]);
  }

  function handleRemovePending(index: number) {
    setPendingFiles((prev) => {
      const removed = prev[index];
      if (removed) URL.revokeObjectURL(removed.previewUrl);
      return prev.filter((_, i) => i !== index);
    });
  }

  const navigateAfterOpen = useCallback(
    (caseId: string) => {
      if (onSuccess) {
        onSuccess(caseId);
      } else {
        window.location.href = `${successBasePath}/${caseId}?opened=1`;
      }
    },
    [onSuccess, successBasePath],
  );

  async function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
    e.preventDefault();
    if (!category || !askOutcome || partialRefundInvalid || isBusy) return;
    setUploadError(null);

    try {
      const result = await mutation.mutateAsync({
        purchaseId: selectedPurchaseId,
        category,
        customerAskOutcome: askOutcome,
        customerAskAmountCents: askAmountCents,
        description,
        escapeRequested: escapeCategory,
      });

      if (pendingFiles.length > 0) {
        setUploading(true);
        try {
          for (const pending of pendingFiles) {
            await uploadAttachment(pending.file, result.caseId);
          }
        } catch (err: unknown) {
          captureCaught(err, { scope: 'features.transaction-cases.OpenCase.upload' });
          setUploadError(tOpen.attachments_upload_error);
        } finally {
          setUploading(false);
        }
      }

      navigateAfterOpen(result.caseId);
    } catch (err: unknown) {
      captureCaught(err, { scope: 'features.transaction-cases.OpenCase.submit' });
    }
  }

  const errCode = (mutation.error as { code?: string } | null)?.code ?? '';
  const errKey =
    (
      {
        NOT_FOUND: 'not_found',
        FORBIDDEN: 'forbidden',
        PURCHASE_NOT_FOUND: 'purchase_not_found',
        DUPLICATE_OPEN: 'duplicate_open',
      } as Record<string, string>
    )[errCode] ?? 'submit_error';

  const pageTitle = selectedPurchaseId ? tOpen.title : tOpen.purchase_picker_title;

  return (
    <ErrorBoundary>
      <AppShell
        mode="customer"
        initialLocale={initialLocale}
        topBar={
          <SiteNav
            variant="mobile"
            title={pageTitle}
            currentPath="/cases"
            isGuest={isGuest}
            isAdmin={isAdmin}
            isVendor={isVendor}
          />
        }
        desktopTopBar={
          <SiteNav
            variant="desktop"
            currentPath="/cases"
            isGuest={isGuest}
            isAdmin={isAdmin}
            isVendor={isVendor}
            userName={userName}
          />
        }
        bottomNav={<BottomNav mode="customer" items={navItems} />}
      >
        <main id="main" className="mx-auto w-full max-w-lg px-4 py-6">
          <section aria-labelledby="open-case-title" className="space-y-6">
            <div>
              <h1
                id="open-case-title"
                className="text-text-primary text-2xl leading-tight font-bold"
              >
                {pageTitle}
              </h1>
              <p className="text-text-muted mt-1 text-sm">
                {selectedPurchaseId ? tOpen.subtitle : tOpen.purchase_picker_hint}
              </p>
            </div>

            {!selectedPurchaseId ? (
              purchasesLoading ? (
                <div className="flex justify-center py-8">
                  <Spinner size="md" label={tCommon('loading')} />
                </div>
              ) : !purchasesData?.active.length ? (
                <p className="text-text-muted text-sm">{tOpen.purchase_picker_hint}</p>
              ) : (
                <ul className="divide-border divide-y rounded-lg border">
                  {purchasesData.active.map((p) => (
                    <li key={p.id}>
                      <Button
                        variant="ghost"
                        size="sm"
                        className="hover:bg-surface-hover w-full px-4 py-3 text-start"
                        onClick={() => setSelectedPurchaseId(p.id)}
                      >
                        <span className="text-text-primary block text-sm font-medium">
                          {p.dealTitle}
                        </span>
                        <span className="text-text-muted block text-xs">{p.businessName}</span>
                      </Button>
                    </li>
                  ))}
                </ul>
              )
            ) : (
              <form onSubmit={handleSubmit} className="space-y-4">
                {selectedPurchase && (
                  <div className="border-border bg-surface-subtle rounded-lg border p-3">
                    <p className="text-text-muted text-xs font-medium">
                      {tOpen.purchase_context_label}
                    </p>
                    <p className="text-text-primary text-sm font-semibold">
                      {selectedPurchase.dealTitle}
                    </p>
                    <p className="text-text-secondary text-xs">{selectedPurchase.businessName}</p>
                  </div>
                )}

                {/* Category */}
                <FormField label={tOpen.category_label} required htmlFor="open-case-category">
                  <Select
                    value={category}
                    onValueChange={(v) => setCategory(v as CaseCategory)}
                    name="category"
                    required
                  >
                    <SelectTrigger id="open-case-category" aria-label={tOpen.category_label}>
                      <SelectValue placeholder={tOpen.category_placeholder} />
                    </SelectTrigger>
                    <SelectContent>
                      {CATEGORIES.map((c) => (
                        <SelectItem key={c} value={c}>
                          {tCategories[c]}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </FormField>

                {/* Escape notice */}
                {escapeCategory && <EscapeRouteNotice category={category as CaseCategory} />}

                {/* Ask */}
                <FormField label={tOpen.ask_label} required htmlFor="open-case-ask-outcome">
                  <Select
                    value={askOutcome}
                    onValueChange={(v) => setAskOutcome(v as AskOutcome)}
                    name="customerAskOutcome"
                    required
                  >
                    <SelectTrigger id="open-case-ask-outcome" aria-label={tOpen.ask_label}>
                      <SelectValue placeholder={tOpen.ask_placeholder} />
                    </SelectTrigger>
                    <SelectContent>
                      {ASK_OUTCOMES.map((o) => (
                        <SelectItem key={o} value={o}>
                          {tOpen.ask_outcome[o]}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </FormField>

                {/* Amount (only for partial refund) */}
                {askOutcome === 'refund_partial' && (
                  <FormField
                    label={tOpen.ask_amount_label}
                    htmlFor="open-case-amount"
                    hint={partialRefundInvalid ? tOpen.ask_amount_helper : undefined}
                  >
                    <NumberInput
                      id="open-case-amount"
                      name="amount"
                      min={0}
                      step={0.01}
                      value={askAmountShekels}
                      onChange={(n) => {
                        setAskAmountShekels(n);
                        setAskAmountCents(n > 0 ? Math.round(n * 100) : null);
                      }}
                    />
                  </FormField>
                )}

                {/* Description */}
                <FormField label={tOpen.description_label} required htmlFor="open-case-description">
                  <Textarea
                    id="open-case-description"
                    name="description"
                    aria-label={tOpen.description_label}
                    required
                    rows={4}
                    value={description}
                    onChange={(e) => setDescription(e.target.value)}
                    placeholder={tOpen.description_placeholder}
                  />
                </FormField>

                {/* Attachments */}
                <fieldset>
                  <legend className="text-text-primary mb-1 text-sm font-medium">
                    {tOpen.step_attachments}: {tOpen.attachments_label}
                  </legend>
                  <p className="text-text-muted mb-2 text-xs">{tOpen.attachments_hint}</p>

                  {pendingFiles.length > 0 && (
                    <ul className="mb-2 flex flex-wrap gap-2" aria-label={tOpen.attachments_label}>
                      {pendingFiles.map((pending, index) => (
                        <li key={pending.previewUrl} className="relative">
                          <Image
                            src={pending.previewUrl}
                            alt=""
                            decorative
                            width={64}
                            height={64}
                            className="border-border h-16 w-16 rounded-md border object-cover"
                          />
                          <IconButton
                            type="button"
                            aria-label={interpolate(tOpen.remove_attachment, {
                              name: pending.file.name,
                            })}
                            variant="ghost"
                            size="sm"
                            shape="circle"
                            onClick={() => handleRemovePending(index)}
                            disabled={isBusy}
                            className="bg-surface-overlay text-text-primary border-border focus-visible:ring-brand-primary-500 absolute -end-1 -top-1 border text-xs"
                          >
                            <X size={14} aria-hidden />
                          </IconButton>
                        </li>
                      ))}
                    </ul>
                  )}

                  {!atAttachmentLimit && (
                    <div className="flex flex-col gap-2">
                      <Button
                        type="button"
                        variant="secondary"
                        size="sm"
                        onClick={() => fileInputRef.current?.click()}
                        disabled={isBusy}
                      >
                        <Icon name="Camera" size="sm" aria-hidden />
                        {tOpen.attachments_label}
                      </Button>
                      <Input
                        ref={fileInputRef}
                        type="file"
                        accept="image/jpeg,image/png,image/webp,image/avif"
                        aria-label={tOpen.attachments_label}
                        onChange={handleFileChange}
                        disabled={isBusy}
                        className="sr-only"
                      />
                    </div>
                  )}
                </fieldset>

                {uploadError && <InlineNotice tone="danger" description={uploadError} />}

                {mutation.isError && (
                  <InlineNotice
                    tone="danger"
                    description={tErrors[errKey] ?? tErrors.submit_error}
                  />
                )}

                <Button
                  type="submit"
                  variant="primary"
                  disabled={isBusy || partialRefundInvalid}
                  loading={isBusy}
                >
                  {uploading
                    ? tOpen.attachments_uploading
                    : mutation.isPending
                      ? tOpen.submitting
                      : escapeCategory
                        ? tOpen.submit_escape
                        : tOpen.submit}
                </Button>
              </form>
            )}
          </section>
        </main>
      </AppShell>
    </ErrorBoundary>
  );
}
