"use client";

import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { AlertTriangle, Check } from "lucide-react";
import { Button, Field, Input, PageHeader, Panel, Select, Skeleton } from "@/components/ui/kit";
import { useToast } from "@/components/ui/toast";
import { BindingEditor, defaultBinding, type Slot } from "@/components/templates/send-template-dialog";
import { componentsToBubble, PreviewFrame, TemplateBubble } from "@/components/templates/template-preview";
import { api, ApiError } from "@/lib/api";
import { useApi } from "@/lib/use-api";
import { formatPaise } from "@/lib/format";
import type { AudiencePreview, Campaign, CampaignAudience, CampaignBindings, CustomField, Group, MessageTemplate } from "@/lib/types";

const STEPS = [
  { id: "details", label: "Details" },
  { id: "audience", label: "Audience" },
  { id: "message", label: "Message" },
  { id: "schedule", label: "Schedule" },
  { id: "review", label: "Review" },
] as const;
type StepId = (typeof STEPS)[number]["id"];

const nf = new Intl.NumberFormat("en-IN");

function stepForField(key: string): StepId {
  if (key.startsWith("title")) return "details";
  if (key.startsWith("audience")) return "audience";
  if (key.startsWith("template") || key.startsWith("bindings") || key.startsWith("account")) return "message";
  if (key.startsWith("schedule")) return "schedule";
  return "review";
}

/** "2026-10-14T10:30" for <input type="datetime-local">, in the given zone. */
function localInput(date: Date, timeZone: string) {
  const p = Object.fromEntries(new Intl.DateTimeFormat("en-CA", { timeZone, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }).formatToParts(date).map((x) => [x.type, x.value]));
  return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}`;
}

export function CampaignWizard({ initial }: { initial?: Campaign }) {
  const router = useRouter();
  const toast = useToast();
  const groupsReq = useApi<{ data: Group[] }>("/groups");
  const templatesReq = useApi<{ data: MessageTemplate[] }>("/templates?status=APPROVED");
  const fieldsReq = useApi<{ data: CustomField[] }>("/custom-fields");
  const groups = groupsReq.data?.data ?? [];
  const templates = useMemo(() => templatesReq.data?.data ?? [], [templatesReq.data]);
  const customFields = fieldsReq.data?.data ?? [];
  // Browsers differ on canonical names (Chromium lists Asia/Calcutta), so the saved zone and Asia/Kolkata are always offered.
  const timeZones = useMemo(() => {
    const list = typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("timeZone") : ["UTC"];
    return [...new Set([...list, "Asia/Kolkata", "UTC", initial?.timezone ?? "Asia/Kolkata"])].sort();
  }, [initial?.timezone]);

  const [step, setStepState] = useState<StepId>("details");
  // Furthest step reached; an existing campaign is complete, so every step is open.
  const [reached, setReached] = useState(initial ? STEPS.length - 1 : 0);
  const setStep = (id: StepId) => {
    setStepState(id);
    setReached((r) => Math.max(r, STEPS.findIndex((s) => s.id === id)));
  };
  const [title, setTitle] = useState(initial?.title ?? "");
  const [audience, setAudience] = useState<CampaignAudience>(initial?.audience ?? { all_contacts: false, group_ids: [], restrict_language: false, opted_in_only: true });
  const [templateId, setTemplateId] = useState<number | null>(initial?.template.id ?? null);
  const [bindings, setBindings] = useState<CampaignBindings>(initial?.bindings ?? { header: {}, body: {}, buttons: {} });
  const [mode, setMode] = useState<"now" | "later">(initial?.scheduled_at && initial.status === "scheduled" ? "later" : "now");
  const [timezone, setTimezone] = useState(initial?.timezone ?? "Asia/Kolkata");
  // The default time ("an hour from now") is filled when Schedule is chosen, not during render.
  const [scheduledLocal, setScheduledLocal] = useState(() => (initial?.scheduled_at ? localInput(new Date(initial.scheduled_at), initial.timezone) : ""));
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [saving, setSaving] = useState<"draft" | "launch" | null>(null);
  const [preview, setPreview] = useState<{ key: string; data: AudiencePreview | null; error?: string } | null>(null);

  const template = templates.find((t) => t.id === templateId) ?? null;
  const slots: Slot[] = useMemo(() => {
    if (!template) return [];
    const v = template.variables;
    return [
      ...v.header.variables.map((k) => ({ group: "header" as const, key: k, label: `Header {{${k}}}` })),
      ...v.body.map((k) => ({ group: "body" as const, key: k, label: `{{${k}}}` })),
      ...v.buttons.filter((b) => b.variable).map((b) => ({ group: "buttons" as const, key: String(b.index), label: `“${b.text}” link ending` })),
    ];
  }, [template]);

  function chooseTemplate(id: number) {
    const t = templates.find((x) => x.id === id);
    setTemplateId(id);
    clearErrors("template");
    clearErrors("bindings");
    if (!t) return;
    const b: CampaignBindings = { header: {}, body: {}, buttons: {} };
    t.variables.header.variables.forEach((k) => (b.header[k] = { source: "text", value: "" }));
    t.variables.body.forEach((k) => (b.body[k] = defaultBinding(k)));
    t.variables.buttons.filter((x) => x.variable).forEach((x) => (b.buttons[String(x.index)] = { source: "text", value: "" }));
    setBindings(b);
  }

  function clearErrors(prefix: string) {
    setErrors((e) => (Object.keys(e).some((k) => k.startsWith(prefix)) ? Object.fromEntries(Object.entries(e).filter(([k]) => !k.startsWith(prefix))) : e));
  }

  // Review step: live counts and the message as the first recipient will see it.
  const previewKey = JSON.stringify({ templateId, audience, bindings });
  const canPreview = step === "review" && templateId !== null && (audience.all_contacts || audience.group_ids.length > 0);
  useEffect(() => {
    if (!canPreview) return;
    let alive = true;
    api
      .post<{ data: AudiencePreview }>("/campaigns/audience-preview", { template_id: templateId, audience, bindings })
      .then((r) => alive && setPreview({ key: previewKey, data: r.data }))
      .catch((e: Error) => alive && setPreview({ key: previewKey, data: null, error: e.message }));
    return () => {
      alive = false;
    };
  }, [canPreview, previewKey, templateId, audience, bindings]);
  const review = preview?.key === previewKey ? preview : null;

  async function save(kind: "draft" | "launch") {
    setSaving(kind);
    setErrors({});
    const body = {
      title,
      template_id: templateId,
      audience,
      bindings,
      schedule: kind === "draft" ? { mode: "draft" } : mode === "now" ? { mode: "now" } : { mode: "later", scheduled_local: scheduledLocal, timezone },
    };
    try {
      const r = initial ? await api.patch<{ data: Campaign }>(`/campaigns/${initial.id}`, body) : await api.post<{ data: Campaign }>("/campaigns", body);
      toast("success", kind === "draft" ? "Draft saved" : mode === "now" ? "Campaign is sending" : "Campaign scheduled");
      router.push(kind === "draft" ? "/campaigns" : `/campaigns/${r.data.id}`);
    } catch (err) {
      if (err instanceof ApiError && Object.keys(err.fields).length) {
        setErrors(err.fields);
        const first = Object.keys(err.fields)[0];
        setStep(stepForField(first));
      } else toast("error", (err as Error).message);
    } finally {
      setSaving(null);
    }
  }

  const stepIndex = STEPS.findIndex((s) => s.id === step);
  const stepErrors = (id: StepId) => Object.keys(errors).some((k) => stepForField(k) === id);
  const fallbackBubble = template ? componentsToBubble(template.components) : null;
  const selectedGroups = groups.filter((g) => audience.group_ids.includes(g.id));

  const next = () => {
    if (step === "details" && !title.trim()) return setErrors({ title: "Give the campaign a name" });
    if (step === "audience" && !audience.all_contacts && !audience.group_ids.length) return setErrors({ "audience.group_ids": "Choose at least one group, or all contacts" });
    if (step === "message" && !templateId) return setErrors({ template_id: "Choose a template" });
    setStep(STEPS[Math.min(stepIndex + 1, STEPS.length - 1)].id);
  };

  return (
    <>
      <PageHeader
        title={initial ? `Edit ${initial.title}` : "New campaign"}
        description="Send an approved template to a group of contacts, now or at a time you choose."
        actions={
          <Button onClick={() => save("draft")} loading={saving === "draft"} disabled={!title.trim() || !templateId}>
            Save draft
          </Button>
        }
      />
      <div className="grid grid-cols-1 gap-6 lg:grid-cols-[220px_minmax(0,1fr)]">
        <nav aria-label="Campaign steps">
          <ol className="flex gap-2 overflow-x-auto lg:flex-col lg:gap-1">
            {STEPS.map((s, i) => {
              const done = i < stepIndex || (i <= reached && i !== stepIndex);
              const current = s.id === step;
              return (
                <li key={s.id} className="shrink-0">
                  <button
                    onClick={() => (i <= reached ? setStep(s.id) : next())}
                    aria-current={current ? "step" : undefined}
                    className={clsx("flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-sm", current ? "bg-white font-medium text-ink shadow-sm ring-1 ring-line" : "text-muted hover:text-text")}
                  >
                    <span
                      className={clsx(
                        "tabular grid size-6 shrink-0 place-items-center rounded-full text-xs font-semibold",
                        stepErrors(s.id) ? "bg-brick text-white" : done ? "bg-teal text-white" : current ? "bg-ink text-white" : "bg-white text-muted ring-1 ring-inset ring-line",
                      )}
                    >
                      {stepErrors(s.id) ? "!" : done ? <Check className="size-3.5" /> : i + 1}
                    </span>
                    {s.label}
                  </button>
                </li>
              );
            })}
          </ol>
        </nav>

        <Panel className="flex min-h-[460px] flex-col">
          <div className="flex-1 p-6">
            {step === "details" && (
              <div className="max-w-lg space-y-5">
                <h2 className="text-lg font-semibold">What is this campaign?</h2>
                <Field label="Campaign name" htmlFor="cw-title" error={errors.title} hint="Only your team sees this, e.g. Diwali offer, October">
                  <Input id="cw-title" value={title} onChange={(e) => (clearErrors("title"), setTitle(e.target.value))} maxLength={150} invalid={!!errors.title} autoFocus />
                </Field>
              </div>
            )}

            {step === "audience" && (
              <div className="max-w-2xl space-y-5">
                <h2 className="text-lg font-semibold">Who should receive it?</h2>
                <div className="grid gap-3 sm:grid-cols-2">
                  {[
                    { all: false, title: "Selected groups", body: "Contacts in any of the groups you choose." },
                    { all: true, title: "All contacts", body: "Every active contact in this workspace." },
                  ].map((o) => (
                    <label key={String(o.all)} className={clsx("cursor-pointer rounded-lg border p-4 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-mint", audience.all_contacts === o.all ? "border-teal bg-teal-soft/50" : "border-line hover:border-teal/40")}>
                      <input type="radio" name="aud" className="sr-only" checked={audience.all_contacts === o.all} onChange={() => (clearErrors("audience"), setAudience((a) => ({ ...a, all_contacts: o.all })))} />
                      <span className="block font-medium">{o.title}</span>
                      <span className="mt-0.5 block text-sm text-muted">{o.body}</span>
                    </label>
                  ))}
                </div>
                {!audience.all_contacts && (
                  <fieldset>
                    <legend className="mb-2 text-[13px] font-medium">Groups</legend>
                    {!groupsReq.data ? (
                      <Skeleton className="h-10 w-full" />
                    ) : groups.length === 0 ? (
                      <p className="text-sm text-muted">
                        No groups yet.{" "}
                        <Link href="/contacts/groups" className="font-medium text-teal hover:underline">
                          Create groups
                        </Link>{" "}
                        or send to all contacts.
                      </p>
                    ) : (
                      <div className="flex flex-wrap gap-2">
                        {groups.map((g) => {
                          const on = audience.group_ids.includes(g.id);
                          return (
                            <label key={g.id} className={clsx("cursor-pointer rounded-full border px-3 py-1.5 text-sm has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-mint", on ? "border-teal bg-teal-soft text-teal" : "border-line text-muted hover:border-teal/40")}>
                              <input
                                type="checkbox"
                                className="sr-only"
                                checked={on}
                                onChange={() => (clearErrors("audience"), setAudience((a) => ({ ...a, group_ids: on ? a.group_ids.filter((x) => x !== g.id) : [...a.group_ids, g.id] })))}
                              />
                              {g.name} <span className="tabular opacity-70">({nf.format(g.contacts_count)})</span>
                            </label>
                          );
                        })}
                      </div>
                    )}
                    {errors["audience.group_ids"] && <p className="mt-2 text-[13px] text-brick">{errors["audience.group_ids"]}</p>}
                  </fieldset>
                )}
                <div className="space-y-3 rounded-lg bg-paper p-4">
                  <label className="flex gap-3">
                    <input type="checkbox" className="mt-1 size-4 accent-teal" checked={audience.opted_in_only} onChange={(e) => setAudience((a) => ({ ...a, opted_in_only: e.target.checked }))} />
                    <span className="text-sm">
                      <span className="font-medium">Only contacts who opted in to marketing</span>
                      <span className="block text-muted">Recommended. Applies to marketing templates; opted-out contacts are always excluded.</span>
                    </span>
                  </label>
                  <label className="flex gap-3">
                    <input type="checkbox" className="mt-1 size-4 accent-teal" checked={audience.restrict_language} onChange={(e) => setAudience((a) => ({ ...a, restrict_language: e.target.checked }))} />
                    <span className="text-sm">
                      <span className="font-medium">Only contacts whose language matches the template</span>
                      <span className="block text-muted">For example an en_US template goes to contacts set to en, en_US or en_GB.</span>
                    </span>
                  </label>
                </div>
              </div>
            )}

            {step === "message" && (
              <div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_300px]">
                <div className="space-y-5">
                  <h2 className="text-lg font-semibold">What will they receive?</h2>
                  {templatesReq.data && templates.length === 0 ? (
                    <p className="text-sm text-muted">
                      No approved templates yet.{" "}
                      <Link href="/templates" className="font-medium text-teal hover:underline">
                        Create or sync templates
                      </Link>{" "}
                      first.
                    </p>
                  ) : (
                    <Field label="Template" htmlFor="cw-template" error={errors.template_id} hint={template?.category === "MARKETING" ? "Marketing template: never sent to opted-out contacts." : undefined}>
                      <Select id="cw-template" value={templateId ?? ""} onChange={(e) => chooseTemplate(Number(e.target.value))} invalid={!!errors.template_id}>
                        <option value="" disabled>
                          Choose an approved template
                        </option>
                        {templates.map((t) => (
                          <option key={t.id} value={t.id}>
                            {t.name} ({t.language}, {t.category.toLowerCase()})
                          </option>
                        ))}
                        {initial && templateId && !template && <option value={templateId}>{initial.template.name} (no longer approved)</option>}
                      </Select>
                    </Field>
                  )}
                  {template?.variables.header.media && (
                    <Field label={`Header ${template.variables.header.format?.toLowerCase()} link`} htmlFor="cw-media" error={errors["bindings.header_media_link"]} hint="A public https link. Every recipient gets the same file.">
                      <Input id="cw-media" value={bindings.header_media_link ?? ""} onChange={(e) => setBindings((b) => ({ ...b, header_media_link: e.target.value || undefined }))} invalid={!!errors["bindings.header_media_link"]} />
                    </Field>
                  )}
                  {slots.length > 0 && <p className="text-[13px] text-muted">Personalise each variable from contact details, or type the same value for everyone. Contacts missing a value with no fallback are skipped.</p>}
                  {slots.map((s) => (
                    <BindingEditor
                      key={`${s.group}-${s.key}`}
                      slot={s}
                      customFields={customFields}
                      value={bindings[s.group][s.key] ?? { source: "text", value: "" }}
                      error={errors[`bindings.${s.group}.${s.key}`]}
                      onChange={(b) => (clearErrors(`bindings.${s.group}.${s.key}`), setBindings((cur) => ({ ...cur, [s.group]: { ...cur[s.group], [s.key]: b } })))}
                    />
                  ))}
                </div>
                <aside>
                  <p className="mb-2 text-[13px] font-medium text-muted">Template</p>
                  <PreviewFrame>{fallbackBubble ? <TemplateBubble {...fallbackBubble} /> : <p className="py-8 text-center text-sm text-[#667781]">Choose a template</p>}</PreviewFrame>
                </aside>
              </div>
            )}

            {step === "schedule" && (
              <div className="max-w-xl space-y-5">
                <h2 className="text-lg font-semibold">When should it go out?</h2>
                <div className="grid gap-3 sm:grid-cols-2">
                  {[
                    { v: "now" as const, title: "Send now", body: "Starts within seconds of launching." },
                    { v: "later" as const, title: "Schedule", body: "Pick a date and time, up to 90 days ahead." },
                  ].map((o) => (
                    <label key={o.v} className={clsx("cursor-pointer rounded-lg border p-4 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-mint", mode === o.v ? "border-teal bg-teal-soft/50" : "border-line hover:border-teal/40")}>
                      <input type="radio" name="when" className="sr-only" checked={mode === o.v} onChange={() => {
                          clearErrors("schedule");
                          setMode(o.v);
                          if (o.v === "later" && !scheduledLocal) setScheduledLocal(localInput(new Date(Date.now() + 3600_000), timezone));
                        }} />
                      <span className="block font-medium">{o.title}</span>
                      <span className="mt-0.5 block text-sm text-muted">{o.body}</span>
                    </label>
                  ))}
                </div>
                {mode === "later" && (
                  <div className="grid gap-4 sm:grid-cols-2">
                    <Field label="Date and time" htmlFor="cw-at" error={errors["schedule.scheduled_local"]}>
                      <Input id="cw-at" type="datetime-local" value={scheduledLocal} onChange={(e) => (clearErrors("schedule"), setScheduledLocal(e.target.value))} invalid={!!errors["schedule.scheduled_local"]} />
                    </Field>
                    <Field label="Time zone" htmlFor="cw-tz" error={errors["schedule.timezone"]}>
                      <Select id="cw-tz" value={timezone} onChange={(e) => setTimezone(e.target.value)}>
                        {timeZones.map((z) => (
                          <option key={z} value={z}>
                            {z.replace(/_/g, " ")}
                          </option>
                        ))}
                      </Select>
                    </Field>
                  </div>
                )}
              </div>
            )}

            {step === "review" && (
              <div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_300px]">
                <div className="space-y-5">
                  <h2 className="text-lg font-semibold">Check and launch</h2>
                  {!review ? (
                    <Skeleton className="h-28 w-full" />
                  ) : review.error ? (
                    <p className="rounded-md bg-brick-soft px-3 py-2 text-sm text-brick">{review.error}</p>
                  ) : (
                    review.data && (
                      <>
                        <div className="flex items-baseline gap-3">
                          <span className="tabular text-[40px] font-semibold leading-none tracking-tight text-ink">{nf.format(review.data.eligible)}</span>
                          <span className="text-sm text-muted">of {nf.format(review.data.targeted)} targeted contacts will receive this message</span>
                        </div>
                        {review.data.estimated_cost && review.data.estimated_cost.total_paise > 0 && (
                          <p className={clsx("rounded-md px-3 py-2 text-sm", review.data.estimated_cost.total_paise > review.data.estimated_cost.available_paise ? "bg-brick-soft text-brick" : "bg-paper text-muted")}>
                            Estimated cost about {formatPaise(review.data.estimated_cost.total_paise)} ({formatPaise(review.data.estimated_cost.per_message_paise)} per delivered message) from your wallet, which has{" "}
                            {formatPaise(review.data.estimated_cost.available_paise)} available.
                            {review.data.estimated_cost.total_paise > review.data.estimated_cost.available_paise && (
                              <>
                                {" "}
                                <Link href="/billing" className="font-medium underline">
                                  Recharge first
                                </Link>
                                .
                              </>
                            )}
                          </p>
                        )}
                        {(Object.keys(review.data.suppressed).length > 0 || review.data.missing_values > 0) && (
                          <ul className="space-y-1.5 rounded-lg bg-paper p-4 text-sm">
                            {Object.entries(review.data.suppressed).map(([reason, n]) => (
                              <li key={reason} className="flex justify-between gap-4">
                                <span className="text-muted">Excluded: {reason.toLowerCase()}</span>
                                <span className="tabular font-medium">{nf.format(n)}</span>
                              </li>
                            ))}
                            {review.data.missing_values > 0 && (
                              <li className="flex justify-between gap-4 text-amber">
                                <span>Skipped: missing a value for a variable (add a fallback to include them)</span>
                                <span className="tabular font-medium">{nf.format(review.data.missing_values)}</span>
                              </li>
                            )}
                          </ul>
                        )}
                        {review.data.eligible === 0 && (
                          <p className="flex items-start gap-2 rounded-md bg-amber-soft px-3 py-2 text-sm text-amber">
                            <AlertTriangle className="mt-0.5 size-4 shrink-0" />
                            Nobody would receive this campaign. Change the audience or the consent setting.
                          </p>
                        )}
                      </>
                    )
                  )}
                  <dl className="grid grid-cols-[110px_1fr] gap-x-4 gap-y-2 border-t border-line pt-4 text-sm">
                    <dt className="text-muted">Name</dt>
                    <dd>{title || "—"}</dd>
                    <dt className="text-muted">Audience</dt>
                    <dd>{audience.all_contacts ? "All contacts" : selectedGroups.map((g) => g.name).join(", ") || "—"}</dd>
                    <dt className="text-muted">Template</dt>
                    <dd>{template ? `${template.name} (${template.language})` : "—"}</dd>
                    <dt className="text-muted">Sends</dt>
                    <dd>{mode === "now" ? "Immediately after launch" : `${scheduledLocal.replace("T", " at ")} (${timezone.replace(/_/g, " ")})`}</dd>
                  </dl>
                </div>
                <aside>
                  <p className="mb-2 text-[13px] font-medium text-muted">{review?.data?.sample[0] ? `As ${review.data.sample[0].name} will see it` : "Message"}</p>
                  <PreviewFrame>
                    {review?.data?.message ? (
                      <TemplateBubble header={review.data.message.header} body={review.data.message.body} footer={review.data.message.footer} buttons={fallbackBubble?.buttons} />
                    ) : fallbackBubble ? (
                      <TemplateBubble {...fallbackBubble} />
                    ) : (
                      <p className="py-8 text-center text-sm text-[#667781]">Choose a template</p>
                    )}
                  </PreviewFrame>
                </aside>
              </div>
            )}
          </div>

          <footer className="flex items-center justify-between gap-3 border-t border-line bg-paper/60 px-6 py-3">
            <Button onClick={() => setStep(STEPS[Math.max(0, stepIndex - 1)].id)} disabled={stepIndex === 0}>
              Back
            </Button>
            {step === "review" ? (
              <Button variant="primary" loading={saving === "launch"} disabled={!review?.data || review.data.eligible === 0} onClick={() => save("launch")}>
                {mode === "now" ? "Launch campaign" : "Schedule campaign"}
              </Button>
            ) : (
              <Button variant="primary" onClick={next}>
                Continue
              </Button>
            )}
          </footer>
        </Panel>
      </div>
    </>
  );
}
