"use client";

import Link from "next/link";
import { useActionState, useId, useRef, useState } from "react";
import { FormErrorBanner } from "@/components/admin/Flash";
import { FormTabs } from "@/components/admin/FormTabs";
import { RichTextEditor } from "@/components/admin/RichTextEditor";
import { SubmitButton } from "@/components/admin/SubmitButton";
import { Button, ErrorText, HelpText, Input, Label, Select } from "@/components/ui";
import type { Tag } from "@/db/schema";
import { type FormState, firstFieldError, initialFormState } from "@/lib/forms";
import type { PostWithTags } from "@/lib/services/posts";
import { slugify } from "@/lib/slug";
import { uploadImageFile } from "@/lib/upload-client";

type Props = {
  post?: PostWithTags;
  /** For authors this is just their granted tags, not every tag. */
  allTags: Tag[];
  defaultAuthor: string;
  /** Admins can mint tags inline; authors cannot. */
  canCreateTags: boolean;
  action: (prev: FormState, formData: FormData) => Promise<FormState>;
};

// Fields living on the settings panel — used to route validation errors
// to the tab the user needs to fix.
const SETTINGS_FIELDS = [
  "title",
  "slug",
  "authorName",
  "featuredImageUrl",
  "featuredImageAlt",
  "tagIds",
  "newTags",
];

/**
 * WordPress-style layout: the Content tab is a full-height writing canvas;
 * everything descriptive (title, slug, author, featured image, tags) lives
 * on the Settings tab. Both panels stay mounted so the single form submits
 * all fields regardless of the visible tab, and the sticky action bar
 * keeps status + save reachable from either.
 */
export function PostForm({ post, allTags, defaultAuthor, canCreateTags, action }: Props) {
  const [state, formAction] = useActionState(action, initialFormState);
  const ids = useId();

  // The visible tab is derived: an explicit click wins until the next
  // action result arrives; a result with field errors routes to the tab
  // that contains them. No effects, no cascading renders.
  const [tabChoice, setTabChoice] = useState<{
    tab: "content" | "settings";
    forState: FormState;
  }>({ tab: "content", forState: initialFormState });
  const [title, setTitle] = useState(post?.title ?? "");
  const [slug, setSlug] = useState(post?.slug ?? "");
  const [slugTouched, setSlugTouched] = useState(post !== undefined);
  const [authorName, setAuthorName] = useState(post?.authorName ?? defaultAuthor);
  const [imageUrl, setImageUrl] = useState(post?.featuredImageUrl ?? "");
  const [imageAlt, setImageAlt] = useState(post?.featuredImageAlt ?? "");
  const [status, setStatus] = useState<string>(post?.status ?? "draft");
  const [selectedTagIds, setSelectedTagIds] = useState<Set<number>>(
    () => new Set(post?.tags.map((t) => t.id) ?? []),
  );
  const [newTags, setNewTags] = useState("");
  const [featuredUpload, setFeaturedUpload] = useState<
    { kind: "idle" } | { kind: "uploading" } | { kind: "error"; message: string }
  >({ kind: "idle" });
  const featuredFileRef = useRef<HTMLInputElement>(null);

  const err = (field: string) => firstFieldError(state, field);
  const errorKeys = Object.keys(state.fieldErrors ?? {});
  const settingsHasError = errorKeys.some((key) => SETTINGS_FIELDS.includes(key));
  const contentHasError = errorKeys.includes("body");

  const tab =
    tabChoice.forState === state
      ? tabChoice.tab
      : settingsHasError
        ? "settings"
        : contentHasError
          ? "content"
          : tabChoice.tab;
  const setTab = (next: "content" | "settings") =>
    setTabChoice({ tab: next, forState: state });

  function toggleTag(id: number) {
    setSelectedTagIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  return (
    <form action={formAction}>
      <div className="sticky top-0 z-20 -mb-px flex flex-wrap items-center justify-between gap-x-4 gap-y-2 border-b border-edge bg-background pt-1">
        <FormTabs
          idBase={ids}
          label="Post editor sections"
          activeId={tab}
          onSelect={(id) => setTab(id as typeof tab)}
          tabs={[
            { id: "content", label: "Content", hasError: contentHasError },
            { id: "settings", label: "Post settings", hasError: settingsHasError },
          ]}
        />
        <div className="flex items-center gap-3 pb-1.5">
          <Select
            aria-label="Status"
            name="status"
            value={status}
            onChange={(e) => setStatus(e.target.value)}
            className="w-32"
          >
            <option value="draft">Draft</option>
            <option value="published">Published</option>
          </Select>
          <SubmitButton>Save post</SubmitButton>
          <Link href="/admin/posts" className="text-sm text-ink-muted hover:text-ink-strong">
            Cancel
          </Link>
        </div>
      </div>

      <div className="pt-5">
        <FormErrorBanner>{state.formError}</FormErrorBanner>
      </div>

      <div
        role="tabpanel"
        id={`${ids}-panel-content`}
        aria-labelledby={`${ids}-tab-content`}
        hidden={tab !== "content"}
      >
        <RichTextEditor
          name="body"
          label="Body"
          initialHTML={post?.body ?? ""}
          error={err("body")}
          minHeightClassName="min-h-[max(24rem,calc(100dvh-24rem))]"
        />
      </div>

      <div
        role="tabpanel"
        id={`${ids}-panel-settings`}
        aria-labelledby={`${ids}-tab-settings`}
        hidden={tab !== "settings"}
        className="max-w-3xl space-y-6"
      >
        <div>
          <Label htmlFor={`${ids}-title`}>Title</Label>
          <Input
            id={`${ids}-title`}
            name="title"
            value={title}
            onChange={(e) => {
              setTitle(e.target.value);
              if (!slugTouched) setSlug(slugify(e.target.value));
            }}
            required
            aria-invalid={err("title") ? true : undefined}
            aria-describedby={err("title") ? `${ids}-title-error` : undefined}
          />
          <ErrorText id={`${ids}-title-error`}>{err("title")}</ErrorText>
        </div>

        <div>
          <Label htmlFor={`${ids}-slug`}>Slug</Label>
          <Input
            id={`${ids}-slug`}
            name="slug"
            value={slug}
            onChange={(e) => {
              setSlug(e.target.value);
              setSlugTouched(e.target.value !== "");
            }}
            aria-invalid={err("slug") ? true : undefined}
            aria-describedby={`${ids}-slug-help${err("slug") ? ` ${ids}-slug-error` : ""}`}
          />
          <HelpText id={`${ids}-slug-help`}>
            Public URL: /posts/{slug || "…"} — leave blank to generate from the title.
          </HelpText>
          <ErrorText id={`${ids}-slug-error`}>{err("slug")}</ErrorText>
        </div>

        <div>
          <Label htmlFor={`${ids}-author`}>Author name</Label>
          <Input
            id={`${ids}-author`}
            name="authorName"
            value={authorName}
            onChange={(e) => setAuthorName(e.target.value)}
            required
            aria-invalid={err("authorName") ? true : undefined}
            aria-describedby={err("authorName") ? `${ids}-author-error` : undefined}
          />
          <ErrorText id={`${ids}-author-error`}>{err("authorName")}</ErrorText>
        </div>

        <fieldset className="rounded-lg border border-edge p-4">
          <legend className="px-1 text-sm font-medium text-ink-strong">Featured image</legend>
          <div className="space-y-4">
            <div>
              <Label htmlFor={`${ids}-image-url`}>Image URL (optional)</Label>
              <div className="flex gap-2">
                <Input
                  id={`${ids}-image-url`}
                  name="featuredImageUrl"
                  placeholder="https://example.com/image.jpg or upload →"
                  value={imageUrl}
                  onChange={(e) => setImageUrl(e.target.value)}
                  aria-invalid={err("featuredImageUrl") ? true : undefined}
                  aria-describedby={
                    err("featuredImageUrl") ? `${ids}-image-url-error` : undefined
                  }
                />
                <Button
                  type="button"
                  variant="secondary"
                  className="shrink-0"
                  disabled={featuredUpload.kind === "uploading"}
                  onClick={() => featuredFileRef.current?.click()}
                >
                  {featuredUpload.kind === "uploading" ? "Uploading…" : "Upload"}
                </Button>
                <input
                  ref={featuredFileRef}
                  type="file"
                  accept="image/png,image/jpeg,image/webp,image/gif,image/avif"
                  hidden
                  data-testid="featured-image-input"
                  onChange={async (event) => {
                    const file = event.target.files?.[0];
                    event.target.value = "";
                    if (!file) return;
                    setFeaturedUpload({ kind: "uploading" });
                    const result = await uploadImageFile(file);
                    if ("error" in result) {
                      setFeaturedUpload({ kind: "error", message: result.error });
                    } else {
                      setImageUrl(result.url);
                      setFeaturedUpload({ kind: "idle" });
                    }
                  }}
                />
              </div>
              {featuredUpload.kind === "error" && (
                <ErrorText>{featuredUpload.message}</ErrorText>
              )}
              <ErrorText id={`${ids}-image-url-error`}>{err("featuredImageUrl")}</ErrorText>
            </div>
            <div>
              <Label htmlFor={`${ids}-image-alt`}>Alt text (optional)</Label>
              <Input
                id={`${ids}-image-alt`}
                name="featuredImageAlt"
                value={imageAlt}
                onChange={(e) => setImageAlt(e.target.value)}
                aria-describedby={`${ids}-image-alt-help`}
              />
              <HelpText id={`${ids}-image-alt-help`}>
                Describe the image for screen-reader users; leave blank if purely decorative.
              </HelpText>
            </div>
          </div>
        </fieldset>

        <fieldset className="rounded-lg border border-edge p-4">
          <legend className="px-1 text-sm font-medium text-ink-strong">Tags</legend>
          {allTags.length > 0 ? (
            <ul className="flex flex-wrap gap-x-5 gap-y-2">
              {allTags.map((tag) => (
                <li key={tag.id}>
                  <label className="inline-flex cursor-pointer items-center gap-2 text-sm text-ink">
                    <input
                      type="checkbox"
                      name="tagIds"
                      value={tag.id}
                      checked={selectedTagIds.has(tag.id)}
                      onChange={() => toggleTag(tag.id)}
                      className="size-4 accent-(--link)"
                    />
                    {tag.name}
                  </label>
                </li>
              ))}
            </ul>
          ) : (
            <p className="text-sm text-ink-muted">
              {canCreateTags
                ? "No tags exist yet — create some below."
                : "You have not been given access to any tags yet — ask the admin."}
            </p>
          )}
          <ErrorText>{err("tagIds")}</ErrorText>
          {canCreateTags ? (
            <div className="mt-4">
              <Label htmlFor={`${ids}-new-tags`}>New tags (optional)</Label>
              <Input
                id={`${ids}-new-tags`}
                name="newTags"
                value={newTags}
                onChange={(e) => setNewTags(e.target.value)}
                placeholder="design, typescript"
                aria-describedby={`${ids}-new-tags-help`}
              />
              <HelpText id={`${ids}-new-tags-help`}>
                Comma-separated. Created and attached to this post on save.
              </HelpText>
              <ErrorText>{err("newTags")}</ErrorText>
            </div>
          ) : (
            <HelpText>Posts must carry at least one of your tags.</HelpText>
          )}
        </fieldset>
      </div>
    </form>
  );
}
