"use client";

import { useMemo, useState } from "react";
import {
  useOrderReceipt,
  useOrderReview,
  useParticipants,
  useSetOrderReview,
  useSetSplits,
  useClearSplits,
  type ItemOpinion,
  type ItemVerdict,
  type OrderReceipt,
  type OrderRating,
} from "@/lib/hooks";

const PLATFORM_LABEL: Record<string, string> = {
  doordash: "DoorDash",
  ubereats: "Uber Eats",
  uber: "Uber",
};

/**
 * Who records verdicts. A two-person household with one primary user: the
 * owner records almost everything, and the only other consumer is Sonu (user,
 * 2026-07-28). Mirrors OWNER_PARTICIPANT_ID / SECOND_CONSUMER_ID in
 * `lib/order-reviews.ts` — duplicated rather than imported because that module
 * pulls in the database client and this is a client component.
 */
const OWNER_PARTICIPANT_ID = 1;
const SECOND_CONSUMER_ID = 4;

const RATING_LABEL: Record<OrderRating, string> = {
  loved: "Loved it",
  liked: "Liked it",
  ok: "OK",
  bad: "Bad",
  never: "Never again",
};

const RATING_STYLE: Record<OrderRating, string> = {
  loved: "border-emerald-600 bg-emerald-950 text-emerald-300",
  liked: "border-emerald-800 bg-emerald-950/50 text-emerald-400",
  ok: "border-zinc-600 bg-zinc-800 text-zinc-300",
  bad: "border-amber-800 bg-amber-950 text-amber-300",
  never: "border-red-800 bg-red-950 text-red-300",
};

const RATING_ORDER: OrderRating[] = ["loved", "liked", "ok", "bad", "never"];

/**
 * The receipt behind a delivery order: what was actually bought, where it went,
 * and what we thought of it.
 *
 * The receipt half is read-only — it is what a provider sent, not something to
 * edit. The verdict half is the only part of an order that changes, and it is
 * the reason the receipts are ingested at all (ING-9): the ledger already knew
 * we had ordered from here, but not that it was bad, so orders got repeated
 * from places we disliked because nobody remembered.
 */
export function OrderDetails({
  transactionId,
  currency,
  bare = false,
}: {
  transactionId: number;
  currency: string | null;
  /** Drop the top border and heading spacing when embedded in a table row. */
  bare?: boolean;
}) {
  const { data: receipt, isLoading } = useOrderReceipt(transactionId);
  const { data: review } = useOrderReview(transactionId);
  const [reviewer, setReviewer] = useState(OWNER_PARTICIPANT_ID);

  // `platform` is the marker of a real receipt. Since phase 2 the endpoint also
  // answers with `{ order_link }` alone for a transaction that is merely LINKED
  // to an order (a BNPL leg has no receipt of its own), and that object is
  // truthy — without this check the panel would render a receipt shell with no
  // merchant, no items and no total.
  if (isLoading || !receipt || !receipt.platform) return null;

  const cur = receipt.currency ?? currency ?? "AUD";
  const fmt = (n: number) => (cur === "AUD" ? `$${n.toFixed(2)}` : `${cur} ${n.toFixed(2)}`);
  const items: OrderReceipt["line_items"] = receipt.line_items ?? [];
  const route: OrderReceipt["route"] = receipt.route ?? [];

  const mine = review?.reviews.find((r) => r.participant_id === reviewer);

  // Verdicts are the MEAL lane's feedback loop — "loved / never again" exists
  // to steer the next restaurant order, and the nudge card warns on re-order.
  // Spine-bridged retail (Amazon boxes, eBay, Officeworks) has no such loop:
  // rating a roller-shade motor "order again?" is noise, so the whole review
  // surface stays hidden for bridge rows.
  const rateable = receipt.source !== "order-bridge";

  return (
    <div className={bare ? "" : "border-t border-zinc-800 pt-4"}>
      <div className="flex items-baseline justify-between mb-2">
        <p className="text-xs text-zinc-500">
          Order details
          {receipt.platform && (
            <span className="ml-1.5 text-zinc-400">{PLATFORM_LABEL[receipt.platform] ?? receipt.platform}</span>
          )}
        </p>
        {receipt.card_last4 && (
          <span className="text-xs text-zinc-600">card ••••{receipt.card_last4}</span>
        )}
      </div>

      {items.length > 0 ? (
        <ul className="space-y-1.5 mb-3">
          {items.map((it, i) => (
            <li key={i} className="flex gap-2 text-xs items-start">
              <span className="text-zinc-600 tabular-nums shrink-0">{it.qty ?? 1}×</span>
              <span className="text-zinc-300 flex-1 min-w-0">
                {it.description}
                {it.options && it.options.length > 0 && (
                  <span className="block text-zinc-600">{it.options.join(" · ")}</span>
                )}
              </span>
              {rateable && (
                <ItemVerdictToggle
                  transactionId={transactionId}
                  reviewer={reviewer}
                  item={it.description}
                  current={mine?.item_verdicts ?? []}
                  rating={mine?.rating ?? null}
                  note={mine?.note ?? null}
                />
              )}
              {/* Spine-bridged receipts (Amazon shipment notices, eBay) often
                  name the goods without per-item prices — a null here is "the
                  mail didn't say", and printing $0.00 would assert it did. */}
              {it.amount != null && (
                <span className="text-zinc-400 tabular-nums shrink-0">{fmt(Number(it.amount))}</span>
              )}
            </li>
          ))}
        </ul>
      ) : (
        // Uber itemises groceries but not restaurant orders, and orders taken
        // before this was parsed have none either. Say which, rather than
        // showing an empty list that reads like a bug.
        <p className="text-xs text-zinc-600 italic mb-3">
          No itemised list on this receipt
        </p>
      )}

      {route.length > 0 && (
        <div className="space-y-1">
          {route.map((pt, i) => (
            <div key={i} className="flex gap-2 text-xs">
              <span className="text-zinc-600 shrink-0 w-24">
                {pt.label}
                {pt.time && <span className="block text-zinc-700">{pt.time}</span>}
              </span>
              <span className="text-zinc-400 flex-1">{pt.address}</span>
            </div>
          ))}
        </div>
      )}

      {receipt.order_reference && !receipt.order_reference.startsWith("msg:") && (
        <p className="mt-3 text-[11px] text-zinc-700 font-mono break-all">
          {receipt.order_reference}
        </p>
      )}

      {rateable && (
        <OrderVerdict
          transactionId={transactionId}
          reviewer={reviewer}
          onReviewerChange={setReviewer}
        />
      )}
    </div>
  );
}

/**
 * Loved / never on a single line item, for the currently selected reviewer.
 *
 * Only the two poles are offered. A per-item "OK" is noise: the question at the
 * next order is "what should I get, what should I avoid", and a middling dish
 * answers neither.
 *
 * Every press sends the whole item array plus the current rating and note,
 * because the endpoint upserts a row rather than patching fields — sending a
 * partial would blank whatever it omitted.
 */
function ItemVerdictToggle({
  transactionId,
  reviewer,
  item,
  current,
  rating,
  note,
}: {
  transactionId: number;
  reviewer: number;
  item: string;
  current: ItemOpinion[];
  rating: OrderRating | null;
  note: string | null;
}) {
  const save = useSetOrderReview();
  const existing = current.find(
    (v) => v.item.trim().toLowerCase() === item.trim().toLowerCase()
  );

  const toggle = (verdict: ItemVerdict) => {
    const rest = current.filter(
      (v) => v.item.trim().toLowerCase() !== item.trim().toLowerCase()
    );
    // Pressing the active verdict clears it — a mis-tap must be reversible, and
    // there is no other route back to "no opinion on this dish".
    const next =
      existing?.verdict === verdict ? rest : [...rest, { item, verdict }];
    save.mutate({
      transactionId,
      participantId: reviewer,
      rating,
      note,
      itemVerdicts: next,
    });
  };

  return (
    <span className="flex gap-0.5 shrink-0">
      {(["loved", "never"] as ItemVerdict[]).map((v) => (
        <button
          key={v}
          type="button"
          onClick={() => toggle(v)}
          disabled={save.isPending}
          title={v === "loved" ? "Loved this item" : "Never order this again"}
          className={`rounded px-1 leading-none transition-opacity disabled:opacity-40 ${
            existing?.verdict === v
              ? "opacity-100"
              : "opacity-25 hover:opacity-60"
          }`}
        >
          {v === "loved" ? "👍" : "👎"}
        </button>
      ))}
    </span>
  );
}

/**
 * Was this order shared? One tap, and the split is the answer.
 *
 * "Shared" means shared in both senses — we both ate it and we both pay for it
 * — so this writes a real 50/50 `transaction_splits` row rather than a
 * decorative flag (user, 2026-07-28: the split was part of the original
 * requirement). There is no separate "shared" column precisely because the
 * split already IS that record, and two records of one fact drift apart.
 *
 * Unsharing clears the splits. That is safe on an order because an ingested
 * order is post-cutover by construction — the DB CHECK forbids credits orders
 * before 2026-01-09 — so no settled historical obligation can be sitting on it
 * to lose.
 */
function SharedToggle({
  transactionId,
  splits,
  otherName,
}: {
  transactionId: number;
  splits: { participant_id: number; share_percent: string }[];
  otherName: string;
}) {
  const setSplits = useSetSplits();
  const clearSplits = useClearSplits();
  const shared = splits.some((s) => s.participant_id === SECOND_CONSUMER_ID);

  return (
    <div className="mb-2 flex items-center gap-2">
      <button
        type="button"
        disabled={setSplits.isPending || clearSplits.isPending}
        onClick={() =>
          // Both halves on the way in, and a real delete on the way out. This
          // used to post one 50% row to share and an empty array to un-share,
          // and the endpoint rejected both — the toggle did nothing either way.
          shared
            ? clearSplits.mutate(transactionId)
            : setSplits.mutate({
                transactionId,
                splits: [
                  { participant_id: OWNER_PARTICIPANT_ID, share_percent: 50 },
                  { participant_id: SECOND_CONSUMER_ID, share_percent: 50 },
                ],
              })
        }
        className={`rounded border px-2 py-1 text-xs transition-colors disabled:opacity-50 ${
          shared
            ? "border-sky-700 bg-sky-950 text-sky-300"
            : "border-zinc-700 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300"
        }`}
      >
        {shared ? `Shared 50/50 with ${otherName}` : "Just me"}
      </button>
      {shared && (
        <span className="text-[11px] text-zinc-600">
          ask {otherName} for her verdict too
        </span>
      )}
    </div>
  );
}

/**
 * The overall verdict, whose it is, and this merchant's track record.
 *
 * The history sits above the buttons deliberately: it is read before the next
 * order, not after, and burying it under the form is how you re-order from a
 * place you already rejected.
 */
function OrderVerdict({
  transactionId,
  reviewer,
  onReviewerChange,
}: {
  transactionId: number;
  reviewer: number;
  onReviewerChange: (id: number) => void;
}) {
  const { data, isLoading } = useOrderReview(transactionId);
  const { data: participants } = useParticipants();
  const save = useSetOrderReview();
  const [noteDraft, setNoteDraft] = useState<string | null>(null);

  const reviewers = useMemo(
    () =>
      [OWNER_PARTICIPANT_ID, SECOND_CONSUMER_ID].map((id) => ({
        id,
        name:
          id === OWNER_PARTICIPANT_ID
            ? "Me"
            : participants?.find((p) => p.id === id)?.name ?? "Them",
      })),
    [participants]
  );

  // No merchant means no receipt behind this row — nothing to have a view on.
  if (isLoading || !data?.merchant) return null;

  const mine = data.reviews.find((r) => r.participant_id === reviewer);
  const current = mine?.rating ?? null;
  const noteValue = noteDraft ?? mine?.note ?? "";
  const { history, warn, items } = data.merchant;
  const others = data.reviews.filter((r) => r.participant_id !== reviewer && r.rating);

  const set = (rating: OrderRating) =>
    save.mutate({
      transactionId,
      participantId: reviewer,
      // Pressing the active rating clears it — otherwise a mis-tap is
      // permanent, and there is no other way back to "no opinion".
      rating: rating === current ? null : rating,
      note: noteValue.trim() || null,
    });

  return (
    <div className="mt-4 border-t border-zinc-800 pt-3">
      {warn && (
        <p className="mb-2 text-xs text-red-400">
          Marked &ldquo;never again&rdquo; here before.
        </p>
      )}

      {items.length > 0 && (
        <p className="mb-2 text-[11px] text-zinc-500">
          {items
            .filter((i) => i.loved > i.never)
            .slice(0, 3)
            .map((i) => `👍 ${i.item}`)
            .concat(
              items
                .filter((i) => i.never > 0)
                .slice(0, 3)
                .map((i) => `👎 ${i.item}`)
            )
            .join(" · ")}
        </p>
      )}

      <SharedToggle
        transactionId={transactionId}
        splits={data.splits}
        otherName={reviewers[1].name}
      />

      <div className="flex items-center gap-2 mb-2">
        {reviewers.map((r) => (
          <button
            key={r.id}
            type="button"
            onClick={() => {
              setNoteDraft(null); // the draft belongs to the person who typed it
              onReviewerChange(r.id);
            }}
            className={`text-xs transition-colors ${
              reviewer === r.id
                ? "text-zinc-200 underline underline-offset-4"
                : "text-zinc-600 hover:text-zinc-400"
            }`}
          >
            {r.name}
            {data.reviews.some((v) => v.participant_id === r.id && v.rating) && " ✓"}
          </button>
        ))}
      </div>

      <div className="flex flex-wrap gap-1.5">
        {RATING_ORDER.map((r) => (
          <button
            key={r}
            type="button"
            onClick={() => set(r)}
            disabled={save.isPending}
            className={`rounded border px-2 py-1 text-xs transition-colors disabled:opacity-50 ${
              current === r
                ? RATING_STYLE[r]
                : "border-zinc-700 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300"
            }`}
          >
            {RATING_LABEL[r]}
          </button>
        ))}
      </div>

      <input
        type="text"
        value={noteValue}
        placeholder="What was wrong (or right)?"
        onChange={(e) => setNoteDraft(e.target.value)}
        onBlur={() => {
          const next = noteValue.trim() || null;
          if (next !== (mine?.note ?? null)) {
            save.mutate({
              transactionId,
              participantId: reviewer,
              rating: current,
              note: next,
            });
          }
        }}
        className="mt-2 w-full rounded border border-zinc-800 bg-zinc-900 px-2 py-1 text-xs text-zinc-300 placeholder:text-zinc-700 focus:border-zinc-600 focus:outline-none"
      />

      {others.map((o) => (
        <p key={o.participant_id} className="mt-1.5 text-[11px] text-zinc-500">
          <span className="text-zinc-400">{o.participant_name}:</span>{" "}
          {o.rating && RATING_LABEL[o.rating]}
          {o.note && <span className="italic"> — {o.note}</span>}
        </p>
      ))}

      {history.length > 0 && (
        <ul className="mt-2 space-y-1">
          {history.map((h) => (
            <li
              key={`${h.transaction_id}-${h.participant_id}`}
              className="text-[11px] text-zinc-600"
            >
              <span className="tabular-nums">{h.transaction_date}</span>
              <span className="ml-1.5 text-zinc-500">
                {h.participant_name}
                {h.rating ? ` · ${RATING_LABEL[h.rating]}` : ""}
              </span>
              {h.note && <span className="ml-1.5 italic">{h.note}</span>}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
