import React, { useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import {
  BrowserRouter,
  Link,
  Route,
  Routes,
  useNavigate,
  useParams,
} from "react-router-dom";
import "./styles.css";

/* =========================================================
   CONTENT TYPES
   ========================================================= */

const TYPES = {
  fanfic: ["Fanfics", "✦"],
  comic: ["Comics", "▣"],
  art: ["Fan Art", "✺"],
  lore: ["Lore", "◈"],
  horror: ["Gallery of Horror", "☠"],
};

const TYPE_PATHS = {
  fanfic: "fanfics",
  comic: "comics",
  art: "art",
  lore: "lore",
  horror: "horror",
};

/* =========================================================
   API
   ========================================================= */

async function api(path, options = {}) {
  const response = await fetch(`/api${path}`, {
    credentials: "include",
    ...options,
    headers: {
      "Content-Type": "application/json",
      ...(options.headers || {}),
    },
  });

  const data = await response.json().catch(() => ({}));

  if (!response.ok) {
    throw new Error(data.error || "Request failed");
  }

  return data;
}

/* =========================================================
   DISCORD AVATAR
   ========================================================= */

function Avatar({ user, size = "normal" }) {
  if (!user) return null;

  const fallback = user.username?.[0]?.toUpperCase() || "?";

  return (
    <div className={`avatar avatar-${size}`}>
      {user.avatar ? (
        <img
          src={user.avatar}
          alt={`${user.username || "Discord"} avatar`}
        />
      ) : (
        <span>{fallback}</span>
      )}
    </div>
  );
}

/* =========================================================
   SHELL
   ========================================================= */

function Shell({ me, children, onLogout }) {
  return (
    <>
      <header className="nav">
        <Link className="brand" to="/">
          <span className="mark">H</span>
          HANGOUT
        </Link>

        <nav>
          {Object.entries(TYPES).map(([type, [name]]) => (
            <Link key={type} to={`/${TYPE_PATHS[type]}`}>
              {name}
            </Link>
          ))}
        </nav>

        <div className="navRight">
          {me ? (
            <div className="account">
              <Link
                className="accountProfile"
                to={`/user/${me.discord_id || me.discordId || me.id}`}
              >
                <Avatar user={me} />

                <div className="accountInfo">
                  <strong>{me.username}</strong>
                  <span>View profile</span>
                </div>
              </Link>

              <Link className="submitButton" to="/submit">
                Submit
              </Link>

              <button
                className="logoutButton"
                type="button"
                onClick={onLogout}
                title="Log out"
              >
                ↪
              </button>
            </div>
          ) : (
            <button
              className="loginButton"
              onClick={() => {
                const state = crypto.randomUUID();

                sessionStorage.setItem(
                  "hangout_oauth_state",
                  state
                );

                window.location.href =
                  `/auth/discord?state=${encodeURIComponent(state)}`;
              }}
            >
              <span className="discordDot">●</span>
              Login with Discord
            </button>
          )}
        </div>
      </header>

      <main>{children}</main>

      <footer>
        <div>
          <b>HANGOUT</b>
          <span>
            An archive of fiction, friendship & unforgivable screenshots.
          </span>
        </div>

        <span>Made for the server. ✦</span>
      </footer>
    </>
  );
}

/* =========================================================
   HOME
   ========================================================= */

function Home() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    api("/posts")
      .then((data) => setPosts(data.posts || []))
      .catch(() => {});
  }, []);

  return (
    <>
      <section className="hero">
        <small>THE OFFICIAL HANGOUT ARCHIVE</small>

        <h1>
          Hangout isn't a server.
          <br />
          <i>It's a cinematic universe.</i>
        </h1>

        <p>
          Fanfictions. Lore. Comics. Art. Cursed screenshots.
          <br />
          Everything our friends somehow created instead of touching grass.
        </p>

        <div className="actions">
          <Link className="primary" to="/lore">
            Explore the lore →
          </Link>

          <Link className="secondary" to="/horror">
            Enter the horror gallery
          </Link>
        </div>
      </section>

      <section className="section">
        <small>THE ARCHIVE</small>

        <h2>Pick your poison.</h2>

        <div className="types">
          {Object.entries(TYPES).map(([type, [name, icon]]) => (
            <Link
              className="type"
              to={`/${TYPE_PATHS[type]}`}
              key={type}
            >
              <strong>{icon}</strong>

              <div>
                <b>{name}</b>
                <small>Enter the archive →</small>
              </div>
            </Link>
          ))}
        </div>
      </section>

      <section className="section">
        <div className="sectionHead">
          <div>
            <small>RECENTLY UNEARTHED</small>
            <h2>From the archive.</h2>
          </div>

          <Link to="/fanfics">View everything →</Link>
        </div>

        <div className="grid">
          {posts.slice(0, 6).map((post) => (
            <Card key={post.id} post={post} />
          ))}

          {!posts.length && <Empty />}
        </div>
      </section>
    </>
  );
}

/* =========================================================
   CARD
   ========================================================= */

function Card({ post }) {
  const type = TYPES[post.type] || ["Archive", "•"];
  const path = TYPE_PATHS[post.type] || post.type;

  const image =
    post.cover_url ||
    post.media_url ||
    post.coverUrl ||
    post.mediaUrl;

  return (
    <Link
      className="card"
      to={`/${path}/${post.slug}`}
    >
      <div className={`thumb ${post.type}`}>
        {image ? (
          <img src={image} alt="" />
        ) : (
          <span>{type[1]}</span>
        )}

        <label>{type[0]}</label>
      </div>

      <div className="cardBody">
        <h3>{post.title}</h3>

        <p>
          {post.excerpt ||
            "An entry from the Hangout archive."}
        </p>

        <small>
          {post.username
            ? `by ${post.username}`
            : "Hangout Archive"}
        </small>
      </div>
    </Link>
  );
}

/* =========================================================
   LISTING
   ========================================================= */

function Listing({ type }) {
  const [posts, setPosts] = useState([]);
  const [name] = TYPES[type];

  useEffect(() => {
    api(`/posts?type=${encodeURIComponent(type)}`)
      .then((data) => setPosts(data.posts || []))
      .catch(() => {});
  }, [type]);

  return (
    <section className="listing section">
      <small>{name.toUpperCase()}</small>

      <h1>{name}</h1>

      <p className="lead">
        {type === "horror"
          ? "Archaeological evidence that humanity should not have discovered the internet."
          : `Everything in the Hangout ${name.toLowerCase()} archive.`}
      </p>

      <div className="grid">
        {posts.map((post) => (
          <Card key={post.id} post={post} />
        ))}

        {!posts.length && <Empty />}
      </div>
    </section>
  );
}

/* =========================================================
   EMPTY
   ========================================================= */

function Empty() {
  return (
    <div className="empty">
      Nothing here yet. Someone needs to commit some crimes.
    </div>
  );
}

/* =========================================================
   DETAIL
   ========================================================= */

function Detail() {
  const { type, slug } = useParams();

  const [data, setData] = useState(null);
  const [error, setError] = useState(false);

  useEffect(() => {
    setData(null);
    setError(false);

    api(`/posts/${encodeURIComponent(slug)}`)
      .then(setData)
      .catch(() => setError(true));
  }, [slug]);

  if (error) {
    return (
      <div className="center">
        <h1>Archive entry not found.</h1>

        <p>
          Either this never existed, or someone buried the evidence.
        </p>

        <Link className="primary" to="/">
          Return home
        </Link>
      </div>
    );
  }

  if (!data) {
    return (
      <div className="loading">
        Loading archive entry…
      </div>
    );
  }

  const post = data.post;

  const typeName =
    TYPES[type]?.[0] || "ARCHIVE";

  const image =
    post.cover_url ||
    post.media_url ||
    post.coverUrl ||
    post.mediaUrl;

  return (
    <article className="detail">
      <small>
        {typeName} · {post.username || "Hangout"}
      </small>

      <h1>{post.title}</h1>

      {post.excerpt && (
        <p className="excerpt">
          {post.excerpt}
        </p>
      )}

      {image && (
        <img
          className="media"
          src={image}
          alt={post.title}
        />
      )}

      {post.body && (
        <div className="prose">
          {post.body.split("\n").map((line, index) => (
            <p key={index}>{line}</p>
          ))}
        </div>
      )}

      {data.chapters?.map((chapter) => (
        <section
          className="chapter"
          key={chapter.id}
        >
          <h2>
            Chapter {chapter.chapter_number} —{" "}
            {chapter.title}
          </h2>

          <div className="prose">
            {(chapter.body || "")
              .split("\n")
              .map((line, index) => (
                <p key={index}>{line}</p>
              ))}
          </div>
        </section>
      ))}

      {data.media?.length > 0 && (
        <div className="mediaGallery">
          {data.media.map((media) => (
            <img
              key={media.id}
              className="media"
              src={media.url}
              alt=""
            />
          ))}
        </div>
      )}
    </article>
  );
}

/* =========================================================
   USER PROFILE
   ========================================================= */

function UserProfile() {
  const { discordId } = useParams();

  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    setLoading(true);
    setError(false);

    Promise.all([
      api(`/users/${encodeURIComponent(discordId)}`),
      api(`/posts?author=${encodeURIComponent(discordId)}`),
    ])
      .then(([userData, postData]) => {
        setUser(userData.user || null);
        setPosts(postData.posts || []);
      })
      .catch(() => {
        setError(true);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [discordId]);

  if (loading) {
    return (
      <div className="loading">
        Loading profile…
      </div>
    );
  }

  if (error || !user) {
    return (
      <div className="center">
        <h1>User not found.</h1>
        <p>
          This Discord account doesn't appear to be part of the archive.
        </p>

        <Link className="primary" to="/">
          Return home
        </Link>
      </div>
    );
  }

  return (
    <section className="profilePage">
      <div className="profileHeader">
        <Avatar user={user} size="large" />

        <div className="profileIdentity">
          <small>HANGOUT MEMBER</small>

          <h1>{user.username}</h1>

          <span className="discordId">
            Discord ID · {user.discord_id}
          </span>
        </div>
      </div>

      <div className="profileDivider" />

      <section className="profilePosts">
        <div className="sectionHead">
          <div>
            <small>ARCHIVE CONTRIBUTIONS</small>
            <h2>From {user.username}.</h2>
          </div>
        </div>

        <div className="grid">
          {posts.map((post) => (
            <Card key={post.id} post={post} />
          ))}

          {!posts.length && (
            <div className="empty">
              No published contributions yet.
            </div>
          )}
        </div>
      </section>
    </section>
  );
}

/* =========================================================
   SUBMIT
   ========================================================= */

function Submit({ me }) {
  const navigate = useNavigate();

  const [form, setForm] = useState({
    type: "fanfic",
    title: "",
    excerpt: "",
    body: "",
  });

  const [file, setFile] = useState(null);
  const [message, setMessage] = useState("");
  const [submitting, setSubmitting] = useState(false);

  if (!me) {
    return (
      <div className="center">
        <h1>Members only.</h1>

        <p>
          Login with Discord to submit something to Hangout.
        </p>

        <a className="primary" href="/auth/discord">
          Login with Discord
        </a>
      </div>
    );
  }

  if (!me.canPost) {
    return (
      <div className="center">
        <h1>Read-only access.</h1>

        <p>
          Your Discord account isn't on the contributor
          whitelist yet.
        </p>
      </div>
    );
  }

  async function submit(event) {
    event.preventDefault();

    if (submitting) return;

    setSubmitting(true);
    setMessage("Uploading…");

    try {
      let mediaUrl = "";

      if (file) {
        if (file.size > 5 * 1024 * 1024) {
          throw new Error(
            "Maximum upload size is 5 MB."
          );
        }

        const uploadResponse = await fetch(
          `/api/upload?folder=${encodeURIComponent(
            form.type
          )}&filename=${encodeURIComponent(file.name)}`,
          {
            method: "POST",
            credentials: "include",
            headers: {
              "Content-Type":
                file.type || "application/octet-stream",
            },
            body: file,
          }
        );

        const uploadData =
          await uploadResponse.json().catch(() => ({}));

        if (!uploadResponse.ok) {
          throw new Error(
            uploadData.error || "Upload failed."
          );
        }

        mediaUrl = uploadData.url || "";
      }

      const response = await api("/posts", {
        method: "POST",
        body: JSON.stringify({
          ...form,
          media_url: mediaUrl,
        }),
      });

      setMessage(
        "Submitted! It is waiting for approval."
      );

      setTimeout(() => {
        const path =
          TYPE_PATHS[form.type] || form.type;

        navigate(
          `/${path}/${response.post.slug}`
        );
      }, 700);
    } catch (error) {
      setMessage(
        error.message || "Something went wrong."
      );
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <section className="formPage">
      <small>CONTRIBUTOR ACCESS</small>

      <h1>Add to the archive.</h1>

      <form onSubmit={submit}>
        <label>
          Type

          <select
            value={form.type}
            onChange={(event) =>
              setForm({
                ...form,
                type: event.target.value,
              })
            }
          >
            {Object.entries(TYPES).map(
              ([type, [name]]) => (
                <option value={type} key={type}>
                  {name}
                </option>
              )
            )}
          </select>
        </label>

        <label>
          Title

          <input
            required
            value={form.title}
            onChange={(event) =>
              setForm({
                ...form,
                title: event.target.value,
              })
            }
          />
        </label>

        <label>
          Description

          <textarea
            rows="3"
            value={form.excerpt}
            onChange={(event) =>
              setForm({
                ...form,
                excerpt: event.target.value,
              })
            }
          />
        </label>

        <label>
          Text

          <textarea
            rows="12"
            value={form.body}
            onChange={(event) =>
              setForm({
                ...form,
                body: event.target.value,
              })
            }
          />
        </label>

        <label>
          Media

          <input
            type="file"
            accept="image/jpeg,image/png,image/webp,image/gif"
            onChange={(event) =>
              setFile(
                event.target.files?.[0] || null
              )
            }
          />

          <small>
            JPG, PNG, WebP or GIF · max 5 MB
          </small>
        </label>

        <button
          className="primary"
          type="submit"
          disabled={submitting}
        >
          {submitting
            ? "Submitting…"
            : "Submit to Hangout →"}
        </button>

        {message && <p>{message}</p>}
      </form>
    </section>
  );
}

/* =========================================================
   APP
   ========================================================= */

function App() {
  const [me, setMe] = useState(null);

  async function loadMe() {
    try {
      const data = await api("/me");
      setMe(data.user || null);
    } catch {
      setMe(null);
    }
  }

  useEffect(() => {
    loadMe();
  }, []);

  async function logout() {
    try {
      await api("/logout", {
        method: "POST",
      });
    } catch {}

    setMe(null);
    window.location.href = "/";
  }

  return (
    <Shell me={me} onLogout={logout}>
      <Routes>
        <Route path="/" element={<Home />} />

        <Route
          path="/fanfics"
          element={<Listing type="fanfic" />}
        />

        <Route
          path="/comics"
          element={<Listing type="comic" />}
        />

        <Route
          path="/art"
          element={<Listing type="art" />}
        />

        <Route
          path="/lore"
          element={<Listing type="lore" />}
        />

        <Route
          path="/horror"
          element={<Listing type="horror" />}
        />

        <Route
          path="/submit"
          element={<Submit me={me} />}
        />

        <Route
          path="/user/:discordId"
          element={<UserProfile />}
        />

        <Route
          path="/:type/:slug"
          element={<Detail />}
        />

        <Route
          path="*"
          element={
            <div className="center">
              <h1>404</h1>

              <p>
                That piece of Hangout history does not exist.
              </p>

              <Link className="primary" to="/">
                Return home
              </Link>
            </div>
          }
        />
      </Routes>
    </Shell>
  );
}

/* =========================================================
   MOUNT
   ========================================================= */

createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);