'use client';
import React, { useState, useMemo, useEffect } from 'react';
import { PageWrapper } from '../shared/page-wrapper';
import { useStatus } from '@/context/status';
import { useUserData } from '@/context/userData';
import { SettingsCard } from '../shared/settings-card';
import { Button, IconButton } from '../ui/button';
import { Modal } from '../ui/modal';
import { Switch } from '../ui/switch';
import {
  DndContext,
  useSensor,
  useSensors,
  PointerSensor,
  TouchSensor,
} from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
  arrayMove,
  SortableContext,
  verticalListSortingStrategy,
  useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { PlusIcon, SearchIcon, FilterIcon } from 'lucide-react';
import TemplateOption from '../shared/template-option';
import * as constants from '../../../../core/src/utils/constants';
import { TextInput } from '../ui/text-input';

import { Popover } from '../ui/popover';
import { BiEdit, BiTrash } from 'react-icons/bi';
import { Option, Resource } from '@aiostreams/core';
import { toast } from 'sonner';
import { Tooltip } from '../ui/tooltip';
import { StaticTabs } from '../ui/tabs';
import {
  LuDownload,
  LuGlobe,
  LuChevronsUp,
  LuChevronsDown,
  LuShuffle,
} from 'react-icons/lu';
import { TbSmartHome, TbSmartHomeOff } from 'react-icons/tb';
import { AnimatePresence } from 'framer-motion';
import { PageControls } from '../shared/page-controls';
import Image from 'next/image';
import { Combobox } from '../ui/combobox';
import { FaPlus, FaRegTrashAlt } from 'react-icons/fa';
import { UserConfigAPI } from '../../services/api';
import {
  ConfirmationDialog,
  useConfirmationDialog,
} from '../shared/confirmation-dialog';
import { MdRefresh } from 'react-icons/md';
import { Alert } from '../ui/alert';
import MarkdownLite from '../shared/markdown-lite';
import {
  Accordion,
  AccordionTrigger,
  AccordionContent,
  AccordionItem,
} from '../ui/accordion';
import { FaArrowRightLong, FaRankingStar, FaShuffle } from 'react-icons/fa6';
import { PiStarFill, PiStarBold } from 'react-icons/pi';
import { IoExtensionPuzzle } from 'react-icons/io5';
import { NumberInput } from '../ui/number-input';

interface CatalogModification {
  id: string;
  type: string;
  name?: string;
  overrideType?: string;
  enabled?: boolean;
  shuffle?: boolean;
  persistShuffleFor?: number;
  rpdb?: boolean;
  onlyOnDiscover?: boolean;
  hideable?: boolean;
  addonName?: string;
}

export function AddonsMenu() {
  return (
    <PageWrapper className="space-y-4 p-4 sm:p-8">
      <Content />
    </PageWrapper>
  );
}

function Content() {
  const { status } = useStatus();
  const { userData, setUserData } = useUserData();
  const [page, setPage] = useState<'installed' | 'marketplace'>('installed');
  const [search, setSearch] = useState('');
  // Filter states
  const [serviceFilters, setServiceFilters] = useState<string[]>([]);
  const [streamTypeFilters, setStreamTypeFilters] = useState<
    constants.StreamType[]
  >([]);
  const [resourceFilters, setResourceFilters] = useState<string[]>([]);
  // Modal states
  const [modalOpen, setModalOpen] = useState(false);
  const [modalMode, setModalMode] = useState<'add' | 'edit'>('add');
  const [modalPreset, setModalPreset] = useState<any | null>(null);
  const [modalInitialValues, setModalInitialValues] = useState<
    Record<string, any>
  >({});
  const [editingAddonId, setEditingAddonId] = useState<string | null>(null);
  const [isDragging, setIsDragging] = useState(false);
  // Filtering and search for marketplace
  const filteredPresets = useMemo(() => {
    if (!status?.settings?.presets) return [];
    return status.settings.presets.filter((preset) => {
      if (preset.ID === 'custom') return true;
      const matchesService =
        serviceFilters.length === 0 ||
        (preset.SUPPORTED_SERVICES &&
          serviceFilters.every((s) => preset.SUPPORTED_SERVICES.includes(s)));
      const matchesStreamType =
        streamTypeFilters.length === 0 ||
        (preset.SUPPORTED_STREAM_TYPES &&
          streamTypeFilters.every((t) =>
            preset.SUPPORTED_STREAM_TYPES.includes(t)
          ));
      const matchesResource =
        resourceFilters.length === 0 ||
        (preset.SUPPORTED_RESOURCES &&
          resourceFilters.every((r) =>
            preset.SUPPORTED_RESOURCES.includes(r as Resource)
          ));
      const matchesSearch =
        !search ||
        preset.NAME.toLowerCase().includes(search.toLowerCase()) ||
        preset.DESCRIPTION.toLowerCase().includes(search.toLowerCase());
      return (
        matchesService && matchesStreamType && matchesResource && matchesSearch
      );
    });
  }, [status, search, serviceFilters, streamTypeFilters, resourceFilters]);

  // My Addons (user's enabled/added presets)

  // AddonModal handlers
  function handleAddPreset(preset: any) {
    setModalPreset(preset);
    setModalInitialValues({
      options: Object.fromEntries(
        (preset.OPTIONS || []).map((opt: any) => [
          opt.id,
          opt.default ?? undefined,
        ])
      ),
    });
    setModalMode('add');
    setEditingAddonId(null);
    setModalOpen(true);
  }
  function getUniqueId() {
    // generate a 3 character long hex string, ensuring it doesn't already exist in the user's presets
    const id = Math.floor(Math.random() * 0xfff)
      .toString(16)
      .padStart(3, '0');
    if (userData.presets.some((a) => a.instanceId === id)) {
      return getUniqueId();
    }
    return id;
  }

  function handleModalSubmit(values: Record<string, any>) {
    if (modalMode === 'add' && modalPreset) {
      // Always add a new preset with default values, never edit
      const newPreset = {
        type: modalPreset.ID,
        instanceId: getUniqueId(),
        enabled: true,
        options: values.options,
      };
      const newKey = getPresetUniqueKey(newPreset);
      // Prevent adding if a preset with the same unique key already exists
      // dont use instanceId here, as that will always be unique
      // only prevent adding the same preset type with the same options
      // so we use getPresetUniqueKey here.
      if (userData.presets.some((a) => getPresetUniqueKey(a) === newKey)) {
        toast.error('You already have an addon with the same options added.');
        setModalOpen(false);
        return;
      }
      setUserData((prev) => ({
        ...prev,
        presets: [...prev.presets, newPreset],
      }));
      toast.info('Addon installed successfully!');
      setModalOpen(false);
    } else if (modalMode === 'edit' && editingAddonId) {
      // Edit existing preset (should not be triggered from marketplace)
      setUserData((prev) => ({
        ...prev,
        presets: prev.presets.map((a) =>
          a.instanceId === editingAddonId
            ? { ...a, options: values.options }
            : a
        ),
      }));
      toast.info('Addon updated successfully!');
      setModalOpen(false);
    }
  }

  // DND for My Addons
  function handleDragEnd(event: any) {
    const { active, over } = event;
    if (!over) return;
    if (active.id !== over.id) {
      const oldIndex = userData.presets.findIndex(
        (a) => a.instanceId === active.id
      );
      const newIndex = userData.presets.findIndex(
        (a) => a.instanceId === over.id
      );
      const newPresets = arrayMove(userData.presets, oldIndex, newIndex);
      setUserData((prev) => ({
        ...prev,
        presets: newPresets,
      }));
    }
    setIsDragging(false);
  }

  function handleDragStart(event: any) {
    setIsDragging(true);
  }

  // Service, stream type, and resource options
  const serviceOptions = Object.values(constants.SERVICE_DETAILS).map(
    (service) => ({ label: service.name, value: service.id })
  );
  const streamTypeOptions = (constants.STREAM_TYPES || [])
    .filter((type) => type !== 'error')
    .map((type: string) => ({ label: type, value: type }));
  const resourceOptions = (constants.RESOURCES || []).map((res: string) => ({
    label: res,
    value: res,
  }));
  const activeFilterCount =
    serviceFilters.length + streamTypeFilters.length + resourceFilters.length;

  // DND-kit setup
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(TouchSensor, {
      activationConstraint: {
        delay: 150,
        tolerance: 8,
      },
    })
  );

  useEffect(() => {
    function preventTouchMove(e: TouchEvent) {
      if (isDragging) {
        e.preventDefault();
      }
    }

    function handleDragEnd() {
      setIsDragging(false);
    }
    if (isDragging) {
      document.body.addEventListener('touchmove', preventTouchMove, {
        passive: false,
      });
      // Add listeners for when drag ends outside context
      document.addEventListener('pointerup', handleDragEnd);
      document.addEventListener('touchend', handleDragEnd);
    } else {
      document.body.removeEventListener('touchmove', preventTouchMove);
    }
    return () => {
      document.body.removeEventListener('touchmove', preventTouchMove);
      document.removeEventListener('pointerup', handleDragEnd);
      document.removeEventListener('touchend', handleDragEnd);
    };
  }, [isDragging]);
  return (
    <>
      {/* <div className="flex items-center w-full">
        <div>
          <h2>Addons</h2>
          <p className="text-[--muted]">Manage your installed addons or</p>
        </div>
        <div className="flex flex-1"></div>
      </div> */}

      <div className="flex items-center justify-between">
        <StaticTabs
          className="h-10 w-fit border rounded-full"
          triggerClass="px-4 py-1 text-md"
          items={[
            {
              name: 'Installed',
              isCurrent: page === 'installed',
              onClick: () => setPage('installed'),
              iconType: LuDownload,
            },
            {
              name: 'Marketplace',
              isCurrent: page === 'marketplace',
              onClick: () => setPage('marketplace'),
              iconType: LuGlobe,
            },
          ]}
        />

        <div className="hidden lg:block lg:ml-auto">
          <PageControls />
        </div>
      </div>

      <AnimatePresence mode="wait">
        {page === 'installed' && (
          <PageWrapper
            {...{
              initial: { opacity: 0, y: 60 },
              animate: { opacity: 1, y: 0 },
              exit: { opacity: 0, scale: 0.99 },
              transition: {
                duration: 0.35,
              },
            }}
            key="installed"
            className="pt-0 space-y-8 relative z-[4]"
          >
            <div>
              <h2>Installed Addons</h2>
              <p className="text-[--muted] text-sm">
                Manage your installed addons.
              </p>
            </div>
            <SettingsCard
              title="My Addons"
              description="Edit, remove, and reorder your installed addons. If you reorder your addons, you will have to refresh the catalogs if you have made any changes, and also reinstall the addon."
            >
              <DndContext
                modifiers={[restrictToVerticalAxis]}
                onDragEnd={handleDragEnd}
                onDragStart={handleDragStart}
                sensors={sensors}
              >
                <SortableContext
                  items={userData.presets.map((a) => a.instanceId)}
                  strategy={verticalListSortingStrategy}
                >
                  <div className="space-y-2">
                    <ul className="space-y-2">
                      {userData.presets.length === 0 ? (
                        <li>
                          <div className="flex flex-col items-center justify-center py-12">
                            <span className="text-lg text-muted-foreground font-semibold text-center">
                              Looks like you don't have any addons...
                              <br />
                              Add some from the marketplace!
                            </span>
                          </div>
                        </li>
                      ) : (
                        userData.presets.map((preset) => {
                          const presetMetadata = status?.settings?.presets.find(
                            (p: any) => p.ID === preset.type
                          );
                          return (
                            <SortableAddonItem
                              key={getPresetUniqueKey(preset)}
                              preset={preset}
                              presetMetadata={presetMetadata}
                              onEdit={() => {
                                setModalPreset(presetMetadata);
                                setModalInitialValues({
                                  options: { ...preset.options },
                                });
                                setModalMode('edit');
                                setEditingAddonId(preset.instanceId);
                                setModalOpen(true);
                              }}
                              onRemove={() => {
                                setUserData((prev) => ({
                                  ...prev,
                                  presets: prev.presets.filter(
                                    (a) => a.instanceId !== preset.instanceId
                                  ),
                                }));
                              }}
                              onToggleEnabled={(v: boolean) => {
                                setUserData((prev) => ({
                                  ...prev,
                                  presets: prev.presets.map((p) =>
                                    p.instanceId === preset.instanceId
                                      ? { ...p, enabled: v }
                                      : p
                                  ),
                                }));
                              }}
                            />
                          );
                        })
                      )}
                    </ul>
                  </div>
                </SortableContext>
              </DndContext>
            </SettingsCard>

            {userData.presets.length > 0 && <CatalogSettingsCard />}

            {userData.presets.length > 0 && <AddonGroupCard />}
          </PageWrapper>
        )}

        {page === 'marketplace' && (
          <PageWrapper
            {...{
              initial: { opacity: 0, y: 60 },
              animate: { opacity: 1, y: 0 },
              exit: { opacity: 0, scale: 0.99 },
              transition: {
                duration: 0.35,
              },
            }}
            key="marketplace"
            className="pt-0 space-y-8 relative z-[4]"
          >
            <div>
              <h2>Marketplace</h2>
              <p className="text-[--muted] text-sm">
                Browse and install addons from the marketplace.
              </p>
            </div>
            <div className="bg-[--card] border border-[--border] rounded-xl p-4 mb-6 shadow-sm">
              <div className="flex justify-center mb-4">
                <div className="w-full sm:w-[500px] flex gap-2">
                  <TextInput
                    value={search}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      setSearch(e.target.value)
                    }
                    placeholder="Search addons..."
                    className="flex-1"
                    leftIcon={<SearchIcon className="w-4 h-4" />}
                  />
                  <AddonFilterPopover
                    serviceOptions={serviceOptions}
                    streamTypeOptions={streamTypeOptions}
                    resourceOptions={resourceOptions}
                    serviceFilters={serviceFilters}
                    setServiceFilters={setServiceFilters}
                    streamTypeFilters={streamTypeFilters}
                    setStreamTypeFilters={setStreamTypeFilters}
                    resourceFilters={resourceFilters}
                    setResourceFilters={setResourceFilters}
                  >
                    <IconButton
                      icon={<FilterIcon className="w-5 h-5" />}
                      intent={
                        activeFilterCount > 0 ? 'primary' : 'primary-outline'
                      }
                      aria-label="Filters"
                    />
                  </AddonFilterPopover>
                </div>
              </div>
              {/* Scrollable Addon Cards Grid */}
              <div className="h-[calc(100vh-300px)] overflow-y-auto pr-1">
                <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
                  {filteredPresets.map((preset: any) => {
                    // Always allow adding, never show edit
                    return (
                      <AddonCard
                        key={preset.ID}
                        preset={preset}
                        isAdded={false}
                        onAdd={() => handleAddPreset(preset)}
                      />
                    );
                  })}
                </div>
              </div>
            </div>
          </PageWrapper>
        )}
        {/* Add/Edit Addon Modal (ensure both tabs can use it)*/}
        <AddonModal
          open={modalOpen}
          onOpenChange={setModalOpen}
          mode={modalMode}
          presetMetadata={modalPreset}
          initialValues={modalInitialValues as any}
          onSubmit={handleModalSubmit}
        />
      </AnimatePresence>
    </>
  );
}

// Helper to generate a key based on an addons id and options
function getPresetUniqueKey(preset: {
  type: string;
  instanceId: string;
  enabled: boolean;
  options: Record<string, any>;
}) {
  // dont include the unique instanceId
  return JSON.stringify({
    type: preset.type,
    enabled: preset.enabled,
    options: preset.options,
  });
}

// Sortable Addon Item for DND (handles both preset and custom addon)
function SortableAddonItem({
  preset,
  presetMetadata,
  onEdit,
  onRemove,
  onToggleEnabled,
}: {
  preset: any;
  presetMetadata: any;
  onEdit: () => void;
  onRemove: () => void;
  onToggleEnabled: (v: boolean) => void;
}) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: preset.instanceId,
  });
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  };
  return (
    <li ref={setNodeRef} style={style}>
      <div className="px-2.5 py-2 bg-[var(--background)] rounded-[--radius-md] border flex gap-2 sm:gap-3 relative">
        <div
          className="rounded-full w-6 h-auto bg-[--muted] md:bg-[--subtle] md:hover:bg-[--subtle-highlight] cursor-move flex-shrink-0"
          {...attributes}
          {...listeners}
        />
        <div className="flex items-center gap-2 sm:gap-3 flex-1 min-w-0">
          <div className="relative flex-shrink-0 h-8 w-8 hidden sm:block">
            {presetMetadata.ID === 'custom' ? (
              <PlusIcon className="w-full h-full object-contain" />
            ) : (
              <Image
                src={presetMetadata.LOGO}
                alt={presetMetadata.NAME}
                fill
                className="w-full h-full object-contain rounded-md"
              />
            )}
          </div>

          <p className="text-base line-clamp-1 truncate block">
            {preset.options.name}
          </p>
        </div>

        <div className="flex items-center gap-1 sm:gap-2">
          <Switch
            value={!!preset.enabled}
            onValueChange={onToggleEnabled}
            size="sm"
          />
          <IconButton
            className="rounded-full h-8 w-8 md:h-10 md:w-10"
            icon={<BiEdit />}
            intent="primary-subtle"
            onClick={onEdit}
          />
          <IconButton
            className="rounded-full h-8 w-8 md:h-10 md:w-10"
            icon={<BiTrash />}
            intent="alert-subtle"
            onClick={onRemove}
          />
        </div>
      </div>
    </li>
  );
}

// AddonCard component
function AddonCard({
  preset,
  isAdded,
  onAdd,
}: {
  preset: any;
  isAdded: boolean;
  onAdd: () => void;
}) {
  return (
    <div className="flex flex-col min-h-72 h-auto bg-[--background] border border-[--border] rounded-lg shadow-sm p-4 relative">
      {/* Top: Logo + Name/Description */}
      <div className="flex gap-4 items-start">
        {preset.ID === 'custom' ? (
          <div className="w-28 h-28 min-w-[7rem] min-h-[7rem] flex items-center justify-center rounded-lg bg-gray-900 text-[--brand] text-4xl">
            <PlusIcon className="w-12 h-12" />
          </div>
        ) : preset.LOGO ? (
          <img
            src={preset.LOGO}
            alt={preset.NAME}
            className="w-28 h-28 min-w-[7rem] min-h-[7rem] object-contain rounded-lg bg-gray-800"
          />
        ) : (
          <div className="w-28 h-28 min-w-[7rem] min-h-[7rem] flex items-center justify-center rounded-lg bg-gray-900 text-[--brand] text-4xl">
            <IoExtensionPuzzle className="w-15 h-15" />
          </div>
        )}
        <div className="flex flex-col min-w-0 flex-1">
          <div className="font-bold text-lg mb-1 truncate">{preset.NAME}</div>
          <div className="text-sm text-muted-foreground mb-2 line-clamp-3 whitespace-pre-line">
            <MarkdownLite>{preset.DESCRIPTION}</MarkdownLite>
          </div>
        </div>
      </div>
      {/* Tags Section */}
      <div className="flex flex-col gap-1 mt-2">
        <div className="flex flex-wrap gap-1 items-center min-h-[1.5rem]">
          {preset.SUPPORTED_SERVICES?.length > 0 && (
            <span className="font-semibold text-xs text-[--muted] mr-1">
              Services:
            </span>
          )}
          {preset.SUPPORTED_SERVICES?.map((sid: string) => {
            const service =
              constants.SERVICE_DETAILS[
                sid as keyof typeof constants.SERVICE_DETAILS
              ];
            return (
              <Tooltip
                key={sid}
                side="top"
                trigger={
                  <span className="bg-gray-800 text-xs px-2 py-0.5 rounded text-[--brand] font-mono">
                    {service?.shortName || sid}
                  </span>
                }
              >
                <span className="bg-gray-800 text-xs px-2 py-0.5 rounded text-[--brand] font-mono">
                  {service?.name || sid}
                </span>
              </Tooltip>
            );
          })}
        </div>
        <div className="flex flex-wrap gap-1 items-center min-h-[1.5rem]">
          {preset.SUPPORTED_RESOURCES?.length > 0 && (
            <span className="font-semibold text-xs text-[--muted] mr-1">
              Resources:
            </span>
          )}
          {preset.SUPPORTED_RESOURCES?.map((res: string) => (
            <span
              key={res}
              className="bg-gray-800 text-xs px-2 py-0.5 rounded text-blue-400 font-mono"
            >
              {res}
            </span>
          ))}
        </div>
        <div className="flex flex-wrap gap-1 items-center min-h-[1.5rem]">
          {preset.SUPPORTED_STREAM_TYPES?.length > 0 && (
            <span className="font-semibold text-xs text-[--muted] mr-1">
              Stream Types:
            </span>
          )}
          {preset.SUPPORTED_STREAM_TYPES?.map((type: string) => (
            <span
              key={type}
              className="bg-gray-800 text-xs px-2 py-0.5 rounded text-green-400 font-mono"
            >
              {type}
            </span>
          ))}
        </div>
      </div>
      {preset.DISABLED ? (
        <div className="mt-auto pt-3 flex items-end">
          <Alert
            intent="alert"
            className="w-full overflow-x-auto whitespace-nowrap"
            description={<MarkdownLite>{preset.DISABLED.reason}</MarkdownLite>}
          />
        </div>
      ) : (
        <div className="mt-auto pt-3 flex items-end">
          <Button size="md" className="w-full" onClick={onAdd}>
            Configure
          </Button>
        </div>
      )}
    </div>
  );
}

function AddonModal({
  open,
  onOpenChange,
  mode,
  presetMetadata,
  initialValues = {},
  onSubmit,
}: {
  open: boolean;
  onOpenChange: (v: boolean) => void;
  mode: 'add' | 'edit';
  presetMetadata?: any;
  initialValues?: Record<string, any>;
  onSubmit: (values: Record<string, any>) => void;
}) {
  const [values, setValues] = useState<Record<string, any>>(initialValues);
  useEffect(() => {
    if (open) {
      setValues(initialValues);
    } else {
      // when closing, delay the reset to allow the animation to finish
      // so that the user doesn't see the values being reset
      setTimeout(() => {
        setValues(initialValues);
      }, 150);
    }
  }, [open, initialValues]);
  const dynamicOptions: Option[] = presetMetadata?.OPTIONS || [];

  // Check if all required fields are filled
  const allRequiredFilled = dynamicOptions.every((opt: any) => {
    if (!opt.required) return true;
    const val = values.options?.[opt.id];
    // For booleans, false is valid; for others, check for empty string/null/undefined
    if (opt.type === 'boolean') return typeof val === 'boolean';
    return val !== undefined && val !== null && val !== '';
  });

  function handleFormSubmit(e: React.FormEvent) {
    e.preventDefault();

    for (const opt of dynamicOptions) {
      if (opt.constraints) {
        const val = values.options?.[opt.id];
        if (typeof val === 'string') {
          if (opt.constraints.min && val.length < opt.constraints.min) {
            toast.error(
              `${opt.name} must be at least ${opt.constraints.min} characters`
            );
            return false;
          }
          if (opt.constraints.max && val.length > opt.constraints.max) {
            toast.error(
              `${opt.name} must be at most ${opt.constraints.max} characters`
            );
            return false;
          }
        } else if (typeof val === 'number') {
          if (opt.constraints.min && val < opt.constraints.min) {
            toast.error(`${opt.name} must be at least ${opt.constraints.min}`);
            return false;
          }
          if (opt.constraints.max && val > opt.constraints.max) {
            toast.error(`${opt.name} must be at most ${opt.constraints.max}`);
            return false;
          }
        }
      }
    }
    if (allRequiredFilled) {
      onSubmit(values);
    } else {
      toast.error('Please fill in all required fields');
    }
  }

  return (
    <Modal
      open={open}
      onOpenChange={onOpenChange}
      title={
        mode === 'add'
          ? `Install ${presetMetadata?.NAME}`
          : `Edit ${presetMetadata?.NAME}`
      }
    >
      <form className="space-y-4" onSubmit={handleFormSubmit}>
        {dynamicOptions.map((opt: any) => (
          <div key={opt.id} className="mb-2">
            <TemplateOption
              option={opt}
              value={values.options?.[opt.id]}
              onChange={(v: any) =>
                setValues((val) => ({
                  ...val,
                  options: { ...val.options, [opt.id]: v },
                }))
              }
              disabled={false}
            />
          </div>
        ))}
        <Button
          className="w-full mt-2"
          type="submit"
          disabled={!allRequiredFilled}
        >
          {mode === 'add' ? 'Install' : 'Update'}
        </Button>
      </form>
    </Modal>
  );
}

function AddonFilterPopover({
  serviceOptions,
  streamTypeOptions,
  resourceOptions,
  serviceFilters,
  setServiceFilters,
  streamTypeFilters,
  setStreamTypeFilters,
  resourceFilters,
  setResourceFilters,
  children,
}: any) {
  const [open, setOpen] = useState(false);
  return (
    <Popover
      open={open}
      onOpenChange={setOpen}
      trigger={children}
      modal={false}
      className="p-4 max-w-full w-full"
    >
      <div className="flex flex-col gap-3">
        <div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
          <div className="flex flex-col max-h-60 overflow-y-auto">
            <div className="mb-2 font-semibold text-sm text-muted-foreground">
              Services
            </div>
            {serviceOptions.map((opt: any) => (
              <div
                key={opt.value}
                className="flex items-center gap-2 mb-2 last:mb-0"
              >
                <Switch
                  value={serviceFilters.includes(opt.value)}
                  onValueChange={(checked: boolean) => {
                    setServiceFilters((prev: string[]) =>
                      checked
                        ? [...prev, opt.value]
                        : prev.filter((v) => v !== opt.value)
                    );
                  }}
                  size="sm"
                />
                <span className="text-xs">{opt.label}</span>
              </div>
            ))}
          </div>
          <div className="flex flex-col max-h-60 overflow-y-auto">
            <div className="mb-2 font-semibold text-sm text-muted-foreground">
              Stream Types
            </div>
            {streamTypeOptions.map((opt: any) => (
              <div
                key={opt.value}
                className="flex items-center gap-2 mb-2 last:mb-0"
              >
                <Switch
                  value={streamTypeFilters.includes(opt.value)}
                  onValueChange={(checked: boolean) => {
                    setStreamTypeFilters((prev: string[]) =>
                      checked
                        ? [...prev, opt.value]
                        : prev.filter((v) => v !== opt.value)
                    );
                  }}
                  size="sm"
                />
                <span className="text-xs">{opt.label}</span>
              </div>
            ))}
          </div>
          <div className="flex flex-col max-h-60 overflow-y-auto">
            <div className="mb-2 font-semibold text-sm text-muted-foreground">
              Resources
            </div>
            {resourceOptions.map((opt: any) => (
              <div
                key={opt.value}
                className="flex items-center gap-2 mb-2 last:mb-0"
              >
                <Switch
                  value={resourceFilters.includes(opt.value)}
                  onValueChange={(checked: boolean) => {
                    setResourceFilters((prev: string[]) =>
                      checked
                        ? [...prev, opt.value]
                        : prev.filter((v) => v !== opt.value)
                    );
                  }}
                  size="sm"
                />
                <span className="text-xs">{opt.label}</span>
              </div>
            ))}
          </div>
        </div>
        <Button className="w-full mt-2" onClick={() => setOpen(false)}>
          Done
        </Button>
      </div>
    </Popover>
  );
}

function AddonGroupCard() {
  const { userData, setUserData } = useUserData();

  // Helper function to get presets that are not in any group except the current one
  const getAvailablePresets = (currentGroupIndex: number) => {
    const presetsInOtherGroups = new Set(
      userData.groups?.flatMap((group, idx) =>
        idx !== currentGroupIndex ? group.addons : []
      ) || []
    );

    return userData.presets
      .filter((preset) => {
        return !presetsInOtherGroups.has(preset.instanceId);
      })
      .map((preset) => ({
        label: preset.options.name,
        value: preset.instanceId,
        textValue: preset.options.name,
      }));
  };

  const updateGroup = (
    index: number,
    updates: Partial<{ addons: string[]; condition: string }>
  ) => {
    setUserData((prev) => {
      // Initialize groups array if it doesn't exist
      const currentGroups = prev.groups || [];

      // Create a new array with all existing groups
      const newGroups = [...currentGroups];

      // Update the specific group with new values, preserving other fields
      newGroups[index] = {
        ...newGroups[index],
        ...updates,
      };

      if (index === 0) {
        // set condition for first group to true
        newGroups[index].condition = 'true';
      }

      return {
        ...prev,
        groups: newGroups,
      };
    });
  };

  return (
    <SettingsCard
      title="Groups"
      //       description="Optionally assign your addons to groups. Streams are only fetched from your first group initially,
      // and only if a certain condition is met, will streams be fetched from the next group, and so on. Leaving this blank will mean streams are
      // fetched from all addons. For a guide and a reference to the group system,"
    >
      <div className="text-sm text-[--muted] mb-2">
        Optionally assign your addons to groups. Streams are only fetched from
        your first group initially, and only if a certain condition is met, will
        streams be fetched from the next group, and so on. Leaving this blank
        will mean streams are fetched from all addons. Check the{' '}
        <a
          href="https://github.com/Viren070/AIOStreams/wiki/Groups"
          target="_blank"
          rel="noopener noreferrer"
          className="text-[--brand] hover:text-[--brand]/80 hover:underline"
        >
          wiki
        </a>
        for a detailed guide to using groups.
      </div>
      {(userData.groups || []).map((group, index) => (
        <div key={index} className="flex gap-2">
          <div className="flex-1 flex gap-2">
            <div className="flex-1">
              <Combobox
                multiple
                value={group.addons}
                options={getAvailablePresets(index)}
                emptyMessage="You haven't installed any addons yet or they are already in a group"
                label="Addons"
                placeholder="Select addons"
                onValueChange={(value) => {
                  updateGroup(index, { addons: value });
                }}
              />
            </div>
            <div className="flex-1">
              <TextInput
                value={index === 0 ? 'true' : group.condition}
                disabled={index === 0}
                label="Condition"
                placeholder="Enter condition"
                onValueChange={(value) => {
                  updateGroup(index, { condition: value });
                }}
              />
            </div>
          </div>
          <IconButton
            size="sm"
            rounded
            icon={<FaRegTrashAlt />}
            intent="alert-subtle"
            onClick={() => {
              setUserData((prev) => {
                const newGroups = [...(prev.groups || [])];
                newGroups.splice(index, 1);
                return {
                  ...prev,
                  groups: newGroups,
                };
              });
            }}
          />
        </div>
      ))}
      <div className="mt-2 flex gap-2 items-center">
        <IconButton
          rounded
          size="sm"
          intent="primary-subtle"
          icon={<FaPlus />}
          onClick={() => {
            setUserData((prev) => {
              const currentGroups = prev.groups || [];
              return {
                ...prev,
                groups: [...currentGroups, { addons: [], condition: '' }],
              };
            });
          }}
        />
      </div>
    </SettingsCard>
  );
}

function CatalogSettingsCard() {
  const { userData, setUserData } = useUserData();
  const [loading, setLoading] = useState(false);

  const fetchCatalogs = async () => {
    setLoading(true);
    try {
      const response = await UserConfigAPI.getCatalogs(userData);
      if (response.success && response.data) {
        setUserData((prev) => {
          const existingMods = prev.catalogModifications || [];
          const existingIds = new Set(
            existingMods.map((mod) => `${mod.id}-${mod.type}`)
          );

          // first we need to handle existing modifications, to ensure that they keep their order
          const modifications = existingMods.map((eMod) => {
            const nMod = response.data!.find(
              (c) => c.id === eMod.id && c.type === eMod.type
            );
            if (nMod) {
              return {
                // keep all the existing attributes, except addonName, type, hideable
                ...eMod,
                addonName: nMod.addonName,
                type: nMod.type,
                hideable: nMod.hideable,
              };
            }
            return eMod;
          });

          // Add new catalogs at the bottom
          response.data!.forEach((catalog) => {
            if (!existingIds.has(`${catalog.id}-${catalog.type}`)) {
              modifications.push({
                id: catalog.id,
                name: catalog.name,
                type: catalog.type,
                enabled: true,
                shuffle: false,
                rpdb: userData.rpdbApiKey ? true : false,
                hideable: catalog.hideable,
                addonName: catalog.addonName,
              });
            }
          });

          // Filter out modifications for catalogs that no longer exist
          const newCatalogIds = new Set(
            response.data!.map((c) => `${c.id}-${c.type}`)
          );
          const filteredMods = modifications.filter((mod) =>
            newCatalogIds.has(`${mod.id}-${mod.type}`)
          );

          return {
            ...prev,
            catalogModifications: filteredMods,
          };
        });
        toast.success('Catalogs fetched successfully');
      } else {
        toast.error(response.error?.message || 'Failed to fetch catalogs');
      }
    } catch (error) {
      toast.error('Failed to fetch catalogs');
    } finally {
      setLoading(false);
    }
  };

  const capitalise = (str: string | undefined) => {
    if (!str) return '';
    return str.charAt(0).toUpperCase() + str.slice(1);
  };

  // DND handlers
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(TouchSensor, {
      activationConstraint: {
        delay: 150,
        tolerance: 8,
      },
    })
  );

  const [isDragging, setIsDragging] = useState(false);

  useEffect(() => {
    function preventTouchMove(e: TouchEvent) {
      if (isDragging) {
        e.preventDefault();
      }
    }

    function handleDragEnd() {
      setIsDragging(false);
    }

    if (isDragging) {
      document.body.addEventListener('touchmove', preventTouchMove, {
        passive: false,
      });
      document.addEventListener('pointerup', handleDragEnd);
      document.addEventListener('touchend', handleDragEnd);
    } else {
      document.body.removeEventListener('touchmove', preventTouchMove);
    }
    return () => {
      document.body.removeEventListener('touchmove', preventTouchMove);
      document.removeEventListener('pointerup', handleDragEnd);
      document.removeEventListener('touchend', handleDragEnd);
    };
  }, [isDragging]);

  const handleDragEnd = (event: any) => {
    const { active, over } = event;
    if (!over) return;
    if (active.id !== over.id) {
      setUserData((prev) => {
        const oldIndex = prev.catalogModifications?.findIndex(
          (c) => `${c.id}-${c.type}` === active.id
        );
        const newIndex = prev.catalogModifications?.findIndex(
          (c) => `${c.id}-${c.type}` === over.id
        );
        if (
          oldIndex === undefined ||
          newIndex === undefined ||
          !prev.catalogModifications
        )
          return prev;
        return {
          ...prev,
          catalogModifications: arrayMove(
            prev.catalogModifications,
            oldIndex,
            newIndex
          ),
        };
      });
    }
    setIsDragging(false);
  };

  const handleDragStart = () => {
    setIsDragging(true);
  };

  const confirmRefreshCatalogs = useConfirmationDialog({
    title: 'Refresh Catalogs',
    description:
      'Are you sure you want to refresh the catalogs? This will remove any catalogs that are no longer available',
    onConfirm: () => {
      fetchCatalogs();
    },
  });

  return (
    <div className="rounded-[--radius] border bg-[--paper] shadow-sm p-4">
      <div className="flex items-center justify-between mb-4">
        <div>
          <h3 className="font-semibold text-xl text-[--muted] transition-colors hover:text-[--brand]">
            Catalogs
          </h3>
          <p className="text-[--muted] text-sm">
            Rename, Reorder, and toggle your catalogs, and apply modifications
            like RPDB posters and shuffling. If you reorder the addons, you need
            to reinstall the addon
          </p>
        </div>
        <IconButton
          size="sm"
          intent="warning-subtle"
          icon={<MdRefresh />}
          rounded
          onClick={() => {
            if (userData.catalogModifications?.length) {
              confirmRefreshCatalogs.open();
            } else {
              fetchCatalogs();
            }
          }}
          loading={loading}
        />
      </div>

      {!userData.catalogModifications?.length && (
        <p className="text-[--muted] text-base text-center my-8">
          Your addons don't have any catalogs... or you haven't fetched them yet
          :/
        </p>
      )}

      {userData.catalogModifications &&
        userData.catalogModifications.length > 0 && (
          <DndContext
            modifiers={[restrictToVerticalAxis]}
            onDragEnd={handleDragEnd}
            onDragStart={handleDragStart}
            sensors={sensors}
          >
            <SortableContext
              items={(userData.catalogModifications || []).map(
                (c) => `${c.id}-${c.type}`
              )}
              strategy={verticalListSortingStrategy}
            >
              <ul className="space-y-2">
                {(userData.catalogModifications || []).map(
                  (catalog: CatalogModification) => (
                    <SortableCatalogItem
                      key={`${catalog.id}-${catalog.type}`}
                      catalog={catalog}
                      onToggleEnabled={(enabled) => {
                        setUserData((prev) => ({
                          ...prev,
                          catalogModifications: prev.catalogModifications?.map(
                            (c) =>
                              c.id === catalog.id && c.type === catalog.type
                                ? { ...c, enabled }
                                : c
                          ),
                        }));
                      }}
                      capitalise={capitalise}
                    />
                  )
                )}
              </ul>
            </SortableContext>
          </DndContext>
        )}

      <ConfirmationDialog {...confirmRefreshCatalogs} />
    </div>
  );
}

// Add the SortableCatalogItem component
function SortableCatalogItem({
  catalog,
  onToggleEnabled,
  capitalise,
}: {
  catalog: CatalogModification;
  onToggleEnabled: (enabled: boolean) => void;
  capitalise: (str: string | undefined) => string;
}) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: `${catalog.id}-${catalog.type}`,
  });

  const { setUserData } = useUserData();

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  };

  const moveToTop = () => {
    setUserData((prev) => {
      if (!prev.catalogModifications) return prev;
      const index = prev.catalogModifications.findIndex(
        (c) => c.id === catalog.id && c.type === catalog.type
      );
      if (index <= 0) return prev;
      const newMods = [...prev.catalogModifications];
      const [item] = newMods.splice(index, 1);
      newMods.unshift(item);
      return { ...prev, catalogModifications: newMods };
    });
  };

  const moveToBottom = () => {
    setUserData((prev) => {
      if (!prev.catalogModifications) return prev;
      const index = prev.catalogModifications.findIndex(
        (c) => c.id === catalog.id && c.type === catalog.type
      );
      if (index === prev.catalogModifications.length - 1) return prev;
      const newMods = [...prev.catalogModifications];
      const [item] = newMods.splice(index, 1);
      newMods.push(item);
      return { ...prev, catalogModifications: newMods };
    });
  };

  const [modalOpen, setModalOpen] = useState(false);
  const [newName, setNewName] = useState(catalog.name || '');
  const [newType, setNewType] = useState(
    catalog.overrideType || catalog.type || ''
  );
  const dynamicIconSize = `text-xl h-8 w-8 lg:text-2xl lg:h-10 lg:w-10`;

  const handleNameAndTypeEdit = () => {
    setUserData((prev) => ({
      ...prev,
      catalogModifications: prev.catalogModifications?.map((c) =>
        c.id === catalog.id && c.type === catalog.type
          ? {
              ...c,
              name: newName,
              overrideType: newType,
            }
          : c
      ),
    }));
    setModalOpen(false);
  };

  return (
    <li ref={setNodeRef} style={style}>
      <div className="relative px-2.5 py-2 bg-[var(--background)] rounded-[--radius-md] border overflow-hidden">
        {/* Full-height drag handle - rounded vertical oval with spacing */}
        <div
          className="absolute top-2 bottom-2 left-2 w-5 bg-[var(--muted)] md:bg-[var(--subtle)] md:hover:bg-[var(--subtle-highlight)] cursor-move flex-shrink-0 rounded-full"
          {...attributes}
          {...listeners}
        />

        {/* Content wrapper */}
        <div className="pl-8 pr-3 py-3">
          {/* Header section */}
          <div className="mb-4 md:mb-6 md:pr-40">
            <div className="flex items-center gap-2 mb-1">
              <h3 className="text-sm md:text-base font-medium line-clamp-1 truncate text-ellipsis">
                {catalog.addonName} - {catalog.name ?? catalog.id}
              </h3>
              <IconButton
                className="rounded-full h-5 w-5 md:h-6 md:w-6 flex-shrink-0"
                icon={<BiEdit />}
                intent="primary-subtle"
                onClick={() => setModalOpen(true)}
              />
            </div>
            <p className="text-xs md:text-sm text-[var(--muted-foreground)] capitalize mb-2 md:mb-0">
              {catalog.overrideType !== undefined &&
              catalog.overrideType !== catalog.type
                ? `${catalog.overrideType} (${catalog.type})`
                : catalog.type}
            </p>

            {/* Mobile Controls Row - only visible on small screens */}
            <div className="flex md:hidden items-center justify-between">
              {/* Position controls - aligned left */}
              <div className="flex items-center gap-1">
                <IconButton
                  rounded
                  className={dynamicIconSize}
                  icon={<LuChevronsUp />}
                  intent="primary-subtle"
                  onClick={moveToTop}
                  title="Move to top"
                />
                <IconButton
                  rounded
                  className={dynamicIconSize}
                  icon={<LuChevronsDown />}
                  intent="primary-subtle"
                  onClick={moveToBottom}
                  title="Move to bottom"
                />
              </div>

              {/* Enable/disable toggle - aligned right */}
              <Switch
                value={catalog.enabled ?? true}
                onValueChange={onToggleEnabled}
                moreHelp="Enable or disable this catalog from being used"
              />
            </div>

            {/* Desktop Controls - only visible on medium screens and up */}
            <div className="hidden md:flex items-center justify-end gap-2 absolute top-4 right-4">
              <div className="flex items-center gap-1">
                <IconButton
                  rounded
                  icon={<LuChevronsUp />}
                  intent="primary-subtle"
                  onClick={moveToTop}
                  title="Move to top"
                />
                <IconButton
                  rounded
                  icon={<LuChevronsDown />}
                  intent="primary-subtle"
                  onClick={moveToBottom}
                  title="Move to bottom"
                />
              </div>
              <Switch
                value={catalog.enabled ?? true}
                onValueChange={onToggleEnabled}
                moreHelp="Enable or disable this catalog from being used"
              />
            </div>
          </div>

          {/* Settings section */}
          <Accordion type="single" collapsible>
            <AccordionItem value="settings">
              <AccordionTrigger>
                <div className="flex items-center justify-between w-full">
                  <h4 className="text-xs font-medium text-[var(--muted-foreground)] uppercase tracking-wide">
                    Settings
                  </h4>

                  {/* Active modifier icons */}
                  <div className="flex items-center gap-2 mr-2">
                    <Tooltip
                      trigger={
                        <IconButton
                          className={dynamicIconSize}
                          icon={
                            catalog.shuffle ? (
                              <FaShuffle />
                            ) : (
                              <FaArrowRightLong />
                            )
                          }
                          intent="primary-subtle"
                          rounded
                          onClick={(e) => {
                            e.stopPropagation();
                            setUserData((prev) => ({
                              ...prev,
                              catalogModifications:
                                prev.catalogModifications?.map((c) =>
                                  c.id === catalog.id && c.type === catalog.type
                                    ? { ...c, shuffle: !c.shuffle }
                                    : c
                                ),
                            }));
                          }}
                        />
                      }
                    >
                      Shuffle
                    </Tooltip>
                    <Tooltip
                      trigger={
                        <IconButton
                          className={dynamicIconSize}
                          icon={catalog.rpdb ? <PiStarFill /> : <PiStarBold />}
                          intent="primary-subtle"
                          rounded
                          onClick={(e) => {
                            e.stopPropagation();
                            setUserData((prev) => ({
                              ...prev,
                              catalogModifications:
                                prev.catalogModifications?.map((c) =>
                                  c.id === catalog.id && c.type === catalog.type
                                    ? { ...c, rpdb: !c.rpdb }
                                    : c
                                ),
                            }));
                          }}
                        />
                      }
                    >
                      RPDB
                    </Tooltip>

                    {catalog.hideable && (
                      <Tooltip
                        trigger={
                          <IconButton
                            className={dynamicIconSize}
                            icon={
                              catalog.onlyOnDiscover ? (
                                <TbSmartHomeOff />
                              ) : (
                                <TbSmartHome />
                              )
                            }
                            intent="primary-subtle"
                            rounded
                            onClick={(e) => {
                              e.stopPropagation();
                              setUserData((prev) => ({
                                ...prev,
                                catalogModifications:
                                  prev.catalogModifications?.map((c) =>
                                    c.id === catalog.id &&
                                    c.type === catalog.type
                                      ? {
                                          ...c,
                                          onlyOnDiscover: !c.onlyOnDiscover,
                                        }
                                      : c
                                  ),
                              }));
                            }}
                          />
                        }
                      >
                        Discover Only
                      </Tooltip>
                    )}
                  </div>
                </div>
              </AccordionTrigger>
              <AccordionContent>
                <div className="space-y-4">
                  {/* Large screens: horizontal layout, Medium and below: vertical layout */}
                  <div className="flex flex-col gap-4">
                    <Switch
                      label="Shuffle"
                      help="Randomize the order of catalog items on each request"
                      side="right"
                      value={catalog.shuffle ?? false}
                      onValueChange={(shuffle) => {
                        setUserData((prev) => ({
                          ...prev,
                          catalogModifications: prev.catalogModifications?.map(
                            (c) =>
                              c.id === catalog.id && c.type === catalog.type
                                ? { ...c, shuffle }
                                : c
                          ),
                        }));
                      }}
                    />

                    <div className="flex flex-col md:flex-row md:items-center gap-2 -mx-2 px-2 hover:bg-[var(--subtle-highlight)] rounded-md">
                      <div className="flex-1 py-2">
                        <label className="text-sm font-medium">
                          Persist Shuffle For
                        </label>
                        <p className="text-xs text-[--muted]">
                          The amount of hours to keep a given shuffled catalog
                          order before shuffling again. Defaults to 0 (Shuffle
                          on every request).
                        </p>
                      </div>
                      <div className="w-full md:w-32 py-2">
                        <NumberInput
                          value={catalog.persistShuffleFor ?? 0}
                          min={0}
                          step={1}
                          max={24}
                          onValueChange={(value) => {
                            setUserData((prev) => ({
                              ...prev,
                              catalogModifications:
                                prev.catalogModifications?.map((c) =>
                                  c.id === catalog.id && c.type === catalog.type
                                    ? { ...c, persistShuffleFor: value }
                                    : c
                                ),
                            }));
                          }}
                        />
                      </div>
                    </div>

                    <Switch
                      label="RPDB"
                      help="Replace movie/show posters with RPDB posters when supported"
                      side="right"
                      value={catalog.rpdb ?? false}
                      onValueChange={(rpdb) => {
                        setUserData((prev) => ({
                          ...prev,
                          catalogModifications: prev.catalogModifications?.map(
                            (c) =>
                              c.id === catalog.id && c.type === catalog.type
                                ? { ...c, rpdb }
                                : c
                          ),
                        }));
                      }}
                    />

                    {catalog.hideable && (
                      <Switch
                        label="Discover Only"
                        help="Hide this catalog from the home page and only show it on the Discover page"
                        moreHelp="This can potentially break the catalog!"
                        side="right"
                        value={catalog.onlyOnDiscover ?? false}
                        onValueChange={(onlyOnDiscover) => {
                          setUserData((prev) => ({
                            ...prev,
                            catalogModifications:
                              prev.catalogModifications?.map((c) =>
                                c.id === catalog.id && c.type === catalog.type
                                  ? { ...c, onlyOnDiscover }
                                  : c
                              ),
                          }));
                        }}
                      />
                    )}
                  </div>
                </div>
              </AccordionContent>
            </AccordionItem>
          </Accordion>
        </div>
      </div>

      {/* Name edit modal */}
      <Modal
        open={modalOpen}
        onOpenChange={setModalOpen}
        title="Edit Catalog Name"
      >
        <form
          className="space-y-4"
          onSubmit={(e) => {
            e.preventDefault();
            handleNameAndTypeEdit();
          }}
        >
          <TextInput
            label="Name"
            placeholder="Enter catalog name"
            value={newName}
            onValueChange={setNewName}
          />

          <TextInput
            label="Type"
            placeholder="Enter catalog type"
            value={newType}
            onValueChange={setNewType}
            required
            help="Override the type of the catalog. This can break the catalog and its behaviour."
          />

          <Button className="w-full" type="submit">
            Save Changes
          </Button>
        </form>
      </Modal>
    </li>
  );
}
