"use client";

import { useEffect, useState } from "react";
import { DateTime } from "luxon";
import { PlusIcon, RepeatIcon, Trash2Icon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import {
  WEEKDAY_LABELS,
  isoWeekdayToCron,
  type RecurrenceSpec,
} from "@/lib/recurrence";

interface RecurrencePickerProps {
  /**
   * First fire — only used to seed defaults for newly-added rules
   * (initial weekday, monthday, time). Each rule then carries its
   * own hour/minute, so changing the date+time inputs above no
   * longer reshapes existing rules.
   */
  firstFire: DateTime;
  value: RecurrenceSpec;
  onChange: (next: RecurrenceSpec) => void;
}

// ---------------------------------------------------------------------------
// Per-row draft (one recurring rule)
// ---------------------------------------------------------------------------

type RuleType = "daily" | "weekly" | "monthly" | "yearly";

interface Draft {
  type: RuleType;
  /** Cron weekday list (0=Sun..6=Sat). */
  weekdays: number[];
  /** Sorted unique day-of-month list (1-31). Used by monthly + yearly. */
  monthDays: number[];
  /** Sorted unique month list (1-12). Used by yearly only. */
  months: number[];
  /** Hour-of-day (0-23) for this rule's fire time. */
  hour: number;
  /** Minute-of-hour (0-59). */
  minute: number;
}

const MAX_RULES = 8;

function defaultDraft(firstFire: DateTime): Draft {
  return {
    type: "daily",
    weekdays: [isoWeekdayToCron(firstFire.weekday)],
    monthDays: [firstFire.day],
    months: [firstFire.month],
    hour: firstFire.hour,
    minute: firstFire.minute,
  };
}

function uniqSortedDays(days: number[]): number[] {
  return Array.from(new Set(days.filter((d) => d >= 1 && d <= 31))).sort((a, b) => a - b);
}

function uniqSortedMonths(months: number[]): number[] {
  return Array.from(new Set(months.filter((m) => m >= 1 && m <= 12))).sort((a, b) => a - b);
}

function pad2(n: number): string {
  return n.toString().padStart(2, "0");
}

function parseHHMM(s: string): { hour: number; minute: number } | null {
  const m = s.match(/^(\d{1,2}):(\d{2})$/);
  if (!m) return null;
  const h = Number(m[1]);
  const min = Number(m[2]);
  if (h < 0 || h > 23 || min < 0 || min > 59) return null;
  return { hour: h, minute: min };
}

function clamp(n: number, lo: number, hi: number): number {
  if (!Number.isFinite(n)) return lo;
  return Math.min(Math.max(Math.floor(n), lo), hi);
}

const MONTH_NAMES = [
  "January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December",
];

function draftToCron(d: Draft): string | null {
  const m = clamp(d.minute, 0, 59);
  const h = clamp(d.hour, 0, 23);
  switch (d.type) {
    case "daily":
      return `${m} ${h} * * *`;
    case "weekly":
      if (!d.weekdays.length) return null;
      return `${m} ${h} * * ${d.weekdays.slice().sort((a, b) => a - b).join(",")}`;
    case "monthly": {
      const days = uniqSortedDays(d.monthDays);
      if (!days.length) return null;
      return `${m} ${h} ${days.join(",")} * *`;
    }
    case "yearly": {
      const days = uniqSortedDays(d.monthDays);
      const months = uniqSortedMonths(d.months);
      if (!days.length || !months.length) return null;
      return `${m} ${h} ${days.join(",")} ${months.join(",")} *`;
    }
  }
}

function describeDraft(d: Draft): string {
  const t = `${pad2(clamp(d.hour, 0, 23))}:${pad2(clamp(d.minute, 0, 59))}`;
  switch (d.type) {
    case "daily":
      return `Every day at ${t}`;
    case "weekly": {
      if (!d.weekdays.length) return "Pick at least one weekday";
      const labels = d.weekdays
        .slice()
        .sort((a, b) => a - b)
        .map((c) => {
          const iso = c === 0 ? 7 : c;
          return WEEKDAY_LABELS[iso - 1]?.short ?? "";
        })
        .filter(Boolean)
        .join(", ");
      return `Every week on ${labels} at ${t}`;
    }
    case "monthly": {
      const days = uniqSortedDays(d.monthDays);
      if (!days.length) return "Pick at least one day";
      const list =
        days.length <= 6
          ? days.join(", ")
          : `${days.slice(0, 6).join(", ")} +${days.length - 6} more`;
      return `Every month on day${days.length > 1 ? "s" : ""} ${list} at ${t}`;
    }
    case "yearly": {
      const days = uniqSortedDays(d.monthDays);
      const months = uniqSortedMonths(d.months);
      if (!days.length) return "Pick at least one day";
      if (!months.length) return "Pick at least one month";
      const monthLabel = months.map((mo) => MONTH_NAMES[mo - 1]?.slice(0, 3)).join(", ");
      const dayLabel =
        days.length <= 6
          ? days.join(", ")
          : `${days.slice(0, 6).join(", ")} +${days.length - 6} more`;
      return `Every year in ${monthLabel} on day${days.length > 1 ? "s" : ""} ${dayLabel} at ${t}`;
    }
  }
}

/** Reverse-engineer a single cron expression into a Draft. Falls back to
 *  daily-every-day if the expression doesn't match a known shape. */
function draftFromCronExpr(expr: string, firstFire: DateTime): Draft {
  const base = defaultDraft(firstFire);
  // Pull MM HH off the front so each rule restores its own time.
  const head = expr.match(/^(\d+)\s+(\d+)\s+(.*)$/);
  if (!head) return base;
  const minute = clamp(Number(head[1]), 0, 59);
  const hour = clamp(Number(head[2]), 0, 23);
  const rest = head[3]!.trim();

  let m: RegExpMatchArray | null;
  if (rest === "* * *") {
    return { ...base, type: "daily", hour, minute };
  }
  // Any DOW list (including the legacy "1-5" weekday-only daily rule)
  // round-trips as a Weekly draft.
  if ((m = rest.match(/^\* \* ([0-9,\-]+)$/))) {
    const days = m[1]!
      .split(",")
      .flatMap((p) => {
        const r = p.match(/^(\d+)-(\d+)$/);
        if (r) {
          const out: number[] = [];
          for (let i = Number(r[1]); i <= Number(r[2]); i++) out.push(i);
          return out;
        }
        return [Number(p)];
      })
      .filter((n) => n >= 0 && n <= 6);
    return { ...base, type: "weekly", weekdays: days, hour, minute };
  }
  if ((m = rest.match(/^([0-9,]+) \* \*$/))) {
    const days = uniqSortedDays(m[1]!.split(",").map((s) => Number(s)));
    return {
      ...base,
      type: "monthly",
      monthDays: days.length ? days : [1],
      hour,
      minute,
    };
  }
  if ((m = rest.match(/^([0-9,]+) ([0-9,]+) \*$/))) {
    const days = uniqSortedDays(m[1]!.split(",").map((s) => Number(s)));
    const months = uniqSortedMonths(m[2]!.split(",").map((s) => Number(s)));
    return {
      ...base,
      type: "yearly",
      monthDays: days.length ? days : [1],
      months: months.length ? months : [1],
      hour,
      minute,
    };
  }
  return { ...base, hour, minute };
}

/** Parse a (possibly multi-line) cron rule into an array of drafts. */
function draftsFromRule(rule: string | null | undefined, firstFire: DateTime): Draft[] {
  if (!rule) return [];
  const expr = rule.startsWith("CRON:") ? rule.slice(5) : rule;
  const lines = expr.split("\n").map((s) => s.trim()).filter(Boolean);
  if (lines.length === 0) return [];
  return lines.map((line) => draftFromCronExpr(line, firstFire));
}

/** Compile an array of drafts to a single multi-line CRON: rule, or null
 *  if there are no rules (= one-off). */
function draftsToRule(drafts: Draft[]): string | null {
  if (drafts.length === 0) return null;
  const exprs = drafts
    .map((d) => draftToCron(d))
    .filter((s): s is string => Boolean(s));
  if (exprs.length === 0) return null;
  return exprs.join("\n");
}

// ---------------------------------------------------------------------------
// The picker
// ---------------------------------------------------------------------------

/**
 * Inline recurrence picker. Lives in the When form right under the
 * date+time inputs.
 *
 *   Repeats
 *   ┌─────────────────────────────────────────────────────┐
 *   │  Schedule 1                              [✕ remove]  │
 *   │  [Daily] [Weekly] [Monthly] [Yearly]                │
 *   │  <per-tab config>                                    │
 *   ├─────────────────────────────────────────────────────┤
 *   │  Schedule 2                              [✕ remove]  │
 *   │  [Daily] [Weekly] [Monthly] [Yearly]                │
 *   │  <per-tab config>                                    │
 *   └─────────────────────────────────────────────────────┘
 *   [+ Add another schedule]
 *
 *   Fires:
 *     • Every weekday at 09:00
 *     • Every Friday at 17:00
 *
 * Zero rules = "Don't repeat" (fires once at date+time and ends).
 * Adding a rule emits a `{ kind: "cron", cron: "<line1>\n<line2>" }`
 * spec; the bot's `nextOccurrence` already supports newline-joined
 * cron and returns the earliest next fire across all rules.
 */
export function RecurrencePicker({ firstFire, value, onChange }: RecurrencePickerProps) {
  const [drafts, setDrafts] = useState<Draft[]>(() =>
    draftsFromRule(value.kind === "cron" ? value.cron ?? null : null, firstFire),
  );

  // Compile drafts back to the parent value whenever they change. Each
  // rule carries its own hour/minute now, so the date+time inputs above
  // no longer drive cron output — they only set the very first fire.
  useEffect(() => {
    const rule = draftsToRule(drafts);
    if (!rule) {
      if (value.kind !== "none") {
        onChange({ kind: "none", interval: 1, weeklyDays: [], end: { kind: "never" } });
      }
      return;
    }
    if (value.kind !== "cron" || value.cron !== rule) {
      onChange({
        kind: "cron",
        interval: 1,
        weeklyDays: [],
        cron: rule,
        end: { kind: "never" },
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [drafts]);

  function updateDraft(idx: number, patch: Partial<Draft>) {
    setDrafts((prev) => prev.map((d, i) => (i === idx ? { ...d, ...patch } : d)));
  }
  function addRule() {
    if (drafts.length >= MAX_RULES) return;
    setDrafts((prev) => [...prev, defaultDraft(firstFire)]);
  }
  function removeRule(idx: number) {
    setDrafts((prev) => prev.filter((_, i) => i !== idx));
  }

  return (
    <div className="space-y-2">
      <Label className="flex items-center gap-1.5">
        <RepeatIcon className="size-3.5" />
        Repeats
      </Label>

      {drafts.length === 0 ? (
        <div className="rounded-xl border border-border bg-muted/20 px-3 py-3 text-sm text-muted-foreground">
          No Repeats
        </div>
      ) : (
        <div className="overflow-hidden rounded-xl border border-border bg-card">
          {drafts.map((draft, idx) => (
            <div
              key={idx}
              className={cn(
                "p-3 space-y-3",
                idx > 0 && "border-t border-border",
              )}
            >
              <div className="flex items-center justify-between gap-2">
                <span className="text-xs font-medium text-muted-foreground">
                  Schedule {idx + 1}
                </span>
                <Button
                  type="button"
                  size="sm"
                  variant="ghost"
                  className="h-7 px-2 text-muted-foreground hover:text-destructive"
                  onClick={() => removeRule(idx)}
                  aria-label={`Remove schedule ${idx + 1}`}
                >
                  <Trash2Icon className="size-3.5" />
                </Button>
              </div>

              <RuleEditor
                draft={draft}
                onChange={(patch) => updateDraft(idx, patch)}
              />

              <p className="rounded-lg bg-primary/5 px-2.5 py-1.5 text-xs text-primary/80">
                {describeDraft(draft)}
              </p>
            </div>
          ))}
        </div>
      )}

      <div>
        <Button
          type="button"
          size="sm"
          variant="outline"
          onClick={addRule}
          disabled={drafts.length >= MAX_RULES}
          className="gap-1.5"
        >
          <PlusIcon className="size-3.5" />
          {drafts.length === 0 ? "Add a recurring schedule" : "Add another schedule"}
        </Button>
        {drafts.length >= MAX_RULES && (
          <span className="ml-2 text-xs text-muted-foreground">
            Up to {MAX_RULES} schedules per reminder
          </span>
        )}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Single-rule editor (tabs + per-type config)
// ---------------------------------------------------------------------------

interface RuleEditorProps {
  draft: Draft;
  onChange: (patch: Partial<Draft>) => void;
}

function RuleEditor({ draft, onChange }: RuleEditorProps) {
  return (
    <Tabs
      value={draft.type}
      onValueChange={(v) => onChange({ type: v as RuleType })}
    >
      <TabsList className="w-full">
        <TabsTrigger value="daily">Daily</TabsTrigger>
        <TabsTrigger value="weekly">Weekly</TabsTrigger>
        <TabsTrigger value="monthly">Monthly</TabsTrigger>
        <TabsTrigger value="yearly">Yearly</TabsTrigger>
      </TabsList>

      <TabsContent value="daily" className="space-y-3 pt-3">
        <p className="text-xs text-muted-foreground">
          Fires once a day at the time below.
        </p>
        <TimeField draft={draft} onChange={onChange} />
      </TabsContent>

      <TabsContent value="weekly" className="space-y-3 pt-3">
        <div className="space-y-2">
          <Label className="text-sm">On these days</Label>
          <div className="flex flex-wrap gap-1.5">
            {WEEKDAY_LABELS.map(({ iso, short }) => {
              const cronDow = isoWeekdayToCron(iso);
              const active = draft.weekdays.includes(cronDow);
              return (
                <button
                  key={iso}
                  type="button"
                  onClick={() =>
                    onChange({
                      weekdays: active
                        ? draft.weekdays.filter((d) => d !== cronDow)
                        : [...draft.weekdays, cronDow].sort((a, b) => a - b),
                    })
                  }
                  aria-pressed={active}
                  className={cn(
                    "inline-flex h-8 min-w-12 items-center justify-center rounded-lg border px-2.5 text-xs font-medium transition-colors",
                    active
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background hover:border-primary/50 hover:bg-primary/5 hover:text-primary",
                  )}
                >
                  {short}
                </button>
              );
            })}
          </div>
        </div>
        <TimeField draft={draft} onChange={onChange} />
      </TabsContent>

      <TabsContent value="monthly" className="space-y-3 pt-3">
        <div className="space-y-2">
          <div className="flex items-center justify-between gap-2">
            <Label className="text-sm">Days of the month</Label>
            <span className="text-xs text-muted-foreground">
              {draft.monthDays.length} selected
            </span>
          </div>
          <div className="grid grid-cols-7 gap-1">
            {Array.from({ length: 31 }, (_, i) => i + 1).map((day) => {
              const active = draft.monthDays.includes(day);
              return (
                <button
                  key={day}
                  type="button"
                  onClick={() =>
                    onChange({
                      monthDays: active
                        ? draft.monthDays.filter((d) => d !== day)
                        : uniqSortedDays([...draft.monthDays, day]),
                    })
                  }
                  aria-pressed={active}
                  className={cn(
                    "inline-flex h-8 items-center justify-center rounded-md border text-xs font-medium transition-colors",
                    active
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background hover:border-primary/50 hover:bg-primary/5 hover:text-primary",
                  )}
                >
                  {day}
                </button>
              );
            })}
          </div>
          <p className="text-xs text-muted-foreground">
            Months without a selected day skip naturally (e.g. day 31 in February).
          </p>
        </div>
        <TimeField draft={draft} onChange={onChange} />
      </TabsContent>

      <TabsContent value="yearly" className="space-y-3 pt-3">
        <div className="space-y-2">
          <div className="flex items-center justify-between gap-2">
            <Label className="text-sm">Months</Label>
            <span className="text-xs text-muted-foreground">
              {draft.months.length} selected
            </span>
          </div>
          <div className="grid grid-cols-6 gap-1">
            {MONTH_NAMES.map((name, i) => {
              const month = i + 1;
              const active = draft.months.includes(month);
              return (
                <button
                  key={month}
                  type="button"
                  onClick={() =>
                    onChange({
                      months: active
                        ? draft.months.filter((mo) => mo !== month)
                        : uniqSortedMonths([...draft.months, month]),
                    })
                  }
                  aria-pressed={active}
                  className={cn(
                    "inline-flex h-8 items-center justify-center rounded-md border text-xs font-medium transition-colors",
                    active
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background hover:border-primary/50 hover:bg-primary/5 hover:text-primary",
                  )}
                >
                  {name.slice(0, 3)}
                </button>
              );
            })}
          </div>
        </div>

        <div className="space-y-2">
          <div className="flex items-center justify-between gap-2">
            <Label className="text-sm">Days</Label>
            <span className="text-xs text-muted-foreground">
              {draft.monthDays.length} selected
            </span>
          </div>
          <div className="grid grid-cols-7 gap-1">
            {Array.from({ length: 31 }, (_, i) => i + 1).map((day) => {
              const active = draft.monthDays.includes(day);
              return (
                <button
                  key={day}
                  type="button"
                  onClick={() =>
                    onChange({
                      monthDays: active
                        ? draft.monthDays.filter((d) => d !== day)
                        : uniqSortedDays([...draft.monthDays, day]),
                    })
                  }
                  aria-pressed={active}
                  className={cn(
                    "inline-flex h-8 items-center justify-center rounded-md border text-xs font-medium transition-colors",
                    active
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background hover:border-primary/50 hover:bg-primary/5 hover:text-primary",
                  )}
                >
                  {day}
                </button>
              );
            })}
          </div>
          <p className="text-xs text-muted-foreground">
            Fires on every selected day of every selected month.
          </p>
        </div>

        <TimeField draft={draft} onChange={onChange} />
      </TabsContent>
    </Tabs>
  );
}

interface TimeFieldProps {
  draft: Draft;
  onChange: (patch: Partial<Draft>) => void;
}

/** Per-rule time picker (HH:MM). Drives the cron expression's MM HH columns. */
function TimeField({ draft, onChange }: TimeFieldProps) {
  const value = `${pad2(clamp(draft.hour, 0, 23))}:${pad2(clamp(draft.minute, 0, 59))}`;
  return (
    <div className="space-y-1">
      <Label className="text-xs text-muted-foreground">Fires at</Label>
      <Input
        type="time"
        value={value}
        onChange={(e) => {
          const parsed = parseHHMM(e.target.value);
          if (parsed) onChange(parsed);
        }}
        className="h-8 w-32"
      />
    </div>
  );
}

