import {
  ButtonHTMLAttributes,
  HTMLAttributes,
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useState,
} from 'react';
import { cn } from '../lib/cn';

type AccordionType = 'single' | 'multiple';

interface AccordionCtx {
  type: AccordionType;
  openSet: Set<string>;
  toggle: (id: string) => void;
}

const RootCtx = createContext<AccordionCtx | null>(null);

interface ItemCtx {
  id: string;
  open: boolean;
}
const ItemContext = createContext<ItemCtx | null>(null);

function useRoot() {
  const ctx = useContext(RootCtx);
  if (!ctx) throw new Error('Accordion.* must be inside <Accordion>');
  return ctx;
}

function useItem() {
  const ctx = useContext(ItemContext);
  if (!ctx) throw new Error('Accordion.Trigger/Content must be inside <Accordion.Item>');
  return ctx;
}

export interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
  type?: AccordionType;
  defaultValue?: string | string[];
  value?: string | string[];
  onValueChange?: (v: string | string[]) => void;
  children?: ReactNode;
}

function toSet(v: string | string[] | undefined): Set<string> {
  if (!v) return new Set();
  return new Set(Array.isArray(v) ? v : [v]);
}

export function Accordion({
  type = 'single',
  defaultValue,
  value,
  onValueChange,
  className,
  children,
  ...rest
}: AccordionProps) {
  const isControlled = value !== undefined;
  const [uncontrolled, setUncontrolled] = useState<Set<string>>(() => toSet(defaultValue));
  const openSet = isControlled ? toSet(value) : uncontrolled;

  const emit = useCallback(
    (next: Set<string>) => {
      if (type === 'single') {
        const first = next.values().next().value ?? '';
        onValueChange?.(first);
      } else {
        onValueChange?.(Array.from(next));
      }
    },
    [type, onValueChange],
  );

  const toggle = useCallback(
    (id: string) => {
      const next = new Set(openSet);
      if (next.has(id)) {
        next.delete(id);
      } else {
        if (type === 'single') next.clear();
        next.add(id);
      }
      if (!isControlled) setUncontrolled(next);
      emit(next);
    },
    [openSet, type, isControlled, emit],
  );

  return (
    <RootCtx.Provider value={{ type, openSet, toggle }}>
      <div
        className={cn('rounded-xl border border-border bg-surface divide-y divide-rule', className)}
        {...rest}
      >
        {children}
      </div>
    </RootCtx.Provider>
  );
}

interface ItemProps extends HTMLAttributes<HTMLDivElement> {
  value: string;
  children?: ReactNode;
}

Accordion.Item = function AccordionItem({ value, className, children, ...rest }: ItemProps) {
  const { openSet } = useRoot();
  const open = openSet.has(value);
  return (
    <ItemContext.Provider value={{ id: value, open }}>
      <div className={cn('px-4', className)} data-state={open ? 'open' : 'closed'} {...rest}>
        {children}
      </div>
    </ItemContext.Provider>
  );
};

Accordion.Trigger = function AccordionTrigger({
  className,
  children,
  onClick,
  ...rest
}: ButtonHTMLAttributes<HTMLButtonElement>) {
  const { toggle } = useRoot();
  const { id, open } = useItem();
  return (
    <button
      type="button"
      aria-expanded={open}
      onClick={(e) => {
        toggle(id);
        onClick?.(e);
      }}
      className={cn(
        'flex w-full items-center justify-between gap-3 py-3 text-left text-sm font-medium text-text hover:text-primary transition-colors',
        className,
      )}
      {...rest}
    >
      <span>{children}</span>
      <svg
        width="14"
        height="14"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth="2.5"
        className={cn('shrink-0 transition-transform text-text-3', open && 'rotate-180')}
        aria-hidden="true"
      >
        <polyline points="6 9 12 15 18 9" />
      </svg>
    </button>
  );
};

Accordion.Content = function AccordionContent({
  className,
  children,
  ...rest
}: HTMLAttributes<HTMLDivElement>) {
  const { open } = useItem();
  if (!open) return null;
  return (
    <div className={cn('pb-3 text-sm text-text-2', className)} {...rest}>
      {children}
    </div>
  );
};
