/* Parley — the product's own parts. Nothing here holds data or assigns a status
   tone: StatusBadge does the tone, metrics.js does the arithmetic, enums.js
   names the transitions. Reported in the README as local additions. */
(() => {
  const {
    Button, Input, Textarea, Select, Checkbox, RadioGroup,
    Field, FieldLabel, FieldDescription, FieldError,
    Card, CardPanel, Badge, MenuItem, MenuGroupLabel, Separator,
    Dialog, DialogPopup, DialogHeader, DialogTitle, DialogDescription, DialogPanel, DialogFooter,
    AlertDialog, AlertDialogPopup, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter,
    Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent,
  } = window.OriginDesignSystem_7c998b;

  /* ── Figures ──────────────────────────────────────────────────────── */

  /* A row of anchor figures. One row definition across all four columns, so a
     wrapped label or a missing qualifier cannot shift its own column. */
  function AnchorRow({ items }) {
    return (
      <div className="pl-anchor">
        {items.map((item) => (
          <div className="pl-part" key={item.label}>
            <span className="pl-anchor__label">{item.label}</span>
            <span className="pl-anchor__figure">{item.value}</span>
            <span className="pl-anchor__qual">{item.qualifier || ""}</span>
          </div>
        ))}
      </div>
    );
  }

  function Bar({ ratio, tone }) {
    const width = Math.max(0, Math.min(1, ratio)) * 100;
    return (
      <span className={"pl-bar" + (tone ? " pl-bar--" + tone : "")}>
        <span className="pl-bar__fill" style={{ width: width + "%" }} />
      </span>
    );
  }

  /* ── Prose ────────────────────────────────────────────────────────── */

  /* One renderer for every message body in the product, so a code block looks
     the same wherever it appears and nothing renders raw markdown. */
  function Prose({ blocks, caret }) {
    const last = blocks.length - 1;
    return (
      <div className="pl-prose">
        {blocks.map((block, i) => {
          const tail = caret && i === last ? <span className="pl-caret" aria-hidden="true" /> : null;
          if (block.t === "h") return <h4 key={i}>{block.text}{tail}</h4>;
          if (block.t === "quote") return <blockquote key={i}>{block.text}{tail}</blockquote>;
          if (block.t === "ul") return <ul key={i}>{block.items.map((item, j) => <li key={j}>{item}{tail && j === block.items.length - 1 ? tail : null}</li>)}</ul>;
          if (block.t === "ol") return <ol key={i}>{block.items.map((item, j) => <li key={j}>{item}{tail && j === block.items.length - 1 ? tail : null}</li>)}</ol>;
          if (block.t === "code") return <CodeBlock key={i} block={block} />;
          return <p key={i}>{block.text}{tail}</p>;
        })}
      </div>
    );
  }

  function CodeBlock({ block }) {
    const [copied, setCopied] = React.useState(false);
    React.useEffect(() => {
      if (!copied) return undefined;
      const id = window.setTimeout(() => setCopied(false), 2000);
      return () => window.clearTimeout(id);
    }, [copied]);
    return (
      <div className="pl-code">
        <div className="pl-code__bar">
          <span>{block.lang}</span>
          <Button variant="ghost" size="xs" onClick={() => { copyText(block.code); setCopied(true); }}>{copied ? "Copied" : "Copy"}</Button>
        </div>
        <pre><code>{block.code}</code></pre>
      </div>
    );
  }

  function copyText(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).catch(() => {});
  }

  /* ── Actions ──────────────────────────────────────────────────────── */

  function Transitions({ transitions, onRun, size = "xs" }) {
    if (!transitions.length) return null;
    return (
      <span className="pl-actions__row">
        {transitions.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 row carries the transition its state asks for; everything else, and
     anything destructive, goes in the row's menu. */
  function RowActions({ transitions, onRun, label, extra }) {
    const primary = transitions.filter((t) => t.primary && !t.destructive);
    const rest = transitions.filter((t) => !t.primary);
    if (!primary.length && !rest.length && !extra) return <span className="pl-muted">—</span>;
    return (
      <span className="pl-actions__row">
        {primary.length ? <Transitions transitions={primary} onRun={onRun} /> : null}
        {rest.length || extra ? (
          <Floating align="end" width="14rem" label={label}
            trigger={<Button variant="ghost" size="icon-xs" aria-label={label}><Icon name="ellipsis" size={14} /></Button>}>
            {(close) => (
              <>
                {extra ? extra(close) : null}
                {rest.map((t) => (
                  <MenuItem key={t.id} variant={t.destructive ? "destructive" : undefined} disabled={!!t.disabled}
                    onClick={() => { close(); onRun(t); }}>{t.label}</MenuItem>
                ))}
              </>
            )}
          </Floating>
        ) : null}
      </span>
    );
  }

  /* ── List furniture: same order on every list screen ──────────────── */

  function FilterBar({ query, onQuery, placeholder, selects = [], onClear, dirty, trailing }) {
    return (
      <div className="pl-filters">
        <div className="pl-filters__text">
          <Input size="sm" value={query} placeholder={placeholder} aria-label={placeholder} startAddon={<Icon name="search" size={14} />} onChange={(e) => onQuery(e.target.value)} />
        </div>
        {selects.map((s) => (
          <span className="pl-filters__select" key={s.id}>
            <label className="pl-sr" htmlFor={"pl-flt-" + s.id}>{s.label}</label>
            <Select id={"pl-flt-" + s.id} size="sm" items={s.items} value={s.value} onValueChange={s.onChange} />
          </span>
        ))}
        {dirty ? <Button variant="ghost" size="sm" onClick={onClear}>Clear filters</Button> : null}
        {trailing}
      </div>
    );
  }

  /* Filters returned nothing, so the primary weight is on clearing them. */
  function NoResults({ title, description, onClear }) {
    return (
      <CardPanel>
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon"><Icon name="search-x" size={18} /></EmptyMedia>
            <EmptyTitle>{title}</EmptyTitle>
            <EmptyDescription>{description}</EmptyDescription>
          </EmptyHeader>
          <EmptyContent><Button onClick={onClear}>Clear filters</Button></EmptyContent>
        </Empty>
      </CardPanel>
    );
  }

  /* ── Commit surfaces ──────────────────────────────────────────────── */

  function ConfirmDialog({ request, onCancel, onConfirm }) {
    if (!request) return null;
    return (
      <AlertDialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <AlertDialogPopup>
          <AlertDialogHeader>
            <AlertDialogTitle>{request.title}</AlertDialogTitle>
            <AlertDialogDescription>{request.description}</AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <Button variant="outline" onClick={onCancel}>Cancel</Button>
            <Button variant={request.destructive ? "destructive" : "default"} onClick={onConfirm}>{request.confirmLabel}</Button>
          </AlertDialogFooter>
        </AlertDialogPopup>
      </AlertDialog>
    );
  }

  /* A staged dialog — its own commit model, so the instant chat screen may open
     one. Submit is disabled until dirty; errors render on submit, never on
     first paint. */
  function FormDialog({ request, onCancel, onSubmit }) {
    /* One state object keyed by the request it belongs to. Resetting in an
       effect leaves one paint where `values` is still {} but `validate` has
       already been called with it, and resetting with setState during render is
       no better — the rest of that render still runs on the old values. So the
       fresh entry is computed here and used immediately. */
    const blank = (req) => ({ req: req, values: (req && req.initial) || {}, touched: false, submitted: false });
    const [entry, setEntry] = React.useState(() => blank(request));
    const current = entry.req === request ? entry : blank(request);
    if (entry.req !== request) setEntry(current);
    if (!request) return null;
    const values = current.values, touched = current.touched, submitted = current.submitted;
    const errors = request.validate ? request.validate(values) : {};
    const valid = Object.keys(errors).length === 0;
    const set = (id, value) => setEntry((e) => Object.assign({}, e, { touched: true, values: Object.assign({}, e.values, { [id]: value }) }));
    const markSubmitted = () => setEntry((e) => Object.assign({}, e, { submitted: true }));
    return (
      <Dialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
        <DialogPopup>
          <DialogHeader>
            <DialogTitle>{request.title}</DialogTitle>
            <DialogDescription>{request.description}</DialogDescription>
          </DialogHeader>
          <DialogPanel>
            <div className="pl-fields">
              {request.fields.map((field) => {
                const id = "pl-f-" + field.id;
                const invalid = submitted && !!errors[field.id];
                return (
                  <Field key={field.id}>
                    <FieldLabel htmlFor={id}>{field.label}</FieldLabel>
                    {field.kind === "radio" ? (
                      <RadioGroup name={id} items={field.options} value={values[field.id] || ""} onValueChange={(v) => set(field.id, v)} />
                    ) : field.kind === "select" ? (
                      <Select id={id} items={field.options} value={values[field.id] || ""} placeholder={field.placeholder} invalid={invalid} onValueChange={(v) => set(field.id, v)} />
                    ) : field.kind === "area" ? (
                      <Textarea id={id} rows={4} placeholder={field.placeholder} value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    ) : (
                      <Input id={id} placeholder={field.placeholder} value={values[field.id] || ""} invalid={invalid} onChange={(e) => set(field.id, e.target.value)} />
                    )}
                    {invalid ? <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 variant={request.destructive ? "destructive" : "default"} disabled={!touched} onClick={() => { markSubmitted(); if (valid) onSubmit(values); }}>{request.submitLabel}</Button>
          </DialogFooter>
        </DialogPopup>
      </Dialog>
    );
  }

  function FormBar({ dirty, note, onCancel, onSave, label }) {
    return (
      <div className="pl-formbar">
        <span className="pl-formbar__note">{note}</span>
        {dirty ? <Button variant="outline" size="sm" onClick={onCancel}>Cancel</Button> : null}
        <Button size="sm" disabled={!dirty} onClick={onSave}>{label}</Button>
      </div>
    );
  }

  /* ── Shared displays ──────────────────────────────────────────────── */

  /* A file, wherever it is named: the icon says what happened to it, so no
     screen has to explain a file twice. */
  function FileLine({ file, showStatus }) {
    const out = !FILE_FLAGS.inContext(file);
    return (
      <span className="pl-record">
        <span className="pl-record__name">{file.name}</span>
        <span className="pl-record__meta">
          {formatBytes(file.bytes)}
          {out ? "" : " · " + formatTokens(file.tokens) + " tokens"}
          {showStatus && file.statusNote ? " · " + file.statusNote : ""}
        </span>
      </span>
    );
  }

  function VisibilityBadge({ value }) {
    return <StatusBadge values={VISIBILITY} value={value} />;
  }

  function RouteNotBuilt({ path, onNavigate }) {
    return (
      <div className="pl-page">
        <Card>
          <CardPanel>
            <Empty>
              <EmptyHeader>
                <EmptyMedia variant="icon"><Icon name="signpost" size={18} /></EmptyMedia>
                <EmptyTitle>Nothing is registered at {path}</EmptyTitle>
                <EmptyDescription>Every screen in Parley has a route in nav.js. This path is not one of them.</EmptyDescription>
              </EmptyHeader>
              <EmptyContent><Button onClick={() => onNavigate("/")}>New chat</Button></EmptyContent>
            </Empty>
          </CardPanel>
        </Card>
      </div>
    );
  }

  Object.assign(window, {
    AnchorRow, Bar, Prose, CodeBlock, copyText, Transitions, RowActions,
    FilterBar, NoResults, ConfirmDialog, FormDialog, FormBar, FileLine, VisibilityBadge, RouteNotBuilt,
  });
})();
