import { ISSUE_DESCRIPTION_MAX_LENGTH, ISSUE_TITLE_MAX_LENGTH } from "@sprint/shared";

import { type FormEvent, useMemo, useState } from "react";
import { toast } from "sonner";
import { MultiAssigneeSelect } from "@/components/multi-assignee-select";
import { useAuthenticatedSession } from "@/components/session-provider";
import { SprintSelect } from "@/components/sprint-select";
import { StatusSelect } from "@/components/status-select";
import StatusTag from "@/components/status-tag";
import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label";
import { SelectTrigger } from "@/components/ui/select";
import {
  useCreateIssue,
  useOrganisationMembers,
  useSelectedOrganisation,
  useSelectedProject,
  useSprints,
} from "@/lib/query/hooks";
import { parseError } from "@/lib/server";
import { cn, issueID } from "@/lib/utils";

export function IssueForm({ trigger }: { trigger?: React.ReactNode }) {
  const { user } = useAuthenticatedSession();
  const selectedOrganisation = useSelectedOrganisation();
  const selectedProject = useSelectedProject();
  const { data: sprints = [] } = useSprints(selectedProject?.Project.id);
  const { data: membersData = [] } = useOrganisationMembers(selectedOrganisation?.Organisation.id);
  const createIssue = useCreateIssue();

  const members = useMemo(() => membersData.map((member) => member.User), [membersData]);
  const statuses = selectedOrganisation?.Organisation.statuses ?? {};
  const statusOptions = useMemo(() => Object.keys(statuses), [statuses]);
  const defaultStatus = statusOptions[0] ?? "";

  const [open, setOpen] = useState(false);
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const [sprintId, setSprintId] = useState<string>("unassigned");
  const [assigneeIds, setAssigneeIds] = useState<string[]>(["unassigned"]);
  const [status, setStatus] = useState<string>(defaultStatus);
  const [submitAttempted, setSubmitAttempted] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const reset = () => {
    setTitle("");
    setDescription("");
    setSprintId("unassigned");
    setAssigneeIds(["unassigned"]);
    setStatus(defaultStatus);
    setSubmitAttempted(false);
    setSubmitting(false);
    setError(null);
  };

  const onOpenChange = (nextOpen: boolean) => {
    setOpen(nextOpen);
    if (!nextOpen) {
      reset();
    }
  };

  const handleSubmit = async (event: FormEvent) => {
    event.preventDefault();
    setError(null);
    setSubmitAttempted(true);

    if (
      title.trim() === "" ||
      description.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH ||
      title.trim().length > ISSUE_TITLE_MAX_LENGTH
    ) {
      return;
    }

    if (!user.id) {
      setError("you must be logged in to create an issue");
      return;
    }

    if (!selectedProject) {
      setError("select a project first");
      return;
    }

    setSubmitting(true);

    try {
      const data = await createIssue.mutateAsync({
        projectId: selectedProject.Project.id,
        title,
        description,
        sprintId: sprintId === "unassigned" ? null : Number(sprintId),
        assigneeIds: assigneeIds.filter((id) => id !== "unassigned").map((id) => Number(id)),
        status: status.trim() === "" ? undefined : status,
      });
      setOpen(false);
      reset();
      toast.success(`Created ${issueID(selectedProject.Project.key, data.number)}`, {
        dismissible: false,
      });
    } catch (err) {
      const message = parseError(err as Error);
      setError(message);
      setSubmitting(false);
      toast.error(`Error creating issue: ${message}`, {
        dismissible: false,
      });
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogTrigger asChild>
        {trigger || (
          <Button variant="outline" disabled={!selectedProject}>
            Create Issue
          </Button>
        )}
      </DialogTrigger>

      <DialogContent className={cn("w-md", error && "border-destructive")}>
        <DialogHeader>
          <DialogTitle>Create Issue</DialogTitle>
        </DialogHeader>

        <form onSubmit={handleSubmit}>
          <div className="grid">
            {statusOptions.length > 0 && (
              <div className="flex items-center gap-2 mb-4">
                <Label>Status</Label>
                <StatusSelect
                  statuses={statuses}
                  value={status}
                  onChange={(newValue) => {
                    if (newValue.trim() === "") return;
                    setStatus(newValue);
                  }}
                  trigger={({ isOpen, value }) => (
                    <SelectTrigger
                      className="group flex items-center w-min"
                      variant="unstyled"
                      chevronClassName="hidden"
                      isOpen={isOpen}
                    >
                      <StatusTag status={value} colour={statuses[value]} className="hover:opacity-85" />
                    </SelectTrigger>
                  )}
                />
              </div>
            )}

            <Field
              label="Title"
              value={title}
              onChange={(event) => setTitle(event.target.value)}
              validate={(value) =>
                value.trim() === ""
                  ? "Cannot be empty"
                  : value.trim().length > ISSUE_TITLE_MAX_LENGTH
                    ? `Too long (${ISSUE_TITLE_MAX_LENGTH} character limit)`
                    : undefined
              }
              submitAttempted={submitAttempted}
              placeholder="Demo Issue"
              maxLength={ISSUE_TITLE_MAX_LENGTH}
            />
            <Field
              label="Description (optional)"
              value={description}
              onChange={(event) => setDescription(event.target.value)}
              validate={(value) =>
                value.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH
                  ? `Too long (${ISSUE_DESCRIPTION_MAX_LENGTH} character limit)`
                  : undefined
              }
              submitAttempted={submitAttempted}
              placeholder="Optional details"
              maxLength={ISSUE_DESCRIPTION_MAX_LENGTH}
            />

            {sprints.length > 0 && (
              <div className="flex items-center gap-2 mt-0">
                <Label className="text-sm">Sprint</Label>
                <SprintSelect sprints={sprints} value={sprintId} onChange={setSprintId} />
              </div>
            )}

            {members.length > 0 && (
              <div className="flex items-start gap-2 mt-4">
                <Label className="text-sm pt-2">Assignees</Label>
                <MultiAssigneeSelect users={members} assigneeIds={assigneeIds} onChange={setAssigneeIds} />
              </div>
            )}

            <div className="flex items-end justify-end w-full text-xs -mb-2 -mt-2">
              {error ? (
                <Label className="text-destructive text-sm">{error}</Label>
              ) : (
                <Label className="opacity-0 text-sm">a</Label>
              )}
            </div>

            <div className="flex gap-2 w-full justify-end mt-2">
              <DialogClose asChild>
                <Button variant="outline" type="button">
                  Cancel
                </Button>
              </DialogClose>
              <Button
                type="submit"
                disabled={
                  submitting ||
                  ((title.trim() === "" || title.trim().length > ISSUE_TITLE_MAX_LENGTH) &&
                    submitAttempted) ||
                  (description.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH && submitAttempted)
                }
              >
                {submitting ? "Creating..." : "Create"}
              </Button>
            </div>
          </div>
        </form>
      </DialogContent>
    </Dialog>
  );
}
