// Surface — Content mapping
//
// Split out of the old combined Roles surface (see §25.14). This page owns
// ONLY the binding matrix: rows are the bindable content (modules + pre/post
// question groups & questions), columns are the roles (grouped by their
// organisation, read-only here). The author's whole job is to tick / untick
// cells. Roles and organisations are CREATED and EDITED on the
// "Organisations & roles" surface — never here. No inline role inspector,
// no add/delete role, no organisation editing.

function SurfaceContentMapping({ roles: rolesIn, course, assessments, onNavigate }) {
  const selectBrand = !!course.selectBrand;
  const brands = course.brands || [];
  const roles = rolesIn || [];

  // ── Bindings · per-role membership in modules + pre/post groups + qs ──────
  const seed = React.useCallback(() => {
    const out = {};
    roles.forEach(r => {
      out[r.code] = {
        modules: new Set(r.modules || []),
        pre: {
          groups: new Set(assessments.pre.groups
            .filter(g => g.roles.includes(r.code)).map(g => g.id)),
          questions: new Set(assessments.pre.groups
            .flatMap(g => g.questions || [])
            .filter(q => (q.roles || []).includes(r.code)).map(q => q.id)),
        },
        post: {
          groups: new Set(assessments.post.groups
            .filter(g => g.roles.includes(r.code)).map(g => g.id)),
          questions: new Set(assessments.post.groups
            .flatMap(g => g.questions || [])
            .filter(q => (q.roles || []).includes(r.code)).map(q => q.id)),
        },
      };
    });
    return out;
  }, [roles, assessments]);
  const [bindings, setBindings] = React.useState(seed);
  const [openGroups, setOpenGroups] = React.useState(new Set());

  const toggle = (roleCode, kind, id) => {
    setBindings(b => {
      const next = { ...b };
      const role = { ...next[roleCode] };
      const [bucket, sub] = kind.includes('.') ? kind.split('.') : [kind, null];
      if (sub) {
        role[bucket] = { ...role[bucket], [sub]: new Set(role[bucket][sub]) };
        const s = role[bucket][sub];
        if (s.has(id)) s.delete(id); else s.add(id);
      } else {
        role[bucket] = new Set(role[bucket]);
        if (role[bucket].has(id)) role[bucket].delete(id); else role[bucket].add(id);
      }
      next[roleCode] = role;
      return next;
    });
  };

  // ── Column order: roles clustered by organisation band ───────────────────
  const orderedRoles = React.useMemo(() => {
    if (!selectBrand) return roles;
    const out = [];
    brands.forEach(b => out.push(...roles.filter(r => r.brand === b.id)));
    out.push(...roles.filter(r => !brands.some(x => x.id === r.brand)));
    return out;
  }, [roles, brands, selectBrand]);

  const brandBands = React.useMemo(() => {
    if (!selectBrand) return null;
    const counts = brands.map(b => ({
      brand: b, count: orderedRoles.filter(r => r.brand === b.id).length,
    }));
    const unbranded = orderedRoles.filter(r => !brands.some(x => x.id === r.brand)).length;
    if (unbranded > 0) {
      counts.push({
        brand: { id: '__none', label: 'No organisation',
          color: 'var(--surface-inset)', textColor: 'var(--text-muted)' },
        count: unbranded,
      });
    }
    return counts.filter(c => c.count > 0);
  }, [orderedRoles, brands, selectBrand]);

  // ── Rows: chapters · pre · post ───────────────────────────────────────────
  const moduleSections = (course.moduleGroups || [{ id: '_', title: 'Modules' }])
    .map(g => ({
      kind: 'section', label: locText(g.title),
      rows: course.modules
        .filter(m => (m.group || '_') === g.id)
        .map(m => ({
          kind: 'module', id: m.id, code: `M${m.n}`, label: locText(m.title),
          meta: { duration: m.duration, mandatory: m.mandatory },
          bindKind: 'modules',
        })),
    }));
  const assessmentSection = (key, label) => ({
    kind: 'section', label,
    rows: (assessments[key].groups || []).flatMap(g => {
      const out = [{
        kind: 'group', section: key, id: g.id, code: g.id.toUpperCase(),
        label: g.label, meta: { mod: g.mod, count: g.questions?.length || 0 },
        bindKind: `${key}.groups`,
      }];
      if (openGroups.has(g.id)) {
        (g.questions || []).forEach((q, qi) => {
          out.push({
            kind: 'question', section: key, groupId: g.id, id: q.id,
            code: `Q${qi + 1}`, label: q.prompt, bindKind: `${key}.questions`,
          });
        });
      }
      return out;
    }),
  });
  const allSections = [
    ...moduleSections,
    assessmentSection('pre',  'Pre-assessment · question groups'),
    assessmentSection('post', 'Post-assessment · question groups'),
  ];

  const COL_W = 130;
  const FIRST_W = 440;
  const ROLE_GRID = `${FIRST_W}px repeat(${orderedRoles.length}, ${COL_W}px)`;
  const BRAND_GRID = brandBands && brandBands.length > 0
    ? `${FIRST_W}px ${brandBands.map(b => `${b.count * COL_W}px`).join(' ')}`
    : ROLE_GRID;

  return (
    <div data-screen-label="Surface · Content mapping" style={{
      display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg)',
    }}>
      <header style={{
        padding: '20px 24px 16px', borderBottom: '1px solid var(--border)',
        background: 'var(--surface)', display: 'flex', alignItems: 'flex-end', gap: 14,
      }}>
        <div style={{ flex: 1 }}>
          <h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>Content mapping</h2>
          <p style={{ margin: '4px 0 0', fontSize: 12.5, color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>
            Decide who sees what — tick a cell to include a module or question for a role.
            Add or rename roles in <strong>Organisations &amp; roles</strong>.
          </p>
        </div>
      </header>

      {orderedRoles.length === 0 ? (
        <ContentMappingEmpty onNavigate={onNavigate} />
      ) : (
        <div style={{ flex: 1, overflow: 'hidden', display: 'flex', padding: '16px 24px 24px' }}>
          <div style={{ flex: 1, minHeight: 0,
            display: 'flex', flexDirection: 'column' }}>
            <div style={{ flex: 1, minHeight: 0, background: 'var(--surface)',
              border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', overflow: 'auto' }}>

              {/* Sticky header bundle */}
              <div style={{ position: 'sticky', top: 0, zIndex: 3, background: 'var(--surface)',
                borderBottom: '1px solid var(--border)' }}>
                {/* Organisation super-header */}
                {brandBands && brandBands.length > 0 && (
                  <div style={{ display: 'grid', gridTemplateColumns: BRAND_GRID }}>
                    <div />
                    {brandBands.map(({ brand }) => (
                      <div key={brand.id} style={{
                        padding: '6px 10px', background: brand.color,
                        color: brand.textColor || '#fff', fontSize: 12, fontWeight: 600,
                        letterSpacing: '.04em', textAlign: 'center',
                        borderLeft: '1px solid rgba(255,255,255,.18)',
                      }}>{brand.label}</div>
                    ))}
                  </div>
                )}
                {/* Role-name row (read-only) */}
                <div style={{ display: 'grid', gridTemplateColumns: ROLE_GRID }}>
                  <div style={{ padding: '12px 14px', fontSize: 11.5, fontWeight: 600,
                    textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--text-muted)' }}>
                    Chapter / module / question group
                  </div>
                  {orderedRoles.map(r => (
                    <RoleColumnHeader key={r.code} role={r}
                      brand={brands.find(b => b.id === r.brand)} />
                  ))}
                </div>
              </div>

              {/* Body — section rows */}
              {allSections.map((section, sIdx) => (
                <React.Fragment key={`s${sIdx}`}>
                  <div style={{
                    gridColumn: '1 / -1', padding: '10px 14px',
                    background: 'var(--surface-inset)',
                    borderTop: sIdx === 0 ? 0 : '1px solid var(--border)',
                    borderBottom: '1px solid var(--border)',
                    fontSize: 11, fontWeight: 600, textTransform: 'uppercase',
                    letterSpacing: '.08em', color: 'var(--text-muted)',
                  }}>{section.label}</div>
                  {section.rows.map(row => (
                    <MatrixRow key={`${row.kind}-${row.id}`} row={row} grid={ROLE_GRID}
                      roles={orderedRoles} bindings={bindings} onToggle={toggle}
                      expanded={openGroups.has(row.id)}
                      onToggleExpand={() => {
                        if (row.kind !== 'group') return;
                        setOpenGroups(s => {
                          const n = new Set(s);
                          n.has(row.id) ? n.delete(row.id) : n.add(row.id);
                          return n;
                        });
                      }} />
                  ))}
                </React.Fragment>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ── ContentMappingEmpty · no roles to map yet ────────────────────────────────
function ContentMappingEmpty({ onNavigate }) {
  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center',
      justifyContent: 'center', gap: 14, padding: '60px 24px', textAlign: 'center',
      color: 'var(--text-muted)' }}>
      <I.Table size={34} style={{ color: 'var(--text-faint)' }} />
      <div>
        <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600, color: 'var(--text)' }}>
          No roles to map yet
        </h3>
        <p style={{ margin: '6px 0 0', fontSize: 12.5, maxWidth: 400, lineHeight: 1.5 }}>
          Content mapping ties modules and assessment questions to roles. Create your
          roles first, then come back to tick what each one sees.
        </p>
      </div>
      {onNavigate && (
        <button className="btn sm primary" onClick={() => onNavigate('org-roles')}>
          <I.Users size={12} />Go to Organisations &amp; roles
        </button>
      )}
    </div>
  );
}

// ── RoleColumnHeader · read-only name + organisation-tinted accent ───────────
function RoleColumnHeader({ role, brand }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
      padding: '14px 8px 12px', minWidth: 0,
      borderLeft: '1px solid var(--border)',
      borderTop: brand ? `2px solid ${brand.color}` : '2px solid transparent',
      textAlign: 'center',
    }}>
      <span style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.2, maxWidth: '100%' }}
        className="truncate" title={role.label}>{role.label}</span>
    </div>
  );
}

// ── MatrixRow ─────────────────────────────────────────────────────────────────
function MatrixRow({ row, grid, roles, bindings, onToggle, expanded, onToggleExpand }) {
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: grid,
      borderBottom: '1px solid var(--border-faint)',
      background: row.kind === 'question' ? 'var(--surface-inset)' : 'transparent',
    }}>
      <RowLabel row={row} expanded={expanded} onToggleExpand={onToggleExpand} />
      {roles.map(r => {
        const b = bindings[r.code];
        let checked = false;
        if (row.bindKind === 'modules') checked = b.modules.has(row.id);
        else if (row.bindKind.endsWith('.groups')) {
          const [sec] = row.bindKind.split('.');
          checked = b[sec].groups.has(row.id);
        } else if (row.bindKind.endsWith('.questions')) {
          const [sec] = row.bindKind.split('.');
          checked = b[sec].questions.has(row.id);
        }
        let inherited = false;
        if (row.kind === 'question') {
          const [sec] = row.bindKind.split('.');
          inherited = !b[sec].groups.has(row.groupId);
        }
        return (
          <MatrixCell key={r.code} checked={checked} inherited={inherited}
            onChange={() => onToggle(r.code, row.bindKind, row.id)} />
        );
      })}
    </div>
  );
}

function RowLabel({ row, expanded, onToggleExpand }) {
  if (row.kind === 'module') {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px' }}>
        <KCode>{row.code}</KCode>
        <span style={{ fontSize: 13, fontWeight: 500 }}>{row.label}</span>
        <div style={{ flex: 1 }} />
        {row.meta.mandatory && <I.Pin size={11} style={{ color: 'var(--text-faint)' }} />}
        <span style={{ fontSize: 11, color: 'var(--text-faint)',
          fontFamily: 'var(--font-mono)' }}>{row.meta.duration}</span>
      </div>
    );
  }
  if (row.kind === 'group') {
    return (
      <button onClick={onToggleExpand} style={{
        display: 'flex', alignItems: 'center', gap: 8, padding: '10px 14px',
        background: 'transparent', border: 0, width: '100%', textAlign: 'left',
        cursor: 'default', fontFamily: 'inherit', color: 'var(--text)' }}>
        {expanded ? <I.ChevronDown size={12} /> : <I.ChevronRight size={12} />}
        <KCode>{row.meta.mod}</KCode>
        <span style={{ fontSize: 13, fontWeight: 500 }}>{row.label}</span>
        <span className="pill" style={{ fontSize: 10 }}>{row.meta.count} Qs</span>
      </button>
    );
  }
  if (row.kind === 'question') {
    return <QuestionRowLabel code={row.code} prompt={row.label} />;
  }
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 14px 8px 38px' }}>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11,
        color: 'var(--text-faint)' }}>{row.code}</span>
      <span className="truncate" style={{ fontSize: 12.5, color: 'var(--text-muted)' }}>{row.label}</span>
    </div>
  );
}

// ── QuestionRowLabel · single-line truncated prompt + full-text popover ──────
// Question prompts often share an opening sentence, so the visible line is
// truncated with an ellipsis and an "expand" affordance opens the FULL prompt
// in a portal popover (rendered to document.body so the matrix's overflow:auto
// never clips it).
function QuestionRowLabel({ code, prompt }) {
  const [open, setOpen] = React.useState(false);
  const [rect, setRect] = React.useState(null);
  const btnRef = React.useRef(null);
  const show = () => { if (btnRef.current) setRect(btnRef.current.getBoundingClientRect()); setOpen(true); };
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 9,
      padding: '8px 12px 8px 38px', minWidth: 0 }}>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11,
        color: 'var(--text-faint)', flexShrink: 0 }}>{code}</span>
      <span className="truncate" style={{ fontSize: 12.5, color: 'var(--text-muted)',
        minWidth: 0, flex: 1 }}>{prompt}</span>
      <button ref={btnRef} onClick={() => (open ? setOpen(false) : show())}
        title="View the full question"
        aria-label="View the full question" aria-expanded={open}
        style={{ flexShrink: 0, width: 22, height: 22, padding: 0,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          background: open ? 'var(--accent-bg)' : 'transparent',
          color: open ? 'var(--accent-text)' : 'var(--text-faint)',
          border: '1px solid', borderColor: open ? 'var(--accent-border)' : 'transparent',
          borderRadius: 'var(--radius)', cursor: 'default' }}
        onMouseOver={e => { if (!open) e.currentTarget.style.color = 'var(--text-muted)'; }}
        onMouseOut={e => { if (!open) e.currentTarget.style.color = 'var(--text-faint)'; }}>
        <I.Eye size={12} />
      </button>
      {open && rect && ReactDOM.createPortal(
        <>
          <div onClick={() => setOpen(false)}
            style={{ position: 'fixed', inset: 0, zIndex: 999 }} />
          <div className="slide-in" style={{
            position: 'fixed', zIndex: 1000, width: 380,
            top: Math.min(rect.bottom + 8, window.innerHeight - 180),
            left: Math.max(12, Math.min(rect.left - 340, window.innerWidth - 392)),
            padding: '13px 15px', background: 'var(--surface)', color: 'var(--text)',
            border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
            boxShadow: 'var(--shadow-xl)', fontSize: 13.5, lineHeight: 1.5 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 7 }}>
              <KCode>{code}</KCode>
              <span style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: '.06em',
                textTransform: 'uppercase', color: 'var(--text-faint)' }}>Full question</span>
            </div>
            <div style={{ color: 'var(--text)', textWrap: 'pretty' }}>{prompt}</div>
          </div>
        </>,
        document.body
      )}
    </div>
  );
}

function KCode({ children }) {
  return (
    <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, fontWeight: 600,
      padding: '1px 6px', borderRadius: 3, background: 'var(--surface-inset)',
      color: 'var(--text-muted)', letterSpacing: '.04em' }}>{children}</span>
  );
}

function MatrixCell({ checked, inherited, onChange }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      borderLeft: '1px solid var(--border-faint)', cursor: 'default',
      opacity: inherited ? 0.4 : 1 }}
      onMouseOver={e => e.currentTarget.style.background = 'var(--surface-inset)'}
      onMouseOut={e => e.currentTarget.style.background = 'transparent'}>
      <input type="checkbox" checked={checked} onChange={onChange}
        style={{ width: 16, height: 16, accentColor: 'var(--accent)', cursor: 'default' }} />
    </label>
  );
}

Object.assign(window, { SurfaceContentMapping });
