import * as Ariakit from "@ariakit/react";
import {
  XMarkIcon,
  PlusIcon,
  CubeIcon,
  MagnifyingGlassIcon,
  ChevronDownIcon,
} from "@heroicons/react/20/solid";
import { useCallback, useMemo, useRef, useState } from "react";
import { CheckboxIndicator } from "~/components/primitives/CheckboxIndicator";
import { cn } from "~/utils/cn";
import { matchSorter } from "match-sorter";
import { ShortcutKey } from "~/components/primitives/ShortcutKey";

const pillColors = [
  "bg-green-800/40 border-green-600/50",
  "bg-teal-800/40 border-teal-600/50",
  "bg-blue-800/40 border-blue-600/50",
  "bg-indigo-800/40 border-indigo-600/50",
  "bg-violet-800/40 border-violet-600/50",
  "bg-purple-800/40 border-purple-600/50",
  "bg-fuchsia-800/40 border-fuchsia-600/50",
  "bg-pink-800/40 border-pink-600/50",
  "bg-rose-800/40 border-rose-600/50",
  "bg-orange-800/40 border-orange-600/50",
  "bg-amber-800/40 border-amber-600/50",
  "bg-yellow-800/40 border-yellow-600/50",
  "bg-lime-800/40 border-lime-600/50",
  "bg-emerald-800/40 border-emerald-600/50",
  "bg-cyan-800/40 border-cyan-600/50",
  "bg-sky-800/40 border-sky-600/50",
];

function getPillColor(value: string): string {
  let hash = 0;
  for (let i = 0; i < value.length; i++) {
    hash = (hash << 5) - hash + value.charCodeAt(i);
    hash |= 0;
  }
  return pillColors[Math.abs(hash) % pillColors.length];
}

export const TECHNOLOGY_OPTIONS = [
  "Airflow",
  "Angular",
  "Anthropic",
  "Astro",
  "Auth0",
  "AWS",
  "AWS SQS",
  "Azure",
  "BigQuery",
  "BullMQ",
  "Bun",
  "Cassandra",
  "Celery",
  "ClickHouse",
  "Clerk",
  "Cloudflare",
  "CockroachDB",
  "Cohere",
  "Convex",
  "Databricks",
  "Datadog",
  "DeepSeek",
  "Deno",
  "DigitalOcean",
  "Django",
  "Docker",
  "Drizzle",
  "DynamoDB",
  "Elasticsearch",
  "Electron",
  "Elevenlabs",
  "Expo",
  "Express",
  "FastAPI",
  "Fastify",
  "Firebase",
  "Flask",
  "Fly.io",
  "Gatsby",
  "GCP",
  "Go",
  "Google Cloud Tasks",
  "Google Gemini",
  "GraphQL",
  "Groq",
  "Heroku",
  "Hono",
  "htmx",
  "Hugging Face",
  "Inngest",
  "Kafka",
  "Kubernetes",
  "LangChain",
  "Laravel",
  "LlamaIndex",
  "MariaDB",
  "Midjourney",
  "Mistral",
  "MongoDB",
  "Mongoose",
  "MySQL",
  "Neo4j",
  "Neon",
  "Nest.js",
  "Netlify",
  "Next.js",
  "Node.js",
  "Nuxt",
  "Ollama",
  "OpenAI",
  "Perplexity",
  "PHP",
  "Pinecone",
  "PlanetScale",
  "Python",
  "PostHog",
  "PostgreSQL",
  "Prisma",
  "Pulumi",
  "RabbitMQ",
  "Railway",
  "React",
  "React Native",
  "Redis",
  "Redshift",
  "Remix",
  "Render",
  "Replicate",
  "Resend",
  "Ruby on Rails",
  "Rust",
  "SendGrid",
  "Sentry",
  "Sidekiq",
  "Snowflake",
  "Solid.js",
  "Spring Boot",
  "SQLite",
  "Stability AI",
  "Stripe",
  "Supabase",
  "Svelte",
  "SvelteKit",
  "Tailwind CSS",
  "Temporal",
  "Terraform",
  "Together AI",
  "tRPC",
  "Turso",
  "Twilio",
  "TypeORM",
  "Upstash",
  "Vercel",
  "Vercel AI SDK",
  "Vite",
  "Vue",
  "Weaviate",
] as const;

type TechnologyPickerProps = {
  value: string[];
  onChange: (value: string[]) => void;
  customValues: string[];
  onCustomValuesChange: (values: string[]) => void;
};

export function TechnologyPicker({
  value,
  onChange,
  customValues,
  onCustomValuesChange,
}: TechnologyPickerProps) {
  const [open, setOpen] = useState(false);
  const [searchValue, setSearchValue] = useState("");
  const [otherInputValue, setOtherInputValue] = useState("");
  const [showOtherInput, setShowOtherInput] = useState(false);
  const otherInputRef = useRef<HTMLInputElement>(null);

  const allSelected = useMemo(() => [...value, ...customValues], [value, customValues]);

  const filteredOptions = useMemo(() => {
    if (!searchValue) return TECHNOLOGY_OPTIONS;
    return matchSorter([...TECHNOLOGY_OPTIONS], searchValue);
  }, [searchValue]);

  const toggleOption = useCallback(
    (option: string) => {
      if (value.includes(option)) {
        onChange(value.filter((v) => v !== option));
      } else {
        onChange([...value, option]);
      }
    },
    [value, onChange]
  );

  const removeItem = useCallback(
    (item: string) => {
      if (value.includes(item)) {
        onChange(value.filter((v) => v !== item));
      } else {
        onCustomValuesChange(customValues.filter((v) => v !== item));
      }
    },
    [value, onChange, customValues, onCustomValuesChange]
  );

  const addCustomValue = useCallback(() => {
    const trimmed = otherInputValue.trim();
    if (!trimmed) return;

    const matchedOption = TECHNOLOGY_OPTIONS.find(
      (opt) => opt.toLowerCase() === trimmed.toLowerCase()
    );

    if (matchedOption) {
      if (!value.includes(matchedOption)) {
        onChange([...value, matchedOption]);
      }
    } else if (!customValues.includes(trimmed) && !value.includes(trimmed)) {
      onCustomValuesChange([...customValues, trimmed]);
    }

    setOtherInputValue("");
  }, [otherInputValue, customValues, onCustomValuesChange, value, onChange]);

  const handleOtherKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.key === "Enter") {
        e.preventDefault();
        e.stopPropagation();
        addCustomValue();
      }
    },
    [addCustomValue]
  );

  return (
    <div className="flex flex-col gap-2">
      {allSelected.length > 0 && (
        <div className="flex flex-wrap gap-1.5">
          {allSelected.map((item) => (
            <span
              key={item}
              className={cn(
                "flex items-center gap-1 rounded-sm border py-0.5 pl-1.5 pr-1 text-xs font-medium text-text-bright",
                getPillColor(item)
              )}
            >
              {item}
              <button
                type="button"
                onClick={() => removeItem(item)}
                aria-label={`Remove ${item}`}
                className="ml-0.5 flex items-center transition hover:text-text-bright/70"
              >
                <XMarkIcon className="size-3.5" />
              </button>
            </span>
          ))}
        </div>
      )}

      <Ariakit.ComboboxProvider
        resetValueOnHide
        setValue={(val) => {
          setSearchValue(val);
        }}
      >
        <Ariakit.SelectProvider
          open={open}
          setOpen={setOpen}
          value={value}
          setValue={(v) => {
            if (Array.isArray(v)) {
              onChange(v);
            }
          }}
          virtualFocus
        >
          <Ariakit.Select className="group flex h-8 w-full items-center rounded bg-background-hover pl-2 pr-2.5 text-sm text-text-dimmed ring-border-bright transition focus-custom hover:bg-secondary hover:ring-1">
            <div className="flex grow items-center">
              <CubeIcon className="mr-1.5 size-4 flex-none text-text-dimmed" />
              <span>Select your technologies…</span>
            </div>
            <ChevronDownIcon className="size-4 flex-none text-text-dimmed transition group-hover:text-text-bright" />
          </Ariakit.Select>

          <Ariakit.SelectPopover
            gutter={5}
            unmountOnHide
            className={cn(
              "z-50 flex flex-col overflow-clip rounded border border-grid-bright bg-background-bright shadow-md outline-hidden animate-in fade-in-40",
              "min-w-[max(180px,var(--popover-anchor-width))]",
              "max-w-[min(480px,var(--popover-available-width))]",
              "max-h-[min(400px,var(--popover-available-height))]"
            )}
          >
            <div className="flex h-9 w-full flex-none items-center gap-2 border-b border-grid-dimmed bg-transparent px-3 text-xs text-text-dimmed outline-hidden">
              <MagnifyingGlassIcon className="size-3.5 flex-none text-text-dimmed" />
              <Ariakit.Combobox
                autoSelect
                placeholder="Search technologies…"
                className="flex-1 bg-transparent text-xs text-text-dimmed outline-hidden"
              />
            </div>

            <Ariakit.ComboboxList className="overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control focus-custom">
              {filteredOptions.map((option) => (
                <Ariakit.ComboboxItem
                  key={option}
                  className="group cursor-pointer px-1 pt-1 text-2sm text-text-dimmed focus-custom last:pb-1"
                  onClick={(e) => {
                    e.preventDefault();
                    toggleOption(option);
                  }}
                >
                  <div className="flex h-8 w-full items-center gap-2 rounded-sm px-2 group-data-[active-item=true]:bg-tertiary hover:bg-tertiary">
                    <CheckboxIndicator checked={value.includes(option)} />
                    <span className="grow truncate text-text-bright">{option}</span>
                  </div>
                </Ariakit.ComboboxItem>
              ))}

              {filteredOptions.length === 0 && searchValue && (
                <div className="px-3 py-2 text-xs text-text-dimmed">
                  No matches for &ldquo;{searchValue}&rdquo;
                </div>
              )}
            </Ariakit.ComboboxList>

            <div className="sticky bottom-0 border-t border-grid-bright bg-background-bright px-1 py-1">
              {showOtherInput ? (
                <div className="flex h-8 w-full items-center rounded-sm bg-tertiary pl-0 pr-2 ring-1 ring-border-bright">
                  <input
                    ref={otherInputRef}
                    type="text"
                    value={otherInputValue}
                    onChange={(e) => setOtherInputValue(e.target.value)}
                    onKeyDown={handleOtherKeyDown}
                    placeholder="Type and press Enter to add"
                    className="flex-1 border-none bg-transparent pl-2 text-2sm text-text-bright shadow-none outline-hidden ring-0 placeholder:text-text-dimmed focus:border-none focus:outline-hidden focus:ring-0"
                    autoFocus
                  />
                  <ShortcutKey
                    shortcut={{ key: "Enter" }}
                    variant="small"
                    className={cn(
                      "mr-1.5 transition-opacity duration-150",
                      otherInputValue.length > 0 ? "opacity-100" : "opacity-0"
                    )}
                  />
                  <button
                    type="button"
                    onClick={() => {
                      setOtherInputValue("");
                      setShowOtherInput(false);
                    }}
                    className="flex items-center text-text-dimmed hover:text-text-bright"
                  >
                    <XMarkIcon className="size-4" />
                  </button>
                </div>
              ) : (
                <button
                  type="button"
                  className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-sm px-2 text-2sm text-text-dimmed hover:bg-tertiary"
                  onClick={() => {
                    setShowOtherInput(true);
                    setTimeout(() => otherInputRef.current?.focus(), 0);
                  }}
                >
                  <PlusIcon className="size-4 flex-none" />
                  <span>Other (not listed)</span>
                </button>
              )}
            </div>
          </Ariakit.SelectPopover>
        </Ariakit.SelectProvider>
      </Ariakit.ComboboxProvider>
    </div>
  );
}
