/**
 * TaskMate Points Display Card
 * A kid-friendly display card showing how many points a child (or all children) has.
 *
 * Modes:
 *   single      — One child, large animated point total. Great for a child's own dashboard.
 *   multi       — Grid showing every child with their own score. Good for family TV display.
 *   cumulative  — Combined family total with individual breakdowns below.
 *
 * Config options:
 *   entity         — sensor.taskmate_overview (required)
 *   mode           — single | multi | cumulative  (default: single)
 *   child_id       — required for single mode
 *   title          — optional header title override
 *   show_streak    — show streak badge (default: true)
 *   show_weekly    — show this week's points alongside all-time (default: true)
 *   show_rank      — show rank medal in multi/cumulative modes (default: true)
 *   animate        — animate number on load (default: true)
 *   header_color   — hex colour for the card header (default: #9b59b6)
 *   primary_display — current_points | career_score  (default: current_points)
 *
 * Version: 1.0.0
 */

const LitElement = customElements.get("hui-masonry-view")
  ? Object.getPrototypeOf(customElements.get("hui-masonry-view"))
  : Object.getPrototypeOf(customElements.get("hui-view"));

const html = LitElement.prototype.html;
const css  = LitElement.prototype.css;

const DEFAULT_HEADER = "#9b59b6";

const CHILD_COLOURS = [
  "#e74c3c", "#3498db", "#27ae60", "#f39c12",
  "#9b59b6", "#1abc9c", "#e67e22", "#2980b9",
];

const RANK_MEDAL = ["\u{1F947}", "\u{1F948}", "\u{1F949}"];

/* ─── Utility helpers ──────────────────────────────────────────────────── */

function getChildren(state) {
  return state.attributes.children || [];
}

function weeklyPoints(child, tz) {
  const todayKey = new Date().toLocaleDateString("en-CA", { timeZone: tz });
  const today = new Date(todayKey + "T12:00:00"); // noon avoids DST edge cases
  const monday = new Date(today);
  monday.setDate(today.getDate() - ((today.getDay() + 6) % 7));
  const mondayKey = monday.toLocaleDateString("en-CA");

  const history = child.history || [];
  return history
    .filter(e => e.approved
      && new Date(e.timestamp).toLocaleDateString("en-CA", { timeZone: tz }) >= mondayKey)
    .reduce((s, e) => s + (e.points || 0), 0);
}

function childAvatar(child, colour) {
  const av = child.avatar || "mdi:account-circle";
  const isIcon = av.startsWith("mdi:");
  const initials = (child.name || "?")
    .split(" ")
    .map(w => w[0])
    .slice(0, 2)
    .join("")
    .toUpperCase();
  return html`
    <div class="avatar" style="background:${colour}">
      ${isIcon
        ? html`<ha-icon icon="${av}"></ha-icon>`
        : av
          ? html`<img src="${av}" alt="${child.name}">`
          : initials}
    </div>`;
}

/* ─── Main card ────────────────────────────────────────────────────────── */

const _safeColor = (c, d) => (typeof c === "string" && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : d);

class TaskMatePointsDisplayCard extends LitElement {

  static get properties() {
    return {
      hass:   { type: Object },
      config: { type: Object },
      _animated: { type: Object },
    };
  }

  shouldUpdate(changedProps) {
    if (changedProps.has("hass")) {
      return window.__taskmate_hasChanged
        ? window.__taskmate_hasChanged(changedProps.get("hass"), this.hass, this.config?.entity)
        : true;
    }
    return true;
  }

  constructor() {
    super();
    this._animated = {};
  }

  _t(key, params) {
    const fn = window.__taskmate_localize;
    return fn ? fn(this.hass, key, params) : key;
  }

  static get styles() {
    const base = css`
      :host { display: block; }
      ha-card { overflow: hidden; font-family: inherit; }

      /* ── Header ── */
      .card-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 14px 18px;
        color: white;
        gap: 10px;
      }
      .header-left {
        display: flex;
        align-items: center;
        gap: 10px;
        min-width: 0;
        flex: 1;
      }
      .header-icon { --mdc-icon-size: 26px; opacity: 0.9; flex-shrink: 0; }
      .card-title {
        font-size: 1.1rem;
        font-weight: 700;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        letter-spacing: 0.01em;
      }
      .mode-badge {
        background: rgba(255,255,255,0.2);
        border-radius: 10px;
        padding: 3px 10px;
        font-size: 0.75rem;
        font-weight: 600;
        text-transform: capitalize;
        flex-shrink: 0;
      }

      /* ── Shared body ── */
      .card-body { padding: 20px 18px 22px; }

      /* ── Avatar ── */
      .avatar {
        width: 56px;
        height: 56px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 1.3rem;
        font-weight: 800;
        color: white;
        flex-shrink: 0;
        overflow: hidden;
      }
      .avatar ha-icon {
        --mdc-icon-size: 32px;
        color: white;
      }
      .avatar img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        border-radius: 50%;
      }

      /* ══════════════════════════════════════════
         SINGLE MODE
      ══════════════════════════════════════════ */
      .single-wrap {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 18px;
        text-align: center;
      }
      .single-identity {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 10px;
      }
      .single-wrap .avatar {
        width: 80px;
        height: 80px;
        font-size: 1.8rem;
        box-shadow: 0 4px 16px rgba(0,0,0,0.15);
      }
      .single-wrap .avatar ha-icon {
        --mdc-icon-size: 48px;
      }
      .child-name {
        font-size: 1.25rem;
        font-weight: 700;
        color: var(--primary-text-color);
      }

      /* Big points number */
      .big-points {
        display: flex;
        flex-direction: column;
        align-items: center;
        background: var(--card-background-color, #fff);
        border: 2px solid var(--divider-color, #e0e0e0);
        border-radius: 24px;
        padding: 22px 40px;
        width: 100%;
        box-sizing: border-box;
        box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        position: relative;
        overflow: hidden;
      }
      .big-points::before {
        content: "\u2B50";
        font-size: 7rem;
        position: absolute;
        right: -12px;
        bottom: -18px;
        opacity: 0.06;
        line-height: 1;
        pointer-events: none;
      }
      .points-label {
        font-size: 0.85rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        color: var(--secondary-text-color);
        margin-bottom: 4px;
      }
      .points-number {
        font-size: 4.5rem;
        font-weight: 900;
        line-height: 1;
        letter-spacing: -0.02em;
        color: var(--primary-text-color);
        transition: color 0.3s;
      }
      .points-star {
        font-size: 2rem;
        animation: star-spin 3s linear infinite;
        display: inline-block;
        margin-right: 6px;
        vertical-align: middle;
      }
      @keyframes star-spin {
        0% { transform: rotate(0deg) scale(1); }
        50% { transform: rotate(180deg) scale(1.15); }
        100% { transform: rotate(360deg) scale(1); }
      }

      /* Secondary metric line */
      .secondary-info {
        font-size: 0.88rem;
        font-weight: 600;
        color: var(--secondary-text-color);
        margin-top: 8px;
      }
      .secondary-info .secondary-value {
        font-weight: 700;
        color: var(--primary-text-color);
      }

      /* Stats row under big number */
      .stats-row {
        display: flex;
        gap: 14px;
        width: 100%;
        justify-content: center;
        flex-wrap: wrap;
      }
      .stat-pill {
        display: flex;
        align-items: center;
        gap: 6px;
        background: var(--secondary-background-color, #f5f5f5);
        border-radius: 20px;
        padding: 7px 14px;
        font-size: 0.88rem;
        font-weight: 600;
        color: var(--primary-text-color);
        white-space: nowrap;
      }
      .stat-pill ha-icon { --mdc-icon-size: 17px; opacity: 0.75; }
      .stat-pill .pill-label {
        font-size: 0.75rem;
        font-weight: 500;
        color: var(--secondary-text-color);
      }


      /* ══════════════════════════════════════════
         MULTI MODE
      ══════════════════════════════════════════ */
      .multi-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
        gap: 14px;
      }
      .child-tile {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 10px;
        padding: 18px 12px;
        background: var(--card-background-color, #fff);
        border: 1px solid var(--divider-color, #e0e0e0);
        border-radius: 18px;
        text-align: center;
        position: relative;
        transition: box-shadow 0.2s;
        overflow: hidden;
      }
      .child-tile:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
      .child-tile .rank-badge {
        position: absolute;
        top: 8px;
        right: 10px;
        font-size: 1.1rem;
        line-height: 1;
      }
      .child-tile .avatar {
        width: 56px;
        height: 56px;
        font-size: 1.2rem;
      }
      .child-tile .child-name {
        font-size: 1rem;
        font-weight: 700;
      }
      .child-tile .tile-points {
        font-size: 2.4rem;
        font-weight: 900;
        line-height: 1;
        letter-spacing: -0.02em;
      }
      .child-tile .tile-star {
        font-size: 1rem;
        vertical-align: middle;
        margin-right: 2px;
        display: inline-block;
        animation: star-spin 4s linear infinite;
      }
      .child-tile .tile-label {
        font-size: 0.72rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.07em;
        color: var(--secondary-text-color);
      }
      .child-tile .tile-weekly {
        font-size: 0.8rem;
        font-weight: 600;
        color: var(--secondary-text-color);
      }
      .child-tile .streak-chip {
        display: flex;
        align-items: center;
        gap: 3px;
        background: var(--secondary-background-color, #f5f5f5);
        border-radius: 12px;
        padding: 3px 8px;
        font-size: 0.75rem;
        font-weight: 700;
      }

      /* Top-child accent bar */
      .child-tile.top-child::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 4px;
        border-radius: 0 0 18px 18px;
      }

      /* ══════════════════════════════════════════
         CUMULATIVE MODE
      ══════════════════════════════════════════ */
      .cumulative-wrap {
        display: flex;
        flex-direction: column;
        gap: 18px;
      }
      .cumulative-total {
        display: flex;
        flex-direction: column;
        align-items: center;
        text-align: center;
        padding: 22px;
        background: var(--card-background-color, #fff);
        border: 2px solid var(--divider-color, #e0e0e0);
        border-radius: 20px;
        box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        position: relative;
        overflow: hidden;
      }
      .cumulative-total::before {
        content: "\u{1F31F}";
        font-size: 6rem;
        position: absolute;
        right: -10px;
        bottom: -14px;
        opacity: 0.07;
        pointer-events: none;
      }
      .cumulative-total .points-label { margin-bottom: 6px; }
      .cumulative-total .points-number { font-size: 3.8rem; }
      .family-label {
        font-size: 0.9rem;
        font-weight: 600;
        color: var(--secondary-text-color);
        margin-top: 4px;
      }
      .divider-label {
        text-align: center;
        font-size: 0.78rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        color: var(--secondary-text-color);
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .divider-label::before,
      .divider-label::after {
        content: "";
        flex: 1;
        height: 1px;
        background: var(--divider-color, #e0e0e0);
      }

      .cumul-row {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 12px 14px;
        background: var(--card-background-color, #fff);
        border: 1px solid var(--divider-color, #e0e0e0);
        border-radius: 14px;
      }
      .cumul-row .child-name {
        font-size: 1rem;
        font-weight: 700;
        flex: 1;
        min-width: 0;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      .cumul-row .cumul-points {
        font-size: 1.5rem;
        font-weight: 900;
        color: var(--primary-text-color);
        flex-shrink: 0;
      }
      .cumul-row .cumul-star {
        font-size: 0.85rem;
        animation: star-spin 5s linear infinite;
        display: inline-block;
        margin-right: 2px;
      }
      .cumul-bar-wrap {
        width: 100%;
        background: var(--secondary-background-color, #f0f0f0);
        border-radius: 6px;
        height: 6px;
        overflow: hidden;
        margin-top: 4px;
      }
      .cumul-bar-fill {
        height: 100%;
        border-radius: 6px;
        transition: width 1s cubic-bezier(.4,0,.2,1);
      }
      .cumul-row-inner {
        flex: 1;
        min-width: 0;
      }

      /* ── Empty state ── */
      .empty-state {
        text-align: center;
        padding: 32px 16px;
        color: var(--secondary-text-color);
        font-size: 0.9rem;
      }
      .empty-state ha-icon { --mdc-icon-size: 40px; opacity: 0.35; display: block; margin: 0 auto 10px; }

      /* Shared .tmd kit + design tokens are provided by taskmate-design.js styles(). */

      /* Points Display — Playroom */
      .pl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 11px; }
      .pl-tile { background: var(--tmd-surface-2); border-radius: 18px; padding: 22px 12px 15px; text-align: center;
                 position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; }
      .pl-medal { position: absolute; top: 9px; left: 11px; font-size: 16px; line-height: 1; }
      .pl-pts { font-family: var(--tmd-font-display); font-weight: 800; font-size: 1.85rem; line-height: 1;
                color: var(--tmd-accent); }
      .pl-pts span { font-size: 0.9rem; margin-left: 2px; }
      .pl-name { font-weight: 800; font-size: 0.95rem; }
      .pl-chips { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }

      /* Points Display — Console */
      .cn-list { display: flex; flex-direction: column; gap: 9px; }
      .cn-row { display: flex; align-items: center; gap: 10px; background: var(--tmd-surface-2);
                border: 1px solid var(--tmd-border); border-radius: 10px; padding: 11px; }
      .cn-rank { font-size: 20px; width: 34px; color: var(--tmd-dim); }
      .cn-mid { flex: 1; min-width: 0; }
      .cn-name { font-weight: 700; margin-bottom: 6px; }
      .cn-right { text-align: right; }
      .cn-pts { font-size: 22px; }
      .cn-sub { font-size: 10px; }

      /* Points Display — Clean Pro */
      .cp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 4px; }
      .cp-tile { text-align: center; padding: 8px 6px; display: flex; flex-direction: column;
                 align-items: center; gap: 6px; }
      .cp-pts { font-size: 1.6rem; }
      .cp-name { font-size: 0.82rem; font-weight: 600; }
      .cp-rank { background: transparent; }
      .cp-chips { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; }
      .cp-total { justify-content: space-between; }

      /* Points Display — Cumulative (designed) */
      .cumul-total-d { text-align: center; padding: 8px 0 14px; }
      .cumul-big { font-size: 2.3rem; color: var(--tmd-accent); margin-top: 2px; }
      .cumul-big-star { font-size: 1rem; }
      .cumul-list { display: flex; flex-direction: column; gap: 10px; }
      .cumul-row-d { display: flex; align-items: center; gap: 11px; background: var(--tmd-surface-2);
                     border: 1px solid var(--tmd-border); border-radius: 14px; padding: 11px; }
      .cumul-mid { flex: 1; min-width: 0; }
      .cumul-head { justify-content: space-between; margin-bottom: 6px; }
      .cumul-name { font-weight: 700; }
    `;
    const tokens = window.__taskmate_design && window.__taskmate_design.styles
      ? window.__taskmate_design.styles() : null;
    return tokens ? [tokens, base] : base;
  }

  setConfig(config) {
    if (!config.entity) throw new Error("TaskMate Points Display: 'entity' is required.");
    this.config = {
      mode: "single",
      primary_display: "current_points",
      show_streak: true,
      show_weekly: true,
      show_rank: true,
      animate: true,
      header_color: DEFAULT_HEADER,
      ...config,
    };
  }

  static getConfigElement() {
    return document.createElement("taskmate-points-display-card-editor");
  }

  static getStubConfig() {
    return { entity: "sensor.taskmate_overview", mode: "multi" };
  }

  getCardSize() { return 3; }

  /* ── Data helpers ───────────────────────────────────────────────────── */

  _stateObj() {
    return this.hass?.states?.[this.config.entity];
  }

  _allChildren() {
    const s = this._stateObj();
    return s ? getChildren(s) : [];
  }

  _isCareerMode() {
    return this.config.primary_display === "career_score";
  }

  _primaryValue(child) {
    return this._isCareerMode() ? (child.career_score || 0) : (child.points || 0);
  }

  _secondaryValue(child) {
    return this._isCareerMode() ? (child.points || 0) : (child.career_score || 0);
  }

  _primaryLabel() {
    return this._isCareerMode()
      ? this._t("points_display.career_score_label")
      : this._t("points_display.total_points");
  }

  _secondaryLabel() {
    return this._isCareerMode()
      ? this._t("points_display.current_points_label")
      : this._t("points_display.career_score_label");
  }

  _rankedChildren() {
    return [...this._allChildren()].sort((a, b) => this._primaryValue(b) - this._primaryValue(a));
  }

  _singleChild() {
    const cid = this.config.child_id;
    return this._allChildren().find(c => c.id === cid) || null;
  }

  /* ── Render helpers ─────────────────────────────────────────────────── */

  _headerStyle() {
    return `background: ${_safeColor(this.config.header_color, DEFAULT_HEADER)};`;
  }

  _defaultTitle() {
    const mode = this.config.mode || "single";
    if (mode === "single") {
      const c = this._singleChild();
      return c ? this._t("points_display.single_title", { name: c.name }) : this._t("points_display.default_title");
    }
    if (mode === "cumulative") return this._t("points_display.family_title");
    return this._t("points_display.board_title");
  }

  /* ── Single mode ────────────────────────────────────────────────────── */

  _renderSingle() {
    const child = this._singleChild();
    if (!child) {
      return html`<div class="empty-state">
        <ha-icon icon="mdi:account-question"></ha-icon>
        ${this._t("points_display.empty_no_child")}<br>
        ${this._t("points_display.empty_set_child_id")}
      </div>`;
    }

    const children = this._allChildren();
    const colour   = CHILD_COLOURS[children.indexOf(child) % CHILD_COLOURS.length];
    const primary  = this._primaryValue(child);
    const secondary = this._secondaryValue(child);
    const tz       = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
    const weekly   = weeklyPoints(child, tz);
    const streak   = child.streak || 0;
    const rank     = this._rankedChildren().findIndex(c => c.id === child.id) + 1;

    return html`
      <div class="single-wrap">
        <div class="single-identity">
          ${childAvatar(child, colour)}
          <div class="child-name">${child.name}</div>
        </div>

        <div class="big-points">
          <div class="points-label">${this._primaryLabel()}</div>
          <div class="points-number" style="color:${colour}">
            <span class="points-star">\u2B50</span>${primary.toLocaleString()}
          </div>
          <div class="secondary-info">
            ${this._secondaryLabel()}: <span class="secondary-value">${secondary.toLocaleString()}</span>
          </div>
        </div>

        <div class="stats-row">
          ${this.config.show_weekly ? html`
            <div class="stat-pill">
              <ha-icon icon="mdi:calendar-week"></ha-icon>
              <div>
                <div>${this._t("points_display.weekly_pts", { count: weekly })}</div>
                <div class="pill-label">${this._t("points_display.this_week")}</div>
              </div>
            </div>` : ""}
          ${this.config.show_streak ? html`
            <div class="stat-pill">
              <ha-icon icon="mdi:fire"></ha-icon>
              <div>
                <div>${streak === 1
                  ? this._t("points_display.streak_day", { count: streak })
                  : this._t("points_display.streak_days", { count: streak })}</div>
                <div class="pill-label">${this._t("points_display.streak")}</div>
              </div>
            </div>` : ""}
          ${this.config.show_rank && children.length > 1 ? html`
            <div class="stat-pill">
              <ha-icon icon="mdi:trophy"></ha-icon>
              <div>
                <div>${rank <= 3 ? RANK_MEDAL[rank - 1] : `#${rank}`}</div>
                <div class="pill-label">${this._t("points_display.rank")}</div>
              </div>
            </div>` : ""}
        </div>
      </div>`;
  }

  /* ── Multi mode ─────────────────────────────────────────────────────── */

  _renderMulti() {
    const ranked = this._rankedChildren();
    if (!ranked.length) {
      return html`<div class="empty-state">
        <ha-icon icon="mdi:account-group"></ha-icon>
        ${this._t("points_display.empty_no_children")}
      </div>`;
    }

    const tz = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;

    return html`
      <div class="multi-grid">
        ${ranked.map((child, idx) => {
          const colour    = CHILD_COLOURS[this._allChildren().indexOf(child) % CHILD_COLOURS.length];
          const primary   = this._primaryValue(child);
          const secondary = this._secondaryValue(child);
          const weekly    = weeklyPoints(child, tz);
          const streak    = child.streak || 0;
          const isTop     = idx === 0;
          return html`
            <div class="child-tile ${isTop ? "top-child" : ""}"
                 style="${isTop ? `border-color:${colour};` : ""}">
              ${isTop ? html`<div class="rank-badge">\u{1F947}</div>` :
                idx < 3 && this.config.show_rank ? html`<div class="rank-badge">${RANK_MEDAL[idx]}</div>` : ""}
              ${childAvatar(child, colour)}
              <div class="child-name">${child.name}</div>
              <div class="tile-points" style="color:${colour}">
                <span class="tile-star">\u2B50</span>${primary.toLocaleString()}
              </div>
              <div class="tile-label">${this._isCareerMode()
                ? this._t("points_display.career_score_label")
                : this._t("points_display.points_label")}</div>
              <div class="secondary-info">
                ${this._secondaryLabel()}: <span class="secondary-value">${secondary.toLocaleString()}</span>
              </div>
              ${this.config.show_weekly ? html`
                <div class="tile-weekly">${this._t("points_display.weekly_plus", { count: weekly })}</div>` : ""}
              ${this.config.show_streak ? html`
                <div class="streak-chip">\u{1F525} ${streak}</div>` : ""}
              ${isTop ? html`<div style="position:absolute;bottom:0;left:0;right:0;height:4px;background:${colour};border-radius:0 0 18px 18px;"></div>` : ""}
            </div>`;
        })}
      </div>`;
  }

  /* ── Cumulative mode ────────────────────────────────────────────────── */

  _renderCumulative() {
    const ranked = this._rankedChildren();
    if (!ranked.length) {
      return html`<div class="empty-state">
        <ha-icon icon="mdi:account-group"></ha-icon>
        ${this._t("points_display.empty_no_children")}
      </div>`;
    }

    const total     = ranked.reduce((s, c) => s + this._primaryValue(c), 0);
    const secTotal  = ranked.reduce((s, c) => s + this._secondaryValue(c), 0);

    return html`
      <div class="cumulative-wrap">
        <div class="cumulative-total">
          <div class="points-label">${this._t("points_display.combined_family_total")}</div>
          <div class="points-number" style="color:${_safeColor(this.config.header_color, DEFAULT_HEADER)}">
            <span class="points-star">\u{1F31F}</span>${total.toLocaleString()}
          </div>
          <div class="secondary-info">
            ${this._secondaryLabel()}: <span class="secondary-value">${secTotal.toLocaleString()}</span>
          </div>
          <div class="family-label">${this._t("points_display.family_subtitle", { count: ranked.length })}</div>
        </div>

        <div class="divider-label">${this._t("points_display.individual_scores")}</div>

        ${ranked.map((child, idx) => {
          const colour    = CHILD_COLOURS[this._allChildren().indexOf(child) % CHILD_COLOURS.length];
          const primary   = this._primaryValue(child);
          const secondary = this._secondaryValue(child);
          const pct       = total > 0 ? Math.round((primary / total) * 100) : 0;
          return html`
            <div class="cumul-row">
              ${childAvatar(child, colour)}
              <div class="cumul-row-inner">
                <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:5px;">
                  <span class="child-name">${child.name}</span>
                  <span class="cumul-points" style="color:${colour}">
                    <span class="cumul-star">\u2B50</span>${primary.toLocaleString()}
                  </span>
                </div>
                <div class="secondary-info" style="margin-top:0;margin-bottom:4px;">
                  ${this._secondaryLabel()}: <span class="secondary-value">${secondary.toLocaleString()}</span>
                </div>
                <div style="display:flex;align-items:center;gap:8px;">
                  <div class="cumul-bar-wrap" style="flex:1;">
                    <div class="cumul-bar-fill" style="width:${pct}%;background:${colour};"></div>
                  </div>
                  <span style="font-size:0.75rem;font-weight:600;color:var(--secondary-text-color);flex-shrink:0;">${pct}%</span>
                </div>
              </div>
              ${this.config.show_rank && idx < 3 ? html`
                <div style="font-size:1.4rem;flex-shrink:0;">${RANK_MEDAL[idx]}</div>` : ""}
            </div>`;
        })}
      </div>`;
  }

  /* ── Main render ────────────────────────────────────────────────────── */

  render() {
    const design = window.__taskmate_design
      ? window.__taskmate_design.apply(this, this.hass, this.config, this.config.entity)
      : "classic";
    if (design !== "classic") return this._renderDesigned(design);

    const mode  = this.config.mode || "single";
    const title = this.config.title || this._defaultTitle();

    const modeLabel = {
      single: this._t("points_display.mode_single"),
      multi: this._t("points_display.mode_multi"),
      cumulative: this._t("points_display.mode_cumulative"),
    }[mode] || mode;

    const modeIcon  = { single: "mdi:star-circle", multi: "mdi:account-group", cumulative: "mdi:sigma" }[mode] || "mdi:star";

    let body;
    if (mode === "multi")       body = this._renderMulti();
    else if (mode === "cumulative") body = this._renderCumulative();
    else                         body = this._renderSingle();

    return html`
      <ha-card>
        <div class="card-header" style="${this._headerStyle()}">
          <div class="header-left">
            <ha-icon class="header-icon" icon="${modeIcon}"></ha-icon>
            <span class="card-title">${title}</span>
          </div>
          <div class="mode-badge">${modeLabel}</div>
        </div>
        <div class="card-body">${body}</div>
      </ha-card>`;
  }

  /* ══════════════════════════════════════════════════════════════════════
     DESIGNED STYLES (playroom / console / cleanpro) — see taskmate-design.js
     Ported from docs/design/redesigns (§card-points-display).
  ══════════════════════════════════════════════════════════════════════ */

  _designTone(i) { return `var(--tmd-c${(i % 6) + 1})`; }

  _av(child, tone, size) {
    const a = child.avatar || "";
    const inner = a.startsWith("mdi:")
      ? html`<ha-icon icon="${a}"></ha-icon>`
      : a
        ? html`<img src="${a}" alt="${child.name}">`
        : (child.name || "?").split(" ").map(w => w[0]).slice(0, 2).join("").toUpperCase();
    return html`<div class="av" style="--av:${size}px;--ac:${tone}">${inner}</div>`;
  }

  _renderDesigned(design) {
    const mode  = this.config.mode || "single";
    const title = this.config.title || this._defaultTitle();
    const hd    = _safeColor(this.config.header_color, DEFAULT_HEADER);
    const tz    = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;

    let ranked = this._rankedChildren();
    if (mode === "single") {
      const c = this._singleChild();
      ranked = c ? [c] : [];
    }

    const header = html`
      <div class="tmd-hd">
        <span class="ic">⭐</span>
        <span class="tt">${title}</span>
        ${mode === "multi" ? html`<span class="pill">${this._t("points_display.mode_multi")}</span>` : ""}
      </div>`;

    if (!ranked.length) {
      return html`<ha-card class="tmd" style="--hd:${hd}">
        ${header}
        <div class="tmd-bd"><div class="tmd-empty">${this._t("points_display.empty_no_children")}</div></div>
      </ha-card>`;
    }

    const rows = ranked.map((child) => ({
      child,
      tone:   this._designTone(this._allChildren().indexOf(child)),
      points: this._primaryValue(child),
      weekly: weeklyPoints(child, tz),
      // Overview summary exposes current_streak (child.streak doesn't exist).
      streak: child.current_streak ?? child.streak ?? 0,
      pct:    0,
    }));
    const total = rows.reduce((s, r) => s + r.points, 0);
    rows.forEach((r) => { r.pct = total > 0 ? Math.round((r.points / total) * 100) : 0; });
    rows.forEach((r, i) => { r.rank = i; });

    const body =
      mode === "cumulative" ? this._cumulBody(rows, total) :
      design === "playroom" ? this._designPlayroom(rows) :
      design === "console"  ? this._designConsole(rows) :
                              this._designCleanpro(rows, total);

    return html`<ha-card class="tmd" style="--hd:${hd}">${header}<div class="tmd-bd">${body}</div></ha-card>`;
  }

  _designPlayroom(rows) {
    const showRank = this.config.show_rank !== false;
    const showWeekly = this.config.show_weekly !== false;
    const showStreak = this.config.show_streak !== false;
    return html`
      <div class="pl-grid">
        ${rows.map((r) => html`
          <div class="pl-tile" style="--ac:${r.tone}">
            ${showRank && r.rank < 3 ? html`<div class="pl-medal">${RANK_MEDAL[r.rank]}</div>` : ""}
            ${this._av(r.child, r.tone, 52)}
            <div class="pl-pts">${r.points.toLocaleString()}<span>⭐</span></div>
            <div class="pl-name">${r.child.name}</div>
            ${showWeekly || (showStreak && r.streak) ? html`
              <div class="pl-chips">
                ${showWeekly ? html`<div class="chip soft">${this._t("points_display.weekly_plus", { count: r.weekly })}</div>` : ""}
                ${showStreak && r.streak ? html`<div class="chip soft">\u{1F525} ${r.streak}</div>` : ""}
              </div>` : ""}
          </div>`)}
      </div>`;
  }

  _designConsole(rows) {
    const max = Math.max(...rows.map((r) => r.points), 1);
    const showRank = this.config.show_rank !== false;
    const showWeekly = this.config.show_weekly !== false;
    const showStreak = this.config.show_streak !== false;
    return html`
      <div class="cn-list">
        ${rows.map((r) => html`
          <div class="cn-row" style="--ac:${r.tone}">
            ${showRank ? html`<div class="num cn-rank ${r.rank === 0 ? "lead" : ""}">#${r.rank + 1}</div>` : ""}
            ${this._av(r.child, r.tone, 38)}
            <div class="cn-mid">
              <div class="cn-name">${r.child.name}</div>
              <div class="bar"><i style="width:${Math.round((r.points / max) * 100)}%"></i></div>
            </div>
            <div class="cn-right">
              <div class="num cn-pts ${r.rank === 0 ? "lead" : ""}">${r.points.toLocaleString()}</div>
              <div class="cn-sub muted">XP${showWeekly ? html` · +${r.weekly}` : ""}${showStreak && r.streak ? html` · \u{1F525}${r.streak}` : ""}</div>
            </div>
          </div>`)}
      </div>`;
  }

  _designCleanpro(rows, total) {
    const showRank = this.config.show_rank !== false;
    const showWeekly = this.config.show_weekly !== false;
    const showStreak = this.config.show_streak !== false;
    return html`
      <div class="cp-grid">
        ${rows.map((r) => html`
          <div class="cp-tile" style="--ac:${r.tone}">
            ${this._av(r.child, r.tone, 46)}
            <div class="big cp-pts">${r.points.toLocaleString()}</div>
            <div class="cp-name">${r.child.name}</div>
            <div class="cp-chips">
              ${showRank ? html`<div class="chip cp-rank">${r.rank === 0
                ? html`<span class="lead-dot">●</span> ` : ""}#${r.rank + 1}</div>` : ""}
              ${showWeekly ? html`<div class="chip cp-rank">+${r.weekly}</div>` : ""}
              ${showStreak && r.streak ? html`<div class="chip cp-rank">\u{1F525} ${r.streak}</div>` : ""}
            </div>
          </div>`)}
      </div>
      ${rows.length > 1 ? html`
        <div class="divide"></div>
        <div class="row cp-total">
          <span class="muted">${this._t("points_display.combined_family_total")}</span>
          <span class="num">${total.toLocaleString()} ${this._t("points_display.points_label")}</span>
        </div>` : ""}`;
  }

  _cumulBody(rows, total) {
    return html`
      <div class="cumul-total-d">
        <div class="muted">${this._t("points_display.combined_family_total")}</div>
        <div class="big cumul-big">${total.toLocaleString()} <span class="cumul-big-star">⭐</span></div>
      </div>
      <div class="cumul-list">
        ${rows.map((r) => html`
          <div class="cumul-row-d" style="--ac:${r.tone}">
            ${this._av(r.child, r.tone, 40)}
            <div class="cumul-mid">
              <div class="row cumul-head">
                <span class="cumul-name">${r.child.name}</span>
                <span class="num">${r.points.toLocaleString()}</span>
              </div>
              <div class="bar"><i style="width:${r.pct}%"></i></div>
            </div>
          </div>`)}
      </div>`;
  }
}

customElements.define("taskmate-points-display-card", TaskMatePointsDisplayCard);


/* ═══════════════════════════════════════════════════════════════════════
   UI EDITOR
═══════════════════════════════════════════════════════════════════════ */

class TaskMatePointsDisplayCardEditor extends LitElement {

  static get properties() {
    return {
      hass:   { type: Object },
      config: { type: Object },
      _children: { type: Array },
    };
  }

  constructor() {
    super();
    this._children = [];
  }

  _t(key, params) {
    const fn = window.__taskmate_localize;
    return fn ? fn(this.hass, key, params) : key;
  }

  static get styles() {
    return css`
      :host { display: block; padding: 8px 0; }
      ha-form { display: block; margin-bottom: 16px; }
      .colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
      .colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
      .colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
      .colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
      .colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
      .colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
      .colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
      .colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
      .preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
      .preset-swatch:hover { transform: scale(1.15); }
      .preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
      .colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-left: auto; }
      .colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
    `;
  }

  setConfig(config) {
    this.config = config;
  }

  updated(changed) {
    if (changed.has('hass') && this.hass && this.config?.entity) {
      this._loadChildren();
    }
  }

  _loadChildren() {
    const state = this.hass?.states?.[this.config.entity];
    if (!state) { this._children = []; return; }
    this._children = getChildren(state);
  }

  _buildSchema() {
    const mode = this.config.mode || 'single';
    const schema = [
      { name: 'entity', selector: { entity: { domain: 'sensor' } } },
      { name: 'title', selector: { text: {} } },
      {
        name: 'mode',
        selector: {
          select: {
            options: [
              { value: 'single', label: this._t('points_display.editor.mode_single') },
              { value: 'multi', label: this._t('points_display.editor.mode_multi') },
              { value: 'cumulative', label: this._t('points_display.editor.mode_cumulative') },
            ],
            mode: 'dropdown',
          },
        },
      },
      {
        name: 'card_design',
        selector: {
          select: {
            options: window.__taskmate_design
              ? window.__taskmate_design.editorOptions(this._t.bind(this))
              : [{ value: 'global', label: 'Use global default' }],
            mode: 'dropdown',
          },
        },
      },
      {
        name: 'primary_display',
        selector: {
          select: {
            options: [
              { value: 'current_points', label: this._t('points_display.editor.primary_current_points') },
              { value: 'career_score', label: this._t('points_display.editor.primary_career_score') },
            ],
            mode: 'dropdown',
          },
        },
      },
    ];

    if (mode === 'single') {
      schema.push({
        name: 'child_id',
        selector: {
          select: {
            options: [
              { value: '', label: this._t('points_display.editor.select_child') },
              ...this._children.map((c) => ({ value: c.id, label: c.name })),
            ],
            mode: 'dropdown',
          },
        },
      });
    }

    schema.push({ name: 'show_weekly', selector: { boolean: {} } });
    schema.push({ name: 'show_streak', selector: { boolean: {} } });
    if (mode !== 'single') {
      schema.push({ name: 'show_rank', selector: { boolean: {} } });
    }
    return schema;
  }

  _computeLabel = (entry) => {
    const labels = {
      entity: this._t('points_display.editor.entity_label'),
      title: this._t('common.editor.card_title'),
      mode: this._t('points_display.editor.mode_label'),
      card_design: this._t('common.design.field_label'),
      primary_display: this._t('points_display.editor.primary_display'),
      child_id: this._t('points_display.editor.child_label'),
      show_weekly: this._t('points_display.editor.show_weekly'),
      show_streak: this._t('points_display.editor.show_streak'),
      show_rank: this._t('points_display.editor.show_rank'),
    };
    return labels[entry.name] ?? entry.name;
  };

  _computeHelper = () => '';

  render() {
    if (!this.config) return html``;
    const mode = this.config.mode || 'single';
    const data = {
      entity: this.config.entity || '',
      title: this.config.title || '',
      mode,
      card_design: this.config.card_design || 'global',
      primary_display: this.config.primary_display || 'current_points',
      child_id: this.config.child_id || '',
      show_weekly: this.config.show_weekly !== false,
      show_streak: this.config.show_streak !== false,
      show_rank: this.config.show_rank !== false,
    };
    return html`
      <ha-form
        .hass=${this.hass}
        .data=${data}
        .schema=${this._buildSchema()}
        .computeLabel=${this._computeLabel}
        .computeHelper=${this._computeHelper}
        @value-changed=${this._formChanged}
      ></ha-form>
      ${this._renderColourPicker('header_color', DEFAULT_HEADER)}
    `;
  }

  _renderColourPicker(key, defaultValue) {
    const d = window.__taskmate_design;
    const current = this.config[key] || defaultValue;
    if (!d || !d.colourPicker) return html``;
    return d.colourPicker({
      defaultValue, current,
      label: this._t('common.editor.header_colour'),
      helper: this._t('common.editor.header_colour_helper'),
      resetLabel: this._t('common.reset'),
      onInput: (v) => this._update(key, v),
      onPreset: (v) => this._update(key, v),
      onReset: () => this._update(key, defaultValue),
    });
  }

  _formChanged(e) {
    const newValues = e.detail.value || {};
    const newConfig = { ...this.config };
    for (const [key, value] of Object.entries(newValues)) {
      if (value === '' || value === null || value === undefined) {
        delete newConfig[key];
      } else if (key === 'card_design' && value === 'global') {
        delete newConfig[key];
      } else if (key === 'mode' && value === 'single') {
        delete newConfig[key];
      } else if (key === 'primary_display' && value === 'current_points') {
        delete newConfig[key];
      } else if ((key === 'show_weekly' || key === 'show_streak' || key === 'show_rank') && value === true) {
        delete newConfig[key];
      } else {
        newConfig[key] = value;
      }
    }
    this._fire(newConfig);
  }

  _fire(config) {
    this.dispatchEvent(new CustomEvent('config-changed', {
      detail: { config },
      bubbles: true,
      composed: true,
    }));
  }

  _set(key, value) {
    this._fire({ ...this.config, [key]: value });
  }
}

customElements.define("taskmate-points-display-card-editor", TaskMatePointsDisplayCardEditor);

/* ── Registration ─────────────────────────────────────────────────────── */

window.customCards = window.customCards || [];
window.customCards.push({
  type:        "taskmate-points-display-card",
  name:        "TaskMate \u2014 Points Display",
  description: "Kid-friendly display of points. Supports single child, all children, or combined family total.",
  preview:     true,
  configElement: "taskmate-points-display-card-editor",
  getEntitySuggestion: (hass, entityId) =>
    window.__taskmate_suggest(hass, entityId, "taskmate-points-display-card", "overview"),
});

// Version is injected by the HA resource URL (?v=x.x.x) and read from the DOM
const _tmVersion = new URLSearchParams(
  Array.from(document.querySelectorAll('script[src*="/taskmate-points-display-card.js"]'))
    .map(s => s.src.split("?")[1]).find(Boolean) || ""
).get("v") || "?";
console.info(
  "%c TASKMATE POINTS DISPLAY CARD %c v" + _tmVersion + " ",
  "background:#566573;color:white;font-weight:bold;padding:2px 4px;border-radius:4px 0 0 4px;",
  "background:#2c3e50;color:white;font-weight:bold;padding:2px 4px;border-radius:0 4px 4px 0;"
);
