/* Marketing Desk — the card layer.

   The shell's pane owns one set of rounded corners; INSIDE the pane the content
   is now tiles rather than full-width bands, so the page has a rhythm of its own:
   a hero tile that spans, stat tiles that auto-fit, and stacks whose heights are
   deliberately unequal. Two rules keep it from becoming a card grid of nothing:
   a tile is a group of figures that belong together, and every figure inside one
   still names the array it came from.

   Curves carry the data as well as the boxes: a donut for a decomposition, an arc
   gauge for a rate against a ceiling, a smoothed spark for a series, a pill meter
   for a proportion whose track is a stated number. Straight bars are kept for the
   day chart, where the reader compares days against each other.

   Nothing here holds data, assigns a status tone, or does arithmetic — metrics.js
   computes, enums.js decides what exists, StatusBadge tones. */
(() => {
  const { Avatar, Button, Badge, Textarea } = window.OriginDesignSystem_7c998b;

  /* ── Tiles ──────────────────────────────────────────────────────────────
     `Tiles` is a 12-track grid; a tile declares how many tracks it takes at
     desktop width and drops to full width below 900px. Spans are deliberately
     uneven — 7/5, 8/4 — so no single grid line runs the length of the page. */
  function Tiles({ children, className }) {
    return <div className={"md-tiles" + (className ? " " + className : "")}>{children}</div>;
  }

  /* `distribute` is how a tile spends height it did not ask for: "spread" pushes
     its blocks apart, "bottom" seats them against the foot. Tiles always fill
     their row, so a tile with little content needs to say which it wants. */
  function Tile({ span, tone, title, note, actions, foot, media, flush, distribute, fit, className, children }) {
    return (
      <section className={"md-tile" + (span ? " md-tile--" + span : "") + (tone ? " md-tile--" + tone : "") + (distribute ? " md-tile--" + distribute : "") + (fit ? " md-tile--fit" : "") + (flush ? " md-tile--flush" : "") + (className ? " " + className : "")}>
        {title || actions ? (
          <header className="md-tile__head">
            <div className="md-tile__heading">
              <h2 className="md-tile__title">{title}</h2>
              {note ? <p className="md-tile__note">{note}</p> : null}
            </div>
            {actions ? <div className="md-tile__actions">{actions}</div> : null}
          </header>
        ) : null}
        {media ? <div className="md-tile__media">{media}</div> : null}
        <div className="md-tile__body">{children}</div>
        {foot ? <footer className="md-tile__foot">{foot}</footer> : null}
      </section>
    );
  }

  /* One figure, its qualifier, and optionally the curve it came from. The
     qualifier decomposes the figure's own set — never a second set. */
  function StatTile({ label, value, qualifier, spark, tone, onClick }) {
    const inner = (
      <>
        <span className="md-stat__label">{label}</span>
        <span className={"md-stat__value" + (tone ? " md-stat__value--" + tone : "")}>{value}</span>
        {spark ? <span className="md-stat__spark">{spark}</span> : null}
        <span className="md-stat__qualifier">{qualifier}</span>
      </>
    );
    return onClick
      ? <button type="button" className="md-stat md-stat--link" onClick={onClick}>{inner}</button>
      : <div className="md-stat">{inner}</div>;
  }

  function StatRow({ children }) {
    return <div className="md-statrow">{children}</div>;
  }

  /* ── Curves ─────────────────────────────────────────────────────────────
     A donut decomposes ONE total: the slices sum to it, and the centre states
     that total in words as well as digits, so the ring is checkable. */
  function Donut({ slices, total, centreValue, centreLabel, size = 132, thickness = 14 }) {
    const r = (size - thickness) / 2;
    const c = 2 * Math.PI * r;
    let offset = 0;
    const sum = slices.reduce((n, s) => n + s.value, 0) || 1;
    return (
      <div className="md-donut" style={{ width: size }}>
        <svg width={size} height={size} viewBox={"0 0 " + size + " " + size} role="img" aria-label={centreLabel + ": " + centreValue}>
          <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--muted)" strokeWidth={thickness} />
          {slices.map((s, i) => {
            const len = (s.value / sum) * c;
            const dash = <circle key={s.key} cx={size / 2} cy={size / 2} r={r} fill="none"
              stroke={"var(--chart-" + ((i % 5) + 1) + ")"} strokeWidth={thickness} strokeLinecap="round"
              strokeDasharray={len + " " + (c - len)} strokeDashoffset={-offset}
              transform={"rotate(-90 " + size / 2 + " " + size / 2 + ")"} />;
            offset += len;
            return dash;
          })}
        </svg>
        <span className="md-donut__centre">
          <span className="md-donut__value">{centreValue}</span>
          <span className="md-donut__label">{centreLabel}</span>
        </span>
      </div>
    );
  }

  function DonutKey({ slices, total }) {
    return (
      <ul className="md-key">
        {slices.map((s, i) => (
          <li className="md-key__row" key={s.key}>
            <span className="md-key__swatch" style={{ background: "var(--chart-" + ((i % 5) + 1) + ")" }} />
            <span className="md-key__label">{s.label}</span>
            <span className="md-key__value">{s.display}</span>
          </li>
        ))}
      </ul>
    );
  }

  /* A rate against a ceiling: 240° of arc, the ceiling stated under it. Tone is
     passed in by the screen from a predicate in enums.js — never decided here. */
  function Gauge({ percent, value, label, note, tone, size = 148 }) {
    const clamped = Math.max(0, Math.min(100, percent));
    const thickness = 11;
    const r = size / 2 - 12;
    const arc = 2 * Math.PI * r * (240 / 360);
    /* The sweep ends 30° below the horizontal, so the lowest painted pixel is
       the centre plus r·sin(30°) plus half the stroke — the box is sized off
       that number rather than a guessed fraction, or the round caps get cut. */
    const boxHeight = Math.ceil(size / 2 + r * Math.sin(Math.PI / 6) + thickness / 2 + 1);
    return (
      <div className="md-gauge" style={{ width: size }}>
        <svg width={size} height={boxHeight} viewBox={"0 0 " + size + " " + boxHeight} role="img" aria-label={label + ": " + value}>
          <g transform={"translate(" + size / 2 + "," + size / 2 + ") rotate(150)"}>
            <circle cx="0" cy="0" r={r} fill="none" stroke="var(--muted)" strokeWidth={thickness} strokeLinecap="round" strokeDasharray={arc + " " + (2 * Math.PI * r)} />
            <circle cx="0" cy="0" r={r} fill="none" strokeWidth={thickness} strokeLinecap="round"
              stroke={tone === "danger" ? "var(--destructive)" : tone === "warning" ? "var(--warning)" : tone === "success" ? "var(--success)" : "var(--chart-1)"}
              strokeDasharray={(arc * clamped) / 100 + " " + 2 * Math.PI * r} />
          </g>
        </svg>
        <span className="md-gauge__text">
          <span className="md-gauge__value">{value}</span>
          <span className="md-gauge__label">{label}</span>
        </span>
        {note ? <span className="md-gauge__note">{note}</span> : null}
      </div>
    );
  }

  /* A smoothed series. Curves, not steps: the reader is being shown a shape, and
     the numbers behind it live in the tooltip of the day chart on Overview. */
  function Spark({ points, tone, width = 132, height = 38, fill = true }) {
    if (!points.length) return null;
    const max = Math.max.apply(null, points) || 1;
    const min = Math.min.apply(null, points);
    const span = max - min || 1;
    const step = width / Math.max(1, points.length - 1);
    const xy = points.map((v, i) => [i * step, height - 3 - ((v - min) / span) * (height - 8)]);
    let d = "M" + xy[0][0] + " " + xy[0][1];
    for (let i = 1; i < xy.length; i++) {
      const [x0, y0] = xy[i - 1], [x1, y1] = xy[i];
      const cx = (x0 + x1) / 2;
      d += " C" + cx + " " + y0 + " " + cx + " " + y1 + " " + x1 + " " + y1;
    }
    const stroke = tone === "danger" ? "var(--destructive)" : tone === "success" ? "var(--success)" : tone === "warning" ? "var(--warning)" : "var(--chart-1)";
    return (
      <svg className="md-spark" width={width} height={height} viewBox={"0 0 " + width + " " + height} aria-hidden="true">
        {fill ? <path d={d + " L" + width + " " + height + " L0 " + height + " Z"} fill={stroke} opacity="0.09" /> : null}
        <path d={d} fill="none" stroke={stroke} strokeWidth="2" strokeLinecap="round" />
      </svg>
    );
  }

  /* A proportion whose track is a stated number, drawn as one pill. */
  function PillMeter({ parts, label, foot }) {
    return (
      <div className="md-pillmeter">
        {label ? <span className="md-pillmeter__label">{label}</span> : null}
        <span className="md-pillmeter__track" role="img" aria-label={label}>
          {parts.map((p) => (
            <span key={p.key} className={"md-pillmeter__fill" + (p.tone ? " md-pillmeter__fill--" + p.tone : "")}
              style={{ width: Math.max(0, Math.min(100, p.percent)) + "%" }} />
          ))}
        </span>
        {foot ? <span className="md-footnote">{foot}</span> : null}
      </div>
    );
  }

  /* ── People ─────────────────────────────────────────────────────────────
     A face is never decoration: it is a person on the record, and the tooltip
     says who and what they do. */
  function Face({ person, size = 28 }) {
    return (
      <FloatingTip label={person.name + (person.role ? " · " + person.role : "")} side="top">
        <span className="md-face"><Avatar size={size} src={person.avatar} alt="" fallback={initialsOf(person.name)} /></span>
      </FloatingTip>
    );
  }

  function FaceStack({ people, max = 5, size = 26 }) {
    if (!people.length) return null;
    const shown = people.slice(0, max);
    const rest = people.length - shown.length;
    return (
      <span className="md-faces">
        {shown.map((p) => <Face key={p.id} person={p} size={size} />)}
        {rest > 0 ? (
          <FloatingTip side="top" label={people.slice(max).map((p) => p.name).join(", ")}>
            <span className="md-faces__more">+{rest}</span>
          </FloatingTip>
        ) : null}
      </span>
    );
  }

  /* A mention in a body is a member id; the name is resolved at render, so
     renaming somebody cannot leave a stale name inside a note. */
  function MentionText({ body, members }) {
    const parts = body.split(/(@mem_\d+)/g);
    return (
      <span>
        {parts.map((part, i) => (part.indexOf("@mem_") === 0
          ? <span className="md-mention" key={i}>@{memberById(members, part.slice(1)).name}</span>
          : <React.Fragment key={i}>{part}</React.Fragment>))}
      </span>
    );
  }

  const REACTIONS = [
    { key: "agree", label: "Agreed", icon: "check" },
    { key: "watching", label: "Watching", icon: "eye" },
  ];

  function Note({ comment, comments, members, viewer, now, onReact }) {
    const author = memberById(members, comment.authorId);
    const owed = comment.mentions.filter((id) => COMMENT_FLAGS.isAwaitingReply(comment, id, comments));
    return (
      <article className="md-note">
        <Face person={author} size={30} />
        <div className="md-note__body">
          <header className="md-note__head">
            <span className="md-note__who">{author.name}{comment.authorId === viewer.id ? " (you)" : ""}</span>
            <span className="md-note__role">{author.role}</span>
            <span className="md-note__when" title={formatAbsolute(comment.at)}>{formatWhen(comment.at, now)}</span>
          </header>
          <p className="md-note__text"><MentionText body={comment.body} members={members} /></p>
          <footer className="md-note__foot">
            {REACTIONS.map((r) => {
              const ids = comment.reactions[r.key] || [];
              const mine = ids.indexOf(viewer.id) > -1;
              return (
                <button type="button" key={r.key} className={"md-react" + (mine ? " md-react--on" : "")}
                  aria-label={r.label + (ids.length ? " — " + ids.map((id) => memberById(members, id).name).join(", ") : "")}
                  onClick={() => onReact(comment, r.key)}>
                  <Icon name={r.icon} size={12} />{ids.length ? ids.length : ""}
                </button>
              );
            })}
            {owed.length ? (
              <span className="md-note__owed">
                <Icon name="clock" size={12} />
                Waiting on {owed.map((id) => memberById(members, id).name.split(" ")[0]).join(" and ")}
              </span>
            ) : null}
          </footer>
        </div>
      </article>
    );
  }

  /* Instant: a note posts on its own, so there is no Save on the screen holding
     it. The button is disabled until there is something to post. */
  function Composer({ viewer, members, placeholder, onPost }) {
    const [body, setBody] = React.useState("");
    /* Mentions are ids in the body, so pulling somebody in is a control rather
       than a spelling test: the chip appends their token and the note renders
       their current name. Everybody except you, because a note that waits on
       yourself waits on nobody. */
    const others = members.filter((m) => m.id !== viewer.id);
    const mentioned = (body.match(/@mem_\d+/g) || []);
    const add = (member) => setBody((b) => (b.replace(/\s+$/, "") + " @" + member.id + " ").replace(/^\s+/, ""));
    return (
      <div className="md-composer">
        <Face person={viewer} size={30} />
        <div className="md-composer__body">
          <Textarea rows={2} value={body} placeholder={placeholder} aria-label={placeholder}
            onChange={(e) => setBody(e.target.value)} />
          <div className="md-composer__mentions">
            <span className="md-footnote">Pull somebody in:</span>
            {others.map((member) => (
              <button type="button" key={member.id} disabled={mentioned.indexOf("@" + member.id) > -1}
                className={"md-react" + (mentioned.indexOf("@" + member.id) > -1 ? " md-react--on" : "")}
                onClick={() => add(member)}>
                @{member.name.split(" ")[0]}
              </button>
            ))}
          </div>
          <div className="md-composer__foot">
            <span className="md-footnote">
              {mentioned.length
                ? "Waits on " + mentioned.map((token) => memberById(members, token.slice(1)).name).join(" and ") + " until they write on this record."
                : "Nobody mentioned — this note will not wait on anyone."}
            </span>
            <Button size="sm" disabled={body.trim().length < 2} onClick={() => { onPost(body.trim()); setBody(""); }}>Post note</Button>
          </div>
        </div>
      </div>
    );
  }

  function Chip({ tone, icon, children }) {
    return (
      <span className={"md-chip" + (tone ? " md-chip--" + tone : "")}>
        {icon ? <Icon name={icon} size={12} /> : null}{children}
      </span>
    );
  }

  /* Label and value on one row, the value at the trailing edge. Used wherever a
     tile carries a handful of attributes rather than a table. */
  function KeyValues({ rows }) {
    return (
      <dl className="md-kv">
        {rows.map((row) => (
          <div className="md-kv__row" key={row.label}>
            <dt>{row.label}</dt>
            <dd>{row.value}</dd>
          </div>
        ))}
      </dl>
    );
  }

  Object.assign(window, { Tiles, Tile, StatTile, StatRow, Donut, DonutKey, Gauge, Spark, PillMeter, Face, FaceStack, MentionText, Note, Composer, Chip, KeyValues });
})();
