"use client";

import {
  Alert,
  Box,
  Button,
  Divider,
  Drawer,
  FileButton,
  Group,
  Loader,
  Radio,
  Stack,
  Stepper,
  Text,
  Textarea,
  TextInput,
  ThemeIcon,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useDisclosure } from "@mantine/hooks";
import { notifications } from "@mantine/notifications";
import {
  IconAlertTriangle,
  IconCheck,
  IconFileText,
  IconLock,
  IconPhoto,
  IconRocket,
  IconTargetArrow,
  IconUpload,
} from "@tabler/icons-react";
import { useState } from "react";
import { useNavigate } from "react-router";
import ConfirmAlert from "@/components/blocks/ConfirmAlert/confirmAlert";
import {
  createDashboard,
  deleteDashboard,
  publishDashboard,
  setDashboardAccess,
  setVersionEntryPoint,
  uploadDashboardCover,
} from "@/lib/features/dashboard/action";
import type { DashboardListItem, UploadedVersion } from "@/lib/features/dashboard/types";
import AccessPicker, { type AccessMode } from "./AccessPicker";
import BundleDropzone, { UploadSummary } from "./BundleDropzone";

type Props = { opened: boolean; onClose: () => void; onCreated: () => void };

const STEPS = ["Details", "Bundle", "Entry point", "Access", "Publish"];

const CreateDashboardWizard: React.FC<Props> = ({ opened, onClose, onCreated }) => {
  const navigate = useNavigate();
  const [step, setStep] = useState(0);
  const [busy, setBusy] = useState(false);
  const [confirmOpen, confirmHandlers] = useDisclosure(false);

  /**
   * The draft this wizard session created, and ONLY that one.
   *
   * Step 1 has to create a real row so the upload has a dashboardId to post to.
   * That means an abandoned wizard would otherwise leave an orphan draft. On
   * cancel we delete it — but scoping matters: this id is set exclusively by
   * this component's own createDashboard call and cleared the moment the
   * dashboard is published. It is never populated from a route param or an
   * existing dashboard, so the cleanup can't reach anything the operator
   * didn't just create here.
   */
  const [draft, setDraft] = useState<DashboardListItem | null>(null);
  const [uploaded, setUploaded] = useState<UploadedVersion | null>(null);
  const [entryPoint, setEntryPoint] = useState<string>("");
  const [accessMode, setAccessMode] = useState<AccessMode>("specific");
  const [accessIds, setAccessIds] = useState<string[]>([]);
  const [cover, setCover] = useState<File | null>(null);

  const form = useForm({
    initialValues: { name: "", description: "" },
    validate: {
      name: (v) => (v.trim().length === 0 ? "Name is required" : null),
    },
  });

  const reset = () => {
    setStep(0);
    setDraft(null);
    setUploaded(null);
    setEntryPoint("");
    setAccessMode("specific");
    setAccessIds([]);
    setCover(null);
    setBusy(false);
    form.reset();
  };

  /** Cancel: bin the draft this session created, then close. */
  const discardAndClose = async () => {
    confirmHandlers.close();
    if (draft) {
      const res = await deleteDashboard(draft.id);
      if (!res.success) {
        notifications.show({
          color: "red",
          title: "Could not remove the draft",
          message: `"${draft.name}" may still be listed as a draft. ${res.message ?? ""}`,
        });
      }
    }
    reset();
    onClose();
  };

  const requestClose = () => {
    // Nothing created yet → nothing to clean up.
    if (!draft) {
      reset();
      onClose();
      return;
    }
    confirmHandlers.open();
  };

  // ---- step 1 -------------------------------------------------------------
  const submitDetails = async () => {
    if (form.validate().hasErrors) return;
    // Re-entering step 1 must not create a second draft.
    if (draft) {
      setStep(1);
      return;
    }
    setBusy(true);
    const res = await createDashboard({
      name: form.values.name.trim(),
      description: form.values.description.trim() || null,
    });
    setBusy(false);
    if (!res.success) {
      notifications.show({ color: "red", title: "Could not create dashboard", message: res.message });
      return;
    }
    setDraft(res.data);
    setStep(1);
  };

  // ---- step 2 -------------------------------------------------------------
  const handleUploaded = (result: UploadedVersion) => {
    setUploaded(result);
    setEntryPoint(result.version.entry_point);
    setStep(2);
  };

  // ---- step 3 -------------------------------------------------------------
  const confirmEntryPoint = async () => {
    if (!draft || !uploaded) return;
    // Only call the backend when the operator actually overrode the resolved
    // value — the version already carries what validation decided.
    if (entryPoint && entryPoint !== uploaded.version.entry_point) {
      setBusy(true);
      const res = await setVersionEntryPoint(draft.id, uploaded.version.id, entryPoint);
      setBusy(false);
      if (!res.success) {
        notifications.show({ color: "red", title: "Could not set entry point", message: res.message });
        return;
      }
      setUploaded({ ...uploaded, version: { ...uploaded.version, entry_point: entryPoint } });
    }
    setStep(3);
  };

  // ---- step 4 -------------------------------------------------------------
  const submitAccess = async () => {
    if (!draft) return;
    setBusy(true);
    const res = await setDashboardAccess(draft.id, accessIds);
    setBusy(false);
    if (!res.success) {
      notifications.show({ color: "red", title: "Could not save access list", message: res.message });
      return;
    }
    setStep(4);
  };

  // ---- step 5 -------------------------------------------------------------
  const publish = async () => {
    if (!draft || !uploaded) return;
    setBusy(true);

    if (cover) {
      const coverRes = await uploadDashboardCover(draft.id, cover);
      if (!coverRes.success) {
        // Non-fatal: the gradient placeholder still works.
        notifications.show({
          color: "yellow",
          title: "Cover not saved",
          message: `${coverRes.message ?? "Upload failed"} — publishing without it.`,
        });
      }
    }

    const res = await publishDashboard(draft.id, uploaded.version.id);
    setBusy(false);
    if (!res.success) {
      notifications.show({ color: "red", title: "Could not publish", message: res.message });
      return;
    }

    const slug = draft.slug;
    notifications.show({
      color: "teal",
      title: "Dashboard published",
      message: `"${draft.name}" is live.`,
      icon: <IconCheck size={16} />,
    });
    // Published — the draft is now a real dashboard, so cancel-cleanup must
    // never touch it again.
    setDraft(null);
    reset();
    onCreated();
    onClose();
    navigate(`/admin/dashboard/${slug}`);
  };

  const htmlChoices = uploaded?.htmlFiles ?? [];
  const canOverrideEntry = htmlChoices.length > 1;

  return (
    <>
      <Drawer
        opened={opened}
        onClose={requestClose}
        position="right"
        size="lg"
        radius="md"
        title={<Text fw={600}>Create dashboard</Text>}
        closeOnClickOutside={false}
      >
        <Stack gap="lg">
          <Stepper active={step} size="xs" iconSize={26} allowNextStepsSelect={false}>
            <Stepper.Step label={STEPS[0]} icon={<IconFileText size={13} />} />
            <Stepper.Step label={STEPS[1]} icon={<IconUpload size={13} />} />
            <Stepper.Step label={STEPS[2]} icon={<IconTargetArrow size={13} />} />
            <Stepper.Step label={STEPS[3]} icon={<IconLock size={13} />} />
            <Stepper.Step label={STEPS[4]} icon={<IconRocket size={13} />} />
          </Stepper>

          <Divider />

          {/* ---------------------------------------------------- step 1 */}
          {step === 0 && (
            <Stack gap="md">
              <TextInput
                label="Name"
                placeholder="Revenue Overview"
                withAsterisk
                radius="md"
                {...form.getInputProps("name")}
              />
              <Textarea
                label="Description"
                placeholder="What does this dashboard show, and who is it for?"
                minRows={3}
                autosize
                radius="md"
                {...form.getInputProps("description")}
              />
              <Alert variant="light" color="gray" radius="md">
                <Text size="xs">
                  Continuing creates this dashboard as a <b>draft</b>. It stays
                  invisible to everyone but managers until you publish it, and
                  cancelling this wizard removes it again.
                </Text>
              </Alert>
              <Group justify="flex-end">
                <Button variant="default" radius="md" onClick={requestClose}>
                  Cancel
                </Button>
                <Button radius="md" loading={busy} onClick={submitDetails}>
                  Continue
                </Button>
              </Group>
            </Stack>
          )}

          {/* ---------------------------------------------------- step 2 */}
          {step === 1 && draft && (
            <Stack gap="md">
              <BundleDropzone dashboardId={draft.id} onUploaded={handleUploaded} />
              <Group justify="space-between">
                <Button variant="subtle" radius="md" onClick={() => setStep(0)}>
                  Back
                </Button>
                <Button variant="default" radius="md" onClick={requestClose}>
                  Cancel
                </Button>
              </Group>
            </Stack>
          )}

          {/* ---------------------------------------------------- step 3 */}
          {step === 2 && uploaded && (
            <Stack gap="md">
              <UploadSummary result={uploaded} />
              {canOverrideEntry ? (
                <Radio.Group
                  value={entryPoint}
                  onChange={setEntryPoint}
                  label="Which page should open first?"
                  description="Resolved from your manifest.json or index.html. Override it if the bundle has several pages."
                >
                  <Stack gap={6} mt="xs">
                    {htmlChoices.map((f) => (
                      <Radio
                        key={f}
                        value={f}
                        label={f}
                        description={
                          f === uploaded.version.entry_point ? "Resolved automatically" : undefined
                        }
                      />
                    ))}
                  </Stack>
                </Radio.Group>
              ) : (
                <Alert variant="light" color="gray" radius="md" icon={<IconTargetArrow size={16} />}>
                  <Text size="xs">
                    Entry point: <b>{uploaded.version.entry_point}</b> — the only
                    page in this bundle, so there's nothing to choose.
                  </Text>
                </Alert>
              )}
              <Alert variant="light" color="gray" radius="md">
                <Text size="xs">
                  The entry point can only be changed before publishing. After
                  that the version is frozen — upload a new one instead.
                </Text>
              </Alert>
              <Group justify="space-between">
                <Button variant="subtle" radius="md" onClick={() => setStep(1)}>
                  Back
                </Button>
                <Button radius="md" loading={busy} onClick={confirmEntryPoint}>
                  Continue
                </Button>
              </Group>
            </Stack>
          )}

          {/* ---------------------------------------------------- step 4 */}
          {step === 3 && (
            <Stack gap="md">
              <AccessPicker
                mode={accessMode}
                onModeChange={setAccessMode}
                selected={accessIds}
                onSelectedChange={setAccessIds}
              />
              <Group justify="space-between">
                <Button variant="subtle" radius="md" onClick={() => setStep(2)}>
                  Back
                </Button>
                <Button radius="md" loading={busy} onClick={submitAccess}>
                  Continue
                </Button>
              </Group>
            </Stack>
          )}

          {/* ---------------------------------------------------- step 5 */}
          {step === 4 && uploaded && draft && (
            <Stack gap="md">
              <Box>
                <Text size="sm" fw={600} mb={6}>
                  Cover image <Text span c="dimmed" fw={400}>(optional)</Text>
                </Text>
                <Group gap="sm">
                  <FileButton onChange={setCover} accept="image/png,image/jpeg,image/webp">
                    {(props) => (
                      <Button {...props} variant="default" radius="md" leftSection={<IconPhoto size={15} />}>
                        {cover ? "Change image" : "Choose image"}
                      </Button>
                    )}
                  </FileButton>
                  <Text size="xs" c="dimmed">
                    {cover ? cover.name : "A generated gradient is used if you skip this."}
                  </Text>
                </Group>
              </Box>

              <Divider label="Review" labelPosition="left" />

              <Stack gap={6}>
                {[
                  ["Name", draft.name],
                  ["Description", form.values.description.trim() || "—"],
                  ["Version", `v${uploaded.version.version_number} · ${uploaded.version.file_count} files · ${(Number(uploaded.version.size_bytes) / 1024 / 1024).toFixed(2)} MB`],
                  ["Entry point", uploaded.version.entry_point],
                  ["Access", accessIds.length === 0 ? "Managers only" : `${accessIds.length} ${accessIds.length === 1 ? "person" : "people"} + all managers`],
                  ["Cover", cover ? cover.name : "Generated gradient"],
                ].map(([k, v]) => (
                  <Group key={k as string} justify="space-between" wrap="nowrap" align="flex-start">
                    <Text size="xs" c="dimmed">{k}</Text>
                    <Text size="xs" ta="right" style={{ maxWidth: "60%" }}>{v}</Text>
                  </Group>
                ))}
              </Stack>

              <Group justify="space-between">
                <Button variant="subtle" radius="md" onClick={() => setStep(3)}>
                  Back
                </Button>
                <Button radius="md" loading={busy} leftSection={<IconRocket size={15} />} onClick={publish}>
                  Publish
                </Button>
              </Group>
            </Stack>
          )}
        </Stack>
      </Drawer>

      <ConfirmAlert
        title="Discard this dashboard?"
        titleIcon={
          <ThemeIcon size={22} radius="xl" variant="light" color="red">
            <IconAlertTriangle size={13} />
          </ThemeIcon>
        }
        message={`"${draft?.name ?? ""}" was created as a draft when you started. Cancelling deletes that draft and anything uploaded to it. This cannot be undone.`}
        modalProps={{ opened: confirmOpen, onClose: confirmHandlers.close }}
        handleConfirm={discardAndClose}
      />
    </>
  );
};

export default CreateDashboardWizard;
