/* Lodestay — shared parts, local to this product and reported in the README.
   Nothing here holds data or assigns a status tone — StatusBadge does that, and
   metrics.js does the arithmetic.

   The three devices that give the product its look:
     Photo    — the one imagery treatment in the kit. The source repo ships no
                listing photography, so every picture is a declared frame with a
                mono caption naming what belongs in it. Never a drawn scene.
     PlaceCard— a place is a photo, a name, where it is, what it costs for the
                dates in the scope, and the one thing you can do with it. The
                search list, the saved lists and the map card all read it.
     QuoteBreakdown — the only itemisation of money in the product, printed from
                metrics.js quoteLines so no screen invents a line. */
(() => {
  const {
    Button, Input, Textarea, Badge, Checkbox, RadioGroup, Select,
    Field, FieldLabel, FieldDescription, FieldError,
    Card, CardHeader, CardTitle, CardDescription, CardPanel, CardFooter,
    Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator,
    AlertDialog, AlertDialogPopup, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter,
    Dialog, DialogPopup, DialogHeader, DialogTitle, DialogDescription, DialogPanel, DialogFooter,
    Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent,
    Avatar, MenuItem, MenuGroupLabel, Separator,
  } = window.OriginDesignSystem_7c998b;

  function Eyebrow({ children }) { return <span className="ls-eyebrow">{children}</span>; }

  /* The one imagery treatment. `label` says what the photograph is of. */
  function Photo({ label, icon, shape, className }) {
    return (
      <span className={"ls-photo" + (shape ? " ls-photo--" + shape : "") + (className ? " " + className : "")}>
        <span className="ls-photo__icon"><Icon name={icon || "image"} size={20} /></span>
        <span className="ls-photo__label">{label}</span>
      </span>
    );
  }

  /* A rating is the mean of the reviews a place carries, or nothing at all. */
  function Rating({ listing, showCount = true }) {
    const rating = ratingOf(listing);
    if (rating === null) {
      return <span className="ls-rating ls-muted">{listing.reviews.length ? listing.reviews.length + (listing.reviews.length === 1 ? " review" : " reviews") + " so far" : "New place"}</span>;
    }
    return (
      <span className="ls-rating">
        <Icon name="star" size={13} />
        {formatRating(rating)}
        {showCount ? <span className="ls-muted">({listing.reviews.length})</span> : null}
      </span>
    );
  }

  /* Instant and harmless, so it applies bare — no toast, no dialog. */
  function SaveButton({ listing, saved, onToggle, size = "icon-sm" }) {
    return (
      <FloatingTip label={saved ? "Saved to Lisbon, October" : "Save this place"} side="bottom">
        <Button
          size={size}
          variant={saved ? "secondary" : "outline"}
          aria-label={saved ? "Remove " + listing.name + " from saved" : "Save " + listing.name}
          aria-pressed={saved}
          onClick={(e) => { e.stopPropagation(); onToggle(listing); }}
        >
          <Icon name="bookmark" size={15} />
        </Button>
      </FloatingTip>
    );
  }

  /* Buttons for a transition set. The set comes from enums.js; this only draws
     it. A transition that does not apply was never in the array; one that
     exists but the guest cannot take yet is disabled with its reason. */
  function Transitions({ transitions, onRun, size = "sm", primaryOnly }) {
    const list = primaryOnly ? transitions.filter((t) => t.primary) : transitions;
    if (!list.length) return null;
    return (
      <span className="ls-row ls-row--tight">
        {list.map((t) => {
          const button = (
            <Button
              key={t.id}
              size={size}
              disabled={!!t.disabled}
              variant={t.destructive ? "destructive-outline" : t.primary ? "default" : "outline"}
              onClick={() => onRun(t)}
            >
              {t.label}
            </Button>
          );
          return t.disabled && t.disabledReason
            ? <FloatingTip key={t.id} label={t.disabledReason} side="bottom">{button}</FloatingTip>
            : button;
        })}
      </span>
    );
  }

  /* ── A place, wherever a place appears in a list. ─────────────────────── */
  function PlaceCard({ listing, scope, saved, selected, onOpen, onSave, onHover, transitions, onRun, footNote }) {
    const quote = quoteForListing(listing, scope);
    const hood = labelOf(NEIGHBOURHOODS, listing.neighbourhood);
    return (
      <div
        className={"ls-card" + (selected ? " ls-card--selected" : "")}
        onMouseEnter={onHover ? () => onHover(listing.id) : undefined}
        onMouseLeave={onHover ? () => onHover(null) : undefined}
      >
        <span className="ls-save">{onSave ? <SaveButton listing={listing} saved={saved} onToggle={onSave} /> : null}</span>
        <Photo label={listing.photos[0]} icon={iconOfKind(listing)} />
        <div className="ls-card__body">
          <div className="ls-spread">
            <button type="button" className="ls-link ls-card__name" onClick={() => onOpen(listing)}>{listing.name}</button>
            <Rating listing={listing} />
          </div>
          <span className="ls-card__meta">{hood} · {labelOf(PLACE_KINDS, listing.kind)} · sleeps {listing.sleeps} · {listing.beds} {listing.beds === 1 ? "bed" : "beds"}</span>
          {listing.availability !== "open" ? (
            <span className="ls-row ls-row--tight">
              <StatusBadge values={LISTING_AVAILABILITY} value={listing.availability} />
              <span className="ls-note">{LISTING_FLAGS.isHeld(listing) ? "Free again at " + formatHoldClock(listing.holdUntil) : listing.availability === "booked" ? "Somebody else has these dates" : "The host has stopped taking bookings"}</span>
            </span>
          ) : null}
          <div className="ls-card__foot">
            {LISTING_FLAGS.isBookable(listing) || LISTING_FLAGS.isHeld(listing) ? (
              <span>
                <span className="ls-price">{formatPrice(quote.total)}</span>
                <span className="ls-card__meta"> for {formatNights(quote.nights)}</span>
                <span className="ls-note" style={{ display: "block" }}>{formatPrice(allInPerNight(quote))} a night, everything in</span>
              </span>
            ) : (
              <span className="ls-note">{formatPrice(listing.nightlyCents)} a night when it opens again</span>
            )}
            {transitions ? <Transitions transitions={transitions} onRun={onRun} primaryOnly /> : null}
          </div>
          {footNote ? <span className="ls-note">{footNote}</span> : null}
        </div>
      </div>
    );
  }

  function Facts({ items }) {
    return (
      <div className="ls-facts">
        {items.map((item) => (
          <div className="ls-fact" key={item.label}>
            <span className="ls-fact__label">{item.label}</span>
            <span className="ls-fact__value">{item.value}</span>
          </div>
        ))}
      </div>
    );
  }

  function AmenityList({ values }) {
    return (
      <div className="ls-amenities">
        {values.map((value) => {
          const amenity = amenityOf(value);
          return <span className="ls-amenity" key={value}><Icon name={amenity.icon} size={15} />{amenity.label}</span>;
        })}
      </div>
    );
  }

  /* The only itemisation of money in the product. `extra` carries lines that
     belong to a booking rather than to a quote — a refund, a deposit split. */
  function QuoteBreakdown({ quote, extra, totalLabel = "Total" }) {
    return (
      <div className="ls-lines">
        {quoteLines(quote).map((line) => (
          <div className="ls-line" key={line.id}>
            <span>{line.label}</span>
            <span className="ls-line__amount">{formatPrice(line.amount)}</span>
            {line.note ? <span className="ls-line__note">{line.note}</span> : null}
          </div>
        ))}
        <div className="ls-line ls-line--total">
          <span>{totalLabel}</span>
          <span className="ls-line__amount">{formatPrice(quote.total)}</span>
        </div>
        {(extra || []).map((line) => (
          <div className="ls-line" key={line.label}>
            <span className={line.muted ? "ls-muted" : undefined}>{line.label}</span>
            <span className="ls-line__amount">{line.amount}</span>
            {line.note ? <span className="ls-line__note">{line.note}</span> : null}
          </div>
        ))}
      </div>
    );
  }

  /* A person, wherever a person appears. */
  function Who({ person, meta, size = 36, onClick }) {
    return (
      <span className="ls-person">
        <Avatar size={size} src={person.avatar} alt="" fallback={person.name.split(" ").map((w) => w[0]).join("")} />
        <span className="ls-person__text">
          {onClick
            ? <button type="button" className="ls-link ls-person__name" onClick={onClick}>{person.name}</button>
            : <span className="ls-person__name">{person.name}</span>}
          <span className="ls-person__meta">{meta}</span>
        </span>
      </span>
    );
  }

  /* How long a host takes to answer, in the product's one spelling. */
  const replyLine = (host) => (host.replyMinutes < 60
    ? "Usually answers within the hour"
    : "Usually answers within " + Math.round(host.replyMinutes / 60) + " hours");

  function SectionHead({ title, note, actions }) {
    return (
      <div className="ls-section__head">
        <h2 className="ls-section__title">{title}</h2>
        {note ? <span className="ls-section__note">{note}</span> : null}
        {actions || null}
      </div>
    );
  }

  const resolvePath = (path, params) => (params ? path.replace(":id", params.id) : path);

  function Crumbs({ path, params, labels, onNavigate }) {
    const trail = breadcrumb(path, LODESTAY_ROUTES);
    return (
      <Breadcrumb>
        <BreadcrumbList>
          {trail.map((seg, i) => {
            const href = resolvePath(seg.path, params);
            const label = (labels && labels[seg.path]) || seg.label;
            return (
              <React.Fragment key={seg.path}>
                {i > 0 ? <BreadcrumbSeparator /> : null}
                <BreadcrumbItem>
                  {i === trail.length - 1
                    ? <BreadcrumbPage>{label}</BreadcrumbPage>
                    : seg.isNamespace
                      ? <span>{label}</span>
                      : <BreadcrumbLink href={"#" + href} onSelect={() => onNavigate(href)}>{label}</BreadcrumbLink>}
                </BreadcrumbItem>
              </React.Fragment>
            );
          })}
        </BreadcrumbList>
      </Breadcrumb>
    );
  }

  function PageHead({ path, params, labels, onNavigate, title, lede, actions, children }) {
    return (
      <header className="ls-head">
        <div className="ls-head__text">
          <Crumbs path={path} params={params} labels={labels} onNavigate={onNavigate} />
          <h1 className="ls-title">{title}</h1>
          {lede ? <p className="ls-lede">{lede}</p> : null}
          {children}
        </div>
        {actions ? <div className="ls-head__actions">{actions}</div> : null}
      </header>
    );
  }

  /* Irreversible only. The consequence is named in the title. */
  function ConfirmDialog({ request, onCancel, onConfirm }) {
    return (
      <AlertDialog open={!!request} onOpenChange={(open) => { if (!open) onCancel(); }}>
        <AlertDialogPopup>
          <AlertDialogHeader>
            <AlertDialogTitle>{request ? request.title : ""}</AlertDialogTitle>
            <AlertDialogDescription>{request ? request.description : ""}</AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <Button variant="outline" onClick={onCancel}>Cancel</Button>
            <Button variant={request && request.destructive ? "destructive" : "default"} onClick={onConfirm}>{request ? request.confirmLabel : ""}</Button>
          </AlertDialogFooter>
        </AlertDialogPopup>
      </AlertDialog>
    );
  }

  /* A staged dialog — its own commit model, so an instant screen may open one.
     fields: [{ id, label, kind: "input"|"text"|"radio"|"select"|"check",
                options, hint, placeholder, when }] */
  function FormDialog({ request, onCancel, onSubmit }) {
    const [values, setValues] = React.useState({});
    const [touched, setTouched] = React.useState(false);
    const [submitted, setSubmitted] = React.useState(false);
    React.useEffect(() => { setValues(request ? request.initial || {} : {}); setTouched(false); setSubmitted(false); }, [request]);
    if (!request) return null;
    const errors = request.validate ? request.validate(values) : {};
    const valid = Object.keys(errors).length === 0;
    const set = (id, value) => { setTouched(true); setValues((v) => Object.assign({}, v, { [id]: value })); };
    return (
      <Dialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <DialogPopup>
          <DialogHeader>
            <DialogTitle>{request.title}</DialogTitle>
            <DialogDescription>{request.description}</DialogDescription>
          </DialogHeader>
          <DialogPanel>
            <div className="ls-stack ls-stack--tight">
              {request.fields.filter((f) => !f.when || f.when(values)).map((field) => (
                <Field key={field.id}>
                  {field.kind === "check" ? null : <FieldLabel htmlFor={"ls-f-" + field.id}>{field.label}</FieldLabel>}
                  {field.kind === "radio" ? (
                    <RadioGroup items={field.options} value={values[field.id] || ""} onValueChange={(v) => set(field.id, v)} name={"ls-f-" + field.id} />
                  ) : field.kind === "select" ? (
                    <Select id={"ls-f-" + field.id} items={field.options} value={values[field.id] || ""} placeholder={field.placeholder} onValueChange={(v) => set(field.id, v)} />
                  ) : field.kind === "text" ? (
                    <Textarea id={"ls-f-" + field.id} rows={4} placeholder={field.placeholder} value={values[field.id] || ""} invalid={submitted && !!errors[field.id]} onChange={(e) => set(field.id, e.target.value)} />
                  ) : field.kind === "check" ? (
                    <label className="ls-row ls-row--tight" style={{ fontSize: "var(--text-sm)" }}>
                      <Checkbox checked={!!values[field.id]} onCheckedChange={(v) => set(field.id, v)} />
                      {field.label}
                    </label>
                  ) : (
                    <Input id={"ls-f-" + field.id} placeholder={field.placeholder} value={values[field.id] || ""} invalid={submitted && !!errors[field.id]} onChange={(e) => set(field.id, e.target.value)} />
                  )}
                  {submitted && errors[field.id]
                    ? <FieldError>{errors[field.id]}</FieldError>
                    : field.hint ? <FieldDescription>{field.hint}</FieldDescription> : null}
                </Field>
              ))}
            </div>
          </DialogPanel>
          <DialogFooter>
            {touched ? <Button variant="outline" onClick={onCancel}>Cancel</Button> : null}
            <Button disabled={!touched} variant={request.destructive ? "destructive" : "default"} onClick={() => { setSubmitted(true); if (valid) onSubmit(values); }}>{request.submitLabel}</Button>
          </DialogFooter>
        </DialogPopup>
      </Dialog>
    );
  }

  /* Nothing in this product links here: every rendered entry point resolves to
     a registered route. It exists for a hand-typed or stale path — which has no
     breadcrumb, because the route table is what breadcrumbs are made of. */
  function RouteNotBuilt({ path, onNavigate }) {
    const route = matchRoute(path, LODESTAY_ROUTES);
    return (
      <div className="ls-page">
        <h1 className="ls-title">{route ? route.label : "Not found"}</h1>
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon"><Icon name="route" size={18} /></EmptyMedia>
            <EmptyTitle>{route ? route.label + " is registered, not built" : "There is nothing at " + path}</EmptyTitle>
            <EmptyDescription>{route ? "The route and its place in the palette are real; the screen behind it is not part of this template." : "This path is not in Lodestay's route table, so it has no breadcrumb and nothing links to it. Four destinations, a place, its checkout, a trip and the account screens are the whole product."}</EmptyDescription>
          </EmptyHeader>
          <EmptyContent>
            <Button variant="outline" onClick={() => onNavigate("/")}>Back to the search</Button>
          </EmptyContent>
        </Empty>
      </div>
    );
  }

  Object.assign(window, {
    Eyebrow, Photo, Rating, SaveButton, Transitions, PlaceCard, Facts, AmenityList, QuoteBreakdown,
    Who, replyLine, SectionHead, Crumbs, PageHead, ConfirmDialog, FormDialog, RouteNotBuilt, resolvePath,
  });
})();
