import {
  Downasaur,
  Github,
  Home as HomeIcon,
  Image,
  ImageDelete,
  Mail,
  Notes,
} from "@nsmr/pixelart-react";
import { useEffect, useState } from "react";
import { Link, Route, Routes, useNavigate, useParams } from "react-router-dom";
import { AskAI } from "@/components/ask-ai";
import { ProjectListItem } from "@/components/ProjectListItem";
import { TimeSince } from "@/components/time-since";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { type ProjectEntry, projectList, projects } from "@/projects";
import { locationPhotos, locations } from "@/travel";
import { ThemeToggle } from "./components/theme-toggle";
import { Button } from "./components/ui/button";
import { cn } from "./lib/utils";

const asciiFiles = [
  "cat-sleep.txt",
  "polar-bear.txt",
  "penguin-surfboard.txt",
  "cat-shock.txt",
  "exclamation.txt",
  "fat-cat-head.txt",
  "grumpy-dog.txt",
  "cat-peek.txt",
  "cat-loaf.txt",
];

const homeTabs = ["work", "travel"] as const;
type HomeTab = (typeof homeTabs)[number];

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/projects/:slug" element={<ProjectRoute />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

export default App;

function Home() {
  const isDevMode = import.meta.env.VITE_PUBLIC_DEV === "1";
  const navigate = useNavigate();
  const [asciiArt, setAsciiArt] = useState("");
  const [activeHomeTab, setActiveHomeTab] = useState<HomeTab>("work");
  const [activeProjectIndex, setActiveProjectIndex] = useState<number | null>(
    null,
  );
  const [activeLocationIndex, setActiveLocationIndex] = useState<number | null>(
    null,
  );
  const [expandedLocationIndex, setExpandedLocationIndex] = useState<
    number | null
  >(null);
  const [previewPhotoPath, setPreviewPhotoPath] = useState<string | null>(null);
  const [travelFocusLevel, setTravelFocusLevel] = useState<
    "location" | "photo"
  >("location");
  const [activePhotoIndexByLocation, setActivePhotoIndexByLocation] = useState<
    Record<number, number | null>
  >({});
  const [asciiFile] = useState(
    () => asciiFiles[Math.floor(Math.random() * asciiFiles.length)],
  );
  const sortedProjects: ProjectEntry[] = [...projectList].sort(
    (a, b) =>
      parseDate(b.metadata.date).getTime() -
      parseDate(a.metadata.date).getTime(),
  );
  const visibleProjects = sortedProjects.filter(
    (project) => isDevMode || !project.metadata.hidden,
  );

  useEffect(() => {
    let isActive = true;
    fetch(`/ascii/${asciiFile}`)
      .then((response) => response.text())
      .then((text) => {
        if (isActive) setAsciiArt(text);
      });
    return () => {
      isActive = false;
    };
  }, [asciiFile]);

  useEffect(() => {
    setActiveProjectIndex((prev) => {
      if (visibleProjects.length === 0) return prev;
      if (prev === null) return null;
      return Math.min(prev, visibleProjects.length - 1);
    });
  }, [visibleProjects.length]);

  useEffect(() => {
    if (activeHomeTab !== "travel") {
      setTravelFocusLevel("location");
      return;
    }

    setActiveLocationIndex((prev) => {
      if (locations.length === 0) return null;
      if (prev === null) return 0;
      return clampIndex(prev, locations.length);
    });

    setExpandedLocationIndex((prev) => {
      if (prev === null || locations.length === 0) return null;
      return clampIndex(prev, locations.length);
    });
  }, [activeHomeTab]);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.defaultPrevented || event.isComposing) return;
      if (event.metaKey || event.ctrlKey || event.altKey) return;

      if (event.key === "Tab") {
        event.preventDefault();
        setActiveHomeTab((prev) => {
          const currentIndex = homeTabs.indexOf(prev);
          const safeIndex = currentIndex === -1 ? 0 : currentIndex;
          const nextIndex = (safeIndex + 1) % homeTabs.length;
          return homeTabs[nextIndex];
        });
        return;
      }

      if (isInteractiveTarget(event.target)) return;

      const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;

      if (activeHomeTab === "work") {
        if (visibleProjects.length === 0) return;

        const isDesktop = window.matchMedia("(min-width: 768px)").matches;
        const columns = isDesktop ? 2 : 1;

        let delta = 0;
        if (key === "ArrowLeft" || key === "h") delta = -1;
        if (key === "ArrowRight" || key === "l") delta = 1;
        if (key === "ArrowUp" || key === "k") delta = -columns;
        if (key === "ArrowDown" || key === "j") delta = columns;

        if (delta !== 0) {
          event.preventDefault();
          setActiveProjectIndex((prev) => {
            if (prev === null) return 0;
            return clampIndex(prev + delta, visibleProjects.length);
          });
          return;
        }

        if (key === "Enter") {
          if (activeProjectIndex === null) return;
          event.preventDefault();
          const target = visibleProjects[activeProjectIndex];
          if (!target) return;
          navigate(`/projects/${target.metadata.slug}`);
        }

        return;
      }

      if (locations.length === 0) return;

      const expandedLocation =
        expandedLocationIndex === null
          ? null
          : locations[expandedLocationIndex];
      const expandedLocationPhotoList = expandedLocation
        ? (locationPhotos[expandedLocation.id] ?? [])
        : [];
      const isAtPhotoLevel =
        travelFocusLevel === "photo" &&
        expandedLocationIndex !== null &&
        expandedLocationPhotoList.length > 0;

      const locationDelta = getLinearDelta(key);
      if (isAtPhotoLevel) {
        if (expandedLocationIndex === null || !expandedLocation) return;

        const locationId = expandedLocation.id;
        const currentPhotoIndex = activePhotoIndexByLocation[locationId] ?? 0;

        if ((key === "ArrowUp" || key === "k") && currentPhotoIndex === 0) {
          event.preventDefault();
          setTravelFocusLevel("location");
          return;
        }

        if (
          (key === "ArrowDown" || key === "j") &&
          currentPhotoIndex === expandedLocationPhotoList.length - 1
        ) {
          event.preventDefault();
          setTravelFocusLevel("location");
          setActiveLocationIndex(
            clampIndex(expandedLocationIndex + 1, locations.length),
          );
          return;
        }

        if (locationDelta !== 0) {
          event.preventDefault();
          const nextPhotoIndex = clampIndex(
            currentPhotoIndex + locationDelta,
            expandedLocationPhotoList.length,
          );

          setActivePhotoIndexByLocation((prev) => ({
            ...prev,
            [locationId]: nextPhotoIndex,
          }));

          const nextPhotoName = expandedLocationPhotoList[nextPhotoIndex];
          if (nextPhotoName) {
            setPreviewPhotoPath(
              getTravelPhotoPath(expandedLocation, nextPhotoName),
            );
          }
          return;
        }

        return;
      }

      if (locationDelta !== 0) {
        if (
          (key === "ArrowUp" || key === "k") &&
          activeLocationIndex !== null
        ) {
          const nextLocationIndex = clampIndex(
            activeLocationIndex - 1,
            locations.length,
          );
          const nextLocation = locations[nextLocationIndex];
          const nextLocationPhotos = nextLocation
            ? (locationPhotos[nextLocation.id] ?? [])
            : [];
          const lastPhotoIndex = nextLocationPhotos.length - 1;
          const lastPhotoName = nextLocationPhotos[lastPhotoIndex];

          if (
            expandedLocationIndex === nextLocationIndex &&
            nextLocation &&
            lastPhotoName
          ) {
            event.preventDefault();
            setActiveLocationIndex(nextLocationIndex);
            setActivePhotoIndexByLocation((prev) => ({
              ...prev,
              [nextLocation.id]: lastPhotoIndex,
            }));
            setPreviewPhotoPath(
              getTravelPhotoPath(nextLocation, lastPhotoName),
            );
            setTravelFocusLevel("photo");
            return;
          }
        }

        if (
          activeLocationIndex !== null &&
          expandedLocationIndex === activeLocationIndex &&
          (key === "ArrowDown" || key === "j")
        ) {
          const location = locations[activeLocationIndex];
          const photoList = location ? (locationPhotos[location.id] ?? []) : [];
          const firstPhotoName = photoList[0];

          if (location && firstPhotoName) {
            event.preventDefault();
            setActivePhotoIndexByLocation((prev) => ({
              ...prev,
              [location.id]: 0,
            }));
            setPreviewPhotoPath(getTravelPhotoPath(location, firstPhotoName));
            setTravelFocusLevel("photo");
            return;
          }
        }

        event.preventDefault();
        setActiveLocationIndex((prev) => {
          if (prev === null) return 0;
          return clampIndex(prev + locationDelta, locations.length);
        });
        return;
      }

      if (key === "Enter") {
        if (activeLocationIndex === null) return;
        event.preventDefault();

        if (expandedLocationIndex === activeLocationIndex) {
          setExpandedLocationIndex(null);
          setPreviewPhotoPath(null);
          setTravelFocusLevel("location");
          return;
        }

        const location = locations[activeLocationIndex];
        if (!location) return;

        const photoList = locationPhotos[location.id] ?? [];
        setExpandedLocationIndex(activeLocationIndex);

        if (photoList.length === 0) {
          setPreviewPhotoPath(null);
          setTravelFocusLevel("location");
          return;
        }

        const nextPhotoIndex = clampIndex(
          activePhotoIndexByLocation[location.id] ?? 0,
          photoList.length,
        );
        const nextPhotoName = photoList[nextPhotoIndex];
        if (!nextPhotoName) return;

        setActivePhotoIndexByLocation((prev) => ({
          ...prev,
          [location.id]: nextPhotoIndex,
        }));
        setPreviewPhotoPath(getTravelPhotoPath(location, nextPhotoName));
        setTravelFocusLevel("photo");
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [
    activeHomeTab,
    activeLocationIndex,
    activePhotoIndexByLocation,
    activeProjectIndex,
    expandedLocationIndex,
    navigate,
    travelFocusLevel,
    visibleProjects,
  ]);

  useEffect(() => {
    if (activeHomeTab !== "travel") return;
    if (travelFocusLevel !== "photo") return;
    if (expandedLocationIndex === null) return;

    const location = locations[expandedLocationIndex];
    if (!location) return;

    const activePhotoIndex = activePhotoIndexByLocation[location.id];
    if (activePhotoIndex === null || activePhotoIndex === undefined) return;

    const photoListElement = document.getElementById(
      getTravelPhotoListId(location.id),
    );
    const activePhotoElement = document.getElementById(
      getTravelPhotoItemId(location.id, activePhotoIndex),
    );

    if (!(photoListElement instanceof HTMLElement)) return;
    if (!(activePhotoElement instanceof HTMLElement)) return;

    const listRect = photoListElement.getBoundingClientRect();
    const activeRect = activePhotoElement.getBoundingClientRect();
    const isAbove = activeRect.top < listRect.top;
    const isBelow = activeRect.bottom > listRect.bottom;

    if (isAbove || isBelow) {
      activePhotoElement.scrollIntoView({ block: "nearest" });
    }
  }, [
    activeHomeTab,
    activePhotoIndexByLocation,
    expandedLocationIndex,
    travelFocusLevel,
  ]);

  return (
    <div className="min-h-dvh flex flex-col items-center gap-2 text-2xl px-6 py-10">
      <div className="flex flex-col items-center gap-4 mb-4">
        {asciiArt ? (
          <pre className="text-[#000000] dark:text-[#ffffff] leading-1.75 tracking-[-1.75px]">
            <code className="commitmono text-[11px]">{asciiArt}</code>
          </pre>
        ) : null}
        <h1 className="text-center picnic text-8xl text-balance">
          Oliver Bryan
        </h1>
        <div className="flex flex-wrap items-center justify-center gap-3 text-base text-fg">
          <a
            href="https://github.com/hex248"
            rel="noreferrer"
            target="_blank"
            className="inline-flex items-center gap-2 hover:text-accent"
          >
            <Github className="size-6" />
            hex248
          </a>
          <span className="text-fg/60">/</span>
          <a
            href="mailto:ob248@proton.me"
            className="inline-flex items-center gap-2 hover:text-accent"
          >
            <Mail className="size-6" />
            ob248@proton.me
          </a>
          <span className="text-fg/60">/</span>
          <a
            href="/cv.pdf"
            className="inline-flex items-center gap-2 hover:text-accent"
          >
            <Notes className="size-6" /> CV
          </a>
        </div>
        <div className="text-base text-fg">
          Age: <TimeSince date={new Date(2004, 10, 4, 11, 47, 0)} />
        </div>
      </div>
      <Tabs
        value={activeHomeTab}
        onValueChange={(value) => setActiveHomeTab(value as HomeTab)}
        className="w-full max-w-5xl gap-0"
      >
        <TabsList
          variant="line"
          className="relative z-0 h-auto w-full gap-0 p-0"
        >
          <TabsTrigger
            value={homeTabs[0]}
            className="border-border -mr-[1px] after:hidden data-[state=active]:text-accent"
          >
            Work
          </TabsTrigger>
          <TabsTrigger
            value={homeTabs[1]}
            className="border-border after:hidden data-[state=active]:text-accent"
          >
            Travel
          </TabsTrigger>
        </TabsList>
        <TabsContent value={homeTabs[0]} className="relative z-10">
          <div className="-mt-[1.5px] border p-2 grid grid-cols-1 gap-2 md:grid-cols-2">
            {visibleProjects.map((project, index) => (
              <ProjectListItem
                key={project.metadata.slug}
                metadata={project.metadata}
                isDevMode={isDevMode}
                isActive={
                  activeProjectIndex !== null && index === activeProjectIndex
                }
              />
            ))}
          </div>
        </TabsContent>
        <TabsContent value={homeTabs[1]} className="relative z-10">
          <div className="-mt-[1px] grid grid-cols-1">
            {locations.map((location, index) => (
              <div key={location.id}>
                <Button
                  className={cn(
                    "text-sm border cursor-pointer hover:border-accent justify-start w-full",
                    activeLocationIndex === index &&
                      activeHomeTab === "travel" &&
                      travelFocusLevel === "location" &&
                      "border-accent",
                  )}
                  onClick={(_e) => {
                    const isExpanded = expandedLocationIndex === index;
                    const photos = locationPhotos[location.id] ?? [];

                    setActiveLocationIndex(index);

                    if (isExpanded) {
                      setExpandedLocationIndex(null);
                      setTravelFocusLevel("location");
                      setPreviewPhotoPath(null);
                      return;
                    }

                    setExpandedLocationIndex(index);

                    if (photos.length === 0) {
                      setTravelFocusLevel("location");
                      setPreviewPhotoPath(null);
                      return;
                    }

                    const nextPhotoIndex = clampIndex(
                      activePhotoIndexByLocation[location.id] ?? 0,
                      photos.length,
                    );
                    const nextPhotoName = photos[nextPhotoIndex];
                    if (!nextPhotoName) {
                      setTravelFocusLevel("location");
                      setPreviewPhotoPath(null);
                      return;
                    }

                    setActivePhotoIndexByLocation((prev) => ({
                      ...prev,
                      [location.id]: nextPhotoIndex,
                    }));
                    setPreviewPhotoPath(
                      getTravelPhotoPath(location, nextPhotoName),
                    );
                    setTravelFocusLevel("photo");
                  }}
                  variant="dummy"
                  size="sm"
                >
                  {location.city}, {location.country} - {location.date}
                </Button>
                {expandedLocationIndex === index &&
                  (locationPhotos[location.id].length === 0 ? (
                    <div className="flex">
                      <div className="flex flex-col flex-1 ml-8">
                        <Button
                          disabled
                          className={cn(
                            "flex text-sm border cursor-pointer hover:border-accent items-center justify-start p-0 pl-2 w-full",
                          )}
                          variant="dummy"
                          size="sm"
                        >
                          <ImageDelete size={16} /> No photos available
                        </Button>
                      </div>
                    </div>
                  ) : (
                    <div className="flex">
                      <div
                        id={getTravelPhotoListId(location.id)}
                        className="flex flex-col flex-1 ml-8 max-h-128 overflow-y-auto"
                      >
                        {locationPhotos[location.id].map(
                          (photo, photoIndex) => (
                            <Button
                              key={photo}
                              id={getTravelPhotoItemId(location.id, photoIndex)}
                              onClick={() => {
                                const path = getTravelPhotoPath(
                                  location,
                                  photo,
                                );
                                setActiveLocationIndex(index);
                                setExpandedLocationIndex(index);
                                setTravelFocusLevel("photo");
                                setActivePhotoIndexByLocation((prev) => ({
                                  ...prev,
                                  [location.id]: photoIndex,
                                }));
                                setPreviewPhotoPath(path);
                              }}
                              className={cn(
                                "flex text-sm border cursor-pointer hover:border-accent items-center justify-start p-0 pl-2 ",
                                activeHomeTab === "travel" &&
                                  travelFocusLevel === "photo" &&
                                  activePhotoIndexByLocation[location.id] ===
                                    photoIndex &&
                                  "border-accent",
                              )}
                              variant="dummy"
                              size="sm"
                            >
                              <Image size={22} />
                              {photo}
                            </Button>
                          ),
                        )}
                      </div>
                      <div
                        className={
                          "flex-1 max-w-lg border flex items-center h-128 justify-center text-sm gap-4"
                        }
                      >
                        {previewPhotoPath ? (
                          <img
                            className={"flex-1 object-contain max-h-full"}
                            src={previewPhotoPath}
                            alt={"active-photo"}
                          />
                        ) : (
                          <span>
                            <ImageDelete />
                            No photo selected
                          </span>
                        )}
                      </div>
                    </div>
                  ))}
              </div>
            ))}
          </div>
        </TabsContent>
      </Tabs>
      <div className="w-full max-w-5xl grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] items-center gap-3 md:gap-4">
        <div className="flex items-center gap-6">
          <AskAI name="me" inline />
        </div>
        <p className="text-xs text-fg/80 text-center text-pretty">
          arrows or hjkl, then enter
        </p>
        <div className="justify-self-center md:justify-self-end">
          <ThemeToggle />
        </div>
      </div>
    </div>
  );
}

function clampIndex(index: number, itemCount: number): number {
  return Math.max(0, Math.min(itemCount - 1, index));
}

function getLinearDelta(key: string): number {
  if (key === "ArrowLeft" || key === "h" || key === "ArrowUp" || key === "k") {
    return -1;
  }
  if (
    key === "ArrowRight" ||
    key === "l" ||
    key === "ArrowDown" ||
    key === "j"
  ) {
    return 1;
  }
  return 0;
}

function getTravelPhotoPath(
  location: { city: string; country: string; date: string },
  photo: string,
): string {
  return `/travel/${location.city} ${location.country} ${location.date}/${photo}`;
}

function getTravelPhotoListId(locationId: number): string {
  return `travel-photo-list-${locationId}`;
}

function getTravelPhotoItemId(locationId: number, photoIndex: number): string {
  return `travel-photo-item-${locationId}-${photoIndex}`;
}

function isInteractiveTarget(target: EventTarget | null): boolean {
  if (!(target instanceof HTMLElement)) return false;
  if (target.isContentEditable) return true;
  const tagName = target.tagName;
  return (
    tagName === "INPUT" ||
    tagName === "TEXTAREA" ||
    tagName === "SELECT" ||
    tagName === "BUTTON" ||
    tagName === "A"
  );
}

function ProjectRoute() {
  const { slug } = useParams();
  if (!slug || !projects[slug]) return <NotFound />;

  const { Component } = projects[slug];
  return <Component />;
}

function NotFound() {
  return (
    <div
      className={`w-full h-[100vh] flex flex-col items-center justify-center gap-4`}
    >
      <span className="-ml-14 -mb-7 -rotate-20 text-xl text-accent">?</span>
      <Downasaur size={72} className="text-accent" />
      <span className="text-7xl">404</span>
      <span className="text-2xl">Not Found</span>

      <Link to="/">
        <HomeIcon className="size-12 hover:text-accent" />
      </Link>
    </div>
  );
}

// function NotFound() {
// 	return (
// 		<div className="min-h-dvh flex flex-col items-center justify-center gap-4 text-2xl">
// 			<Downasaur className="size-24 text-accent" />
// 			<Link to="/">
// 				<HomeIcon className="size-12 hover:text-accent" />
// 			</Link>
// 		</div>
// 	);
// }

function parseDate(dateStr: string): Date {
  const lower = dateStr.toLowerCase();

  if (lower.includes("q1")) return new Date("2023-01-01");
  if (lower.includes("q2")) return new Date("2023-04-01");
  if (lower.includes("q3")) return new Date("2023-07-01");
  if (lower.includes("q4")) return new Date("2023-10-01");

  const months: Record<string, number> = {
    january: 0,
    february: 1,
    march: 2,
    april: 3,
    may: 4,
    june: 5,
    july: 6,
    august: 7,
    september: 8,
    october: 9,
    november: 10,
    december: 11,
  };

  for (const [monthName, monthIndex] of Object.entries(months)) {
    if (lower.includes(monthName)) {
      const yearMatch = dateStr.match(/\b(20\d{2})\b/);
      if (yearMatch) {
        return new Date(Number.parseInt(yearMatch[1], 10), monthIndex, 1);
      }
    }
  }

  const yearMatch = dateStr.match(/\b(20\d{2})\b/);
  if (yearMatch) {
    return new Date(Number.parseInt(yearMatch[1], 10), 0, 1);
  }

  return new Date(0);
}
