import type { ViewpointBookingRow } from "@/lib/features/viewpoint/query";

/**
 * The columns the Viewpoint bookings table can show, and which are on by default.
 *
 * Declared once so the header, the cell and the export all read from the same list.
 * With the column set now chosen by a super admin, three separate lists would drift
 * the moment one is edited — a header with no cell under it, or an export missing a
 * column the table shows.
 *
 * WHY THIS IS A CONSOLE-WIDE SETTING AND NOT A PER-USER ONE
 *
 * Which columns are worth showing depends on what the imported reports actually
 * carry, and Viewpoint's report layouts vary — a report with no amount column makes
 * "Total amount" dead weight for everyone, not just for whoever noticed. So it is a
 * judgement about the loaded data, and the answer should be the same for every user.
 * It is stored under the `viewpoint-booking-columns` console setting, alongside the
 * promo theme, and written only by a super admin.
 */

export interface ViewpointColumnDef {
  key: string;
  label: string;
  /**
   * Cannot be turned off.
   *
   * The booking number is how a row is identified, in Viewpoint and in this table.
   * A table of bookings with no booking number cannot be reconciled against
   * anything, so it is not offered as a choice.
   */
  locked?: boolean;
  /** Right-aligned, for numbers and dates. */
  numeric?: boolean;
  /** Export column width, in characters. */
  width: number;
  /** The value used for the Excel export. Kept next to the label it belongs to. */
  exportValue: (row: ViewpointBookingRow) => string;
}

/**
 * Every available column, in the order the table renders them.
 *
 * Order is fixed rather than part of the saved setting: reordering is a separate
 * feature with its own interface, and storing an order would silently drop any column
 * added later from everyone's saved list.
 */
export const VIEWPOINT_COLUMNS: ViewpointColumnDef[] = [
  {
    key: "booking_no",
    label: "Booking",
    locked: true,
    numeric: true,
    width: 16,
    exportValue: (r) => r.booking_no,
  },
  {
    key: "status",
    label: "Status",
    width: 16,
    exportValue: (r) => r.status ?? "",
  },
  {
    key: "booking_source",
    /*
     * Renamed from "Source". The booking's source is Subito-or-Viewpoint (the `origin`
     * column below); this is what the *report* called it, which on a real export is
     * "KCGM" for 159 of 160 rows and says nothing about which system took the booking.
     */
    label: "Report source",
    width: 20,
    exportValue: (r) => r.booking_source ?? "",
  },
  {
    key: "booking_source_code",
    label: "Source code",
    width: 14,
    exportValue: (r) => r.booking_source_code ?? "",
  },
  {
    key: "booking_type",
    label: "Booking type",
    width: 16,
    exportValue: (r) => r.booking_type ?? "",
  },
  {
    key: "resort_name",
    label: "Resort",
    width: 24,
    // Falls back to the code: a report that carries only the code should still
    // identify the property rather than showing an empty cell.
    exportValue: (r) => r.resort_name ?? r.resort_code ?? "",
  },
  {
    key: "room_type",
    label: "Room type",
    width: 18,
    exportValue: (r) => r.room_type ?? "",
  },
  {
    key: "arrival",
    label: "Arrival",
    numeric: true,
    width: 14,
    exportValue: (r) => r.arrival ?? "",
  },
  {
    key: "departure",
    label: "Departure",
    numeric: true,
    width: 14,
    exportValue: (r) => r.departure ?? "",
  },
  {
    key: "booking_date",
    label: "Booked on",
    numeric: true,
    width: 14,
    exportValue: (r) => r.booking_date ?? "",
  },
  {
    key: "guests",
    label: "Guests",
    numeric: true,
    width: 10,
    exportValue: (r) => formatGuests(r),
  },
  {
    key: "guest_name",
    label: "Guest",
    width: 24,
    exportValue: (r) => r.guest_name ?? "",
  },
  {
    key: "total_amount",
    label: "Amount",
    numeric: true,
    width: 14,
    // Currency travels with the figure — an amount column with mixed currencies and
    // no unit is worse than no column.
    exportValue: (r) =>
      r.total_amount ? `${r.currency ?? ""} ${r.total_amount}`.trim() : "",
  },
  {
    key: "account_id",
    label: "Account ID",
    width: 16,
    exportValue: (r) => r.account_id ?? "",
  },
  {
    key: "membership_number",
    label: "Membership #",
    numeric: true,
    width: 16,
    exportValue: (r) => r.membership_number ?? "",
  },
  {
    key: "member",
    label: "Member",
    width: 24,
    exportValue: (r) =>
      r.member
        ? [r.member.firstName, r.member.lastName].filter(Boolean).join(" ")
        : "",
  },
  {
    key: "member_match",
    label: "Match",
    width: 20,
    // The match caveat travels with the export, so a spreadsheet reader sees what
    // the table shows rather than an unexplained blank Member cell.
    exportValue: (r) =>
      !r.has_membership_number
        ? "no number"
        : r.member_ambiguous
          ? "ambiguous membership"
          : r.member
            ? `matched on ${r.member_matched_on}`
            : "unmatched",
  },
  {
    key: "promo_code",
    label: "Promo code",
    width: 18,
    exportValue: (r) => r.member?.signupPromoCode ?? "",
  },
  {
    key: "source_of_truth",
    label: "Data from",
    width: 12,
    exportValue: (r) => r.source_of_truth,
  },
  {
    key: "modify_date_time",
    label: "Modified",
    numeric: true,
    width: 18,
    exportValue: (r) => r.modify_date_time ?? "",
  },

  /*
   * From a real Viewpoint export. All off by default — the table would be unreadable
   * with 36 columns — but available, which is the whole point of the column setting.
   */
  {
    key: "confirmed_date",
    label: "Confirmed",
    numeric: true,
    width: 14,
    exportValue: (r) => r.confirmed_date ?? "",
  },
  {
    key: "infants",
    label: "Infants",
    numeric: true,
    width: 10,
    exportValue: (r) => (r.infants === null ? "" : String(r.infants)),
  },
  {
    key: "guest_nationality",
    label: "Nationality",
    width: 18,
    exportValue: (r) => r.guest_nationality ?? "",
  },
  {
    key: "guest_email",
    label: "Guest email",
    width: 28,
    exportValue: (r) => r.guest_email ?? "",
  },
  {
    key: "guest_phone",
    label: "Guest phone",
    width: 18,
    exportValue: (r) => r.guest_phone ?? "",
  },
  {
    key: "guest_mobile",
    label: "Guest mobile",
    width: 18,
    exportValue: (r) => r.guest_mobile ?? "",
  },
  {
    key: "guest_city",
    label: "Guest city",
    width: 18,
    exportValue: (r) => r.guest_city ?? "",
  },
  {
    key: "guest_state",
    label: "Guest state",
    width: 18,
    exportValue: (r) => r.guest_state ?? "",
  },
  {
    key: "guest_country",
    label: "Guest country",
    width: 18,
    exportValue: (r) => r.guest_country ?? "",
  },
  {
    key: "guest_dob",
    label: "Guest DOB",
    numeric: true,
    width: 14,
    exportValue: (r) => r.guest_dob ?? "",
  },
  {
    key: "guest_marital_status",
    label: "Marital status",
    width: 14,
    exportValue: (r) => r.guest_marital_status ?? "",
  },
  {
    key: "flight_arrival",
    label: "Arrival flight",
    width: 18,
    // Number and time in one cell: neither is useful without the other, and two
    // columns for one fact wastes the table's width.
    exportValue: (r) =>
      [r.flight_arrival_no, r.flight_arrival_time].filter(Boolean).join(" "),
  },
  {
    key: "flight_departure",
    label: "Departure flight",
    width: 18,
    exportValue: (r) =>
      [r.flight_departure_no, r.flight_departure_time].filter(Boolean).join(" "),
  },
  {
    key: "comments",
    label: "Comments",
    width: 40,
    exportValue: (r) => r.comments ?? "",
  },

  /*
   * Reconciliation against core. On by default: whether a booking also exists in
   * Subito is the question the import is there to answer, so it should not have to be
   * switched on to be seen.
   */
  {
    key: "origin",
    // The booking's actual source: which system it was taken in.
    label: "Booking source",
    width: 16,
    exportValue: (r) => originOf(r),
  },
  {
    key: "core_booking_status",
    label: "Core status",
    width: 16,
    // Only meaningful where a match exists; blank rather than "—" so a spreadsheet
    // reader isn't invited to compare a status against nothing.
    exportValue: (r) => r.core_booking_status ?? "",
  },
];

/**
 * Which system a booking came from.
 *
 * Three outcomes, not two. "Not checked" is separate from "only in Viewpoint" because
 * before a reconciliation has run nothing is known about overlap, and calling that
 * Viewpoint-only would be reporting an absence of evidence as a finding.
 */
export function originOf(row: ViewpointBookingRow): string {
  if (row.core_booking_id) return "Karma Subito";
  // "Manual" describes how the booking was made — keyed into Viewpoint by hand rather
  // than taken through Karma Subito. Naming it after the system it was read from said
  // where we found it, not how it happened.
  if (row.core_matched_at) return "Manual booking";
  return "Not checked";
}

/** Adults + children as one cell, since most reports carry both or neither. */
export function formatGuests(row: ViewpointBookingRow): string {
  const parts: string[] = [];
  if (row.adults !== null && row.adults !== undefined) {
    parts.push(`${row.adults}A`);
  }
  if (row.children !== null && row.children !== undefined && row.children > 0) {
    parts.push(`${row.children}C`);
  }
  return parts.join(" ");
}

/**
 * The columns shown before a super admin has chosen anything.
 *
 * The subset that a Viewpoint booking report almost always carries, plus the member
 * join this dashboard exists to provide. Everything else is available but off, so the
 * table starts readable rather than needing to be trimmed.
 */
export const DEFAULT_VIEWPOINT_COLUMNS = [
  "origin",
  "booking_no",
  "status",
  "booking_source",
  "resort_name",
  "arrival",
  "departure",
  "guest_name",
  "membership_number",
  "member",
  "promo_code",
  "source_of_truth",
];

/** The shape stored under the `viewpoint-booking-columns` console setting. */
export interface ViewpointColumnSetting {
  columns: string[];
}

/**
 * Resolves a saved setting into the columns to render.
 *
 * Deliberately tolerant of a stale saved list, because the catalogue can gain and
 * lose columns after a setting was written:
 *
 *   - a saved key that no longer exists is dropped rather than rendering a blank
 *   - locked columns are forced in even if a saved list omits them
 *   - order always comes from the catalogue, not from the saved array, so a column
 *     added later appears in its intended place instead of at the end
 *   - an empty or absent setting falls back to the defaults, so a bad save can never
 *     leave every user looking at a table with no columns
 */
export function resolveViewpointColumns(
  saved: string[] | null | undefined,
): ViewpointColumnDef[] {
  const wanted = new Set(
    saved && saved.length > 0 ? saved : DEFAULT_VIEWPOINT_COLUMNS,
  );
  return VIEWPOINT_COLUMNS.filter((c) => c.locked || wanted.has(c.key));
}
