import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
import {
  deletePet,
  fetchPets,
  fetchWorkerBackends,
  fetchWorkers,
  importPet,
  petAssetUrl,
  updatePetSettings,
  type PetSettings,
  type PetSummary,
  type WorkerBackendsResponse,
  type WorkerInfo,
} from '../../api';
import { PetSprite } from '../pets/PetSprite';
import { usePetFrameAnalysis } from '../../hooks/usePetFrameAnalysis';

type ShowToast = (message: string, variant?: 'success' | 'error') => void;

function ToggleRow({
  label,
  checked,
  onChange,
  disabled,
}: {
  label: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
  disabled?: boolean;
}) {
  return (
    <label className="flex items-center justify-between gap-3 py-2">
      <span className="text-[13px] font-medium text-slate-700">{label}</span>
      <button
        type="button"
        role="switch"
        aria-checked={checked}
        disabled={disabled}
        onClick={() => onChange(!checked)}
        className={`relative h-6 w-11 rounded-full text-left transition-colors ${
          checked ? 'bg-accent' : 'bg-slate-200'
        }`}
      >
        <span
          className={`absolute left-0 top-0.5 h-5 w-5 rounded-full bg-canvas shadow-sm transition-transform ${
            checked ? 'translate-x-5' : 'translate-x-0.5'
          }`}
        />
      </button>
    </label>
  );
}

function usePrefersReducedMotion(): boolean {
  const [reduced, setReduced] = useState(false);
  useEffect(() => {
    const q = window.matchMedia('(prefers-reduced-motion: reduce)');
    const update = () => setReduced(q.matches);
    update();
    q.addEventListener('change', update);
    return () => q.removeEventListener('change', update);
  }, []);
  return reduced;
}

function PetPreview({ pet, size = 56 }: { pet: PetSummary; size?: number }) {
  const reduced = usePrefersReducedMotion();
  const spriteUrl = pet.spriteFile ? petAssetUrl(pet.id, pet.spriteFile) : null;
  const framesPerRow = usePetFrameAnalysis(spriteUrl, pet.gridCols, pet.gridRows);
  const canAnimate = !!(spriteUrl && pet.gridCols && pet.gridRows);

  return (
    <div className="rounded-md border border-hairline bg-surface overflow-hidden grid place-items-center shrink-0" style={{ width: size, height: size }}>
      {canAnimate ? (
        <PetSprite
          name={pet.name}
          imageUrl={spriteUrl}
          frameWidth={pet.frameWidth}
          frameHeight={pet.frameHeight}
          gridCols={pet.gridCols}
          gridRows={pet.gridRows}
          framesPerRow={framesPerRow}
          state="idle"
          size={size}
          reducedMotion={reduced}
        />
      ) : pet.previewFile || pet.spriteFile ? (
        <img
          src={petAssetUrl(pet.id, (pet.previewFile ?? pet.spriteFile)!)}
          alt=""
          className="w-full h-full object-contain"
          draggable={false}
        />
      ) : (
        <span className="text-slate-400 text-xs font-mono">pet</span>
      )}
    </div>
  );
}

function WorkerMappingRow({
  workerLabel,
  workerSubLabel,
  selectedPetId,
  pets,
  disabled,
  onChange,
}: {
  workerLabel: string;
  workerSubLabel?: string | null;
  selectedPetId: string;
  pets: PetSummary[];
  disabled: boolean;
  onChange: (petId: string) => void;
}) {
  const { t } = useTranslation('userfolder');
  return (
    <div className="flex items-center justify-between gap-3 py-2">
      <div className="min-w-0 flex-1">
        <div className="text-[13px] font-medium text-slate-700 truncate">{workerLabel}</div>
        {workerSubLabel && (
          <div className="text-2xs text-slate-500 font-mono truncate">{workerSubLabel}</div>
        )}
      </div>
      <select
        className="input w-40"
        value={selectedPetId}
        disabled={disabled}
        onChange={(event) => onChange(event.target.value)}
      >
        <option value="">{t('pets.none')}</option>
        {pets.map(p => (
          <option key={p.id} value={p.id}>{p.name}</option>
        ))}
      </select>
    </div>
  );
}

export function PetsPanel({ showToast }: { showToast?: ShowToast }) {
  const { t } = useTranslation('userfolder');
  const inputRef = useRef<HTMLInputElement>(null);
  const [importError, setImportError] = useState<string | null>(null);
  const qc = useQueryClient();
  const query = useQuery({
    queryKey: ['user-pets'],
    queryFn: fetchPets,
    staleTime: 30_000,
  });
  const workersQuery = useQuery<WorkerInfo[]>({
    queryKey: ['workers'],
    queryFn: fetchWorkers,
    staleTime: 60_000,
  });

  const invalidate = () => {
    qc.invalidateQueries({ queryKey: ['user-pets'] });
  };

  const importMutation = useMutation({
    mutationFn: (file: File) => importPet(file),
    onSuccess: (pet) => {
      setImportError(null);
      showToast?.(t('pets.imported', { name: pet.name }), 'success');
      invalidate();
    },
    onError: (err) => {
      const message = err instanceof Error ? err.message : 'Import failed';
      setImportError(message);
      showToast?.(t('pets.importFailed', { error: message }), 'error');
    },
  });

  const settingsMutation = useMutation({
    mutationFn: (patch: Partial<PetSettings>) => updatePetSettings(patch),
    onSuccess: () => {
      invalidate();
    },
    onError: (err) => {
      const message = err instanceof Error ? err.message : 'Settings update failed';
      showToast?.(t('pets.settingsSaveFailed', { error: message }), 'error');
    },
  });

  const deleteMutation = useMutation({
    mutationFn: deletePet,
    onSuccess: () => {
      invalidate();
    },
    onError: (err) => {
      const message = err instanceof Error ? err.message : 'Delete failed';
      showToast?.(t('pets.deleteFailed', { error: message }), 'error');
    },
  });

  const settings = query.data?.settings;
  const pets = query.data?.pets ?? [];
  const workers = workersQuery.data ?? [];
  // Show the workers section whenever the user has at least one worker
  // AND we have a settings row to read from. Two prior gates were
  // dropped, each having broken a real verification flow:
  //
  //   1. `pets.length > 0` (removed earlier) hid the topology until
  //      a Pet was imported, even though the topology view is what
  //      operators want immediately after wiring a proxy worker.
  //   2. `workers.length > 1` hid the section in single-proxy-worker
  //      setups — exactly the canonical "AAO client → AAO Gateway
  //      with N backends" deployment. The user has 1 worker config
  //      (the gateway) but the tree below expands to N backends that
  //      they want to assign Pets to. Requiring >1 worker turned the
  //      Pets page into a blank slate for this whole class of users.
  //
  // With workers.length >= 1, single-direct-worker setups show a
  // mildly redundant "default + 1 worker" pair, which is harmless.
  const showWorkerMapping = workers.length >= 1 && !!settings;
  const noPetsYet = pets.length === 0;
  const workerPets = settings?.workerPets ?? {};

  // For each proxy worker, fetch its backend list so the panel can render
  // a tree (worker → backends) and allow per-backend pet assignment. Direct
  // workers don't fire the query (enabled=false) so we don't waste a
  // round-trip on a known-empty result.
  const proxyWorkers = workers.filter(w => w.proxy === true);
  const backendsQueries = useQueries({
    queries: proxyWorkers.map(w => ({
      queryKey: ['worker-backends', w.id] as const,
      queryFn: () => fetchWorkerBackends(w.id),
      staleTime: 60_000,
    })),
  });
  const backendsByWorker = new Map<string, WorkerBackendsResponse | undefined>();
  proxyWorkers.forEach((w, i) => backendsByWorker.set(w.id, backendsQueries[i]?.data));

  // Per-proxy-worker collapsed/expanded state. Default: expanded so the
  // user sees their backends immediately on first load; toggling is a
  // local convenience for installs with many backends per pool.
  const [collapsedProxies, setCollapsedProxies] = useState<Record<string, boolean>>({});
  const toggleCollapse = (workerId: string) => {
    setCollapsedProxies(prev => ({ ...prev, [workerId]: !prev[workerId] }));
  };

  const updateSettings = (patch: Partial<PetSettings>) => {
    settingsMutation.mutate(patch);
  };

  const setDefaultPet = (petId: string) => {
    updateSettings({ activePetId: petId === '' ? null : petId });
  };

  const setWorkerPet = (workerId: string, petId: string) => {
    const next = { ...workerPets };
    if (petId === '') {
      delete next[workerId];
    } else {
      next[workerId] = petId;
    }
    updateSettings({ workerPets: next });
  };

  return (
    <div className="h-full overflow-y-auto">
      <div className="max-w-2xl mx-auto px-6 py-8">
        <div className="mb-6">
          <h2 className="text-base font-semibold text-slate-900 mb-1">{t('pets.title')}</h2>
          <p className="text-[13px] text-slate-500 leading-relaxed">
            {t('pets.intro')}
          </p>
        </div>

        <div className="flex items-center gap-2 mb-6">
          <input
            ref={inputRef}
            type="file"
            accept=".zip,application/zip,application/x-zip-compressed"
            className="hidden"
            onChange={(event) => {
              const file = event.target.files?.[0];
              event.target.value = '';
              if (file) importMutation.mutate(file);
            }}
          />
          <button
            type="button"
            className="btn btn-primary"
            disabled={importMutation.isPending}
            onClick={() => inputRef.current?.click()}
          >
            {importMutation.isPending ? t('pets.importing') : t('pets.importButton')}
          </button>
          {importError && <span className="text-xs text-red-600 truncate">{importError}</span>}
        </div>

        {query.isLoading && <div className="text-[13px] text-slate-400">{t('common.loading')}</div>}
        {query.error && <div className="text-[13px] text-red-500">{t('pets.loadFailed', { error: String(query.error) })}</div>}

        {settings && (
          <div className="mb-6 border-y border-hairline py-2">
            <ToggleRow
              label={t('pets.showInChat')}
              checked={settings.enabled}
              disabled={settingsMutation.isPending}
              onChange={(enabled) => updateSettings({ enabled })}
            />
            <ToggleRow
              label={t('pets.showToolSpark')}
              checked={settings.toolSparkEnabled}
              disabled={settingsMutation.isPending}
              onChange={(toolSparkEnabled) => updateSettings({ toolSparkEnabled })}
            />
            <ToggleRow
              label={t('pets.reduceMotion')}
              checked={settings.reducedMotion}
              disabled={settingsMutation.isPending}
              onChange={(reducedMotion) => updateSettings({ reducedMotion })}
            />
            <div className="flex items-center justify-between gap-3 py-2">
              <span className="text-[13px] font-medium text-slate-700">{t('pets.size')}</span>
              <select
                className="input w-24"
                value={settings.size}
                disabled={settingsMutation.isPending}
                onChange={(event) => updateSettings({ size: Number(event.target.value) as PetSettings['size'] })}
              >
                <option value={32}>32</option>
                <option value={48}>48</option>
                <option value={64}>64</option>
                <option value={80}>80</option>
              </select>
            </div>
          </div>
        )}

        {showWorkerMapping && (
          <section className="mb-6 border-y border-hairline py-2" aria-labelledby="workers-heading">
            <div className="flex items-baseline justify-between pt-1 pb-2">
              <h3 id="workers-heading" className="text-[13px] font-semibold text-slate-700">
                {t('pets.workers', { count: workers.length })}
              </h3>
              <span className="text-2xs text-slate-500">
                {noPetsYet ? t('pets.workerHintTopology') : t('pets.workerHintAssign')}
              </span>
            </div>
            {noPetsYet && (
              <div className="text-2xs text-slate-500 bg-slate-50 border border-slate-200 rounded px-2 py-1 mb-2">
                {t('pets.noPetsImportHint')}
              </div>
            )}
            <WorkerMappingRow
              workerLabel={t('pets.defaultWorker')}
              workerSubLabel={t('pets.defaultWorkerSub')}
              selectedPetId={settings?.activePetId ?? ''}
              pets={pets}
              disabled={settingsMutation.isPending || noPetsYet}
              onChange={setDefaultPet}
            />
            {workers.map(worker => {
              const isProxy = worker.proxy === true;
              const rolesLabel = worker.roles.length > 0 ? worker.roles.join(',') : null;
              const subLabel = isProxy
                ? `proxy${worker.proxyType ? `: ${worker.proxyType}` : ''}${rolesLabel ? ` · ${rolesLabel}` : ''}`
                : rolesLabel;

              if (!isProxy) {
                return (
                  <WorkerMappingRow
                    key={worker.id}
                    workerLabel={worker.id}
                    workerSubLabel={subLabel}
                    selectedPetId={workerPets[worker.id] ?? ''}
                    pets={pets}
                    disabled={settingsMutation.isPending || noPetsYet}
                    onChange={(petId) => setWorkerPet(worker.id, petId)}
                  />
                );
              }

              // Proxy worker — collapsible tree rendering. The worker row
              // itself maps to workerPets[worker.id] (fallback when no
              // backend matches); each backend row maps to workerPets[backendId].
              const backendsResp = backendsByWorker.get(worker.id);
              const backends = backendsResp?.backends ?? [];
              const collapsed = !!collapsedProxies[worker.id];
              const backendError = backendsResp?.error ?? null;
              return (
                <div key={worker.id} className="border-l-2 border-slate-100 ml-1 pl-2">
                  <div className="flex items-center justify-between gap-2 py-2">
                    <button
                      type="button"
                      onClick={() => toggleCollapse(worker.id)}
                      className="flex-1 min-w-0 text-left"
                      aria-expanded={!collapsed}
                    >
                      <div className="text-[13px] font-medium text-slate-700 truncate">
                        <span className="inline-block w-3 text-slate-400">{collapsed ? '▶' : '▼'}</span>
                        {' '}
                        {worker.id}
                        <span className="ml-2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-violet-50 dark:bg-violet-500/15 text-violet-700 dark:text-violet-300">
                          proxy
                        </span>
                      </div>
                      {subLabel && (
                        <div className="text-2xs text-slate-500 font-mono truncate pl-5">{subLabel}</div>
                      )}
                    </button>
                    <select
                      className="input w-40"
                      value={workerPets[worker.id] ?? ''}
                      disabled={settingsMutation.isPending || noPetsYet}
                      onChange={(event) => setWorkerPet(worker.id, event.target.value)}
                      title={t('pets.proxyDefaultTitle')}
                    >
                      <option value="">{t('pets.proxyDefaultOption')}</option>
                      {pets.map(p => (
                        <option key={p.id} value={p.id}>{p.name}</option>
                      ))}
                    </select>
                  </div>
                  {!collapsed && (
                    <div className="pl-5">
                      {backendError && (
                        <div className="text-2xs text-red-600 py-1">
                          {t('pets.backendListFailed', { error: backendError })}
                        </div>
                      )}
                      {!backendError && backends.length === 0 && (
                        <div className="text-2xs text-slate-400 py-1">
                          {t('pets.backendNotFound')}
                        </div>
                      )}
                      {backends.map(backend => (
                        <WorkerMappingRow
                          key={backend.id}
                          workerLabel={backend.id}
                          workerSubLabel={backend.model && backend.model !== backend.id ? backend.model : null}
                          selectedPetId={workerPets[backend.id] ?? ''}
                          pets={pets}
                          disabled={settingsMutation.isPending || noPetsYet}
                          onChange={(petId) => setWorkerPet(backend.id, petId)}
                        />
                      ))}
                    </div>
                  )}
                </div>
              );
            })}
          </section>
        )}

        {!query.isLoading && pets.length === 0 && (
          <div className="text-[13px] text-slate-400">
            {t('pets.noPetsYet')}
          </div>
        )}

        <ul className="divide-y divide-hairline">
          {pets.map((pet) => {
            const active = settings?.activePetId === pet.id;
            return (
              <li key={pet.id} className="py-3 flex items-center gap-3">
                <PetPreview pet={pet} />
                <div className="min-w-0 flex-1">
                  <div className="flex items-center gap-2">
                    <span className="text-[13px] font-semibold text-slate-900 truncate">{pet.name}</span>
                    {active && (
                      <span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300">
                        default
                      </span>
                    )}
                  </div>
                  <div className="text-2xs text-slate-500 font-mono truncate">{pet.id}</div>
                  {pet.description && <p className="text-xs text-slate-500 mt-1 line-clamp-2">{pet.description}</p>}
                </div>
                <div className="flex items-center gap-2 shrink-0">
                  <button
                    type="button"
                    className="btn btn-ghost"
                    disabled={active || settingsMutation.isPending}
                    onClick={() => updateSettings({ activePetId: pet.id })}
                  >
                    {t('pets.makeDefault')}
                  </button>
                  <button
                    type="button"
                    className="btn btn-danger"
                    disabled={deleteMutation.isPending}
                    onClick={() => {
                      if (window.confirm(t('pets.confirmDelete', { name: pet.name }))) {
                        deleteMutation.mutate(pet.id);
                      }
                    }}
                  >
                    {t('pets.delete')}
                  </button>
                </div>
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}
