"use client";

import clsx from "clsx";
import { useLayoutEffect, useMemo, useRef, useState, type FormEvent, type KeyboardEvent } from "react";
import { AlertCircle, ArrowLeft, Bot, Check, CheckCheck, Clock, ExternalLink, FileText, Info, List, Loader2, Paperclip, Reply, Send, X } from "lucide-react";
import { Button, Select, Skeleton } from "@/components/ui/kit";
import { useToast } from "@/components/ui/toast";
import { useSession } from "@/components/session";
import { api, ApiError } from "@/lib/api";
import { formatPhone, initials } from "@/lib/format";
import type { ChatMessage, ConversationDetail, ConversationEvent, TeamMember } from "@/lib/types";

function hoursLeft(expires: string | null) {
  if (!expires) return 0;
  return Math.max(0, (new Date(expires).getTime() - Date.now()) / 3600_000);
}

function StatusTick({ m }: { m: ChatMessage }) {
  if (m.status === "failed") return <AlertCircle className="size-3.5 text-brick" aria-label="Failed" />;
  if (m.status === "read") return <CheckCheck className="size-3.5 text-[#53bdeb]" aria-label="Read" />;
  if (m.status === "delivered") return <CheckCheck className="size-3.5 text-[#8696a0]" aria-label="Delivered" />;
  if (m.status === "sent") return <Check className="size-3.5 text-[#8696a0]" aria-label="Sent" />;
  return <Clock className="size-3 text-[#8696a0]" aria-label="Sending" />;
}

const dayLabel = (iso: string) => {
  const d = new Date(iso);
  const today = new Date();
  const y = new Date(today);
  y.setDate(today.getDate() - 1);
  if (d.toDateString() === today.toDateString()) return "Today";
  if (d.toDateString() === y.toDateString()) return "Yesterday";
  return d.toLocaleDateString("en-IN", { weekday: "short", day: "numeric", month: "short", year: d.getFullYear() === today.getFullYear() ? undefined : "numeric" });
};
const timeOf = (iso: string) => new Date(iso).toLocaleTimeString("en-IN", { hour: "numeric", minute: "2-digit" });

function Bubble({ m }: { m: ChatMessage }) {
  const out = m.direction === "out";
  const isImage = m.media && m.type === "image";
  const isVideo = m.media && m.type === "video";
  const isAudio = m.media && m.type === "audio";
  const card = m.interactive && !m.interactive.reply_to ? m.interactive : null;
  const replied = m.interactive && m.interactive.reply_to ? m.interactive : null;
  return (
    <div className={clsx("flex", out ? "justify-end" : "justify-start")}>
      <div className={clsx("max-w-[min(520px,85%)] overflow-hidden rounded-lg text-[14px] leading-snug shadow-sm", out ? "rounded-tr-none bg-[#d9fdd3]" : "rounded-tl-none bg-white")}>
        <div className="px-2.5 pb-1.5 pt-1.5">
          {m.type === "template" && <div className="mb-1 text-[11px] font-medium uppercase tracking-wide text-teal">Template</div>}
          {m.automation && (
            <div className="mb-1 flex items-center gap-1 text-[11px] font-medium uppercase tracking-wide text-teal">
              <Bot className="size-3.5" />
              {m.automation.kind === "flow" ? "Flow" : "Bot reply"}
              {m.automation.name && <span className="truncate normal-case tracking-normal text-[#667781]">{m.automation.name}</span>}
            </div>
          )}
          {replied && (
            <div className="mb-1 flex items-center gap-1 text-[11px] font-medium uppercase tracking-wide text-[#667781]">
              <Reply className="size-3.5" /> Tapped
            </div>
          )}
          {card?.header_media && <div className="mb-1 rounded-md bg-black/5 px-3 py-2 text-[12px] text-[#667781]">[{card.header_media} header]</div>}
          {isImage && (
            <a href={m.media!.url} target="_blank" rel="noreferrer" className="mb-1 block overflow-hidden rounded-md bg-black/5">
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img src={m.media!.url} alt={m.body || "Image"} className="max-h-72 w-full object-cover" loading="lazy" />
            </a>
          )}
          {isVideo && <video src={m.media!.url} controls preload="metadata" className="mb-1 max-h-72 w-full rounded-md bg-black" />}
          {isAudio && <audio src={m.media!.url} controls preload="none" className="mb-1 w-64 max-w-full" />}
          {m.media && !isImage && !isVideo && !isAudio && (
            <a href={m.media.url} className="mb-1 flex items-center gap-2 rounded-md bg-black/5 px-3 py-2 text-[13px] hover:bg-black/10">
              <FileText className="size-5 shrink-0 text-teal" />
              <span className="min-w-0 flex-1 truncate">{m.media.filename ?? `${m.type} file`}</span>
            </a>
          )}
          {m.body && <div className="whitespace-pre-wrap break-words text-[#111b21]">{m.body}</div>}
          {m.redacted && <div className="italic text-[#667781]">Content erased under this workspace&apos;s message retention setting</div>}
          {!m.redacted && !m.body && !m.media && <div className="italic text-[#667781]">[{m.type} message]</div>}
          {m.status === "failed" && m.error_message && <div className="mt-1 text-[12px] text-brick">Not delivered: {m.error_message}</div>}
          <div className="mt-0.5 flex items-center justify-end gap-1 text-[11px] text-[#667781]">
            {out && m.sent_by_name && <span className="mr-1 truncate">{m.sent_by_name}</span>}
            <span>{timeOf(m.created_at)}</span>
            {out && <StatusTick m={m} />}
          </div>
        </div>
        {card && (card.buttons.length > 0 || card.cta || card.list_button) && (
          <div className="divide-y divide-black/10 border-t border-black/10">
            {card.buttons.map((b, i) => (
              <div key={i} className="flex items-center justify-center gap-1.5 py-1.5 text-[13px] font-medium text-[#027eb5]">
                <Reply className="size-3.5" />
                {b}
              </div>
            ))}
            {card.cta && (
              <a href={card.cta.url} target="_blank" rel="noreferrer" className="flex items-center justify-center gap-1.5 py-1.5 text-[13px] font-medium text-[#027eb5] hover:underline">
                <ExternalLink className="size-3.5" />
                {card.cta.text}
              </a>
            )}
            {card.list_button && (
              <div className="py-1.5 text-center text-[13px] font-medium text-[#027eb5]" title={card.list_rows.join(", ")}>
                <List className="mr-1.5 inline size-3.5" />
                {card.list_button}
                <span className="ml-1 font-normal text-[#667781]">({card.list_rows.length} options)</span>
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

export function ChatPanel({
  conversation,
  messages,
  events,
  hasMore,
  loadingOlder,
  onLoadOlder,
  team,
  onBack,
  onSent,
  onChanged,
  onSendTemplate,
  onToggleDetails,
}: {
  conversation: ConversationDetail | null;
  messages: ChatMessage[] | null;
  events: ConversationEvent[];
  hasMore: boolean;
  loadingOlder: boolean;
  onLoadOlder: () => void;
  team: TeamMember[];
  onBack: () => void;
  onSent: () => void;
  onChanged: () => void;
  onSendTemplate: () => void;
  onToggleDetails: () => void;
}) {
  const { me, can } = useSession();
  const toast = useToast();
  const [text, setText] = useState("");
  const [file, setFile] = useState<File | null>(null);
  const [sending, setSending] = useState(false);
  const [busy, setBusy] = useState<string | null>(null);
  const fileRef = useRef<HTMLInputElement>(null);
  const scroller = useRef<HTMLDivElement>(null);
  const lastId = messages?.at(-1)?.id;

  // Stick to the newest message when one arrives.
  useLayoutEffect(() => {
    const el = scroller.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [lastId, conversation?.id]);

  const timeline = useMemo(() => {
    const items: ({ kind: "msg"; at: string; m: ChatMessage } | { kind: "event"; at: string; e: ConversationEvent })[] = [
      ...(messages ?? []).map((m) => ({ kind: "msg" as const, at: m.created_at, m })),
      ...events.map((e) => ({ kind: "event" as const, at: e.created_at, e })),
    ];
    items.sort((a, b) => a.at.localeCompare(b.at));
    // Precompute where a new day starts so render stays pure.
    return items.map((item, i) => ({ ...item, day: dayLabel(item.at), showDay: i === 0 || dayLabel(items[i - 1].at) !== dayLabel(item.at) }));
  }, [messages, events]);

  if (!conversation) {
    return (
      <div className="hidden h-full flex-1 flex-col items-center justify-center bg-[#efeae2] p-8 text-center md:flex">
        <p className="max-w-xs text-sm text-[#54656f]">Choose a conversation to read and reply. New WhatsApp messages appear here as they arrive.</p>
      </div>
    );
  }

  const c = conversation;
  const left = hoursLeft(c.window.expires_at);
  const anyone = can("inbox.assign_any");
  const myId = me?.user.id;
  const blockedByAssignment = !anyone && c.assigned_user && c.assigned_user.id !== myId;
  const canReply = can("inbox.reply") && !blockedByAssignment;

  async function act(name: string, fn: () => Promise<unknown>, msg?: string) {
    setBusy(name);
    try {
      await fn();
      if (msg) toast("success", msg);
      onChanged();
    } catch (e) {
      toast("error", (e as Error).message);
    } finally {
      setBusy(null);
    }
  }

  async function send(e?: FormEvent) {
    e?.preventDefault();
    if (sending || (!text.trim() && !file)) return;
    setSending(true);
    try {
      if (file) {
        const fd = new FormData();
        fd.append("file", file);
        if (text.trim()) fd.append("caption", text.trim());
        await api.post(`/inbox/conversations/${c.id}/messages`, fd);
      } else {
        await api.post(`/inbox/conversations/${c.id}/messages`, { text: text.trim() });
      }
      setText("");
      setFile(null);
      onSent();
    } catch (err) {
      const msg = err instanceof ApiError ? (Object.values(err.fields)[0] ?? err.message) : (err as Error).message;
      toast("error", msg);
      if (err instanceof ApiError && err.code === "window_closed") onChanged();
    } finally {
      setSending(false);
    }
  }

  function onKey(e: KeyboardEvent<HTMLTextAreaElement>) {
    if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
      e.preventDefault();
      send();
    }
  }

  return (
    <section className="flex h-full min-h-0 flex-1 flex-col bg-[#efeae2]" aria-label={`Conversation with ${c.contact.name}`}>
      <header className="flex flex-wrap items-center gap-3 border-b border-line bg-white px-3 py-2.5 sm:px-4">
        <button onClick={onBack} aria-label="Back to conversations" className="rounded p-1 text-muted hover:bg-paper md:hidden">
          <ArrowLeft className="size-5" />
        </button>
        <span className="grid size-10 shrink-0 place-items-center rounded-full bg-teal-soft text-sm font-semibold text-teal">{initials(c.contact.name)}</span>
        <div className="min-w-0 flex-1">
          <div className="truncate font-semibold text-ink">{c.contact.name}</div>
          <div className="flex flex-wrap items-center gap-x-2 text-xs">
            <span className="tabular text-muted">{formatPhone(c.contact.phone)}</span>
            {c.window.open ? (
              <span className="text-teal">Reply window open, {left >= 1 ? `${Math.floor(left)}h` : `${Math.max(1, Math.round(left * 60))}m`} left</span>
            ) : (
              <span className="text-amber">Reply window closed</span>
            )}
          </div>
        </div>
        <div className="flex flex-wrap items-center gap-2">
          {anyone ? (
            <Select
              aria-label="Assigned to"
              className="!h-8 w-auto max-w-44 text-[13px]"
              value={c.assigned_user?.id ?? ""}
              disabled={busy === "assign"}
              onChange={(e) => act("assign", () => api.post(`/inbox/conversations/${c.id}/assign`, { user_id: e.target.value ? Number(e.target.value) : null }), "Assignment updated")}
            >
              <option value="">Unassigned</option>
              {team.map((t) => (
                <option key={t.id} value={t.id}>
                  {t.id === myId ? `${t.name} (you)` : t.name}
                </option>
              ))}
            </Select>
          ) : c.assigned_user?.id === myId ? (
            <Button size="sm" loading={busy === "assign"} onClick={() => act("assign", () => api.post(`/inbox/conversations/${c.id}/assign`, { user_id: null }), "Released")}>
              Release
            </Button>
          ) : !c.assigned_user ? (
            <Button size="sm" loading={busy === "assign"} onClick={() => act("assign", () => api.post(`/inbox/conversations/${c.id}/assign`, { user_id: myId }), "Assigned to you")}>
              Assign to me
            </Button>
          ) : (
            <span className="text-xs text-muted">Assigned to {c.assigned_user.name}</span>
          )}
          {canReply &&
            (c.status === "open" ? (
              <Button size="sm" loading={busy === "close"} onClick={() => act("close", () => api.post(`/inbox/conversations/${c.id}/close`), "Conversation closed")}>
                Close
              </Button>
            ) : (
              <Button size="sm" loading={busy === "reopen"} onClick={() => act("reopen", () => api.post(`/inbox/conversations/${c.id}/reopen`), "Conversation reopened")}>
                Reopen
              </Button>
            ))}
          <Button size="sm" variant="ghost" aria-label="Contact details" icon={<Info className="size-4" />} onClick={onToggleDetails} className="xl:hidden" />
        </div>
      </header>

      <div ref={scroller} className="min-h-0 flex-1 space-y-1.5 overflow-y-auto px-3 py-4 [background-image:radial-gradient(#d9d2c5_1px,transparent_1px)] [background-size:14px_14px] sm:px-6">
        {hasMore && (
          <div className="pb-2 text-center">
            <Button size="sm" onClick={onLoadOlder} loading={loadingOlder}>
              Load earlier messages
            </Button>
          </div>
        )}
        {!messages && (
          <div className="space-y-3">
            <Skeleton className="h-12 w-2/3" />
            <Skeleton className="ml-auto h-12 w-1/2" />
          </div>
        )}
        {messages?.length === 0 && (
          <p className="mx-auto mt-8 max-w-sm rounded-lg bg-[#fff8c5] px-4 py-3 text-center text-[13px] text-[#54656f]">
            No messages yet. Start with an approved template: WhatsApp only allows free-form messages after the customer writes to you.
          </p>
        )}
        {timeline.map((item) => {
          const { day, showDay } = item;
          return (
            <div key={`${item.kind}-${item.kind === "msg" ? item.m.id : item.e.id}`}>
              {showDay && (
                <div className="my-3 text-center">
                  <span className="rounded-md bg-white/90 px-2.5 py-1 text-[11.5px] text-[#54656f] shadow-sm">{day}</span>
                </div>
              )}
              {item.kind === "msg" ? (
                <Bubble m={item.m} />
              ) : (
                <div className="my-2 text-center">
                  <span className="rounded-md bg-[#e1f2fb] px-2.5 py-1 text-[11.5px] text-[#54656f]">{item.e.description}</span>
                </div>
              )}
            </div>
          );
        })}
      </div>

      <footer className="border-t border-line bg-[#f0f2f5] p-2.5 sm:p-3">
        {!can("inbox.reply") ? (
          <p className="py-2 text-center text-sm text-muted">You can read this conversation but not reply.</p>
        ) : blockedByAssignment ? (
          <p className="py-2 text-center text-sm text-muted">Assigned to {c.assigned_user!.name}. Ask an owner to reassign it to reply.</p>
        ) : !c.window.open ? (
          <div className="flex flex-wrap items-center justify-between gap-3 rounded-lg bg-white px-4 py-3">
            <p className="max-w-lg text-sm text-muted">
              {c.contact.first_name} hasn&apos;t messaged in the last 24 hours, so WhatsApp only allows an approved template. Free replies open again when they respond.
            </p>
            <Button variant="primary" size="sm" onClick={onSendTemplate}>
              Send template
            </Button>
          </div>
        ) : (
          <form onSubmit={send} className="space-y-2">
            {file && (
              <div className="flex items-center gap-2 rounded-md bg-white px-3 py-2 text-sm">
                <Paperclip className="size-4 text-teal" />
                <span className="min-w-0 flex-1 truncate">{file.name}</span>
                <span className="text-xs text-muted">{(file.size / 1024).toFixed(0)} KB</span>
                <button type="button" aria-label="Remove attachment" onClick={() => setFile(null)} className="text-muted hover:text-brick">
                  <X className="size-4" />
                </button>
              </div>
            )}
            <div className="flex items-end gap-2">
              <button type="button" aria-label="Attach a file" onClick={() => fileRef.current?.click()} className="grid size-10 shrink-0 place-items-center rounded-full text-[#54656f] hover:bg-black/5">
                <Paperclip className="size-5" />
              </button>
              <input
                ref={fileRef}
                type="file"
                className="sr-only"
                accept="image/jpeg,image/png,application/pdf,text/plain,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
                onChange={(e) => {
                  setFile(e.target.files?.[0] ?? null);
                  e.target.value = "";
                }}
              />
              <label className="min-w-0 flex-1">
                <span className="sr-only">{file ? "Caption" : "Message"}</span>
                <textarea
                  rows={1}
                  value={text}
                  onChange={(e) => setText(e.target.value)}
                  onKeyDown={onKey}
                  maxLength={file ? 1024 : 4096}
                  placeholder={file ? "Add a caption (optional)" : "Type a message"}
                  className="block max-h-40 min-h-10 w-full resize-none rounded-lg border-0 bg-white px-3 py-2.5 text-[14px] focus:outline-none focus:ring-2 focus:ring-teal/20 [field-sizing:content]"
                />
              </label>
              <button type="submit" aria-label="Send" disabled={sending || (!text.trim() && !file)} className="grid size-10 shrink-0 place-items-center rounded-full bg-teal text-white hover:bg-teal-dark disabled:bg-teal/40">
                {sending ? <Loader2 className="size-5 animate-spin" /> : <Send className="size-[18px]" />}
              </button>
            </div>
            <div className="flex justify-between px-1 text-[11.5px] text-muted">
              <span>Enter to send, Shift+Enter for a new line</span>
              <button type="button" onClick={onSendTemplate} className="font-medium text-teal hover:underline">
                Send a template
              </button>
            </div>
          </form>
        )}
      </footer>
    </section>
  );
}

